Skip to content
Open
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
53 changes: 1 addition & 52 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ ismp-parachain = { git = "https://github.com/polytope-labs/hyperbridge.git", tag
ismp-parachain-inherent = { git = "https://github.com/polytope-labs/hyperbridge.git", tag = "hyperbridge-v1.3.0", default-features = false }
pallet-ismp-runtime-api = { git = "https://github.com/polytope-labs/hyperbridge.git", tag = "hyperbridge-v1.3.0", default-features = false }
ismp-parachain-runtime-api = { git = "https://github.com/polytope-labs/hyperbridge.git", tag = "hyperbridge-v1.3.0", default-features = false }
pallet-mmr-tree = { git = "https://github.com/polytope-labs/hyperbridge.git", tag = "hyperbridge-v1.3.0", default-features = false }
pallet-mmr-runtime-api = { git = "https://github.com/polytope-labs/hyperbridge.git", tag = "hyperbridge-v1.3.0", default-features = false }
ismp-testsuite = { git = "https://github.com/polytope-labs/hyperbridge.git", tag = "hyperbridge-v1.3.0", default-features = false }

# Local
Expand Down
135 changes: 101 additions & 34 deletions e2e_tests/ismp.common.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ApiPromise } from '@polkadot/api';
import { KeyringPair } from '@polkadot/keyring/types';
import { submitExtrinsic, submitUnsigned } from './common';
import { sleep, submitExtrinsic, submitUnsigned } from './common';
import { Get, IsmpRequest } from './types';
import { encodePacked, keccak256, toHex } from 'viem';
import { keccakAsHex } from '@polkadot/util-crypto';

async function ismpAddParachain(signer: KeyringPair, regionXApi: ApiPromise) {
const addParaCall = regionXApi.tx.ismpParachain.addParachain([{ id: 1005, slotDuration: 6000 }]);
Expand All @@ -13,6 +15,7 @@ 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.
console.log(requests.toJSON());
return requests.toJSON()[0] as IsmpRequest;
}

Expand All @@ -22,44 +25,108 @@ async function makeIsmpResponse(
request: IsmpRequest,
responderAddress: string
): Promise<void> {
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);
console.log(request);
if (!isGetRequest(request)) {
console.log('not get request');
new Error('Expected a Get request');
return;
}

