Skip to content

Commit

Permalink
test: 🎲 randomize contract size smoke test (#3028)
Browse files Browse the repository at this point in the history
* extract some storage key helpers

* test random contract prefixes

* add reproduction steps to error

* fmt

* delete smoke/now

* improve error/debug msg

* slice if prefixes > 25

* fmt

* remove unused import

* fmt

* remove label from loop

* remove unused fn

* fmt

* appease linter
  • Loading branch information
pLabarta authored Dec 19, 2024
1 parent 265ce20 commit 823fc90
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 31 deletions.
96 changes: 96 additions & 0 deletions test/helpers/storageQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = /(?<moduleKey>0x[a-f0-9]{32})(?<fnKey>[a-f0-9]{32})(?<paramsKey>[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,
};
};
28 changes: 18 additions & 10 deletions test/suites/smoke/test-ethereum-contract-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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;
Expand Down
22 changes: 1 addition & 21 deletions test/suites/smoke/test-polkadot-decoding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = /(?<moduleKey>0x[a-f0-9]{32})(?<fnKey>[a-f0-9]{32})(?<paramsKey>[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))
Expand Down

0 comments on commit 823fc90

Please sign in to comment.