From c8309d83c6fb73da47c743c405f64f5a8b69c34a Mon Sep 17 00:00:00 2001 From: crystalin Date: Mon, 28 Oct 2024 16:31:57 +0100 Subject: [PATCH 01/15] fix identity --- src/utils/monitoring.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/monitoring.ts b/src/utils/monitoring.ts index 5d2990e..ac549ca 100644 --- a/src/utils/monitoring.ts +++ b/src/utils/monitoring.ts @@ -183,7 +183,7 @@ export const getAccountIdentity = async ( : [null, null]; identityCache[account] = { lastUpdate: Date.now(), - identity: identity[0], + identity: identity?.[0], superOf: superOfIdentity && ("info" in superOfIdentity ? superOfIdentity : superOfIdentity[0]), }; From 32050dcddd5fa131a0b5b5713f31af0bd459dae5 Mon Sep 17 00:00:00 2001 From: Alan Sapede Date: Sat, 9 Nov 2024 06:13:35 -0300 Subject: [PATCH 02/15] Migrates to ESM, bun and vitest (#57) --- README.md | 28 +- bun.lockb | Bin 0 -> 466052 bytes jest.config.js | 5 - package-lock.json | 23819 ++++------------ package.json | 50 +- rollup.config.js | 63 - scripts/update-readme.sh | 4 +- ...untime-1300-fix-force-unreserve-balance.ts | 8 +- .../runtime-1300-fix-orphan-requests.ts | 35 +- .../runtime-1500-find-non-sufficient-code.ts | 12 +- ...03-fix-orphaned-delegation-request-keys.ts | 36 +- .../runtime-1900-fix-at-stake-old-rounds.ts | 50 +- ...0-fix-staking-reserve-to-lock-migration.ts | 36 +- ...time-2000-lazy-migrate-pallet-democracy.ts | 22 +- src/index.ts | 20 +- src/indexers/block-fees-indexer.ts | 26 +- src/indexers/smart-contract-extracter.ts | 8 +- src/indexers/smart-contract-indexer.ts | 10 +- .../001-unlock-democracy-funds.ts | 28 +- .../002-clear-local-assets-storage.ts | 28 +- .../003-clear-suicided-contracts.ts | 35 +- .../state-manipulator/asset-manipulator.ts | 6 +- .../author-filtering-manipulator.ts | 6 +- .../authorize-upgrade-manipulator.ts | 8 +- .../state-manipulator/balances-manipulator.ts | 4 +- .../state-manipulator/collator-manipulator.ts | 6 +- .../collective-manipulator.ts | 5 +- .../state-manipulator/genesis-parser.ts | 7 +- .../state-manipulator/hrmp-manipulator.ts | 4 +- src/libs/helpers/state-manipulator/index.ts | 22 +- .../state-manipulator/round-manipulator.ts | 6 +- .../state-manipulator/spec-manipulator.ts | 6 +- .../state-manipulator/state-manager.ts | 36 +- .../state-manipulator/sudo-manipulator.ts | 4 +- .../validation-manipulator.ts | 4 +- .../state-manipulator/xcmp-manipulator.ts | 4 +- src/samples/set-proxies.ts | 33 +- src/scenarios/fill-staking-collators.ts | 9 +- src/scenarios/fill-staking-delegators.ts | 10 +- src/tools/auction-info.ts | 19 +- src/tools/availibity-core-info.ts | 7 +- src/tools/batch-csv-transfers.ts | 5 +- src/tools/block-polkadot-contribution.ts | 3 +- src/tools/block-timestamp.ts | 2 +- src/tools/check-balance.ts | 4 +- src/tools/check-finality.ts | 7 +- src/tools/collator-delegations.ts | 4 +- src/tools/collator-staking-requests.ts | 2 +- src/tools/evm-contract-storage-map.ts | 2 +- src/tools/evm-contract-storage.ts | 4 +- src/tools/execute-revocations.ts | 11 +- src/tools/export-state.ts | 14 +- src/tools/fast-execute-chopstick-proposal.ts | 13 +- src/tools/fast-track-proposal.ts | 33 +- src/tools/flood-evm-compute.ts | 13 +- src/tools/flood-evm-transfers.ts | 12 +- src/tools/flood-remarks.ts | 12 +- src/tools/fork-exported-state.ts | 5 +- src/tools/get-parachains-xcm-versions.ts | 2 +- src/tools/get-relay-runtime.ts | 2 +- src/tools/kill-prefix-delegators.ts | 13 +- src/tools/list-active-evm-accounts.ts | 6 +- src/tools/list-collator-candidates.ts | 5 +- src/tools/list-collator-rewards.ts | 7 +- src/tools/list-delegations.ts | 6 +- src/tools/list-delegators.ts | 6 +- src/tools/list-limbo-balance-accounts.ts | 18 +- src/tools/list-methods.ts | 2 +- src/tools/list-precompiles.ts | 11 +- src/tools/list-referendum.ts | 14 +- src/tools/list-storages.ts | 2 +- src/tools/monitor.ts | 5 +- src/tools/propose-motion.ts | 33 +- src/tools/proxy-announcements.ts | 5 +- src/tools/relay-hrmp.ts | 7 +- src/tools/run-moonbeam-fork.ts | 22 +- src/tools/scale.ts | 10 +- src/tools/search-account-transactions.ts | 3 +- .../send-clear-origin-messages-from-relay.ts | 19 +- src/tools/storage-module-key.ts | 2 +- src/tools/upgrade-network.ts | 35 +- src/tools/vote-motion.ts | 33 +- src/utils/monitoring.ts | 36 +- src/utils/networks.ts | 14 +- src/utils/proxy-chain.ts | 7 +- src/utils/referenda.ts | 33 +- src/utils/runner.ts | 3 +- src/utils/storage.ts | 4 +- src/utils/transactions.ts | 2 - src/utils/types.ts | 10 +- src/utils/web3/contracts.ts | 12 +- src/utils/web3/solidity.ts | 3 +- src/utils/web3/transactions.ts | 7 +- test/sample-state.json | 2 +- test/state-manipulation.spec.ts | 42 +- tsconfig.json | 14 +- vitest.config.ts | 7 + 97 files changed, 6699 insertions(+), 18440 deletions(-) create mode 100755 bun.lockb delete mode 100644 jest.config.js delete mode 100644 rollup.config.js create mode 100644 vitest.config.ts diff --git a/README.md b/README.md index e4c2512..87b81fe 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,21 @@ Tools related to Moonbeam blockchains # Requirements -* NodeJS v14+ +* bun v1+ + +## Installation + +``` +npm install +``` + +## Tests + +``` +bun run test +``` + +You can use `DEBUG=helper:*` for logs on the state manipation # Tools @@ -40,7 +54,7 @@ Options: Monitoring - + + + + +
+
+
+ + + + \ No newline at end of file diff --git a/package.json b/package.json index 4d93ccb..d0c1857 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moonbeam-tools", - "version": "0.1.0", + "version": "0.1.1", "description": "Set of tools for Moonbeam blockchain", "exports": { ".": { From 101b9062d862bf10266c083322851655451b9ef0 Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 9 Nov 2024 17:03:58 +0100 Subject: [PATCH 04/15] Add MBIP5 report to monitoring --- index.html | 61 ------------------------ src/utils/monitoring.ts | 101 ++++++++++++++++++++++++---------------- 2 files changed, 61 insertions(+), 101 deletions(-) delete mode 100644 index.html diff --git a/index.html b/index.html deleted file mode 100644 index 0148542..0000000 --- a/index.html +++ /dev/null @@ -1,61 +0,0 @@ - - - - - Monitoring - - - - - -
-
-
- - - - \ No newline at end of file diff --git a/src/utils/monitoring.ts b/src/utils/monitoring.ts index dcbb1ea..c3f3795 100644 --- a/src/utils/monitoring.ts +++ b/src/utils/monitoring.ts @@ -37,8 +37,10 @@ export interface BlockDetails { records: EventRecord[]; txWithEvents: TxWithEventAndFee[]; weightPercentage: number; + storageUsed: number; } + // TODO: Improve with cache and eviction const authorMappingCache: { [author: string]: { @@ -108,11 +110,11 @@ export const getAccountIdentities = async ( const superIdentityOpts = validSuperOfs.length > 0 ? await api.rpc.state.queryStorageAt[]>( - validSuperOfs.map( - (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), - at, - ), - ) + validSuperOfs.map( + (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), + at, + ), + ) : []; let index = 0; return superOfs.map((superOf) => { @@ -147,9 +149,8 @@ export const getAccountIdentities = async ( return account && identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf && superOf.identity - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ - (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }); }; @@ -164,22 +165,22 @@ export const getAccountIdentity = async ( if (!identityCache[account] || identityCache[account].lastUpdate < Date.now() - 3600 * 1000) { const [identity, superOfIdentity] = api.query.identity ? await Promise.all([ - api.query.identity - .identityOf(account.toString()) - .then((a) => (a.isSome ? a.unwrap() : null)), - api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { - const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; - if (!superOf) { - return null; - } - const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); - const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; - return { - identity, - data: superOf[1], - }; - }), - ]) + api.query.identity + .identityOf(account.toString()) + .then((a) => (a.isSome ? a.unwrap() : null)), + api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { + const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; + if (!superOf) { + return null; + } + const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); + const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; + return { + identity, + data: superOf[1], + }; + }), + ]) : [null, null]; identityCache[account] = { lastUpdate: Date.now(), @@ -193,9 +194,8 @@ export const getAccountIdentity = async ( return identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ - (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }; @@ -295,16 +295,27 @@ export const getBlockDetails = async (api: ApiPromise, blockHash: BlockHash) => fees.map((fee) => fee.inclusionFee.unwrapOrDefault()), feeMultiplier, ); - const blockWeight = txWithEvents.reduce((totalWeight, tx, index) => { + + const [blockWeight, ethWeight] = txWithEvents.reduce((stats, tx, index) => { // TODO: support weight v1/2 if (!tx.dispatchInfo) { - return totalWeight; + return stats; } const refTime = (tx.dispatchInfo.weight as any).toBn ? (tx.dispatchInfo.weight as any).toBigInt() : tx.dispatchInfo.weight.refTime?.toBigInt(); - return totalWeight + refTime; - }, 0n); + return [stats[0] + refTime, stats[1] + (tx.extrinsic.method.section == "ethereum" ? refTime : 0n)] + }, [0n, 0n]); + + const gasUsed = (await api.rpc.eth.getBlockByNumber(block.header.number.toNumber(), false)).unwrap().gasUsed.toBigInt(); + + const WEIGHT_TO_GAS_RATIO = 25_000n; // TODO: Find a way to retrieve dynamically + const GAS_LIMIT_STORAGE_GROWTH_RATIO = 366n;// TODO: Find a way to retrieve dynamically + const gasByRefTime = ethWeight / WEIGHT_TO_GAS_RATIO; + // console.log(`[${block.header.number.toNumber()} ${blockWeight}/${ethWeight}: ${gasByRefTime}/${gasUsed}`); + const storageUsed = gasByRefTime != gasUsed ? + Number(gasByRefTime / GAS_LIMIT_STORAGE_GROWTH_RATIO) : 0; // in bytes + return { block, isAuthorOrbiter: @@ -315,6 +326,7 @@ export const getBlockDetails = async (api: ApiPromise, blockHash: BlockHash) => weightPercentage: Number((blockWeight * 10000n) / maxBlockWeight) / 100, txWithEvents, records, + storageUsed } as BlockDetails; }; @@ -454,6 +466,16 @@ export function generateBlockDetailsLog( ? chalk.green(weight) : weight; + const storage = blockDetails.storageUsed.toFixed(0).padStart(5, " "); + const storageText = + blockDetails.storageUsed > 100000 + ? chalk.red(storage) + : blockDetails.storageUsed > 10000 + ? chalk.yellow(storage) + : blockDetails.storageUsed > 1000 + ? chalk.green(storage) + : storage; + let txPoolText = null; let poolIncText = null; if ("pendingTxs" in blockDetails) { @@ -510,7 +532,7 @@ export function generateBlockDetailsLog( ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); const refTime = (dispatchInfo.weight as any).toBn @@ -541,7 +563,7 @@ export function generateBlockDetailsLog( ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); } return tx.events.reduce((total, event) => { @@ -566,8 +588,8 @@ export function generateBlockDetailsLog( const authorId = blockDetails.authorName.length > 24 ? `${blockDetails.authorName.substring(0, 9)}..${blockDetails.authorName.substring( - blockDetails.authorName.length - 6, - )}` + blockDetails.authorName.length - 6, + )}` : blockDetails.authorName; const authorName = blockDetails.isAuthorOrbiter ? chalk.yellow(authorId) : authorId; @@ -583,11 +605,10 @@ export function generateBlockDetailsLog( .padEnd( 7, " ", - )} [${weightText}%, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${ - txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` - }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( - hash.length - 4, - )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; + )} [${weightText}%, ${storageText}B, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` + }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( + hash.length - 4, + )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; } export function printBlockDetails( From 788e36880721089b2283d9015921fbb030d7f48c Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 9 Nov 2024 17:11:12 +0100 Subject: [PATCH 05/15] Moves CI to bun --- .github/workflows/verify-compilation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/verify-compilation.yml b/.github/workflows/verify-compilation.yml index 1c33242..5d4edf5 100644 --- a/.github/workflows/verify-compilation.yml +++ b/.github/workflows/verify-compilation.yml @@ -19,4 +19,4 @@ jobs: run: npm install - name: Verify TypeScript compilation - run: npm run build + run: bun run build From bd4c88d3cac61eb3075e6d56e0ba5736f52ba338 Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 9 Nov 2024 17:11:50 +0100 Subject: [PATCH 06/15] Adds test to CI --- .github/workflows/verify-compilation.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/verify-compilation.yml b/.github/workflows/verify-compilation.yml index 5d4edf5..08f3c32 100644 --- a/.github/workflows/verify-compilation.yml +++ b/.github/workflows/verify-compilation.yml @@ -20,3 +20,6 @@ jobs: - name: Verify TypeScript compilation run: bun run build + + - name: Run tests + run: bun run test From f16f88f0b28904e193d5a01984de069e1d4fa795 Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 9 Nov 2024 17:13:44 +0100 Subject: [PATCH 07/15] Adds bun to CI --- .github/workflows/verify-compilation.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/verify-compilation.yml b/.github/workflows/verify-compilation.yml index 08f3c32..221e495 100644 --- a/.github/workflows/verify-compilation.yml +++ b/.github/workflows/verify-compilation.yml @@ -13,11 +13,14 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v2 with: - node-version: "14" + node-version: "20" - name: Install dependencies run: npm install + - name: Setup bun + uses: oven-sh/setup-bun@v2 + - name: Verify TypeScript compilation run: bun run build From 1ac1620d7c8bc4e583e2fdd506c0658b432c5c75 Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 9 Nov 2024 17:15:20 +0100 Subject: [PATCH 08/15] lint --- src/utils/monitoring.ts | 108 ++++++++++++++++++++---------------- src/utils/web3/contracts.ts | 2 +- 2 files changed, 60 insertions(+), 50 deletions(-) diff --git a/src/utils/monitoring.ts b/src/utils/monitoring.ts index c3f3795..bd4d487 100644 --- a/src/utils/monitoring.ts +++ b/src/utils/monitoring.ts @@ -40,7 +40,6 @@ export interface BlockDetails { storageUsed: number; } - // TODO: Improve with cache and eviction const authorMappingCache: { [author: string]: { @@ -110,11 +109,11 @@ export const getAccountIdentities = async ( const superIdentityOpts = validSuperOfs.length > 0 ? await api.rpc.state.queryStorageAt[]>( - validSuperOfs.map( - (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), - at, - ), - ) + validSuperOfs.map( + (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), + at, + ), + ) : []; let index = 0; return superOfs.map((superOf) => { @@ -149,8 +148,9 @@ export const getAccountIdentities = async ( return account && identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf && superOf.identity - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ + (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }); }; @@ -165,22 +165,22 @@ export const getAccountIdentity = async ( if (!identityCache[account] || identityCache[account].lastUpdate < Date.now() - 3600 * 1000) { const [identity, superOfIdentity] = api.query.identity ? await Promise.all([ - api.query.identity - .identityOf(account.toString()) - .then((a) => (a.isSome ? a.unwrap() : null)), - api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { - const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; - if (!superOf) { - return null; - } - const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); - const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; - return { - identity, - data: superOf[1], - }; - }), - ]) + api.query.identity + .identityOf(account.toString()) + .then((a) => (a.isSome ? a.unwrap() : null)), + api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { + const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; + if (!superOf) { + return null; + } + const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); + const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; + return { + identity, + data: superOf[1], + }; + }), + ]) : [null, null]; identityCache[account] = { lastUpdate: Date.now(), @@ -194,8 +194,9 @@ export const getAccountIdentity = async ( return identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ + (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }; @@ -296,25 +297,33 @@ export const getBlockDetails = async (api: ApiPromise, blockHash: BlockHash) => feeMultiplier, ); - const [blockWeight, ethWeight] = txWithEvents.reduce((stats, tx, index) => { - // TODO: support weight v1/2 - if (!tx.dispatchInfo) { - return stats; - } - const refTime = (tx.dispatchInfo.weight as any).toBn - ? (tx.dispatchInfo.weight as any).toBigInt() - : tx.dispatchInfo.weight.refTime?.toBigInt(); - return [stats[0] + refTime, stats[1] + (tx.extrinsic.method.section == "ethereum" ? refTime : 0n)] - }, [0n, 0n]); + const [blockWeight, ethWeight] = txWithEvents.reduce( + (stats, tx, index) => { + // TODO: support weight v1/2 + if (!tx.dispatchInfo) { + return stats; + } + const refTime = (tx.dispatchInfo.weight as any).toBn + ? (tx.dispatchInfo.weight as any).toBigInt() + : tx.dispatchInfo.weight.refTime?.toBigInt(); + return [ + stats[0] + refTime, + stats[1] + (tx.extrinsic.method.section == "ethereum" ? refTime : 0n), + ]; + }, + [0n, 0n], + ); - const gasUsed = (await api.rpc.eth.getBlockByNumber(block.header.number.toNumber(), false)).unwrap().gasUsed.toBigInt(); + const gasUsed = (await api.rpc.eth.getBlockByNumber(block.header.number.toNumber(), false)) + .unwrap() + .gasUsed.toBigInt(); const WEIGHT_TO_GAS_RATIO = 25_000n; // TODO: Find a way to retrieve dynamically - const GAS_LIMIT_STORAGE_GROWTH_RATIO = 366n;// TODO: Find a way to retrieve dynamically + const GAS_LIMIT_STORAGE_GROWTH_RATIO = 366n; // TODO: Find a way to retrieve dynamically const gasByRefTime = ethWeight / WEIGHT_TO_GAS_RATIO; // console.log(`[${block.header.number.toNumber()} ${blockWeight}/${ethWeight}: ${gasByRefTime}/${gasUsed}`); - const storageUsed = gasByRefTime != gasUsed ? - Number(gasByRefTime / GAS_LIMIT_STORAGE_GROWTH_RATIO) : 0; // in bytes + const storageUsed = + gasByRefTime != gasUsed ? Number(gasByRefTime / GAS_LIMIT_STORAGE_GROWTH_RATIO) : 0; // in bytes return { block, @@ -326,7 +335,7 @@ export const getBlockDetails = async (api: ApiPromise, blockHash: BlockHash) => weightPercentage: Number((blockWeight * 10000n) / maxBlockWeight) / 100, txWithEvents, records, - storageUsed + storageUsed, } as BlockDetails; }; @@ -532,7 +541,7 @@ export function generateBlockDetailsLog( ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); const refTime = (dispatchInfo.weight as any).toBn @@ -563,7 +572,7 @@ export function generateBlockDetailsLog( ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); } return tx.events.reduce((total, event) => { @@ -588,8 +597,8 @@ export function generateBlockDetailsLog( const authorId = blockDetails.authorName.length > 24 ? `${blockDetails.authorName.substring(0, 9)}..${blockDetails.authorName.substring( - blockDetails.authorName.length - 6, - )}` + blockDetails.authorName.length - 6, + )}` : blockDetails.authorName; const authorName = blockDetails.isAuthorOrbiter ? chalk.yellow(authorId) : authorId; @@ -605,10 +614,11 @@ export function generateBlockDetailsLog( .padEnd( 7, " ", - )} [${weightText}%, ${storageText}B, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` - }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( - hash.length - 4, - )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; + )} [${weightText}%, ${storageText}B, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${ + txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` + }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( + hash.length - 4, + )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; } export function printBlockDetails( diff --git a/src/utils/web3/contracts.ts b/src/utils/web3/contracts.ts index e22963d..0517801 100644 --- a/src/utils/web3/contracts.ts +++ b/src/utils/web3/contracts.ts @@ -27,7 +27,7 @@ export const deployContract = async ( const code = await customWeb3Request(web3, "eth_getCode", [contractAddress]); if (code && code.result && code.result != "0x") { - console.log(`Contract already deployed: ${code.result?.['length']} bytes`); + console.log(`Contract already deployed: ${code.result?.["length"]} bytes`); return; } From 2e6713daa7bb74c5ddc313ac958e15fec6204285 Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 9 Nov 2024 17:20:58 +0100 Subject: [PATCH 09/15] Improved readme --- README.md | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 7ca2edc..f924f32 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,29 @@ # moonbeam-tools -Tools related to Moonbeam blockchains +Tools related to Moonbeam blockchains. -# Requirements +## Requirements * bun v1+ -## Installation +## Actions +__Installation__: `npm install` +__Test__: `bun run test` +__Build__: `bun run build` +__Publish__: `bun publish` -``` -npm install -``` - -## Tests - -``` -bun run test -``` +## Debug You can use `DEBUG=helper:*` for logs on the state manipation # Tools -## Installation - -``` -sudo npm install -g moonbeam-tools@latest -``` - -## Running moonbeam-monitor +## Monitoring chains Allows to monitor a Moonbeam network. To do so, run the following command: ``` -moonbeam-monitor --networks moonriver +bunx moonbeam-tools --networks moonbeam moonriver ``` ``` @@ -119,7 +109,7 @@ The script `run-moonbeam-fork.ts` has been provided which allows you to fork the The simplest way to run a forked-network of `Moonbeam` is by calling: ``` -npm run fork +bun run fork ``` Which will grab the latest polkadot and moonbeam binaries, grab the latest snapshot of live state, modify some values (such as validator keys, and inject balances), From 7bb137d11e06bfbf39641be6bad79dec4f02e162 Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 9 Nov 2024 20:03:23 +0100 Subject: [PATCH 10/15] new release --- README.md | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f924f32..6ba0deb 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Options: Monitoring - + + + + + + +`, +); +// editorconfig-checker-enable + +const openCmd = (() => { + switch (process.platform) { + case "darwin": + return "open"; + case "win32": + return "start"; + default: + return "xdg-open"; + } +})(); + +exec(`${openCmd} ${outputFile}`); diff --git a/src/tools/fees.ts b/src/tools/fees.ts new file mode 100644 index 0000000..b2daeb6 --- /dev/null +++ b/src/tools/fees.ts @@ -0,0 +1,1120 @@ +import { exec as execProcess } from "child_process"; +import fs from "fs"; +import assert from "node:assert/strict"; +import util from "node:util"; +import { setTimeout } from "timers/promises"; + +import "@moonbeam-network/api-augment"; +import { ApiPromise, WsProvider } from "@polkadot/api"; +import "@polkadot/api-augment"; +import { SubmittableExtrinsic as SubmittableExtrinsicPromise } from "@polkadot/api/promise/types"; +import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types"; +import Keyring from "@polkadot/keyring"; +import { DispatchError, EventRecord } from "@polkadot/types/interfaces"; +import { EvmCoreErrorExitReason } from "@polkadot/types/lookup"; +import { BN, u8aToHex } from "@polkadot/util"; +import { ethers } from "ethers"; +import { AccessListish } from "ethers/lib/utils.js"; +import * as RLP from "rlp"; +import solc from "solc"; +import { JsonRpcResponseWithResult, Web3 } from "web3"; +import { Contract } from "web3-eth-contract"; +import yargs from "yargs"; +import { + ALITH_PRIVATE_KEY, + BALTATHAR_ADDRESS, + BALTATHAR_PRIVATE_KEY, + CHARLETH_ADDRESS, + CHARLETH_PRIVATE_KEY, + DOROTHY_PRIVATE_KEY, +} from "../utils/constants.ts"; + +const httpUrl = "http://127.0.0.1:9933"; +const wssUrl = "ws://127.0.0.1:9944"; + +const exec = util.promisify(execProcess); +const ethersApi = new ethers.providers.JsonRpcProvider(httpUrl); +const keyringEth = new Keyring({ type: "ethereum" }); +export const alith = keyringEth.addFromUri(ALITH_PRIVATE_KEY); +export const baltathar = keyringEth.addFromUri(BALTATHAR_PRIVATE_KEY); +export const charleth = keyringEth.addFromUri(CHARLETH_PRIVATE_KEY); +export const dorothy = keyringEth.addFromUri(DOROTHY_PRIVATE_KEY); +const web3 = new Web3(wssUrl); +web3.eth.accounts.wallet.add(ALITH_PRIVATE_KEY); + +/** + * This test assumes the following: + * + moonbeam + * - EVM calls are unfiltered. + * NormalFilter - Call::EVM(_) => true + * - EVM origin is allowed for all. + * type CallOrigin = EnsureAddressAlways; + * + * pub struct EnsureAddressAlways; + * impl EnsureAddressOrigin for EnsureAddressAlways { + * type Success = (); + * + * fn try_address_origin( + * _address: &H160, + * _origin: OuterOrigin, + * ) -> Result { + * Ok(()) + * } + * + * fn ensure_address_origin( + * _address: &H160, + * _origin: OuterOrigin, + * ) -> Result { + * Ok(()) + * } + * } + * + frontier + * - Baltathar pays no EVM fees and full substrate fees, while Charleth pays the opposite. + * let baltathar_addr = H160::from_str("0x3cd0a705a2dc65e5b1e1205896baa2be8a07c6e0").unwrap(); + * let (validate, payable) = if source == baltathar_addr { + * (false, Pays::Yes) + * } else { + * (true, Pays::No) + * }; + * + * Then start the node with the following command + * ./target/release/moonbeam \ + * --execution=Native \ + * --wasm-execution=interpreted-i-know-what-i-do \ + * --ethapi=txpool \ + * --no-hardware-benchmarks \ + * --no-telemetry \ + * --no-prometheus \ + * --force-authoring \ + * --rpc-cors=all \ + * --alice \ + * --chain=moonbase-dev \ + * --sealing=manual \ + * --in-peers=0 \ + * --out-peers=0 -linfo \ + * --tmp + * + * Examples: + * ts-node ./src/tools/fees.ts --name fees --type compute + * ts-node ./src/tools/fees.ts --name fees --type compute --multiplier 300000000 + * ts-node ./src/tools/fees.ts --name fees --type length-small + * ts-node ./src/tools/fees.ts --name fees --type length-big + * + * The result will open in the browser once done + */ + +/** + * Observations + * - The first EVM call causes the SmartContract storage to be initialized and costs around 20,000 gas (we avoid this by pre-initializing the storage) + * - The fees sometime jump abruptly and stay at that level, this is due to nonce going from 1 byte to 2 bytes and so on + * - The block fill ratio is computed differently by transaction-payment; the multiplier is updated only due to `Normal` weight class (actual / max). + * The actual weight also contains some extra weight that doesn't belong to extrinsics (maybe coming from on_initialize/on_finalize) + */ + +/// === test methods === /// + +const TESTER_CONTRACT = `// SPDX-License-Identifier: GPL-3.0-only + pragma solidity >=0.8.3; + + contract Tester { + + uint256 public count; + + function infinite() public pure { + while (true) {} + } + + function incrementalLoop(uint256 n) public { + uint256 i = 0; + while (i < n) { + count = count + 1; + i += 1; + } + } + + function bigData(bytes memory b) public { + // do nothing + } + }`; +const TESTER_JSON = compileSolidity(TESTER_CONTRACT); +const TESTER_INTERFACE = new ethers.utils.Interface(TESTER_JSON.contract.abi); + +async function runTest( + api: ApiPromise, + options: { callType: "compute" | "length-small" | "length-big"; multiplier: BN | null }, +) { + const result = []; + console.log(`options: ${JSON.stringify(options)}`); + let contractAddr = "0xc01Ee7f10EA4aF4673cFff62710E1D7792aBa8f3"; + + // deploy contract + const maxBlockWeight = api.consts.system.blockWeights.maxBlock.refTime.toBn(); + const blockNumber = (await api.rpc.chain.getBlock()).block.header.number.toNumber(); + const nextFeeMultiplierOriginal = await api.query.transactionPayment.nextFeeMultiplier(); + if (blockNumber === 0) { + const { contract, rawTx } = await createContract(TESTER_JSON, { + ...ALITH_TRANSACTION_TEMPLATE, + gas: 900_000, + gasPrice: 1_250_000_000, + }); + + const results = await createBlock(api, rawTx); + assert.equal(results, true, "failure during block creation"); + await expectEVMSuccess(api); + console.log(`contractAddress: ${contract.options.address.toString()}`); + contractAddr = contract.options.address; + } + + // use the specified call type + const contractCall = (() => { + switch (options.callType) { + case "compute": + return TESTER_INTERFACE.encodeFunctionData("incrementalLoop", [10]); + case "length-small": + return TESTER_INTERFACE.encodeFunctionData("bigData", [new Array(100).fill(0x01)]); + case "length-big": + return TESTER_INTERFACE.encodeFunctionData("bigData", [new Array(50 * 1024).fill(0x01)]); + default: + throw new Error(`invalid options.callType ${options.callType}`); + } + })(); + + // init the smart contract storage, if not done then the first tx has a storage initialization cost of around 20,000 + await createBlock(api, [ + await api.tx.evm + .call( + dorothy.address, + contractAddr, + contractCall, + 0, + 900_000n, + 2_000_000_000n, + null, + null, + [], + ) + .signAsync(dorothy), + ]); + + // override nextFeeMultiplier if needed, note that its value will change immediately after block creation + const nextFeeMultiplierOverride = options.multiplier || nextFeeMultiplierOriginal; + if (nextFeeMultiplierOverride) { + console.log(`overriding nextFeeMultiplier to ${nextFeeMultiplierOverride}`); + await createBlock(api, [ + await api.tx.balances.transfer(contractAddr, 0).signAsync(alith), + await api.tx.sudo + .sudo( + await api.tx.system + .setStorage([ + [ + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc", + u8aToHex(api.createType("u128", nextFeeMultiplierOverride).toU8a()), + ], + ]) + .signAsync(alith), + ) + .signAsync(alith), + ]); + } + + // start load test + // const loadFactors = [...generateLoad(60, 1), ...Array(10).fill(0)]; + // const loadFactors = [...Array(183).fill(19)]; + const loadFactors = [...Array(183).fill(55)]; + // const loadFactors = [...Array(1).fill(0)]; + const repsPerLoad = 30; + for await (const [loadFactorIndex, loadFactor] of loadFactors.entries()) { + console.log( + `load: ${loadFactor} (${repsPerLoad} reps) ${loadFactorIndex + 1}/${loadFactors.length}`, + ); + for await (const rep of new Array(repsPerLoad).keys()) { + // uncomment the following code to reduce feeMultiplier by 10 each 100 blocks + // if (blockN % 100 === 0) { + // console.log(`feeMultiplier ${feeMultiplier.toString()}`); + // await createBlock(api, [ + // await api.tx.sudo + // .sudo( + // await api.tx.system + // .setStorage([ + // [ + // "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc", + // u8aToHex(api.createType("u128", feeMultiplier).toU8a()), + // ], + // ]) + // .signAsync(alith) + // ) + // .signAsync(alith), + // ]); + // if (feeMultiplier.eqn(1)) { + // feeMultiplier = BN_ZERO; + // break; + // } + // feeMultiplier = feeMultiplier.divn(10); + // } + // blockN++; + + const multiplierBefore = await api.query.transactionPayment.nextFeeMultiplier(); + const fees = await txObserveFeeDiff(api, async () => { + const txs = [ + // fill block + await api.tx.sudo + .sudo(await api.tx.system.fillBlock(loadFactor * 10_000_000).signAsync(alith)) + .signAsync(alith), + + // charge substrate fees + await api.tx.evm + .call(baltathar.address, contractAddr, contractCall, 0, 11_000_000n, 0n, null, null, []) + .signAsync(baltathar, { tip: 1n * 10n ** 15n }), + + // charge EVM fees + await api.tx.evm + .call( + charleth.address, + contractAddr, + contractCall, + 0, + 11_000_000n, + 2_000_000_000_000_000n, + null, + null, + [], + ) + .signAsync(charleth, { tip: 1n * 10n ** 15n }), + ]; + + txs.forEach((t) => { + console.log(t.hash.toString()); + }); + + return txs; + }); + + // get block details + const transactions = { + substrate: null, + evm: null, + }; + const block = await api.rpc.chain.getBlock(); + for (const [i, ext] of block.block.extrinsics.entries()) { + if (ext.signer.eq(BALTATHAR_ADDRESS)) { + transactions.substrate = { + index: i, + extrinsicLength: ext.encodedLength, + extrinsic: ext, + }; + } else if (ext.signer.eq(CHARLETH_ADDRESS)) { + transactions.evm = { + index: i, + extrinsicLength: ext.encodedLength, + extrinsic: ext, + }; + } + } + + // compute block weight, from events + const weights = {}; + const events = await api.query.system.events(); + let totalBlockWeight = new BN(0); + for (const { phase, event } of events) { + if (phase.isApplyExtrinsic) { + if ( + api.events.system.ExtrinsicSuccess.is(event) || + api.events.system.ExtrinsicFailed.is(event) + ) { + weights[phase.asApplyExtrinsic.toNumber()] = + event.data.dispatchInfo.weight.refTime.toBn(); + } + } + } + if (!transactions.substrate || transactions.evm) { + } + for (const i of Object.keys(weights)) { + const key = parseInt(i); + if (transactions.substrate && transactions.substrate.index === key) { + transactions.substrate.weight = weights[i].toString(); + } else if (transactions.evm && transactions.evm.index === key) { + transactions.evm.weight = weights[i].toString(); + } + switch (parseInt(i)) { + case transactions.substrate.index: + transactions.substrate.weight = weights[i].toString(); + break; + case transactions.evm.index: + transactions.evm.weight = weights[i].toString(); + break; + } + totalBlockWeight = totalBlockWeight.add(weights[i]); + } + + // get feeDetails + + const feeDetails = ( + await api.rpc.payment.queryFeeDetails(transactions.substrate.extrinsic.toHex()) + ).inclusionFee.unwrap(); + const supplyFactor = 1; // 100 for moonbeam, 1 otherwise + const substrateFeeDetails = { + baseFee: feeDetails.baseFee.toString(), + lengthFee: feeDetails.lenFee.toString(), + adjustedWeightFee: multiplierBefore + .mul(new BN(transactions.substrate.weight).muln(50_000 * supplyFactor)) + .div(new BN("1000000000000000000")) + .toString(), + total: null, + }; + substrateFeeDetails.total = Object.values(substrateFeeDetails) + .reduce((acc, v) => acc.add(new BN(v)), new BN(0)) + .toString(); + + const multiplierAfter = await api.query.transactionPayment.nextFeeMultiplier(); + + delete transactions.substrate.extrinsic; + delete transactions.evm.extrinsic; + const data = { + fullPercent: totalBlockWeight.muln(100).div(maxBlockWeight).toNumber(), + ...fees, + transactions, + substrateFeeDetails, + multiplier: { + before: multiplierBefore.toString(), + after: multiplierAfter.toString(), + }, + block: (await api.rpc.chain.getBlock()).block.header.number.toNumber(), + }; + result.push(data); + if (data.block === 4) { + throw Error("FOUR!"); + } + } + } + + return { + multiplier: nextFeeMultiplierOverride.toString(), + callType: options.callType, + result, + }; +} + +function generateLoad(middle: number, inc: number = 1): number[] { + const load = []; + for (let i = 0; i <= middle; i += inc) { + load.push(i); + } + for (let i = 0; i <= 50; i++) { + load.push(middle); + } + for (let i = middle; i >= 0; i -= inc) { + load.push(i); + } + + return load; +} + +async function txObserveFeeDiff( + api: ApiPromise, + txFunc: () => Promise, +) { + const txs = await txFunc(); + const balanceBeforeBaltathar = await api.query.system.account(BALTATHAR_ADDRESS); + const balanceBeforeCharleth = await api.query.system.account(CHARLETH_ADDRESS); + await createBlock(api, txs); + const balanceAfterBaltathar = await api.query.system.account(BALTATHAR_ADDRESS); + const balanceAfterCharleth = await api.query.system.account(CHARLETH_ADDRESS); + + return { + substrate: balanceBeforeBaltathar.data.free.sub(balanceAfterBaltathar.data.free).toString(), + evm: balanceBeforeCharleth.data.free.sub(balanceAfterCharleth.data.free).toString(), + }; +} + +/// === block creation methods === /// + +async function expectEVMSuccess(api: ApiPromise) { + const events = await api.query.system.events(); + const ethereumResult = events.find( + ({ event: { section, method } }) => section == "ethereum" && method == "Executed", + ).event.data[3] as EvmCoreErrorExitReason; + assert.equal(ethereumResult.isSucceed, true, "EVM operation failed"); +} + +function extractError(events: EventRecord[] = []): DispatchError | undefined { + return events + .filter(({ event }) => "system" === event.section && ["ExtrinsicFailed"].includes(event.method)) + .map( + ({ + event: { + data: [dispatchError], + }, + }) => dispatchError as DispatchError, + )[0]; +} + +async function customWeb3Request(web3: Web3, method: string, params: any[]) { + return new Promise((resolve, reject) => { + (web3.currentProvider as any).send( + { + jsonrpc: "2.0", + id: 1, + method, + params, + }, + (error: Error | null, result?: JsonRpcResponseWithResult) => { + if (error) { + reject( + `Failed to send custom request (${method} (${params + .map((p) => { + const str = p.toString(); + return str.length > 128 ? `${str.slice(0, 96)}...${str.slice(-28)}` : str; + }) + .join(",")})): ${error.message || error.toString()}`, + ); + } + resolve(result); + }, + ); + }); +} + +interface BlockCreation { + parentHash?: string; + finalize?: boolean; +} +type ExtrinsicCreation = boolean; +async function createBlock< + ApiType extends ApiTypes, + Call extends + | SubmittableExtrinsic + | Promise> + | string + | Promise, + Calls extends Call | Call[], +>(api: ApiPromise, transactions?: Calls, options: BlockCreation = {}) { + const results: ({ type: "eth"; hash: string } | { type: "sub"; hash: string })[] = []; + const txs = + transactions == undefined ? [] : Array.isArray(transactions) ? transactions : [transactions]; + for await (const call of txs) { + if (typeof call == "string") { + // Ethereum + results.push({ + type: "eth", + hash: (await customWeb3Request(web3, "eth_sendRawTransaction", [call])).result.toString(), + }); + } else if (call.isSigned) { + results.push({ + type: "sub", + hash: (await call.send()).toString(), + }); + } else { + results.push({ + type: "sub", + hash: (await call.signAndSend(alith)).toString(), + }); + } + } + + const { parentHash, finalize } = options; + const block = parentHash + ? await api.rpc.engine.createBlock(true, finalize, parentHash) + : await api.rpc.engine.createBlock(true, finalize); + const blockHash = block.get("hash").toString(); + + // No need to extract events if no transactions + if (results.length == 0) { + return { + block, + result: null, + }; + } + + // We retrieve the events for that block + const allRecords: EventRecord[] = (await (await api.at(blockHash)).query.system.events()) as any; + // We retrieve the block (including the extrinsics) + const blockData = await api.rpc.chain.getBlock(blockHash); + + const result: ExtrinsicCreation[] = results.map((result) => { + const extrinsicIndex = + result.type == "eth" + ? allRecords + .find( + ({ phase, event: { section, method, data } }) => + phase.isApplyExtrinsic && + section == "ethereum" && + method == "Executed" && + data[2].toString() == result.hash, + ) + ?.phase?.asApplyExtrinsic?.toNumber() + : blockData.block.extrinsics.findIndex((ext) => ext.hash.toHex() == result.hash); + // We retrieve the events associated with the extrinsic + const events = allRecords.filter( + ({ phase }) => phase.isApplyExtrinsic && phase.asApplyExtrinsic.toNumber() === extrinsicIndex, + ); + const failure = extractError(events); + const successful = extrinsicIndex !== undefined && !failure; + return successful; + }); + + // Adds extra time to avoid empty transaction when querying it + if (results.find((r) => r.type == "eth")) { + await setTimeout(2); + } + + return Array.isArray(transactions) ? result : (result[0] as boolean); +} + +const ALITH_TRANSACTION_TEMPLATE: TransactionOptions = { + from: alith.address, + privateKey: ALITH_PRIVATE_KEY, + nonce: null, + gas: 500_000, + gasPrice: 1_000_000_000, + value: "0x00", +}; +interface TransactionOptions { + from?: string; + to?: string; + privateKey?: string; + nonce?: number; + gas?: string | number; + gasPrice?: string | number; + maxFeePerGas?: string | number; + maxPriorityFeePerGas?: string | number; + value?: string | number; + data?: string; + accessList?: AccessListish; // AccessList | Array<[string, Array]> +} +async function createTransaction( + options: TransactionOptions, + ethTransactionType = "Legacy", +): Promise { + const isLegacy = ethTransactionType === "Legacy"; + const isEip2930 = ethTransactionType === "EIP2930"; + const isEip1559 = ethTransactionType === "EIP1559"; + + const gasPrice = options.gasPrice !== undefined ? options.gasPrice : 1_000_000_000; + const maxPriorityFeePerGas = + options.maxPriorityFeePerGas !== undefined ? options.maxPriorityFeePerGas : 0; + const value = options.value !== undefined ? options.value : "0x00"; + const from = options.from || alith.address; + const privateKey = options.privateKey !== undefined ? options.privateKey : ALITH_PRIVATE_KEY; + + // Instead of hardcoding the gas limit, we estimate the gas + const gas = + options.gas || + (await web3.eth.estimateGas({ + from: from, + to: options.to, + data: options.data, + })); + + const maxFeePerGas = options.maxFeePerGas || 1_000_000_000; + const accessList = options.accessList || []; + const nonce = + options.nonce != null ? options.nonce : await web3.eth.getTransactionCount(from, "pending"); + + let data, rawTransaction; + if (isLegacy) { + data = { + from, + to: options.to, + value: value && value.toString(), + gasPrice, + gas, + nonce: nonce, + data: options.data, + }; + const tx = await web3.eth.accounts.signTransaction(data, privateKey); + rawTransaction = tx.rawTransaction; + } else { + const signer = new ethers.Wallet(privateKey, ethersApi); + const chainId = await web3.eth.getChainId(); + if (isEip2930) { + data = { + from, + to: options.to, + value: value && value.toString(), + gasPrice, + gasLimit: gas, + nonce: nonce, + data: options.data, + accessList, + chainId, + type: 1, + }; + } else if (isEip1559) { + data = { + from, + to: options.to, + value: value && value.toString(), + maxFeePerGas, + maxPriorityFeePerGas, + gasLimit: gas, + nonce: nonce, + data: options.data, + accessList, + chainId, + type: 2, + }; + } + rawTransaction = await signer.signTransaction(data); + } + + return rawTransaction; +} + +async function createContract( + contractCompiled: Compiled, + options: TransactionOptions = ALITH_TRANSACTION_TEMPLATE, + contractArguments: any[] = [], +): Promise<{ rawTx: string; contract: Contract; contractAddress: string }> { + const from = options.from !== undefined ? options.from : alith.address; + const nonce = options.nonce || Number(await web3.eth.getTransactionCount(from)); + + const contractAddress = + "0x" + + web3.utils + .sha3(RLP.encode([from, nonce]) as any) + .slice(12) + .substring(14); + + const contract = new web3.eth.Contract(contractCompiled.contract.abi, contractAddress); + const data = contract + .deploy({ + data: contractCompiled.byteCode, + arguments: contractArguments, + }) + .encodeABI(); + + const rawTx = await createTransaction({ ...options, from, nonce, data }); + + return { + rawTx, + contract, + contractAddress, + }; +} + +/// === solidity compile methods === /// + +export interface Compiled { + byteCode: string; + contract: any; + sourceCode: string; +} +function compileSolidity(fileContents: string): Compiled { + // const fileContents = fs.readFileSync(filepath).toString(); + const result = JSON.parse( + solc.compile( + JSON.stringify({ + language: "Solidity", + sources: { + "main.sol": { + content: fileContents, + }, + }, + settings: { + outputSelection: { + "*": { + "*": ["*"], + }, + }, + }, + }), + { + import: (_: string) => { + return { error: "imports not supported" }; + }, + }, + ), + ); + if (!result.contracts) { + throw result; + } + const allContractNames = Object.keys(result.contracts["main.sol"]); + const reduced = allContractNames.reduce((p, contractName) => { + p[contractName] = { + byteCode: "0x" + result.contracts["main.sol"][contractName].evm.bytecode.object, + contract: result.contracts["main.sol"][contractName], + sourceCode: fileContents, + }; + return p; + }, {}); + return reduced[allContractNames[0]]; +} + +/// === main === /// + +async function view(input: string, output: string, open: boolean) { + const data = JSON.parse(fs.readFileSync(input).toString("utf-8")); + const labels = data.result.map((x: any) => x["block"]); + const fullPercent = data.result.map((x: any) => x["fullPercent"]); + const substrateFees = data.result.map((x: any) => new BN(x["substrate"]).toString()); + const evmFees = data.result.map((x: any) => new BN(x["evm"]).toString()); + const multiplier = data.result.map((x: any) => new BN(x["multiplier"]["before"]).toString()); + const diff = data.result.map((x: any) => { + const a = new BN(x["substrate"]); + const b = new BN(x["evm"]); + return a.sub(b).abs().muln(100).div(a.add(b).divn(2)).toString(); + }); + const diffSubstrate = data.result.map((x: any) => { + const a = new BN(x["substrate"]); + const b = new BN(x["evm"]); + return a.sub(b).toString(); + }); + + // editorconfig-checker-disable + fs.writeFileSync( + output, + ` + + + + + + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + + + `, + ); + // editorconfig-checker-enable + + const openCmd = (() => { + switch (process.platform) { + case "darwin": + return "open"; + case "win32": + return "start"; + default: + return "xdg-open"; + } + })(); + + if (open) { + await exec(`${openCmd} ${output}`); + } +} + +const argv = yargs(process.argv.slice(2)) + .usage("Usage: $0") + .version("1.0.0") + .options({ + name: { + type: "string", + description: "The output file name", + demandOption: true, + }, + multiplier: { + type: "string", + description: "The multiplier override", + default: "", + }, + view: { + type: "boolean", + description: "View existing file", + }, + type: { + type: "string", + description: "View existing file", + choices: ["compute", "length-small", "length-big"], + demandOption: false, + default: "compute", + }, + }).argv; + +async function main() { + const name = `${argv.name}-${argv.type}`; + if (argv.view) { + await view(`${name}.json`, `${name}.html`, true); + return; + } + + const api = await ApiPromise.create({ + initWasm: false, + provider: new WsProvider(wssUrl), + }); + + try { + const results = await runTest(api, { + callType: argv.type as any, + multiplier: argv.multiplier.length === 0 ? null : new BN(argv.multiplier), + }); + fs.writeFileSync(`${name}.json`, JSON.stringify(results, null, 2)); + await view(`${name}.json`, `${name}.html`, true); + } finally { + await api.disconnect(); + } +} + +main() + .catch((err) => console.error("ERR!", err)) + .finally(() => process.exit(0)); From 059c731ee34d40c68071d4a1ae6e7ca06a859e31 Mon Sep 17 00:00:00 2001 From: crystalin Date: Wed, 20 Nov 2024 10:53:26 +0100 Subject: [PATCH 12/15] Adds get transaction data tool --- src/tools/get-transaction-data.ts | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/tools/get-transaction-data.ts diff --git a/src/tools/get-transaction-data.ts b/src/tools/get-transaction-data.ts new file mode 100644 index 0000000..a49579a --- /dev/null +++ b/src/tools/get-transaction-data.ts @@ -0,0 +1,39 @@ +import { ApiPromise, WsProvider } from "@polkadot/api"; +import fs from "fs"; +import { getApiFor, NETWORK_YARGS_OPTIONS } from "src/utils/networks.ts"; +import yargs from "yargs"; + +export const NETWORK_WS_URLS: { [name: string]: string } = { + rococo: "wss://rococo-rpc.polkadot.io", + westend: "wss://westend.api.onfinality.io/public-ws", + kusama: "wss://kusama.api.onfinality.io/public-ws", + polkadot: "wss://polkadot.api.onfinality.io/public-ws", +}; + +const argv = yargs(process.argv.slice(2)) + .usage("Usage: $0") + .version("1.0.0") + .options({ + ...NETWORK_YARGS_OPTIONS, + at: { + type: "number", + description: "Block number", + } + }).argv; + +const main = async () => { + const api = await getApiFor(argv); + + const blockHash = argv.at + ? await api.rpc.chain.getBlockHash(argv.at) + : await api.rpc.chain.getBlockHash(); + const block = await api.rpc.chain.getBlock(blockHash); + + block.block.extrinsics.forEach((ex, index) => { + console.log(index, `${ex.method.section.toString()}.${ex.method.method.toString()} [${ex.hash.toHex()}]\n${ex.method.method.toString() == "setValidationData" ? "..." : ex.toHex()}`); + }); + + await api.disconnect(); +}; + +main(); From 1936f4f026c5865630317dc13a9373f350165e19 Mon Sep 17 00:00:00 2001 From: crystalin Date: Fri, 22 Nov 2024 11:28:31 +0100 Subject: [PATCH 13/15] Colorize Storage list --- src/tools/list-storages.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/tools/list-storages.ts b/src/tools/list-storages.ts index 8ca6763..e80c977 100644 --- a/src/tools/list-storages.ts +++ b/src/tools/list-storages.ts @@ -1,6 +1,8 @@ import yargs from "yargs"; +import chalk from "chalk"; import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { xxhashAsHex } from "@polkadot/util-crypto"; const argv = yargs(process.argv.slice(2)) .usage("Usage: $0") @@ -9,15 +11,26 @@ const argv = yargs(process.argv.slice(2)) ...NETWORK_YARGS_OPTIONS, }).argv; +const capitalize = (s) => { + return String(s[0]).toUpperCase() + String(s).slice(1); +} const main = async () => { const api = await getApiFor(argv); for (const section of Object.keys(api.query)) { - console.log(`${section}`); + const palletName = section == "evm" ? "EVM" : capitalize(section); + const sectionKey = xxhashAsHex(palletName, 128); + console.log(`${chalk.yellow(palletName)}`); for (const method of Object.keys(api.query[section])) { - console.log( - ` ${`${section}.${method}`.padStart(50, " ")}: ${api.query[section][method].keyPrefix()}`, - ); + if (api.query[section][method].keyPrefix().includes(sectionKey.slice(2))) { + console.log( + ` ${`${section}.${method}`.padStart(50, " ")}: ${chalk.yellow(sectionKey)}${api.query[section][method].keyPrefix().slice(34)}`, + ); + } else { + console.log( + ` ${`${section}.${method}`.padStart(50, " ")}: ${api.query[section][method].keyPrefix()}`, + ); + } } } await api.disconnect(); From bb21a7d2e38114160c53f4fe781c8efad48c85dd Mon Sep 17 00:00:00 2001 From: crystalin Date: Sat, 23 Nov 2024 21:58:18 +0100 Subject: [PATCH 14/15] Adds support for AsyncBacking SlotInfo in State Manipulator --- .../state-manipulator/cumulus-manipulator.ts | 43 +++++++ .../state-manipulator/state-manager.ts | 4 +- src/tools/benchmark-lazy-nodes.ts | 108 ++++++++++++++++++ src/utils/monitoring.ts | 72 ++++++------ src/utils/networks.ts | 2 +- test/state-manipulation.spec.ts | 8 ++ 6 files changed, 199 insertions(+), 38 deletions(-) create mode 100644 src/libs/helpers/state-manipulator/cumulus-manipulator.ts create mode 100644 src/tools/benchmark-lazy-nodes.ts diff --git a/src/libs/helpers/state-manipulator/cumulus-manipulator.ts b/src/libs/helpers/state-manipulator/cumulus-manipulator.ts new file mode 100644 index 0000000..23923b1 --- /dev/null +++ b/src/libs/helpers/state-manipulator/cumulus-manipulator.ts @@ -0,0 +1,43 @@ +import Debug from "debug"; + +import { + Action, + encodeStorageBlake128MapKey, + encodeStorageKey, + StateLine, + StateManipulator, +} from "./genesis-parser.ts"; +import { nToHex } from "@polkadot/util"; + +const debug = Debug("helper:cumulus-manipulator"); + +export class CumulusManipulator implements StateManipulator { + private readonly newTimestamp: bigint; + + private readonly slotInfoKey: string; + + constructor(newTimestamp: bigint) { + this.newTimestamp = newTimestamp; + this.slotInfoKey = encodeStorageKey("AsyncBacking", "SlotInfo"); + } + + processRead = (_) => {}; + + prepareWrite = () => { + }; + + processWrite = ({ key, value }) => { + if (key.startsWith(this.slotInfoKey)) { + debug(`Found async backing SlotInfo: ${value}. Resetting to ${this.newTimestamp}`); + return { + action: "remove" as Action, + extraLines: [ + { + key: key, + value: `0x${nToHex(this.newTimestamp, { isLe: true, bitLength: 64 }).slice(2)}01000000`, + }, + ], + }; + } + }; +} diff --git a/src/libs/helpers/state-manipulator/state-manager.ts b/src/libs/helpers/state-manipulator/state-manager.ts index ae41ef0..6a16297 100644 --- a/src/libs/helpers/state-manipulator/state-manager.ts +++ b/src/libs/helpers/state-manipulator/state-manager.ts @@ -24,6 +24,7 @@ import { SpecManipulator } from "./spec-manipulator.ts"; import { SudoManipulator } from "./sudo-manipulator.ts"; import { ValidationManipulator } from "./validation-manipulator.ts"; import { XCMPManipulator } from "./xcmp-manipulator.ts"; +import { CumulusManipulator } from "./cumulus-manipulator.ts"; const debug = Debug("helper:state-manager"); @@ -115,7 +116,7 @@ export async function downloadExportedState( return { stateFile, stateInfo }; } } - const client = new Client(`http://states.kaki.dev`); + const client = new Client(`https://export.kaki.dev`); const downloadedStateInfo: StateInfo = (await ( await client.request({ path: `/${network}-state${stateDate ? `-${stateDate}` : ""}.info.json`, @@ -215,6 +216,7 @@ export async function neutralizeExportedState( return { current, first: 0, length: 100 }; }), new AuthorFilteringManipulator(100), + new CumulusManipulator(0n), new SudoManipulator(ALITH_ADDRESS), new CollatorManipulator(ALITH_ADDRESS, ALITH_SESSION_ADDRESS), new HRMPManipulator(), diff --git a/src/tools/benchmark-lazy-nodes.ts b/src/tools/benchmark-lazy-nodes.ts new file mode 100644 index 0000000..c08bc69 --- /dev/null +++ b/src/tools/benchmark-lazy-nodes.ts @@ -0,0 +1,108 @@ +import yargs from "yargs"; +import chalk from "chalk"; + +import { getApiFor, NETWORK_YARGS_OPTIONS } from "../utils/networks.ts"; +import { mapExtrinsics } from "src/utils/types.ts"; +import { getBlockDetails } from "src/utils/monitoring.ts"; + +const argv = yargs(process.argv.slice(2)) + .usage("Usage: $0") + .version("1.0.0") + .options({ + ...NETWORK_YARGS_OPTIONS, + at: { + type: "number", + description: "Block number to look into", + }, + tx: { + type: "string", + description: "transaction to replay", + demandOption: true, + }, + }).argv; + +const main = async () => { + const api = await getApiFor(argv); + + const blockHash = argv.at + ? await api.rpc.chain.getBlockHash(argv.at) + : await api.rpc.chain.getBlockHash(); + + const block = await getBlockDetails(api, blockHash); + + const tx = block.txWithEvents.find((tx) => { + return tx.extrinsic.hash.toHex().toLocaleLowerCase() == argv.tx.toLocaleLowerCase() + }); + + if (!tx) { + console.error(`Transaction ${argv.tx} not found`); + process.exit(1); + } + console.log(`Transaction ${argv.tx} found ${!tx.dispatchError ? "✅" : "🟥"} (ref: ${tx.dispatchInfo.weight.refTime.toString().padStart(12)}, pov: ${tx.dispatchInfo.weight.proofSize.toString().padStart(9)})`); + + const extrinsicData = tx.extrinsic.toHex(); + + const servers = [{ + "name": "original", + "color": "gray", + "url": "ws://127.0.0.1:9947", + }, { + "name": "pov refund", + "color": "yellow", + "url": "ws://127.0.0.1:9948", + }, { + "name": "pov refund + fix", + "color": "green", + "url": "ws://127.0.0.1:9949", + }]; + + + + await Promise.all(servers.map(async (server) => { + const api = await getApiFor({ url: server.url }); + const serverName = chalk[server.color](server.name.padStart(20)); + let valid = 0; + + const unsubscribe = await api.rpc.chain.subscribeNewHeads(async (header) => { + // console.log(`[${serverName}] Chain is at block: #${header.number}`); + const block = await getBlockDetails(api, header.hash); + + for (const tx of block.txWithEvents) { + if (tx.extrinsic.hash.toHex().toLocaleLowerCase() == argv.tx.toLocaleLowerCase()) { + console.log(`[${serverName}] Transaction ${argv.tx} found in block ${header.number} ${!tx.dispatchError ? "✅" : "🟥"} (ref: ${tx.dispatchInfo.weight.refTime.toString().padStart(12)}, pov: ${tx.dispatchInfo.weight.proofSize.toString().padStart(9)})`); + unsubscribe(); + valid = 1; + break; + } + }; + }) + + const extrinsic = api.tx(extrinsicData); + + try { + const hash = await api.rpc.author.submitExtrinsic(extrinsic); + console.log(`[${serverName}] Submitted hash: ${hash}`); + } catch (error) { + console.error(`[${serverName}] Failed to submit:`, error); + } + + while (valid == 0) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + + await api.disconnect(); + })); + + await api.disconnect(); +}; + +async function start() { + try { + await main(); + } catch (e) { + console.error(e); + process.exit(1); + } +} + +start(); diff --git a/src/utils/monitoring.ts b/src/utils/monitoring.ts index bd4d487..5337b96 100644 --- a/src/utils/monitoring.ts +++ b/src/utils/monitoring.ts @@ -109,11 +109,11 @@ export const getAccountIdentities = async ( const superIdentityOpts = validSuperOfs.length > 0 ? await api.rpc.state.queryStorageAt[]>( - validSuperOfs.map( - (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), - at, - ), - ) + validSuperOfs.map( + (superOf) => api.query.identity.identityOf.key(superOf[0].toString()), + at, + ), + ) : []; let index = 0; return superOfs.map((superOf) => { @@ -148,9 +148,8 @@ export const getAccountIdentities = async ( return account && identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf && superOf.identity - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ - (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }); }; @@ -165,22 +164,22 @@ export const getAccountIdentity = async ( if (!identityCache[account] || identityCache[account].lastUpdate < Date.now() - 3600 * 1000) { const [identity, superOfIdentity] = api.query.identity ? await Promise.all([ - api.query.identity - .identityOf(account.toString()) - .then((a) => (a.isSome ? a.unwrap() : null)), - api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { - const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; - if (!superOf) { - return null; - } - const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); - const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; - return { - identity, - data: superOf[1], - }; - }), - ]) + api.query.identity + .identityOf(account.toString()) + .then((a) => (a.isSome ? a.unwrap() : null)), + api.query.identity.superOf(account.toString()).then(async (superOfOpt) => { + const superOf = (superOfOpt.isSome && superOfOpt.unwrap()) || null; + if (!superOf) { + return null; + } + const identityOpt = await api.query.identity.identityOf(superOf[0].toString()); + const identity = (identityOpt.isSome && identityOpt.unwrap()) || null; + return { + identity, + data: superOf[1], + }; + }), + ]) : [null, null]; identityCache[account] = { lastUpdate: Date.now(), @@ -194,9 +193,8 @@ export const getAccountIdentity = async ( return identity ? u8aToString(identity.info.display.asRaw.toU8a(true)) : superOf - ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${ - (superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" - }` + ? `${u8aToString(superOf.identity.info.display.asRaw.toU8a(true))} - Sub ${(superOf.data && u8aToString(superOf.data.asRaw.toU8a(true))) || "" + }` : account?.toString(); }; @@ -204,6 +202,9 @@ export const getAccountFromNimbusKey = async ( api: ApiPromise | ApiDecoration<"promise">, nmbsKey: string, ): Promise => { + if (!nmbsKey) { + return null + } if ( !authorMappingCache[nmbsKey] || authorMappingCache[nmbsKey].lastUpdate < Date.now() - 3600 * 1000 @@ -541,7 +542,7 @@ export function generateBlockDetailsLog( ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); const refTime = (dispatchInfo.weight as any).toBn @@ -572,7 +573,7 @@ export function generateBlockDetailsLog( ? payload.asEip2930?.gasPrice.toBigInt() : payload.isEip1559 ? // If gasPrice is not indicated, we should use the base fee defined in that block - payload.asEip1559?.maxFeePerGas.toBigInt() || 0n + payload.asEip1559?.maxFeePerGas.toBigInt() || 0n : (payload as any as LegacyTransaction).gasPrice?.toBigInt(); } return tx.events.reduce((total, event) => { @@ -597,8 +598,8 @@ export function generateBlockDetailsLog( const authorId = blockDetails.authorName.length > 24 ? `${blockDetails.authorName.substring(0, 9)}..${blockDetails.authorName.substring( - blockDetails.authorName.length - 6, - )}` + blockDetails.authorName.length - 6, + )}` : blockDetails.authorName; const authorName = blockDetails.isAuthorOrbiter ? chalk.yellow(authorId) : authorId; @@ -614,11 +615,10 @@ export function generateBlockDetailsLog( .padEnd( 7, " ", - )} [${weightText}%, ${storageText}B, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${ - txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` - }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( - hash.length - 4, - )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; + )} [${weightText}%, ${storageText}B, ${feesText} fees, ${extText} Txs (${evmText} Eth)(<->${coloredTransferred})]${txPoolText ? `[Pool:${txPoolText}${poolIncText ? `(+${poolIncText})` : ""}]` : `` + }${secondText ? `[${secondText}s]` : ""}(hash: ${hash.substring(0, 7)}..${hash.substring( + hash.length - 4, + )})${options?.suffix ? ` ${options.suffix}` : ""} by ${authorName}`; } export function printBlockDetails( diff --git a/src/utils/networks.ts b/src/utils/networks.ts index 9b70604..12b1876 100644 --- a/src/utils/networks.ts +++ b/src/utils/networks.ts @@ -125,7 +125,7 @@ export const getWsProviderFor = (argv: Argv) => { if (isKnownNetwork(argv.network)) { return getWsProviderForNetwork(argv.network); } - return new WsProvider(argv.url); + return new WsProvider(argv.url || process.env.MOONBEAM_TOOLS_WS_URL); }; export const getHttpProviderForNetwork = (name: NETWORK_NAME) => { diff --git a/test/state-manipulation.spec.ts b/test/state-manipulation.spec.ts index f911247..6e14b7b 100644 --- a/test/state-manipulation.spec.ts +++ b/test/state-manipulation.spec.ts @@ -21,6 +21,7 @@ import { HEATH_ADDRESS, JUDITH_ADDRESS, } from "../src/utils/constants.ts"; +import { CumulusManipulator } from "src/libs/helpers/state-manipulator/cumulus-manipulator.ts"; describe("State Manipulation", () => { const inFile = path.join(__dirname, "sample-state.json"); @@ -35,6 +36,7 @@ describe("State Manipulation", () => { }), new SudoManipulator(JUDITH_ADDRESS), new AuthorFilteringManipulator(100), + new CumulusManipulator(288730710n), new AuthorizeUpgradeManipulator( "0xfb9f16ba6b3433ba2a273974207260c7ace6aa629992d492bad0ba873b39762d", ), @@ -216,4 +218,10 @@ describe("State Manipulation", () => { finalState["0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b"], ).toEqual(JUDITH_ADDRESS); }); + + it("Should have the slot info reset to 0", async () => { + expect( + finalState["0x8985dff79e6002d0deba9ddac46f32a5a70806914c906d747e668a21f9021729"], + ).toEqual("0x56ae35110000000001000000"); + }); }); From bef7dd8ec7e14f9f6e9e7b7e4e7fe9525e0d408c Mon Sep 17 00:00:00 2001 From: crystalin Date: Sun, 24 Nov 2024 00:25:03 +0100 Subject: [PATCH 15/15] fixes runtime upgrade script --- src/tools/upgrade-network.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/tools/upgrade-network.ts b/src/tools/upgrade-network.ts index 51fe681..73c17ed 100644 --- a/src/tools/upgrade-network.ts +++ b/src/tools/upgrade-network.ts @@ -50,6 +50,11 @@ const argv = yargs(process.argv.slice(2)) demandOption: false, conflicts: ["send-proposal-as", "collective-threshold"], }, + enact: { + type: "boolean", + demandOption: false, + conflicts: ["sudo", "send-proposal-as", "collective-threshold"], + }, alith: { type: "boolean", demandOption: false, @@ -89,7 +94,7 @@ async function main() { const collectiveThreshold = argv["collective-threshold"] || Math.ceil(((await api.query.openTechCommitteeCollective.members()).length * 3) / 5); - const proposalAmount = api.consts.democracy.minimumDeposit; + const proposalAmount = api.consts?.democracy?.minimumDeposit || 0n; let account: KeyringPair; let nonce; @@ -109,13 +114,19 @@ async function main() { console.log(`Unexpected runtime ${codeHash} size: ${code.length}`); process.exit(1); } - console.log(`Using runtime wasm with size: ${code.length}`); + console.log(`Using runtime wasm with size: ${code.length} [hash: ${codeHash}]`); const tryProxy = (call) => { return maybeProxyCall(api, call, argv["proxy"], argv["proxy-type"]); }; - if (argv["sudo"]) { + if (argv["enact"]) { + await tryProxy(api.tx.system.applyAuthorizedUpgrade(codeHex)).signAndSend( + account, + { nonce: nonce++ }, + monitorSubmittedExtrinsic(api, { id: "sudo" }), + ); + } else if (argv["sudo"]) { const proposal = api.tx.system.setCode(codeHex); await tryProxy(api.tx.sudo.sudo(proposal)).signAndSend( account,