diff --git a/test/helpers/storageQueries.ts b/test/helpers/storageQueries.ts index 0cb879712a..c506d867b3 100644 --- a/test/helpers/storageQueries.ts +++ b/test/helpers/storageQueries.ts @@ -96,3 +96,99 @@ export async function processAllStorage( await limiter.disconnect(); } + +export async function processRandomStoragePrefixes( + api: ApiPromise, + storagePrefix: string, + blockHash: string, + processor: (batchResult: { key: `0x${string}`; value: string }[]) => void, + override = "" +) { + const maxKeys = 1000; + let total = 0; + const preFilteredPrefixes = splitPrefix(storagePrefix); + const chanceToSample = 0.05; + let prefixes = override + ? [override] + : preFilteredPrefixes.filter(() => Math.random() < chanceToSample); + if (prefixes.length > 25) { + prefixes = prefixes.slice(0, 25); + } + console.log(`Processing ${prefixes.length} prefixes: ${prefixes.join(", ")}`); + const limiter = rateLimiter(); + const stopReport = startReport(() => total); + + try { + await Promise.all( + prefixes.map(async (prefix) => + limiter.schedule(async () => { + let startKey: string | undefined = undefined; + while (true) { + // @ts-expect-error _rpcCore is not yet exposed + const keys: string = await api._rpcCore.provider.send("state_getKeysPaged", [ + prefix, + maxKeys, + startKey, + blockHash, + ]); + + if (!keys.length) { + break; + } + + // @ts-expect-error _rpcCore is not yet exposed + const response = await api._rpcCore.provider.send("state_queryStorageAt", [ + keys, + blockHash, + ]); + + try { + processor( + response[0].changes.map((pair: [string, string]) => ({ + key: pair[0], + value: pair[1], + })) + ); + } catch (e) { + console.log(`Error processing ${prefix}: ${e}`); + console.log(`Replace the empty string in smoke/test-ethereum-contract-code.ts + with the prefix to reproduce`); + } + + total += keys.length; + + if (keys.length !== maxKeys) { + break; + } + startKey = keys[keys.length - 1]; + } + }) + ) + ); + } finally { + stopReport(); + } + + await limiter.disconnect(); +} + +export const extractStorageKeyComponents = (storageKey: string) => { + // The full storage key is composed of + // - The 0x prefix (2 characters) + // - The module prefix (32 characters) + // - The method name (32 characters) + // - The parameters (variable length) + const regex = /(?0x[a-f0-9]{32})(?[a-f0-9]{32})(?[a-f0-9]*)/i; + const match = regex.exec(storageKey); + + if (!match) { + throw new Error("Invalid storage key format"); + } + + const { moduleKey, fnKey, paramsKey } = match.groups!; + return { + moduleKey, + fnKey, + paramsKey, + }; +}; diff --git a/test/suites/smoke/test-ethereum-contract-code.ts b/test/suites/smoke/test-ethereum-contract-code.ts index fd33c861c4..31ed8c4521 100644 --- a/test/suites/smoke/test-ethereum-contract-code.ts +++ b/test/suites/smoke/test-ethereum-contract-code.ts @@ -4,7 +4,7 @@ import { ONE_HOURS } from "@moonwall/util"; import { compactStripLength, hexToU8a, u8aConcat, u8aToHex } from "@polkadot/util"; import { xxhashAsU8a } from "@polkadot/util-crypto"; import chalk from "chalk"; -import { processAllStorage } from "../../helpers/storageQueries.js"; +import { processRandomStoragePrefixes } from "../../helpers/storageQueries.js"; describeSuite({ id: "S08", @@ -37,16 +37,24 @@ describeSuite({ u8aConcat(xxhashAsU8a("EVM", 128), xxhashAsU8a("AccountCodes", 128)) ); const t0 = performance.now(); - await processAllStorage(paraApi, keyPrefix, blockHash, (items) => { - for (const item of items) { - const codesize = getBytecodeSize(hexToU8a(item.value)); - if (codesize > MAX_CONTRACT_SIZE_BYTES) { - const accountId = "0x" + item.key.slice(-40); - failedContractCodes.push({ accountId, codesize }); + + await processRandomStoragePrefixes( + paraApi, + keyPrefix, + blockHash, + (items) => { + for (const item of items) { + const codesize = getBytecodeSize(hexToU8a(item.value)); + if (codesize > MAX_CONTRACT_SIZE_BYTES) { + const accountId = "0x" + item.key.slice(-40); + failedContractCodes.push({ accountId, codesize }); + } } - } - totalContracts += BigInt(items.length); - }); + totalContracts += BigInt(items.length); + }, + // WHEN DEBUGGING REPLACE THE EMPTY STRING WITH A PREFIX TO FETCH + "" + ); const t1 = performance.now(); const checkTime = (t1 - t0) / 1000; diff --git a/test/suites/smoke/test-polkadot-decoding.ts b/test/suites/smoke/test-polkadot-decoding.ts index 392a022298..cb49db1876 100644 --- a/test/suites/smoke/test-polkadot-decoding.ts +++ b/test/suites/smoke/test-polkadot-decoding.ts @@ -3,6 +3,7 @@ import chalk from "chalk"; import { describeSuite, beforeAll } from "@moonwall/cli"; import { ONE_HOURS } from "@moonwall/util"; import type { ApiPromise } from "@polkadot/api"; +import { extractStorageKeyComponents } from "../../helpers/storageQueries"; import { fail } from "node:assert"; // Change the following line to reproduce a particular case @@ -12,27 +13,6 @@ const FN_NAME = ""; const pageSize = (process.env.PAGE_SIZE && Number.parseInt(process.env.PAGE_SIZE)) || 500; -const extractStorageKeyComponents = (storageKey: string) => { - // The full storage key is composed of - // - The 0x prefix (2 characters) - // - The module prefix (32 characters) - // - The method name (32 characters) - // - The parameters (variable length) - const regex = /(?0x[a-f0-9]{32})(?[a-f0-9]{32})(?[a-f0-9]*)/i; - const match = regex.exec(storageKey); - - if (!match) { - throw new Error("Invalid storage key format"); - } - - const { moduleKey, fnKey, paramsKey } = match.groups!; - return { - moduleKey, - fnKey, - paramsKey, - }; -}; - const randomHex = (nBytes) => [...crypto.getRandomValues(new Uint8Array(nBytes))] .map((m) => ("0" + m.toString(16)).slice(-2))