From a4a6152eebefc8e46a0b1c353f34dc99d18de53b Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 17:37:53 +0900 Subject: [PATCH 01/10] Fix CreateClient datagram's deserialization code --- core/src/ibc/transaction_handler/datagrams.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, }) } From 6f34e5d53223c7f47925785bcaa157f08383c72d Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 17:38:10 +0900 Subject: [PATCH 02/10] Fix IBC transaction decode error --- types/src/transaction/action.rs | 1 + 1 file changed, 1 insertion(+) 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")), } From 800ab89908bae092d2f4b43e555e50dd4b0fc40f Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 17:57:22 +0900 Subject: [PATCH 03/10] Change the type of proof to String in the rpc code The default deserialized format of Vec is hard to use. It is an array of numbers like [249, 1, 148, ...]. Change RPCs to return hex string instead. --- rpc/src/v1/impls/ibc.rs | 9 +++++---- rpc/src/v1/types/ibc.rs | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) 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/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 From 6aaf3b53812b4f1223ad40ea8acf16c46e5737c9 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 17:39:16 +0900 Subject: [PATCH 04/10] Add keystore path in the config Each chain in IBC scenario will use its own keystore. --- ibc.ts/src/common/chain.ts | 7 ++++++- ibc.ts/src/relayer/config.ts | 7 +++++-- ibc.ts/src/relayer/index.ts | 6 ++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index f76d02f8c6..e84929c978 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; diff --git a/ibc.ts/src/relayer/config.ts b/ibc.ts/src/relayer/config.ts index f08210b6d1..7d9744f28f 100644 --- a/ibc.ts/src/relayer/config.ts +++ b/ibc.ts/src/relayer/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/relayer/index.ts b/ibc.ts/src/relayer/index.ts index d477d16d73..31190dad80 100644 --- a/ibc.ts/src/relayer/index.ts +++ b/ibc.ts/src/relayer/index.ts @@ -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) { From 25daca1153bfb427fec20f76395be8ee10ccec20 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 14:23:52 +0900 Subject: [PATCH 05/10] Move config to common The same config will be used in the IBC scenario. --- ibc.ts/src/{relayer => common}/config.ts | 0 ibc.ts/src/relayer/index.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename ibc.ts/src/{relayer => common}/config.ts (100%) diff --git a/ibc.ts/src/relayer/config.ts b/ibc.ts/src/common/config.ts similarity index 100% rename from ibc.ts/src/relayer/config.ts rename to ibc.ts/src/common/config.ts diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts index 31190dad80..9f072d813a 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"; From 62e2dd05907d14ae563026995d14cdcf4ec1f7ae Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 12:30:24 +0900 Subject: [PATCH 06/10] Remove unnecessary HelloWorld variable We created the HelloWorld variable to explain how to use the common code in the ibc.ts directory. --- ibc.ts/src/common/example.ts | 1 - ibc.ts/src/scenario/index.ts | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 ibc.ts/src/common/example.ts 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/scenario/index.ts b/ibc.ts/src/scenario/index.ts index f120bc805c..ceb80a6ab7 100644 --- a/ibc.ts/src/scenario/index.ts +++ b/ibc.ts/src/scenario/index.ts @@ -1,7 +1,5 @@ -import { HelloWorld } from "../common/example"; - async function main() { - console.log(HelloWorld); + console.log("Not implemented"); } main().catch(console.error); From 94363b53469d1308e4e09c374e2524a3671e20d0 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 15:03:43 +0900 Subject: [PATCH 07/10] Rename queryHeader to queryIBCHeader We will add queryRawHeader in the Chain class. To make code clear, I renamed the queryHeader. --- ibc.ts/src/common/chain.ts | 4 +++- ibc.ts/src/relayer/index.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index e84929c978..939a49c6ff 100644 --- a/ibc.ts/src/common/chain.ts +++ b/ibc.ts/src/common/chain.ts @@ -80,7 +80,9 @@ export class Chain { ]); } - public async queryHeader(blockNumber: number): Promise { + public async queryIBCHeader( + blockNumber: number + ): Promise { return this.sdk.rpc.sendRpcRequest("ibc_compose_header", [blockNumber]); } } diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts index 9f072d813a..6442e3af85 100644 --- a/ibc.ts/src/relayer/index.ts +++ b/ibc.ts/src/relayer/index.ts @@ -124,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( From b40f9ecdf63abc7125e1e8c515bcf63a1c87cd56 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 15:37:45 +0900 Subject: [PATCH 08/10] Add chain_getRawHeaderByNumber RPC To create a light client we need raw bytes of a header. It will be included in the CreateClient datagram. --- rpc/src/v1/impls/chain.rs | 6 ++++++ rpc/src/v1/traits/chain.rs | 4 ++++ 2 files changed, 10 insertions(+) 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/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>; From 73caefee75211dbc93155b7bd4e2d703b4789207 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 15:49:08 +0900 Subject: [PATCH 09/10] Create IBC light clients in ICS scenario --- ibc.ts/src/common/chain.ts | 8 +++- ibc.ts/src/scenario/index.ts | 84 +++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index 939a49c6ff..d988d5bfba 100644 --- a/ibc.ts/src/common/chain.ts +++ b/ibc.ts/src/common/chain.ts @@ -72,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, @@ -85,6 +85,12 @@ export class Chain { ): 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/scenario/index.ts b/ibc.ts/src/scenario/index.ts index ceb80a6ab7..c33e2944d9 100644 --- a/ibc.ts/src/scenario/index.ts +++ b/ibc.ts/src/scenario/index.ts @@ -1,5 +1,87 @@ +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("Not implemented"); + 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)}`); +} From cf70921526eacc150eb019b7a8097c0f410626fe Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Mon, 2 Mar 2020 18:17:24 +0900 Subject: [PATCH 10/10] Add "How to run a scenario" in the README.md --- ibc.ts/README.md | 5 +++++ 1 file changed, 5 insertions(+) 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.