From 38a24e6a6f0116262ced925d0e0f9903c44124d1 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas Date: Tue, 24 Sep 2024 12:55:19 +0100 Subject: [PATCH 1/4] feat(dev_newBlock): Adds option to override relaychain state --- .../inherent/parachain/validation-data.ts | 8 +++++- packages/core/src/blockchain/txpool.ts | 3 ++ packages/core/src/rpc/dev/new-block.ts | 10 ++++++- packages/core/src/utils/proof.ts | 5 ++++ packages/e2e/src/build-block.test.ts | 28 +++++++++++++++++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/packages/core/src/blockchain/inherent/parachain/validation-data.ts b/packages/core/src/blockchain/inherent/parachain/validation-data.ts index 82d0669a..07fdf048 100644 --- a/packages/core/src/blockchain/inherent/parachain/validation-data.ts +++ b/packages/core/src/blockchain/inherent/parachain/validation-data.ts @@ -98,7 +98,7 @@ export class SetValidationData implements InherentProvider { const extrinsic = await getValidationData(parent) - const newEntries: [HexString, HexString | null][] = [] + let newEntries: [HexString, HexString | null][] = [] const downwardMessages: DownwardMessage[] = [] const horizontalMessages: Record = {} @@ -268,6 +268,12 @@ export class SetValidationData implements InherentProvider { newEntries.push([upgradeKey, null]) } + // Apply relay state overrides + newEntries = (params.relayChainStateOverrides || []).reduce( + (entries, [key, value]) => [...entries.filter(([k, _]) => k != key), [key, value]], + newEntries, + ) + const { trieRootHash, nodes } = await createProof(extrinsic.relayChainState.trieNodes, newEntries) const newData = { diff --git a/packages/core/src/blockchain/txpool.ts b/packages/core/src/blockchain/txpool.ts index 26c7c67e..f12c807f 100644 --- a/packages/core/src/blockchain/txpool.ts +++ b/packages/core/src/blockchain/txpool.ts @@ -39,6 +39,7 @@ export interface BuildBlockParams { horizontalMessages: Record transactions: HexString[] unsafeBlockHeight?: number + relayChainStateOverrides?: [HexString, HexString | null][] } export class TxPool { @@ -177,6 +178,7 @@ export class TxPool { const downwardMessages = params?.downwardMessages || this.#dmp.splice(0) const horizontalMessages = params?.horizontalMessages || { ...this.#hrmp } const unsafeBlockHeight = params?.unsafeBlockHeight + const relayChainStateOverrides = params?.relayChainStateOverrides if (!params?.upwardMessages) { for (const id of Object.keys(this.#ump)) { delete this.#ump[id] @@ -195,6 +197,7 @@ export class TxPool { downwardMessages, horizontalMessages, unsafeBlockHeight, + relayChainStateOverrides, }) // with the latest message queue, messages could be processed in the upcoming block diff --git a/packages/core/src/rpc/dev/new-block.ts b/packages/core/src/rpc/dev/new-block.ts index 93f1004d..1889084e 100644 --- a/packages/core/src/rpc/dev/new-block.ts +++ b/packages/core/src/rpc/dev/new-block.ts @@ -32,6 +32,7 @@ const schema = z.object({ .optional(), transactions: z.array(zHex).min(1).optional(), unsafeBlockHeight: z.number().optional(), + relayChainStateOverrides: z.array(z.tuple([zHex, z.union([zHex, z.null()])])).optional(), }) type Params = z.infer @@ -65,6 +66,10 @@ export interface NewBlockParams { * Build block using a specific block height (unsafe) */ unsafeBlockHeight: Params['unsafeBlockHeight'] + /** + * Build block using a specific block height (unsafe) + */ + relayChainStateOverrides: Params['relayChainStateOverrides'] } /** @@ -106,7 +111,9 @@ export interface NewBlockParams { * ``` */ export const dev_newBlock = async (context: Context, [params]: [NewBlockParams]) => { - const { count, to, hrmp, ump, dmp, transactions, unsafeBlockHeight } = schema.parse(params || {}) + const { count, to, hrmp, ump, dmp, transactions, unsafeBlockHeight, relayChainStateOverrides } = schema.parse( + params || {}, + ) const now = context.chain.head.number const diff = to ? to - now : count const finalCount = diff !== undefined ? Math.max(diff, 1) : 1 @@ -124,6 +131,7 @@ export const dev_newBlock = async (context: Context, [params]: [NewBlockParams]) upwardMessages: ump, downwardMessages: dmp, unsafeBlockHeight: i === 0 ? unsafeBlockHeight : undefined, + relayChainStateOverrides: relayChainStateOverrides, }) .catch((error) => { throw new ResponseError(1, error.toString()) diff --git a/packages/core/src/utils/proof.ts b/packages/core/src/utils/proof.ts index 4cbcfee0..b7da288b 100644 --- a/packages/core/src/utils/proof.ts +++ b/packages/core/src/utils/proof.ts @@ -27,6 +27,11 @@ export const upgradeGoAheadSignal = (paraId: u32) => { return hash(prefix, paraId.toU8a()) } +export const upgradeRestrictionSignal = (paraId: u32) => { + const prefix = '0xcd710b30bd2eab0352ddcc26417aa194f27bbb460270642b5bcaf032ea04d56a' + return hash(prefix, paraId.toU8a()) +} + export const hrmpIngressChannelIndex = (paraId: u32) => { const prefix = '0x6a0da05ca59913bc38a8630590f2627c1d3719f5b0b12c7105c073c507445948' return hash(prefix, paraId.toU8a()) diff --git a/packages/e2e/src/build-block.test.ts b/packages/e2e/src/build-block.test.ts index 7d5d655b..a1dcfd50 100644 --- a/packages/e2e/src/build-block.test.ts +++ b/packages/e2e/src/build-block.test.ts @@ -1,6 +1,9 @@ import { afterAll, describe, expect, it } from 'vitest' +import { TypeRegistry } from '@polkadot/types' +import { decodeProof } from '@acala-network/chopsticks-core' import { setupAll } from './helper.js' +import { upgradeRestrictionSignal } from '@acala-network/chopsticks-core/utils/proof.js' const KUSAMA_STORAGE = { FellowshipCollective: { @@ -65,4 +68,29 @@ describe.runIf(process.env.CI || process.env.RUN_ALL).each([ await teardown() }) + + it('build block using relayChainStateOverrides', async () => { + const { ws, api, teardown } = await setupPjs() + const registry = new TypeRegistry() + const paraId = registry.createType('u32', 1000) + + const keyToOverride = upgradeRestrictionSignal(paraId) + const value = '0x00' + const relayChainStateOverrides = [[keyToOverride, value]] + + await ws.send('dev_newBlock', [{ relayChainStateOverrides }]) + const block = await api.rpc.chain.getBlock() + const setValidationData = block.block.extrinsics + .filter(({ method }) => method.method == 'setValidationData')[0] + .method.toJSON().args.data + + const relayParentStorageRoot = setValidationData.validationData.relayParentStorageRoot + const trieNodes = setValidationData.relayChainState.trieNodes + + const relayChainState = await decodeProof(relayParentStorageRoot, trieNodes) + + expect(relayChainState[keyToOverride]).to.be.eq(value) + + await teardown() + }) }) From 287198e295d95793364c6c5fa5d63ef5d6698305 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas Date: Tue, 24 Sep 2024 13:08:47 +0100 Subject: [PATCH 2/4] fix comment --- packages/core/src/rpc/dev/new-block.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/rpc/dev/new-block.ts b/packages/core/src/rpc/dev/new-block.ts index 1889084e..e7a09752 100644 --- a/packages/core/src/rpc/dev/new-block.ts +++ b/packages/core/src/rpc/dev/new-block.ts @@ -67,7 +67,7 @@ export interface NewBlockParams { */ unsafeBlockHeight: Params['unsafeBlockHeight'] /** - * Build block using a specific block height (unsafe) + * Build block using a custom relay chain state */ relayChainStateOverrides: Params['relayChainStateOverrides'] } From 7fc9eab8a9025a163e6d3d1a2524f16524a8c858 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas Date: Tue, 24 Sep 2024 16:37:21 +0100 Subject: [PATCH 3/4] test: only test relayChainStateOverrides on parachains --- packages/e2e/src/build-block.test.ts | 28 ----------- .../e2e/src/build-parachain-block.test.ts | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 28 deletions(-) create mode 100644 packages/e2e/src/build-parachain-block.test.ts diff --git a/packages/e2e/src/build-block.test.ts b/packages/e2e/src/build-block.test.ts index a1dcfd50..7d5d655b 100644 --- a/packages/e2e/src/build-block.test.ts +++ b/packages/e2e/src/build-block.test.ts @@ -1,9 +1,6 @@ import { afterAll, describe, expect, it } from 'vitest' -import { TypeRegistry } from '@polkadot/types' -import { decodeProof } from '@acala-network/chopsticks-core' import { setupAll } from './helper.js' -import { upgradeRestrictionSignal } from '@acala-network/chopsticks-core/utils/proof.js' const KUSAMA_STORAGE = { FellowshipCollective: { @@ -68,29 +65,4 @@ describe.runIf(process.env.CI || process.env.RUN_ALL).each([ await teardown() }) - - it('build block using relayChainStateOverrides', async () => { - const { ws, api, teardown } = await setupPjs() - const registry = new TypeRegistry() - const paraId = registry.createType('u32', 1000) - - const keyToOverride = upgradeRestrictionSignal(paraId) - const value = '0x00' - const relayChainStateOverrides = [[keyToOverride, value]] - - await ws.send('dev_newBlock', [{ relayChainStateOverrides }]) - const block = await api.rpc.chain.getBlock() - const setValidationData = block.block.extrinsics - .filter(({ method }) => method.method == 'setValidationData')[0] - .method.toJSON().args.data - - const relayParentStorageRoot = setValidationData.validationData.relayParentStorageRoot - const trieNodes = setValidationData.relayChainState.trieNodes - - const relayChainState = await decodeProof(relayParentStorageRoot, trieNodes) - - expect(relayChainState[keyToOverride]).to.be.eq(value) - - await teardown() - }) }) diff --git a/packages/e2e/src/build-parachain-block.test.ts b/packages/e2e/src/build-parachain-block.test.ts new file mode 100644 index 00000000..d80269f5 --- /dev/null +++ b/packages/e2e/src/build-parachain-block.test.ts @@ -0,0 +1,50 @@ +import { afterAll, describe, expect, it } from 'vitest' + +import { TypeRegistry } from '@polkadot/types' +import { decodeProof } from '@acala-network/chopsticks-core' +import { setupAll } from './helper.js' +import { upgradeRestrictionSignal } from '@acala-network/chopsticks-core/utils/proof.js' + +describe.runIf(process.env.CI || process.env.RUN_ALL).each([ + { chain: 'Statemint', endpoint: 'wss://statemint-rpc.dwellir.com' }, + { chain: 'Polkadot Collectives', endpoint: 'wss://sys.ibp.network/collectives-polkadot' }, + { chain: 'Acala', endpoint: 'wss://acala-rpc.aca-api.network' }, + { chain: 'Statemine', endpoint: 'wss://statemine-rpc-tn.dwellir.com' }, + { + chain: 'Karura', + endpoint: 'wss://karura-rpc.aca-api.network', + }, + { chain: 'Westmint', endpoint: 'wss://westmint-rpc.polkadot.io' }, + { chain: 'Westend Collectives', endpoint: 'wss://sys.ibp.network/collectives-westend' }, +])('Latest $chain can build blocks', async ({ endpoint }) => { + const { setupPjs, teardownAll } = await setupAll({ endpoint }) + + afterAll(async () => { + await teardownAll() + }) + + it('build block using relayChainStateOverrides', async () => { + const { ws, api, teardown } = await setupPjs() + const registry = new TypeRegistry() + const paraId = registry.createType('u32', 1000) + + const keyToOverride = upgradeRestrictionSignal(paraId) + const value = '0x00' + const relayChainStateOverrides = [[keyToOverride, value]] + + await ws.send('dev_newBlock', [{ relayChainStateOverrides }]) + const block = await api.rpc.chain.getBlock() + const setValidationData = block.block.extrinsics + .find(({ method }) => method.method == 'setValidationData') + ?.method.toJSON().args.data + + const relayParentStorageRoot = setValidationData.validationData.relayParentStorageRoot + const trieNodes = setValidationData.relayChainState.trieNodes + + const relayChainState = await decodeProof(relayParentStorageRoot, trieNodes) + + expect(relayChainState[keyToOverride]).to.be.eq(value) + + await teardown() + }) +}) From 3eed8189e24b88fd2918fc13ea2c94e9d155a2e1 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas Date: Wed, 25 Sep 2024 08:48:54 +0100 Subject: [PATCH 4/4] apply suggestions from review --- .../inherent/parachain/validation-data.ts | 14 +++++++---- .../e2e/src/build-parachain-block.test.ts | 25 +++---------------- 2 files changed, 13 insertions(+), 26 deletions(-) diff --git a/packages/core/src/blockchain/inherent/parachain/validation-data.ts b/packages/core/src/blockchain/inherent/parachain/validation-data.ts index 07fdf048..9839906c 100644 --- a/packages/core/src/blockchain/inherent/parachain/validation-data.ts +++ b/packages/core/src/blockchain/inherent/parachain/validation-data.ts @@ -268,11 +268,15 @@ export class SetValidationData implements InherentProvider { newEntries.push([upgradeKey, null]) } - // Apply relay state overrides - newEntries = (params.relayChainStateOverrides || []).reduce( - (entries, [key, value]) => [...entries.filter(([k, _]) => k != key), [key, value]], - newEntries, - ) + // Apply relay chain state overrides + if (params.relayChainStateOverrides) { + for (const [key, value] of params.relayChainStateOverrides) { + // Remove any entry that matches the key being overridden + newEntries = newEntries.filter(([k, _]) => k != key) + // Push override + newEntries.push([key, value]) + } + } const { trieRootHash, nodes } = await createProof(extrinsic.relayChainState.trieNodes, newEntries) diff --git a/packages/e2e/src/build-parachain-block.test.ts b/packages/e2e/src/build-parachain-block.test.ts index d80269f5..a911a30a 100644 --- a/packages/e2e/src/build-parachain-block.test.ts +++ b/packages/e2e/src/build-parachain-block.test.ts @@ -1,30 +1,13 @@ -import { afterAll, describe, expect, it } from 'vitest' +import { describe, expect, it } from 'vitest' import { TypeRegistry } from '@polkadot/types' import { decodeProof } from '@acala-network/chopsticks-core' -import { setupAll } from './helper.js' import { upgradeRestrictionSignal } from '@acala-network/chopsticks-core/utils/proof.js' +import networks from './networks.js' -describe.runIf(process.env.CI || process.env.RUN_ALL).each([ - { chain: 'Statemint', endpoint: 'wss://statemint-rpc.dwellir.com' }, - { chain: 'Polkadot Collectives', endpoint: 'wss://sys.ibp.network/collectives-polkadot' }, - { chain: 'Acala', endpoint: 'wss://acala-rpc.aca-api.network' }, - { chain: 'Statemine', endpoint: 'wss://statemine-rpc-tn.dwellir.com' }, - { - chain: 'Karura', - endpoint: 'wss://karura-rpc.aca-api.network', - }, - { chain: 'Westmint', endpoint: 'wss://westmint-rpc.polkadot.io' }, - { chain: 'Westend Collectives', endpoint: 'wss://sys.ibp.network/collectives-westend' }, -])('Latest $chain can build blocks', async ({ endpoint }) => { - const { setupPjs, teardownAll } = await setupAll({ endpoint }) - - afterAll(async () => { - await teardownAll() - }) - +describe('override-relay-state-proof', async () => { it('build block using relayChainStateOverrides', async () => { - const { ws, api, teardown } = await setupPjs() + const { ws, api, teardown } = await networks.acala() const registry = new TypeRegistry() const paraId = registry.createType('u32', 1000)