const stateMachineProof = regionXApi.createType('StateMachineProof', {
hasher: 'Blake2',
storage_proof: proofData.proof,
});
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 substrateStateProof = regionXApi.createType('SubstrateStateProof', {
StateProof: stateMachineProof,
});
const response = regionXApi.tx.ismp.handleUnsigned([
{
Response: {
datagram: {
Request: [request],
},
proof: {
height: {
id: {
stateId: 1005,
consensusStateId: 'PAS0',
},
height: request.get.height.toString(),
},
proof: substrateStateProof.toHex(),
const stateMachineProof = regionXApi.createType('StateMachineProof', {
hasher: 'Blake2',
storage_proof: proofData.proof,
});

const substrateStateProof = regionXApi.createType('SubstrateStateProof', {
StateProof: stateMachineProof,
});

const response = [{
Response: {
datagram: {
Request: [{
Get: {
source: { Kusama: 2000 },
dest: { Kusama: 1005 },
nonce: request.get.nonce,
from: request.get.from,
keys: request.get.keys,
height: request.get.height,
context: request.get.context,
timeoutTimestamp: request.get.timeout_timestamp,
}
}]
},
proof: {
height: {
id: {
stateId: { Kusama: 1005 },
consensusStateId: 'PAS0',
},
signer: responderAddress,
height: request.get.height,
},
proof: substrateStateProof.toHex(),
},
]);
await submitUnsigned(response);
} else {
new Error('Expected a Get request');
}
signer: responderAddress,
},
}];

// console.log(getRequestCommitment({
// source: 'KUSAMA-2000',
// dest: 'KUSAMA-1005',
// nonce: request.get.nonce,
// from: request.get.from,
// keys: request.get.keys,
// height: request.get.height,
// context: request.get.context,
// timeoutTimestamp: request.get.timeout_timestamp,
// }));

console.log(JSON.stringify(response));

await submitUnsigned(regionXApi.tx.ismp.handleUnsigned(response));
await sleep(360 * 1000);
}

export function getRequestCommitment(regionXApi: ApiPromise, get: any): string {
// const keysEncoding = "0x".concat(get.keys.map((key: string) => key.slice(2)).join(""))
// return keccak256(
// encodePacked(
// ["bytes", "bytes", "uint64", "uint64", "uint64", "bytes", "bytes", "bytes"],
// [
// toHex(get.source),
// toHex(get.dest),
// get.nonce,
// get.height,
// get.timeoutTimestamp,
// get.from,
// keysEncoding as any,
// get.context,
// ],
// ),
// )
const reqEnum = regionXApi.createType('Request', {
Get: {
source: get.source, // e.g. { Kusama: 1005 }
dest: get.dest, // e.g. { Kusama: 2000 }
nonce: get.nonce, // u64
from: get.from, // Bytes
keys: get.keys, // Vec<Vec<u8>>
height: get.height, // u64
timeout_timestamp: get.timeoutTimestamp, // u64
}
});

const bytes = reqEnum.toU8a();

// ISMP uses keccak256 for request/response commitments
const commitment = keccakAsHex(bytes);
return commitment;
}

const isGetRequest = (request: IsmpRequest): request is { get: Get } => {
Expand Down
20 changes: 16 additions & 4 deletions e2e_tests/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { Enum, Struct, u32, u64, u8, Vector, Option } from "scale-ts";

export type StateMachine = { Polkadot: number } | { Kusama: number };

export interface Get {
source: StateMachine;
dest: StateMachine;
source: string;
dest: string;
nonce: bigint;
from: string;
keys: Array<string>;
height: bigint;
context: string;
timeout_timestamp: bigint;
}

Expand Down Expand Up @@ -42,12 +45,20 @@ export const REGIONX_API_TYPES = {
LeafIndexQuery: {
commitment: 'H256',
},
ConsensusStateId: '[u8; 4]',
Relay: {
relay: 'ConsensusStateId',
para_id: 'u32'
},
StateMachine: {
_enum: {
Ethereum: 'Vec<u8>',
Evm: 'u32',
Polkadot: 'u32',
Kusama: 'u32',
},
Substrate: 'ConsensusStateId',
Tendermint: 'ConsensusStateId',
Relay: 'Relay'
}
},
Post: {},
Get: {
Expand All @@ -57,6 +68,7 @@ export const REGIONX_API_TYPES = {
from: 'Vec<u8>',
keys: 'Vec<Vec<u8>>',
height: 'u64',
context: 'Vec<u8>',
timeout_timestamp: 'u64',
},
Request: {
Expand Down
14 changes: 10 additions & 4 deletions e2e_tests/xc-regions.common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getEncodedRegionId, RegionId } from 'coretime-utils';
import assert from 'node:assert';
import { sleep, submitExtrinsic, submitUnsigned } from './common';
import { makeIsmpResponse, queryRequest } from './ismp.common';
import { encodeAddress } from '@polkadot/util-crypto';

const REGIONX_SOVEREIGN_ACCOUNT = '5Eg2fntJ27qsari4FGrGhrMqKFDRnkNSR6UshkZYBGXmSuC8';

Expand Down Expand Up @@ -75,8 +76,8 @@ async function transferRegionToRegionX(
assert.equal(regions.length, 1);
assert.deepStrictEqual(regions[0][0].toHuman(), [regionId]);

let region = regions[0][1].toHuman() as any;
assert(region.owner == sender.address);
const region = regions[0][1].toHuman() as any;
assert(encodeAddress(region.owner, 42) == encodeAddress(sender.address, 42));
assert(typeof region.record.Pending === 'string');

// Check the data on the Coretime chain:
Expand All @@ -86,13 +87,18 @@ async function transferRegionToRegionX(
assert.equal((regions[0][1].toHuman() as any).owner, REGIONX_SOVEREIGN_ACCOUNT);

// Respond to the ISMP get request:
console.log(region.record.Pending);
const request = await queryRequest(regionXApi, region.record.Pending);
await makeIsmpResponse(regionXApi, coretimeApi, request, sender.address);
await makeIsmpResponse(regionXApi, coretimeApi, request, encodeAddress(sender.address, 74));

// await sleep(360 * 1000);

/*
// 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 == sender.address);
assert(encodeAddress(region.owner, 42) == encodeAddress(sender.address, 42));
*/
}

async function transferRegionToCoretimeChain(
Expand Down
Loading