From 53c31cd8f61142b05d014a1907188e38045ffb15 Mon Sep 17 00:00:00 2001 From: Sergej Date: Mon, 27 May 2024 12:54:38 +0200 Subject: [PATCH] Add ismp to e2e tests --- e2e_tests/xc-transfer/region-transfer.ts | 117 ++++++++++++++++-- e2e_tests/xc-transfer/types.ts | 83 +++++++++++++ .../xc-transfer/region-transfer.toml | 2 +- 3 files changed, 189 insertions(+), 13 deletions(-) create mode 100644 e2e_tests/xc-transfer/types.ts diff --git a/e2e_tests/xc-transfer/region-transfer.ts b/e2e_tests/xc-transfer/region-transfer.ts index 3d3168d1..bbcb87d0 100644 --- a/e2e_tests/xc-transfer/region-transfer.ts +++ b/e2e_tests/xc-transfer/region-transfer.ts @@ -1,9 +1,11 @@ import { ApiPromise, Keyring, WsProvider } from '@polkadot/api'; import { KeyringPair } from '@polkadot/keyring/types'; -import { getEncodedRegionId, Id, RegionId } from 'coretime-utils'; +import { ISubmittableResult } from '@polkadot/types/types'; +import { getEncodedRegionId, RegionId } from 'coretime-utils'; import assert from 'node:assert'; import { setupRelayAsset, sleep, submitExtrinsic, transferRelayAssetToPara } from '../common'; import { CONFIG, CORE_COUNT, INITIAL_PRICE, UNIT } from '../consts'; +import { Get, IsmpRequest, REGIONX_API_TYPES, REGIONX_CUSTOM_RPC } from './types'; const REGIONX_SOVEREIGN_ACCOUNT = '5Eg2fntJ27qsari4FGrGhrMqKFDRnkNSR6UshkZYBGXmSuC8'; @@ -12,15 +14,13 @@ async function run(_nodeName: any, networkInfo: any, _jsArgs: any) { const { wsUri: coretimeUri } = networkInfo.nodesByName['coretime-collator01']; const { wsUri: rococoUri } = networkInfo.nodesByName['rococo-validator01']; - const regionXApi = await ApiPromise.create({ provider: new WsProvider(regionXUri) }); - const rococoApi = await ApiPromise.create({ - provider: new WsProvider(rococoUri), - types: { Id }, - }); - const coretimeApi = await ApiPromise.create({ - provider: new WsProvider(coretimeUri), - types: { Id }, + const regionXApi = await ApiPromise.create({ + provider: new WsProvider(regionXUri), + types: { ...REGIONX_API_TYPES }, + rpc: REGIONX_CUSTOM_RPC, }); + const rococoApi = await ApiPromise.create({ provider: new WsProvider(rococoUri) }); + const coretimeApi = await ApiPromise.create({ provider: new WsProvider(coretimeUri) }); // account to submit tx const keyring = new Keyring({ type: 'sr25519' }); @@ -109,15 +109,32 @@ async function run(_nodeName: any, networkInfo: any, _jsArgs: any) { let regions = await regionXApi.query.regions.regions.entries(); assert.equal(regions.length, 1); assert.deepStrictEqual(regions[0][0].toHuman(), [regionId]); - // record is unavailable because we did not setup ismp. - assert((regions[0][1].toHuman() as any).owner == alice.address); - assert(typeof (regions[0][1].toHuman() as any).record.Pending === 'string'); + let region = regions[0][1].toHuman() as any; + assert(region.owner == alice.address); + assert(typeof region.record.Pending === 'string'); + + // Check the data on the Coretime chain: regions = await coretimeApi.query.broker.regions.entries(); assert.equal(regions.length, 1); assert.deepStrictEqual(regions[0][0].toHuman(), [regionId]); assert.equal((regions[0][1].toHuman() as any).owner, REGIONX_SOVEREIGN_ACCOUNT); + // Respond to the ISMP get request: + const request = await queryRequest(regionXApi, region.record.Pending); + await makeIsmpResponse(regionXApi, coretimeApi, request, alice.address); + + // The record should be set after ISMP response: + regions = await regionXApi.query.regions.regions.entries(); + region = regions[0][1].toHuman() as any; + assert(region.owner == alice.address); + assert.deepStrictEqual(region.record.Available, { + end: '66', + owner: '5C6cBwdHw3agsBKzjGABaMq1kgXnmKyaBPN8J6c8MkHBnKu5', + paid: null, + }); + + // Transfer the region back to the Coretime chain: const reserveTransferToCoretime = regionXApi.tx.polkadotXcm.limitedReserveTransferAssets( { V3: { parents: 1, interior: { X1: { Parachain: 1005 } } } }, // dest { @@ -244,4 +261,80 @@ async function getRegionId(coretimeApi: ApiPromise): Promise { return null; } +async function queryRequest(regionxApi: ApiPromise, commitment: string): Promise { + const leafIndex = regionxApi.createType('LeafIndexQuery', { commitment }); + const requests = await (regionxApi as any).rpc.ismp.queryRequests([leafIndex]); + // We only requested a single request so we only get one in the response. + return requests.toJSON()[0] as IsmpRequest; +} + +async function makeIsmpResponse( + regionXApi: ApiPromise, + coretimeApi: ApiPromise, + request: IsmpRequest, + responderAddress: string +): Promise { + if (isGetRequest(request)) { + const hashAt = ( + await coretimeApi.query.system.blockHash(Number(request.get.height)) + ).toString(); + const proofData = await coretimeApi.rpc.state.getReadProof([request.get.keys[0]], hashAt); + + const stateMachineProof = regionXApi.createType('StateMachineProof', { + hasher: 'Blake2', + storage_proof: proofData.proof, + }); + + const substrateStateProof = regionXApi.createType('SubstrateStateProof', { + StateProof: stateMachineProof, + }); + const response = regionXApi.tx.ismp.handleUnsigned([ + { + Response: { + datagram: { + Request: [request], + }, + proof: { + height: { + id: { + stateId: { + Kusama: 1005, + }, + consensusStateId: 'PARA', + }, + height: request.get.height.toString(), + }, + proof: substrateStateProof.toHex(), + }, + signer: responderAddress, + }, + }, + ]); + + return new Promise((resolve, reject) => { + const unsub = response.send((result: ISubmittableResult) => { + const { status, isError } = result; + console.log(`Current status is ${status}`); + if (status.isInBlock) { + console.log(`Transaction included at blockHash ${status.asInBlock}`); + } else if (status.isFinalized) { + console.log(`Transaction finalized at blockHash ${status.asFinalized}`); + unsub.then(); + return resolve(); + } else if (isError) { + console.log('Transaction error'); + unsub.then(); + return reject(); + } + }); + }); + } else { + new Error('Expected a Get request'); + } +} + +const isGetRequest = (request: IsmpRequest): request is { get: Get } => { + return (request as { get: Get }).get !== undefined; +}; + export { run }; diff --git a/e2e_tests/xc-transfer/types.ts b/e2e_tests/xc-transfer/types.ts new file mode 100644 index 00000000..94719760 --- /dev/null +++ b/e2e_tests/xc-transfer/types.ts @@ -0,0 +1,83 @@ +export type StateMachine = { Polkadot: number } | { Kusama: number }; + +export interface Get { + source: StateMachine; + dest: StateMachine; + nonce: bigint; + from: string; + keys: Array; + height: bigint; + timeout_timestamp: bigint; +} + +export type IsmpRequest = { post: any } | { get: Get }; + +export const REGIONX_API_TYPES = { + CoreIndex: 'u32', + CoreMask: 'Vec', + Timeslice: 'u32', + RegionId: { + begin: 'Timeslice', + core: 'CoreIndex', + mask: 'CoreMask', + }, + RegionRecord: { + end: 'Timeslice', + owner: 'AccountId', + paid: 'Option', + }, + HashAlgorithm: { + _enum: ['Keccak', 'Blake2'], + }, + StateMachineProof: { + hasher: 'HashAlgorithm', + storage_proof: 'Vec>', + }, + SubstrateStateProof: { + _enum: { + OverlayProof: 'StateMachineProof', + StateProof: 'StateMachineProof', + }, + }, + LeafIndexQuery: { + commitment: 'H256', + }, + StateMachine: { + _enum: { + Ethereum: 'Vec', + Polkadot: 'u32', + Kusama: 'u32', + }, + }, + Post: {}, + Get: { + source: 'StateMachine', + dest: 'StateMachine', + nonce: 'u64', + from: 'Vec', + keys: 'Vec>', + height: 'u64', + timeout_timestamp: 'u64', + }, + Request: { + _enum: { + Post: 'Post', + Get: 'Get', + }, + }, +}; + +export const REGIONX_CUSTOM_RPC = { + ismp: { + queryRequests: { + description: '', + params: [ + { + name: 'query', + type: 'Vec', + }, + ], + type: 'Vec', + }, + }, +}; diff --git a/zombienet_tests/xc-transfer/region-transfer.toml b/zombienet_tests/xc-transfer/region-transfer.toml index 14f1d0b0..10022fef 100644 --- a/zombienet_tests/xc-transfer/region-transfer.toml +++ b/zombienet_tests/xc-transfer/region-transfer.toml @@ -30,4 +30,4 @@ addToGenesis = false [parachains.collator] name = "regionx-collator01" command = "regionx-node" - args = [ "--log=xcm=trace,regions=trace" ] + args = [ "--enable-offchain-indexing true --log=xcm=trace,regions=trace" ]