From 5b5c169ee538222ff8d36b5d59ab8af55d772eef Mon Sep 17 00:00:00 2001 From: Ermal Kaleci Date: Mon, 30 Jan 2023 01:18:07 +0100 Subject: [PATCH] add feature to dry-run preimage --- src/blockchain/block-builder.ts | 2 +- src/blockchain/index.ts | 6 ++ src/cli.ts | 13 +++- src/dry-run-preimage.ts | 120 ++++++++++++++++++++++++++++++++ src/utils/set-storage.ts | 6 +- 5 files changed, 142 insertions(+), 5 deletions(-) create mode 100644 src/dry-run-preimage.ts diff --git a/src/blockchain/block-builder.ts b/src/blockchain/block-builder.ts index 7fd27026..5356398e 100644 --- a/src/blockchain/block-builder.ts +++ b/src/blockchain/block-builder.ts @@ -45,7 +45,7 @@ const getNewSlot = (digest: RawBabePreDigest, slotNumber: number) => { return digest.toJSON() } -const newHeader = async (head: Block) => { +export const newHeader = async (head: Block) => { const meta = await head.meta const parentHeader = await head.header diff --git a/src/blockchain/index.ts b/src/blockchain/index.ts index 7bc6aaa8..25b89025 100644 --- a/src/blockchain/index.ts +++ b/src/blockchain/index.ts @@ -192,4 +192,10 @@ export class Blockchain { const inherents = await this.#inherentProvider.createInherents(head, { horizontalMessages: hrmp }) return dryRunInherents(head, inherents) } + + async getInherents(): Promise { + await this.api.isReady + const inherents = await this.#inherentProvider.createInherents(this.head) + return inherents + } } diff --git a/src/cli.ts b/src/cli.ts index 0382f8f7..0308eb1a 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -9,6 +9,7 @@ import { Blockchain, BuildBlockMode, connectParachains, connectVertical, setup, import { configSchema } from './schema' import { decodeKey } from './utils/decoder' import { dryRun } from './dry-run' +import { dryRunPreimage } from './dry-run-preimage' import { isUrl } from './utils' import { runBlock } from './run-block' @@ -88,12 +89,15 @@ yargs(hideBin(process.argv)) extrinsic: { desc: 'Extrinsic or call to dry run. If you pass call here then address is required to fake signature', string: true, - required: true, }, address: { desc: 'Address to fake sign extrinsic', string: true, }, + preimage: { + desc: 'Preimage to dry run', + string: true, + }, at: { desc: 'Block hash to dry run', string: true, @@ -110,7 +114,12 @@ yargs(hideBin(process.argv)) }, }), async (argv) => { - await dryRun(await processArgv(argv)) + const config = await processArgv(argv) + if (config.preimage) { + await dryRunPreimage(config) + } else { + await dryRun(config) + } } ) .command( diff --git a/src/dry-run-preimage.ts b/src/dry-run-preimage.ts new file mode 100644 index 00000000..1d832781 --- /dev/null +++ b/src/dry-run-preimage.ts @@ -0,0 +1,120 @@ +import { HexString } from '@polkadot/util/types' +import { blake2AsHex } from '@polkadot/util-crypto' +import { hexToU8a } from '@polkadot/util' + +import { Config } from './schema' +import { defaultLogger } from './logger' +import { generateHtmlDiffPreviewFile } from './utils/generate-html-diff' +import { newHeader } from './blockchain/block-builder' +import { openHtml } from './utils/open-html' +import { runTask, taskHandler } from './executor' +import { setStorage } from './utils/set-storage' +import { setup } from './setup' + +export const dryRunPreimage = async (argv: Config) => { + const context = await setup(argv) + + const extrinsic = argv['preimage'] + + const block = context.chain.head + const registry = await block.registry + + const header = await newHeader(block) + + const data = hexToU8a(extrinsic) + const hash = blake2AsHex(data, 256) + + await setStorage(context.chain, { + Preimage: { + PreimageFor: [[[[hash, data.byteLength]], extrinsic]], + StatusFor: [ + [ + [hash], + { + Requested: { + count: 1, + len: data.byteLength, + }, + }, + ], + ], + }, + Scheduler: { + Agenda: [ + [ + [block.number + 1], + [ + { + maybeId: '0x64656d6f637261633a0000000000000000000000000000000000000000000000', + priority: 63, + call: { + Lookup: { + hash: hash, + len: data.byteLength, + }, + }, + origin: { system: { Root: null } }, + }, + ], + ], + ], + Lookup: [[['0x64656d6f637261633a0000000000000000000000000000000000000000000000'], [block.number + 1, 0]]], + }, + }) + + const calls: [string, HexString[]][] = [['Core_initialize_block', [header.toHex()]]] + + for (const inherent of await block.chain.getInherents()) { + calls.push(['BlockBuilder_apply_extrinsic', [inherent]]) + } + + calls.push(['BlockBuilder_finalize_block', []]) + + defaultLogger.info({ preimage: registry.createType('Call', data).toHuman() }, 'Dry run preimage') + + const result = await runTask( + { + wasm: await block.wasm, + calls, + storage: [], + mockSignatureHost: false, + allowUnresolvedImports: false, + }, + taskHandler(block) + ) + + if (result.Error) { + throw new Error(result.Error) + } + + const filePath = await generateHtmlDiffPreviewFile(block, result.Call.storageDiff, hash) + console.log(`Generated preview ${filePath}`) + if (argv['open']) { + openHtml(filePath) + } + + // if dry-run preimage has extrinsic arguments then dry-run extrinsic + // this is usefull to test something after preimage is applied + if (argv['extrinsic']) { + await context.chain.newBlock() + const input = argv['address'] ? { call: argv['extrinsic'], address: argv['address'] } : argv['extrinsic'] + const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(input) + if (outcome.isErr) { + throw new Error(outcome.asErr.toString()) + } else { + defaultLogger.info(outcome.toHuman(), 'dry_run_outcome') + } + + const filePath = await generateHtmlDiffPreviewFile( + context.chain.head, + storageDiff, + blake2AsHex(argv['extrinsic'], 256) + ) + console.log(`Generated preview ${filePath}`) + if (argv['open']) { + openHtml(filePath) + } + } + + process.exit(0) +} diff --git a/src/utils/set-storage.ts b/src/utils/set-storage.ts index 4f9932d2..4f028c96 100644 --- a/src/utils/set-storage.ts +++ b/src/utils/set-storage.ts @@ -38,11 +38,13 @@ function objectToStorageItems(meta: DecoratedMeta, storage: StorageConfig): RawS if (storageEntry.meta.type.isPlain) { const key = new StorageKey(meta.registry, [storageEntry]) - storageItems.push([key.toHex(), storage ? meta.registry.createType(key.outputType, storage).toHex(true) : null]) + const type = storageEntry.meta.modifier.isOptional ? `Option<${key.outputType}>` : key.outputType + storageItems.push([key.toHex(), storage ? meta.registry.createType(type, storage).toHex() : null]) } else { for (const [keys, value] of storage) { const key = new StorageKey(meta.registry, [storageEntry, keys]) - storageItems.push([key.toHex(), value ? meta.registry.createType(key.outputType, value).toHex(true) : null]) + const type = storageEntry.meta.modifier.isOptional ? `Option<${key.outputType}>` : key.outputType + storageItems.push([key.toHex(), value ? meta.registry.createType(type, value).toHex() : null]) } } }