diff --git a/e2e/__snapshots__/xcm.test.ts.snap b/e2e/__snapshots__/xcm.test.ts.snap index a9f5d569..fefc513a 100644 --- a/e2e/__snapshots__/xcm.test.ts.snap +++ b/e2e/__snapshots__/xcm.test.ts.snap @@ -317,6 +317,24 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` }, "topics": [], }, + { + "event": { + "data": { + "dispatchInfo": { + "class": "Mandatory", + "paysFee": "Yes", + "weight": "85,212,000", + }, + }, + "index": "0x0000", + "method": "ExtrinsicSuccess", + "section": "system", + }, + "phase": { + "ApplyExtrinsic": "1", + }, + "topics": [], + }, { "event": { "data": { @@ -328,7 +346,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "balances", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, @@ -344,7 +362,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "balances", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, @@ -360,7 +378,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "xcmPallet", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, @@ -375,7 +393,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "balances", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, @@ -389,7 +407,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "treasury", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, @@ -404,7 +422,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "balances", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, @@ -420,7 +438,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "transactionPayment", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, @@ -438,7 +456,7 @@ exports[`XCM > Polkadot send downward messages to Acala 1`] = ` "section": "system", }, "phase": { - "ApplyExtrinsic": "1", + "ApplyExtrinsic": "2", }, "topics": [], }, diff --git a/e2e/helper.ts b/e2e/helper.ts index fa9559a7..53dac639 100644 --- a/e2e/helper.ts +++ b/e2e/helper.ts @@ -1,5 +1,6 @@ import { ApiPromise, WsProvider } from '@polkadot/api' import { Codec } from '@polkadot/types/types' +import { HexString } from '@polkadot/util/types' import { Keyring } from '@polkadot/keyring' import { beforeAll, beforeEach, expect, vi } from 'vitest' @@ -7,14 +8,14 @@ import { Api } from '../src/api' import { Blockchain } from '../src/blockchain' import { BuildBlockMode } from '../src/blockchain/txpool' import { GenesisProvider } from '../src/genesis-provider' -import { InherentProviders, SetTimestamp, SetValidationData } from '../src/blockchain/inherent' +import { InherentProviders, ParaInherentEnter, SetTimestamp, SetValidationData } from '../src/blockchain/inherent' import { StorageValues } from '../src/utils/set-storage' import { createServer } from '../src/server' import { handler } from '../src/rpc' export type SetupOption = { endpoint?: string - blockHash?: string + blockHash?: HexString mockSignatureHost?: boolean allowUnresolvedImports?: boolean genesis?: string @@ -23,11 +24,11 @@ export type SetupOption = { export const env = { mandala: { endpoint: 'wss://mandala-rpc.aca-staging.network/ws', - blockHash: '0x062327512615cd62ea8c57652a04a6c937b112f1410520d83e2fafb9776cdbe1', + blockHash: '0x062327512615cd62ea8c57652a04a6c937b112f1410520d83e2fafb9776cdbe1' as HexString, }, rococo: { endpoint: 'wss://rococo-rpc.polkadot.io', - blockHash: '0xd7fef00504decd41d5d2e9a04346f6bc639fd428083e3ca941f636a8f88d456a', + blockHash: '0xd7fef00504decd41d5d2e9a04346f6bc639fd428083e3ca941f636a8f88d456a' as HexString, }, mandalaGenesis: { genesis: 'https://raw.githubusercontent.com/AcalaNetwork/Acala/master/resources/mandala-dist.json', @@ -51,7 +52,7 @@ export const setupAll = async ({ return { async setup() { - const inherents = new InherentProviders(new SetTimestamp(), [new SetValidationData()]) + const inherents = new InherentProviders(new SetTimestamp(), [new SetValidationData(), new ParaInherentEnter()]) const chain = new Blockchain({ api, diff --git a/e2e/time-travel.test.ts b/e2e/time-travel.test.ts index 46843b4b..df930c00 100644 --- a/e2e/time-travel.test.ts +++ b/e2e/time-travel.test.ts @@ -1,3 +1,4 @@ +import { HexString } from '@polkadot/util/types' import { chain, setupApi, ws } from './helper' import { describe, expect, it } from 'vitest' import { getCurrentTimestamp, getSlotDuration, timeTravel } from '../src/utils/time-travel' @@ -6,12 +7,12 @@ describe.each([ { chain: 'Polkadot', endpoint: 'wss://rpc.polkadot.io', - blockHash: '0xb7fb7cfe79142652036e73f8044e0efbbbe7d3fb71cabc212efd5968c9041950', + blockHash: '0xb7fb7cfe79142652036e73f8044e0efbbbe7d3fb71cabc212efd5968c9041950' as HexString, }, { chain: 'Acala', endpoint: 'wss://acala-rpc-1.aca-api.network', - blockHash: '0x1d9223c88161b512ebaac53c2c7df6dc6bd2731b12273b898f582af929cc5331', + blockHash: '0x1d9223c88161b512ebaac53c2c7df6dc6bd2731b12273b898f582af929cc5331' as HexString, }, ])('Can time-travel on $chain', async ({ endpoint, blockHash }) => { setupApi({ endpoint, blockHash }) diff --git a/src/api.ts b/src/api.ts index e004a1a2..7aac1cdb 100644 --- a/src/api.ts +++ b/src/api.ts @@ -87,7 +87,7 @@ export class Api { } async getBlockHash(blockNumber?: number) { - return this.#provider.send('chain_getBlockHash', Number.isInteger(blockNumber) ? [blockNumber] : []) + return this.#provider.send('chain_getBlockHash', Number.isInteger(blockNumber) ? [blockNumber] : []) } async getHeader(hash?: string) { diff --git a/src/blockchain/block.ts b/src/blockchain/block.ts index d23091ad..391fda11 100644 --- a/src/blockchain/block.ts +++ b/src/blockchain/block.ts @@ -37,7 +37,7 @@ export class Block { constructor( chain: Blockchain, public readonly number: number, - public readonly hash: string, + public readonly hash: HexString, parentBlock?: Block, block?: { header: Header; extrinsics: HexString[]; storage?: StorageLayerProvider } ) { diff --git a/src/blockchain/index.ts b/src/blockchain/index.ts index ec5e1837..0386028b 100644 --- a/src/blockchain/index.ts +++ b/src/blockchain/index.ts @@ -20,7 +20,7 @@ export interface Options { buildBlockMode?: BuildBlockMode inherentProvider: InherentProvider db?: DataSource - header: { number: number; hash: string } + header: { number: number; hash: HexString } mockSignatureHost?: boolean allowUnresolvedImports?: boolean } @@ -89,7 +89,7 @@ export class Blockchain { return this.#blocksByNumber[number] } - async getBlock(hash?: string): Promise { + async getBlock(hash?: HexString): Promise { await this.api.isReady if (hash == null) { hash = this.head.hash @@ -110,11 +110,10 @@ export class Blockchain { newTempBlock(parent: Block, header: Header): Block { const number = parent.number + 1 - const hash = - '0x' + + const hash = ('0x' + Math.round(Math.random() * 100000000) .toString(16) - .padEnd(64, '0') + .padEnd(64, '0')) as HexString const block = new Block(this, number, hash, parent, { header, extrinsics: [], storage: parent.storage }) this.#blocksByHash[hash] = block return block diff --git a/src/blockchain/inherent/index.ts b/src/blockchain/inherent/index.ts index 4ca18511..dd1fb691 100644 --- a/src/blockchain/inherent/index.ts +++ b/src/blockchain/inherent/index.ts @@ -5,6 +5,7 @@ import { HexString } from '@polkadot/util/types' import { getCurrentTimestamp, getSlotDuration } from '../../utils/time-travel' export { SetValidationData } from './parachain/validation-data' +export { ParaInherentEnter } from './para-enter' export interface CreateInherents { createInherents(parent: Block, params?: BuildBlockParams['inherent']): Promise diff --git a/src/blockchain/inherent/para-enter.ts b/src/blockchain/inherent/para-enter.ts new file mode 100644 index 00000000..e3938f7d --- /dev/null +++ b/src/blockchain/inherent/para-enter.ts @@ -0,0 +1,41 @@ +import { GenericExtrinsic } from '@polkadot/types' +import { HexString } from '@polkadot/util/types' + +import { Block } from '../block' +import { BuildBlockParams } from '../txpool' +import { CreateInherents } from '.' + +export class ParaInherentEnter implements CreateInherents { + async createInherents(parent: Block, _params?: BuildBlockParams['inherent']): Promise { + const meta = await parent.meta + if (!meta.tx.paraInherent?.enter) { + return [] + } + + const extrinsics = await parent.extrinsics + + const paraEnterExtrinsic = extrinsics.find((extrinsic) => { + const firstArg = meta.registry.createType('GenericExtrinsic', extrinsic)?.args?.[0] + return firstArg && 'bitfields' in firstArg + }) + if (!paraEnterExtrinsic) { + throw new Error('Missing paraInherent data from block') + } + const extrinsic = meta.registry + .createType('GenericExtrinsic', paraEnterExtrinsic) + .args[0].toJSON() as any + + const parentHeader = (await parent.header).toJSON() + + const newData = { + ...extrinsic, + bitfields: [], + backedCandidates: [], + parentHeader, + } + + // TODO: fill with data + + return [new GenericExtrinsic(meta.registry, meta.tx.paraInherent.enter(newData)).toHex()] + } +} diff --git a/src/blockchain/inherent/parachain/validation-data.ts b/src/blockchain/inherent/parachain/validation-data.ts index 611aab09..c3fc1be7 100644 --- a/src/blockchain/inherent/parachain/validation-data.ts +++ b/src/blockchain/inherent/parachain/validation-data.ts @@ -56,15 +56,11 @@ export class SetValidationData implements CreateInherents { return [] } - const parentBlock = await parent.parentBlock - if (!parentBlock) { - throw new Error('Parent block not found') - } - const extrinsics = await parentBlock.extrinsics + const extrinsics = await parent.extrinsics let newData: ValidationData - if (parentBlock.number === 0) { + if (parent.number === 0) { // chain started with genesis, mock 1st validationData newData = MOCK_VALIDATION_DATA as ValidationData } else { @@ -83,7 +79,7 @@ export class SetValidationData implements CreateInherents { const downwardMessages: { msg: HexString; sent_at: number }[] = [] const horizontalMessages: Record = {} - const paraId = await getParaId(parentBlock.chain) + const paraId = await getParaId(parent.chain) const dmqMqcHeadKey = dmqMqcHead(paraId) const hrmpIngressChannelIndexKey = hrmpIngressChannelIndex(paraId) @@ -173,7 +169,7 @@ export class SetValidationData implements CreateInherents { } const upgradeKey = upgradeGoAheadSignal(paraId) - const pendingUpgrade = await parentBlock.get(compactHex(meta.query.parachainSystem.pendingValidationCode())) + const pendingUpgrade = await parent.get(compactHex(meta.query.parachainSystem.pendingValidationCode())) if (pendingUpgrade) { // send goAhead signal const goAhead = meta.registry.createType('UpgradeGoAhead', 'GoAhead') diff --git a/src/blockchain/txpool.ts b/src/blockchain/txpool.ts index 500b201c..4cf0016b 100644 --- a/src/blockchain/txpool.ts +++ b/src/blockchain/txpool.ts @@ -7,7 +7,6 @@ import { Block } from './block' import { Blockchain } from '.' import { InherentProvider } from './inherent' import { ResponseError } from '../rpc/shared' -import { compactHex } from '../utils' import { defaultLogger, truncate, truncateStorageDiff } from '../logger' import { getCurrentSlot } from '../utils/time-travel' @@ -165,7 +164,7 @@ export class TxPool { newBlock.pushStorageLayer().setAll(resp.storageDiff) - const inherents = await this.#inherentProvider.createInherents(newBlock, params?.inherent) + const inherents = await this.#inherentProvider.createInherents(head, params?.inherent) for (const extrinsic of inherents) { try { const resp = await newBlock.call('BlockBuilder_apply_extrinsic', extrinsic) @@ -188,14 +187,6 @@ export class TxPool { } } - if (meta.query.paraInherent?.included) { - // TODO: remvoe this once paraInherent.enter is implemented - // we are relaychain, however as we have not yet implemented the paraInherent.enter - // so need to do some trick to make the on_finalize check happy - const paraInherentIncludedKey = compactHex(meta.query.paraInherent.included()) - newBlock.pushStorageLayer().set(paraInherentIncludedKey, '0x01') - } - const resp2 = await newBlock.call('BlockBuilder_finalize_block', '0x') newBlock.pushStorageLayer().setAll(resp2.storageDiff) diff --git a/src/rpc/dev.ts b/src/rpc/dev.ts index bd6c744b..8b643c11 100644 --- a/src/rpc/dev.ts +++ b/src/rpc/dev.ts @@ -1,4 +1,5 @@ import { Handlers, ResponseError } from './shared' +import { HexString } from '@polkadot/util/types' import { StorageValues, setStorage } from '../utils/set-storage' import { defaultLogger } from '../logger' import { timeTravel } from '../utils/time-travel' @@ -23,7 +24,7 @@ const handlers: Handlers = { return finalHash }, dev_setStorages: async (context, params) => { - const [values, blockHash] = params as [StorageValues, string?] + const [values, blockHash] = params as [StorageValues, HexString?] const hash = await setStorage(context.chain, values, blockHash).catch((error) => { throw new ResponseError(1, error.toString()) }) diff --git a/src/schema/index.ts b/src/schema/index.ts index f6f2ab4c..7e7a7c51 100644 --- a/src/schema/index.ts +++ b/src/schema/index.ts @@ -18,7 +18,7 @@ export const configSchema = z .object({ port: z.number().optional(), endpoint: z.string().optional(), - block: z.union([z.string(), z.number()]).optional(), + block: z.union([z.string().length(66).startsWith('0x'), z.number()]).optional(), 'build-block-mode': z.nativeEnum(BuildBlockMode).optional(), 'import-storage': z.any().optional(), 'mock-signature-host': z.boolean().optional(), diff --git a/src/setup.ts b/src/setup.ts index 83c43d39..eda8943b 100644 --- a/src/setup.ts +++ b/src/setup.ts @@ -1,5 +1,6 @@ import '@polkadot/types-codec' import { DataSource } from 'typeorm' +import { HexString } from '@polkadot/util/types' import { ProviderInterface } from '@polkadot/rpc-provider/types' import { WsProvider } from '@polkadot/api' @@ -7,7 +8,7 @@ import { Api } from './api' import { Blockchain } from './blockchain' import { Config } from './schema' import { GenesisProvider } from './genesis-provider' -import { InherentProviders, SetTimestamp, SetValidationData } from './blockchain/inherent' +import { InherentProviders, ParaInherentEnter, SetTimestamp, SetValidationData } from './blockchain/inherent' import { defaultLogger } from './logger' import { importStorage, overrideWasm } from './utils/import-storage' import { openDb } from './db' @@ -45,7 +46,7 @@ export const setup = async (argv: Config) => { const header = await api.getHeader(blockHash) - const inherents = new InherentProviders(new SetTimestamp(), [new SetValidationData()]) + const inherents = new InherentProviders(new SetTimestamp(), [new SetValidationData(), new ParaInherentEnter()]) const chain = new Blockchain({ api, @@ -53,7 +54,7 @@ export const setup = async (argv: Config) => { inherentProvider: inherents, db, header: { - hash: blockHash, + hash: blockHash as HexString, number: Number(header.number), }, }) diff --git a/src/utils/set-storage.ts b/src/utils/set-storage.ts index f58f9440..4f9932d2 100644 --- a/src/utils/set-storage.ts +++ b/src/utils/set-storage.ts @@ -1,4 +1,5 @@ import { DecoratedMeta } from '@polkadot/types/metadata/decorate/types' +import { HexString } from '@polkadot/util/types' import { StorageKey } from '@polkadot/types' import { stringCamelCase } from '@polkadot/util/string' import { u8aToHex } from '@polkadot/util' @@ -49,7 +50,11 @@ function objectToStorageItems(meta: DecoratedMeta, storage: StorageConfig): RawS return storageItems } -export const setStorage = async (chain: Blockchain, storage: StorageValues, blockHash?: string): Promise => { +export const setStorage = async ( + chain: Blockchain, + storage: StorageValues, + blockHash?: HexString +): Promise => { const block = await chain.getBlock(blockHash) if (!block) throw Error(`Cannot find block ${blockHash || 'latest'}`)