Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dryRun rpc #136

Merged
merged 4 commits into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
480 changes: 480 additions & 0 deletions e2e/__snapshots__/dev.test.ts.snap

Large diffs are not rendered by default.

20 changes: 19 additions & 1 deletion e2e/dev.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'
import { u8aToHex } from '@polkadot/util'

import { api, dev, env, expectJson, setupApi, testingPairs } from './helper'
import { api, dev, env, expectJson, setupApi, testingPairs, ws } from './helper'

setupApi(env.mandala)

Expand Down Expand Up @@ -63,4 +63,22 @@ describe('dev rpc', () => {
const timestamp = await dev.timeTravel(date)
expect(timestamp).eq(Date.parse(date))
})

it('dryRun', async () => {
const params = [
{
raw: false,
hrmp: {
'2034': [
{
send_at: 13740000,
data: '0x000210000400000106540254a37a01cd75b616d63e0ab665bffdb0143c52ae0013000064a7b3b6e00d0a1300000106540254a37a01cd75b616d63e0ab665bffdb0143c52ae0013000064a7b3b6e00d010700f2052a010d010004000101008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48',
},
],
},
},
]
const resp = await ws.send('dev_dryRun', params)
expect(resp.delta).toMatchSnapshot()
})
})
2 changes: 1 addition & 1 deletion e2e/dry-run-extrinsic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('dry-run-extrinsic', () => {
},
})
const extrinsic = await api.tx.balances.transfer(bob.address, 1e12).signAsync(alice)
const { outcome, storageDiff } = await chain.dryRun(extrinsic.toHex())
const { outcome, storageDiff } = await chain.dryRunExtrinsic(extrinsic.toHex())
expect(outcome.toHuman()).toMatchSnapshot()
expect(storageDiff).toMatchSnapshot()
})
Expand Down
9 changes: 9 additions & 0 deletions src/blockchain/block-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,12 @@ export const dryRunExtrinsic = async (
const newBlock = await initNewBlock(head, header, inherents)
return newBlock.call('BlockBuilder_apply_extrinsic', extrinsic)
}

export const dryRunInherents = async (
head: Block,
inherents: HexString[]
): Promise<[HexString, HexString | null][]> => {
const header = await newHeader(head)
const newBlock = await initNewBlock(head, header, inherents)
return Object.entries(await newBlock.storageDiff()) as [HexString, HexString | null][]
}
2 changes: 1 addition & 1 deletion src/blockchain/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export class Block {
this.#storages.pop()
}

