diff --git a/core/src/ibc/transaction_handler/datagrams.rs b/core/src/ibc/transaction_handler/datagrams.rs index 7addc95ece..72fa45f546 100644 --- a/core/src/ibc/transaction_handler/datagrams.rs +++ b/core/src/ibc/transaction_handler/datagrams.rs @@ -186,9 +186,9 @@ impl Decodable for Datagram { match tag { DatagramTag::CreateClient => { let item_count = rlp.item_count()?; - if item_count != 4 { + if item_count != 5 { return Err(DecoderError::RlpInvalidLength { - expected: 4, + expected: 5, got: item_count, }) } diff --git a/ibc.ts/README.md b/ibc.ts/README.md index 8d19ac5082..83fe20a5d6 100644 --- a/ibc.ts/README.md +++ b/ibc.ts/README.md @@ -12,6 +12,11 @@ This directory contains IBC relayer implementation and IBC demo scenario script. Run `yarn run runChains` +## How to run a scenario + +Run `yarn run scenario`. It will create light clients, a connection, and a channel. +Finally, it will send a packet. + ## Print debug log Please use `DEBUG` environment variable. diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index f76d02f8c6..d988d5bfba 100644 --- a/ibc.ts/src/common/chain.ts +++ b/ibc.ts/src/common/chain.ts @@ -32,6 +32,7 @@ export interface ChainConfig { networkId: string; faucetAddress: PlatformAddress; counterpartyIdentifiers: CounterpartyIdentifiers; + keystorePath: string; } export class Chain { @@ -42,7 +43,11 @@ export class Chain { public constructor(config: ChainConfig) { this.sdk = new SDK({ server: config.server, - networkId: config.networkId + networkId: config.networkId, + keyStoreType: { + type: "local", + path: config.keystorePath + } }); this.faucetAddress = config.faucetAddress; this.counterpartyIdentifiers = config.counterpartyIdentifiers; @@ -67,7 +72,7 @@ export class Chain { } public async queryClient( - blockNumber: number + blockNumber?: number ): Promise | null> { return this.sdk.rpc.sendRpcRequest("ibc_query_client_state", [ this.counterpartyIdentifiers.client, @@ -75,9 +80,17 @@ export class Chain { ]); } - public async queryHeader(blockNumber: number): Promise { + public async queryIBCHeader( + blockNumber: number + ): Promise { return this.sdk.rpc.sendRpcRequest("ibc_compose_header", [blockNumber]); } + + public async queryChainHeader(blockNumber: number): Promise { + return this.sdk.rpc.sendRpcRequest("chain_getRawHeaderByNumber", [ + blockNumber + ]); + } } async function waitForTx(sdk: SDK, txHash: H256) { diff --git a/ibc.ts/src/relayer/config.ts b/ibc.ts/src/common/config.ts similarity index 90% rename from ibc.ts/src/relayer/config.ts rename to ibc.ts/src/common/config.ts index f08210b6d1..7d9744f28f 100644 --- a/ibc.ts/src/relayer/config.ts +++ b/ibc.ts/src/common/config.ts @@ -14,6 +14,7 @@ interface FoundryChainConfig { counterpartyClientId: string; counterpartyConnectionId: string; counterpartyChannelId: string; + keystorePath: string; } export function getConfig(): Config { @@ -26,7 +27,8 @@ export function getConfig(): Config { counterpartyConnectionId: getEnv( "CHAIN_A_COUNTERPARTY_CONNECTION_ID" ), - counterpartyChannelId: getEnv("CHAIN_A_COUNTERPARTY_CHANNEL_ID") + counterpartyChannelId: getEnv("CHAIN_A_COUNTERPARTY_CHANNEL_ID"), + keystorePath: "./chainA/keystore.db" }, chainB: { rpcURL: getEnv("CHAIN_B_RPC_URL"), @@ -36,7 +38,8 @@ export function getConfig(): Config { counterpartyConnectionId: getEnv( "CHAIN_B_COUNTERPARTY_CONNECTION_ID" ), - counterpartyChannelId: getEnv("CHAIN_B_COUNTERPARTY_CHANNEL_ID") + counterpartyChannelId: getEnv("CHAIN_B_COUNTERPARTY_CHANNEL_ID"), + keystorePath: "./chainB/keystore.db" } }; } diff --git a/ibc.ts/src/common/example.ts b/ibc.ts/src/common/example.ts deleted file mode 100644 index 5a027ec8e9..0000000000 --- a/ibc.ts/src/common/example.ts +++ /dev/null @@ -1 +0,0 @@ -export let HelloWorld = "HelloWorld"; diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts index d477d16d73..6442e3af85 100644 --- a/ibc.ts/src/relayer/index.ts +++ b/ibc.ts/src/relayer/index.ts @@ -2,7 +2,7 @@ import Debug from "debug"; import { Chain } from "../common/chain"; import { Datagram } from "../common/datagram/index"; import { delay } from "../common/util"; -import { getConfig } from "./config"; +import { getConfig } from "../common/config"; import { PlatformAddress } from "codechain-primitives/lib"; import { UpdateClientDatagram } from "../common/datagram/updateClient"; @@ -20,7 +20,8 @@ async function main() { client: config.chainA.counterpartyClientId, connection: config.chainA.counterpartyConnectionId, channel: config.chainA.counterpartyChannelId - } + }, + keystorePath: config.chainA.keystorePath }); const chainB = new Chain({ server: config.chainB.rpcURL, @@ -30,7 +31,8 @@ async function main() { client: config.chainB.counterpartyClientId, connection: config.chainB.counterpartyConnectionId, channel: config.chainB.counterpartyChannelId - } + }, + keystorePath: config.chainB.keystorePath }); while (true) { @@ -122,7 +124,7 @@ async function updateLightClient({ } let currentBlockNumber = clientState!.data!.number; while (currentBlockNumber < counterpartyChainHeight) { - const header = (await counterpartyChain.queryHeader( + const header = (await counterpartyChain.queryIBCHeader( currentBlockNumber + 1 ))!; datagrams.push( diff --git a/ibc.ts/src/scenario/index.ts b/ibc.ts/src/scenario/index.ts index f120bc805c..c33e2944d9 100644 --- a/ibc.ts/src/scenario/index.ts +++ b/ibc.ts/src/scenario/index.ts @@ -1,7 +1,87 @@ -import { HelloWorld } from "../common/example"; +import Debug from "debug"; +import { getConfig } from "../common/config"; +import { Chain } from "../common/chain"; +import { PlatformAddress } from "codechain-primitives/lib"; +import { CreateClientDatagram } from "../common/datagram/createClient"; +import { strict as assert } from "assert"; + +require("dotenv").config(); + +const debug = Debug("scenario:main"); async function main() { - console.log(HelloWorld); + const config = getConfig(); + const chainA = new Chain({ + server: config.chainA.rpcURL, + networkId: config.chainA.networkId, + faucetAddress: PlatformAddress.fromString(config.chainA.faucetAddress), + counterpartyIdentifiers: { + client: config.chainA.counterpartyClientId, + connection: config.chainA.counterpartyConnectionId, + channel: config.chainA.counterpartyChannelId + }, + keystorePath: config.chainA.keystorePath + }); + const chainB = new Chain({ + server: config.chainB.rpcURL, + networkId: config.chainB.networkId, + faucetAddress: PlatformAddress.fromString(config.chainB.faucetAddress), + counterpartyIdentifiers: { + client: config.chainB.counterpartyClientId, + connection: config.chainB.counterpartyConnectionId, + channel: config.chainB.counterpartyChannelId + }, + keystorePath: config.chainB.keystorePath + }); + + console.log("Create a light client in chain A"); + await createLightClient({ chain: chainA, counterpartyChain: chainB }); + console.log("Create a light client in chain B"); + await createLightClient({ chain: chainB, counterpartyChain: chainA }); } main().catch(console.error); + +async function createLightClient({ + chain, + counterpartyChain +}: { + chain: Chain; + counterpartyChain: Chain; +}) { + debug("Create light client"); + const counterpartyBlockNumber = await counterpartyChain.latestHeight(); + const blockNumber = await chain.latestHeight(); + debug(`height is ${counterpartyBlockNumber}`); + const counterpartyRawHeader = await counterpartyChain.queryChainHeader( + counterpartyBlockNumber + ); + debug(`rawHeader is ${counterpartyRawHeader}`); + + assert(counterpartyRawHeader, "header should not be empty"); + assert.notStrictEqual( + counterpartyRawHeader!.substr(0, 2), + "0x", + "should not start with 0x" + ); + + debug(`Get queryClient`); + const clientStateBefore = await chain.queryClient(blockNumber); + assert.notEqual(clientStateBefore, null, "querying on the best block"); + assert.equal(clientStateBefore!.data, null, "client is not initialized"); + + const createClient = new CreateClientDatagram({ + id: chain.counterpartyIdentifiers.client, + kind: 0, + consensusState: Buffer.alloc(0), + data: Buffer.from(counterpartyRawHeader!, "hex") + }); + + debug(`Submit datagram`); + await chain.submitDatagram(createClient); + + const clientStateAfter = await chain.queryClient(); + assert.notEqual(clientStateAfter, null, "querying on the best block"); + assert.notEqual(clientStateAfter!.data, null, "client is initialized"); + debug(`Create client is ${JSON.stringify(clientStateAfter)}`); +} diff --git a/rpc/src/v1/impls/chain.rs b/rpc/src/v1/impls/chain.rs index dcea46a28f..2a9df2cd32 100644 --- a/rpc/src/v1/impls/chain.rs +++ b/rpc/src/v1/impls/chain.rs @@ -26,6 +26,7 @@ use ctypes::transaction::Action; use ctypes::{BlockHash, BlockNumber, ShardId, Tracker, TxHash}; use jsonrpc_core::Result; use primitives::H256; +use rustc_hex::ToHex; use std::convert::TryFrom; use std::sync::Arc; @@ -172,6 +173,11 @@ where })) } + fn get_raw_header_by_number(&self, block_number: u64) -> Result> { + let id = BlockId::Number(block_number); + Ok(self.client.block_header(&id).map(|header| header.into_inner().as_slice().to_hex())) + } + fn get_block_transaction_count_by_hash(&self, block_hash: BlockHash) -> Result> { Ok(self.client.block(&BlockId::Hash(block_hash)).map(|block| block.transactions_count())) } diff --git a/rpc/src/v1/impls/ibc.rs b/rpc/src/v1/impls/ibc.rs index a54f0104a0..95c8a2101c 100644 --- a/rpc/src/v1/impls/ibc.rs +++ b/rpc/src/v1/impls/ibc.rs @@ -23,6 +23,7 @@ use ccore::{BlockChainClient, BlockId, StateInfo}; use ibc::client_02::types::Header; use jsonrpc_core::Result; use primitives::Bytes; +use rustc_hex::ToHex; use std::sync::Arc; #[allow(dead_code)] @@ -69,7 +70,7 @@ where Ok(Some(IBCQuery { number: block_number, data: client_state.map(|x| ClientState::from_core(&x)), - proof: querier::make_proof(&context, &path), + proof: querier::make_proof(&context, &path).to_hex(), })) } @@ -96,7 +97,7 @@ where Ok(Some(IBCQuery { number: block_number, data: consensus_state.map(|x| ConsensusState::from_core(&x)), - proof: querier::make_proof(&context, &path), + proof: querier::make_proof(&context, &path).to_hex(), })) } @@ -147,7 +148,7 @@ where let response = IBCQuery { number: block_number, data: connection_end.map(ConnectionEnd::from_core), - proof: querier::make_proof(&context, &path), + proof: querier::make_proof(&context, &path).to_hex(), }; Ok(Some(response)) } @@ -172,7 +173,7 @@ where let response = IBCQuery { number: block_number, data: connections_in_client.map(|from_core| from_core.into_vec()), - proof: querier::make_proof(&context, &path), + proof: querier::make_proof(&context, &path).to_hex(), }; Ok(Some(response)) } diff --git a/rpc/src/v1/traits/chain.rs b/rpc/src/v1/traits/chain.rs index c3afff623e..666a9883ed 100644 --- a/rpc/src/v1/traits/chain.rs +++ b/rpc/src/v1/traits/chain.rs @@ -107,6 +107,10 @@ pub trait Chain { #[rpc(name = "chain_getBlockTransactionCountByHash")] fn get_block_transaction_count_by_hash(&self, block_hash: BlockHash) -> Result>; + ///Gets the raw bytes of a header with given number. + #[rpc(name = "chain_getRawHeaderByNumber")] + fn get_raw_header_by_number(&self, block_number: u64) -> Result>; + ///Gets the minimum transaction fee of the given name. #[rpc(name = "chain_getMinTransactionFee")] fn get_min_transaction_fee(&self, action_type: String, block_number: Option) -> Result>; diff --git a/rpc/src/v1/types/ibc.rs b/rpc/src/v1/types/ibc.rs index 8a6ee191d1..ab940a563a 100644 --- a/rpc/src/v1/types/ibc.rs +++ b/rpc/src/v1/types/ibc.rs @@ -18,7 +18,7 @@ use codechain_core::ibc::client_02::types::{ClientState as CoreClientState, Cons use codechain_core::ibc::connection_03::types::{ ConnectionEnd as CoreConnectionEnd, ConnectionState as CoreConnectionState, }; -use primitives::{Bytes, H256}; +use primitives::H256; use serde::Serialize; type Identifier = String; @@ -36,7 +36,7 @@ type CommitmentPrefix = String; pub struct IBCQuery { pub number: u64, pub data: Option, - pub proof: Bytes, + pub proof: String, } /// Client 02 related types diff --git a/types/src/transaction/action.rs b/types/src/transaction/action.rs index 112c872a46..18793152a3 100644 --- a/types/src/transaction/action.rs +++ b/types/src/transaction/action.rs @@ -51,6 +51,7 @@ impl Decodable for ActionTag { 0x05 => Ok(Self::SetShardOwners), 0x06 => Ok(Self::SetShardUsers), 0x19 => Ok(Self::ShardStore), + 0x20 => Ok(Self::IBC), 0xFF => Ok(Self::Custom), _ => Err(DecoderError::Custom("Unexpected action prefix")), }