async storageDiff(): Promise<Record<string, string>> {
async storageDiff(): Promise<Record<HexString, HexString | null>> {
const storage = {}

for (const layer of this.#storages) {
Expand Down
13 changes: 10 additions & 3 deletions src/blockchain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import type { TransactionValidity } from '@polkadot/types/interfaces/txqueue'

import { Api } from '../api'
import { Block } from './block'
import { BuildBlockMode, BuildBlockParams, TxPool } from './txpool'
import { BuildBlockMode, BuildBlockParams, HorizontalMessage, TxPool } from './txpool'
import { HeadState } from './head-state'
import { InherentProvider } from './inherent'
import { defaultLogger } from '../logger'
import { dryRunExtrinsic } from './block-builder'
import { dryRunExtrinsic, dryRunInherents } from './block-builder'

const logger = defaultLogger.child({ name: 'blockchain' })

Expand Down Expand Up @@ -160,7 +160,7 @@ export class Blockchain {
return this.#head
}

async dryRun(
async dryRunExtrinsic(
extrinsic: HexString
): Promise<{ outcome: ApplyExtrinsicResult; storageDiff: [HexString, HexString | null][] }> {
await this.api.isReady
Expand All @@ -171,4 +171,11 @@ export class Blockchain {
const outcome = registry.createType<ApplyExtrinsicResult>('ApplyExtrinsicResult', result)
return { outcome, storageDiff }
}

async dryRunHrmp(hrmp: Record<number, HorizontalMessage[]>): Promise<[HexString, HexString | null][]> {
await this.api.isReady
const head = this.head
const inherents = await this.#inherentProvider.createInherents(head, { horizontalMessages: hrmp })
return dryRunInherents(head, inherents)
}
}
10 changes: 7 additions & 3 deletions src/dry-run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,25 @@ import { blake2AsHex } from '@polkadot/util-crypto'
import { writeFileSync } from 'node:fs'

import { Config } from './schema'
import { generateHtmlDiff } from './utils/generate-html-diff'
import { generateHtmlDiffPreviewFile } from './utils/generate-html-diff'
import { openHtml } from './utils/open-html'
import { setup } from './setup'

export const dryRun = async (argv: Config) => {
const context = await setup(argv)

const { outcome, storageDiff } = await context.chain.dryRun(argv['extrinsic'])
const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(argv['extrinsic'])

if (outcome.isErr) {
throw new Error(outcome.asErr.toString())
}

if (argv['html']) {
const filePath = await generateHtmlDiff(context.chain.head, storageDiff, blake2AsHex(argv['extrinsic'], 256))
const filePath = await generateHtmlDiffPreviewFile(
context.chain.head,
storageDiff,
blake2AsHex(argv['extrinsic'], 256)
)
console.log(`Generated preview ${filePath}`)
if (argv['open']) {
openHtml(filePath)
Expand Down
30 changes: 29 additions & 1 deletion src/rpc/dev.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Handlers, ResponseError } from './shared'
import { HexString } from '@polkadot/util/types'

import { Handlers, ResponseError } from './shared'
import { StorageValues, setStorage } from '../utils/set-storage'
import { decodeStorageDiff } from '../utils/decoder'
import { defaultLogger } from '../logger'
import { generateHtmlDiff } from '../utils/generate-html-diff'
import { timeTravel } from '../utils/time-travel'

const logger = defaultLogger.child({ name: 'rpc-dev' })
Expand Down Expand Up @@ -45,6 +48,31 @@ const handlers: Handlers = {
await timeTravel(context.chain, timestamp)
return timestamp
},
dev_dryRun: async (context, [{ html, extrinsic, hrmp, raw }]) => {
const dryRun = async () => {
if (extrinsic) {
const { outcome, storageDiff } = await context.chain.dryRunExtrinsic(extrinsic)
if (outcome.isErr) {
throw new Error(outcome.asErr.toString())
}
return storageDiff
}
return context.chain.dryRunHrmp(hrmp)
}
const storageDiff = await dryRun()
if (html) {
return generateHtmlDiff(context.chain.head, storageDiff)
}
if (raw) {
return storageDiff
}
const [oldData, newData, delta] = await decodeStorageDiff(context.chain.head, storageDiff)
return {
old: oldData,
new: newData,
delta,
}
},
}

export default handlers
4 changes: 2 additions & 2 deletions src/run-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HexString } from '@polkadot/util/types'
import { writeFileSync } from 'node:fs'

import { Config } from './schema'
import { generateHtmlDiff } from './utils/generate-html-diff'
import { generateHtmlDiffPreviewFile } from './utils/generate-html-diff'
import { openHtml } from './utils/open-html'
import { runTask, taskHandler } from './executor'
import { setup } from './setup'
Expand Down Expand Up @@ -40,7 +40,7 @@ export const runBlock = async (argv: Config) => {
}

if (argv['html']) {
const filePath = await generateHtmlDiff(block, result.Call.storageDiff, block.hash)
const filePath = await generateHtmlDiffPreviewFile(block, result.Call.storageDiff, block.hash)
console.log(`Generated preview ${filePath}`)
if (argv['open']) {
openHtml(filePath)
Expand Down
15 changes: 11 additions & 4 deletions src/utils/decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Block } from '../blockchain/block'
import { HexString } from '@polkadot/util/types'
import { StorageEntry } from '@polkadot/types/primitive/types'
import { StorageKey } from '@polkadot/types'
import { blake2AsHex } from '@polkadot/util-crypto'
import { create } from 'jsondiffpatch'
import { hexToU8a, u8aToHex } from '@polkadot/util'
import _ from 'lodash'
Expand Down Expand Up @@ -53,15 +54,21 @@ export const decodeKeyValue = async (block: Block, key: HexString, value?: HexSt
return { [key]: value }
}

const decodedValue = value ? meta.registry.createType(decodedKey.outputType, hexToU8a(value)).toHuman() : null
const decodeValue = () => {
if (!value) return null
if (storage.section === 'substrate' && storage.method === 'code') {
return `:code blake2_256 ${blake2AsHex(value, 256)} (${hexToU8a(value).length} bytes)`
}
return meta.registry.createType(decodedKey.outputType, hexToU8a(value)).toHuman()
}

switch (decodedKey.args.length) {
case 2: {
return {
[storage.section]: {
[storage.method]: {
[decodedKey.args[0].toString()]: {
[decodedKey.args[1].toString()]: decodedValue,
[decodedKey.args[1].toString()]: decodeValue(),
},
},
},
Expand All @@ -71,15 +78,15 @@ export const decodeKeyValue = async (block: Block, key: HexString, value?: HexSt
return {
[storage.section]: {
[storage.method]: {
[decodedKey.args[0].toString()]: decodedValue,
[decodedKey.args[0].toString()]: decodeValue(),
},
},
}
}
default:
return {
[storage.section]: {
[storage.method]: decodedValue,
[storage.method]: decodeValue(),
},
}
}
Expand Down
12 changes: 10 additions & 2 deletions src/utils/generate-html-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@ import { decodeStorageDiff } from './decoder'
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'
import { template } from 'lodash'

export const generateHtmlDiff = async (block: Block, diff: [HexString, HexString | null][], filename: string) => {
export const generateHtmlDiff = async (block: Block, diff: [HexString, HexString | null][]) => {
const [left, _right, delta] = await decodeStorageDiff(block, diff)
const htmlTemplate = readFileSync('./template/diff.html', 'utf-8')
const html = template(htmlTemplate)({ left: JSON.stringify(left), delta: JSON.stringify(delta) })
return template(htmlTemplate)({ left: JSON.stringify(left), delta: JSON.stringify(delta) })
}

export const generateHtmlDiffPreviewFile = async (
block: Block,
diff: [HexString, HexString | null][],
filename: string
) => {
const html = await generateHtmlDiff(block, diff)
mkdirSync('./preview', { recursive: true })
const filePath = `./preview/${filename}.html`
writeFileSync(filePath, html)
Expand Down