From 58f2b86dc190fd84f56e153136d564aaeaf60716 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Thu, 27 Feb 2020 18:24:19 +0900 Subject: [PATCH 1/5] Add DatagramTag to the CreateClient datagram --- ibc.ts/src/common/datagram/createClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibc.ts/src/common/datagram/createClient.ts b/ibc.ts/src/common/datagram/createClient.ts index fb0a513358..150b4adfd6 100644 --- a/ibc.ts/src/common/datagram/createClient.ts +++ b/ibc.ts/src/common/datagram/createClient.ts @@ -28,6 +28,6 @@ export class CreateClientDatagram { } public toEncodeObject(): any[] { - return [this.id, this.kind, this.consensusState, this.data]; + return [1, this.id, this.kind, this.consensusState, this.data]; } } From a304708b74cdb2be04c073e6c4be3e9f099b2be8 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Thu, 27 Feb 2020 15:39:24 +0900 Subject: [PATCH 2/5] Implement light client update logic --- ibc.ts/src/common/chain.ts | 4 +++ ibc.ts/src/relayer/index.ts | 50 ++++++++++++++++++++++++++++++++++--- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index 1f28bd541e..6885d1ac2f 100644 --- a/ibc.ts/src/common/chain.ts +++ b/ibc.ts/src/common/chain.ts @@ -41,6 +41,10 @@ export class Chain { const txHash = await this.sdk.rpc.chain.sendSignedTransaction(signedTx); waitForTx(this.sdk, txHash); } + + public async latestHeight(): Promise { + return await this.sdk.rpc.chain.getBestBlockNumber(); + } } async function waitForTx(sdk: SDK, txHash: H256) { diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts index ce55775d92..a827da4aba 100644 --- a/ibc.ts/src/relayer/index.ts +++ b/ibc.ts/src/relayer/index.ts @@ -57,8 +57,50 @@ async function relayFromTo({ } } -async function pendingDatagrams( - args: any -): Promise<{ localDatagrams: Datagram[]; counterpartyDatagrams: Datagram[] }> { - return { localDatagrams: [], counterpartyDatagrams: [] }; +async function pendingDatagrams({ + chain, + counterpartyChain +}: { + chain: Chain; + counterpartyChain: Chain; +}): Promise<{ localDatagrams: Datagram[]; counterpartyDatagrams: Datagram[] }> { + const height = await chain.latestHeight(); + const counterpartyChainHeight = await chain.latestHeight(); + let localDatagrams: Datagram[] = []; + let counterpartyDatagrams: Datagram[] = []; + + localDatagrams = localDatagrams.concat( + await updateLightClient({ + chain, + counterpartyChain, + height, + counterpartyChainHeight + }) + ); + + counterpartyDatagrams = counterpartyDatagrams.concat( + await updateLightClient({ + chain: counterpartyChain, + counterpartyChain: chain, + height: counterpartyChainHeight, + counterpartyChainHeight: height + }) + ); + + return { localDatagrams, counterpartyDatagrams }; +} + +async function updateLightClient({ + chain, + counterpartyChain, + height, + counterpartyChainHeight +}: { + chain: Chain; + counterpartyChain: Chain; + height: number; + counterpartyChainHeight: number; +}): Promise { + console.error("Not implemented"); + return []; } From 70129cc7c89616997f11da16b63df9cac008198b Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Fri, 28 Feb 2020 12:18:32 +0900 Subject: [PATCH 3/5] Add format rule for .env.default --- ibc.ts/.editorconfig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ibc.ts/.editorconfig b/ibc.ts/.editorconfig index e9149aad0b..8e987400fb 100644 --- a/ibc.ts/.editorconfig +++ b/ibc.ts/.editorconfig @@ -3,3 +3,7 @@ trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 + +[.env.default] +trim_trailing_whitespace = true +insert_final_newline = true From fe8dacbbdd1c2a8ce16b2ea5ee8b4e9ec3c6aad3 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Thu, 27 Feb 2020 16:51:49 +0900 Subject: [PATCH 4/5] Add identifiers in the config --- ibc.ts/.env.default | 6 ++++++ ibc.ts/src/common/chain.ts | 19 +++++++++++++++++++ ibc.ts/src/relayer/config.ts | 17 +++++++++++++++-- ibc.ts/src/relayer/index.ts | 14 ++++++++++++-- 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/ibc.ts/.env.default b/ibc.ts/.env.default index cff08b4f12..05b9ceaa48 100644 --- a/ibc.ts/.env.default +++ b/ibc.ts/.env.default @@ -1,6 +1,12 @@ CHAIN_A_RPC_URL="http://localhost:8080" CHAIN_A_NETWORK_ID="tc" CHAIN_A_FAUCET_ADDRESS="tccqym6znlgc48qeelrzccehkcaut7yz39wwq96q3y7" +CHAIN_A_COUNTERPARTY_CLIENT_ID="BClient" +CHAIN_A_COUNTERPARTY_CONNECTION_ID="BConnection" +CHAIN_A_COUNTERPARTY_CHANNEL_ID="BChannel" CHAIN_B_RPC_URL="http://localhost:8081" CHAIN_B_NETWORK_ID="fc" CHAIN_B_FAUCET_ADDRESS="fccqyd6clszl2aeq4agrk8sgq8whkty6ktljuemc9y3" +CHAIN_B_COUNTERPARTY_CLIENT_ID="AClient" +CHAIN_B_COUNTERPARTY_CONNECTION_ID="AConnection" +CHAIN_B_COUNTERPARTY_CHANNEL_ID="AChannel" diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index 6885d1ac2f..4cb8af2619 100644 --- a/ibc.ts/src/common/chain.ts +++ b/ibc.ts/src/common/chain.ts @@ -4,9 +4,25 @@ import { H256, PlatformAddress } from "codechain-primitives"; import { IBC } from "./foundry/transaction"; import { delay } from "./util"; import Debug from "debug"; +import { ClientState } from "./foundry/types"; const debug = Debug("common:tx"); +export interface CounterpartyIdentifiers { + /** + * Identifier for counter party chain's light client saved in this chain + */ + client: string; + /** + * Identifier for connection with counterparty chain + */ + connection: string; + /** + * Identifier for channel with counterparty chain + */ + channel: string; +} + export interface ChainConfig { /** * Example: "http://localhost:8080" @@ -14,11 +30,13 @@ export interface ChainConfig { server: string; networkId: string; faucetAddress: PlatformAddress; + counterpartyIdentifiers: CounterpartyIdentifiers; } export class Chain { private readonly sdk: SDK; private readonly faucetAddress: PlatformAddress; + private readonly counterpartyIdentifiers: CounterpartyIdentifiers; public constructor(config: ChainConfig) { this.sdk = new SDK({ @@ -26,6 +44,7 @@ export class Chain { networkId: config.networkId }); this.faucetAddress = config.faucetAddress; + this.counterpartyIdentifiers = config.counterpartyIdentifiers; } public async submitDatagram(datagram: Datagram): Promise { diff --git a/ibc.ts/src/relayer/config.ts b/ibc.ts/src/relayer/config.ts index 18344b0d94..f08210b6d1 100644 --- a/ibc.ts/src/relayer/config.ts +++ b/ibc.ts/src/relayer/config.ts @@ -11,6 +11,9 @@ interface FoundryChainConfig { rpcURL: string; networkId: string; faucetAddress: string; + counterpartyClientId: string; + counterpartyConnectionId: string; + counterpartyChannelId: string; } export function getConfig(): Config { @@ -18,12 +21,22 @@ export function getConfig(): Config { chainA: { rpcURL: getEnv("CHAIN_A_RPC_URL"), networkId: getEnv("CHAIN_A_NETWORK_ID"), - faucetAddress: getEnv("CHAIN_A_FAUCET_ADDRESS") + faucetAddress: getEnv("CHAIN_A_FAUCET_ADDRESS"), + counterpartyClientId: getEnv("CHAIN_A_COUNTERPARTY_CLIENT_ID"), + counterpartyConnectionId: getEnv( + "CHAIN_A_COUNTERPARTY_CONNECTION_ID" + ), + counterpartyChannelId: getEnv("CHAIN_A_COUNTERPARTY_CHANNEL_ID") }, chainB: { rpcURL: getEnv("CHAIN_B_RPC_URL"), networkId: getEnv("CHAIN_B_NETWORK_ID"), - faucetAddress: getEnv("CHAIN_B_FAUCET_ADDRESS") + faucetAddress: getEnv("CHAIN_B_FAUCET_ADDRESS"), + counterpartyClientId: getEnv("CHAIN_B_COUNTERPARTY_CLIENT_ID"), + counterpartyConnectionId: getEnv( + "CHAIN_B_COUNTERPARTY_CONNECTION_ID" + ), + counterpartyChannelId: getEnv("CHAIN_B_COUNTERPARTY_CHANNEL_ID") } }; } diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts index a827da4aba..788168d9cc 100644 --- a/ibc.ts/src/relayer/index.ts +++ b/ibc.ts/src/relayer/index.ts @@ -14,12 +14,22 @@ async function main() { const chainA = new Chain({ server: config.chainA.rpcURL, networkId: config.chainA.networkId, - faucetAddress: PlatformAddress.fromString(config.chainA.faucetAddress) + faucetAddress: PlatformAddress.fromString(config.chainA.faucetAddress), + counterpartyIdentifiers: { + client: config.chainA.counterpartyClientId, + connection: config.chainA.counterpartyConnectionId, + channel: config.chainA.counterpartyChannelId + } }); const chainB = new Chain({ server: config.chainB.rpcURL, networkId: config.chainB.networkId, - faucetAddress: PlatformAddress.fromString(config.chainB.faucetAddress) + faucetAddress: PlatformAddress.fromString(config.chainB.faucetAddress), + counterpartyIdentifiers: { + client: config.chainB.counterpartyClientId, + connection: config.chainB.counterpartyConnectionId, + channel: config.chainB.counterpartyChannelId + } }); while (true) { From 8a08e45593f8bc4f1a47f9e9a7837acdc7d011d3 Mon Sep 17 00:00:00 2001 From: Park Juhyung Date: Thu, 27 Feb 2020 18:45:45 +0900 Subject: [PATCH 5/5] Implementing light client algorithm --- ibc.ts/src/common/chain.ts | 16 ++++++++++++- ibc.ts/src/common/datagram/updateClient.ts | 19 ++++++++++++++++ ibc.ts/src/common/foundry/types.ts | 4 ++++ ibc.ts/src/common/types.ts | 6 +++++ ibc.ts/src/relayer/index.ts | 26 ++++++++++++++++++++-- 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 ibc.ts/src/common/datagram/updateClient.ts create mode 100644 ibc.ts/src/common/foundry/types.ts create mode 100644 ibc.ts/src/common/types.ts diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index 4cb8af2619..f76d02f8c6 100644 --- a/ibc.ts/src/common/chain.ts +++ b/ibc.ts/src/common/chain.ts @@ -5,6 +5,7 @@ import { IBC } from "./foundry/transaction"; import { delay } from "./util"; import Debug from "debug"; import { ClientState } from "./foundry/types"; +import { IBCHeader, IBCQueryResult } from "./types"; const debug = Debug("common:tx"); @@ -36,7 +37,7 @@ export interface ChainConfig { export class Chain { private readonly sdk: SDK; private readonly faucetAddress: PlatformAddress; - private readonly counterpartyIdentifiers: CounterpartyIdentifiers; + public readonly counterpartyIdentifiers: CounterpartyIdentifiers; public constructor(config: ChainConfig) { this.sdk = new SDK({ @@ -64,6 +65,19 @@ export class Chain { public async latestHeight(): Promise { return await this.sdk.rpc.chain.getBestBlockNumber(); } + + public async queryClient( + blockNumber: number + ): Promise | null> { + return this.sdk.rpc.sendRpcRequest("ibc_query_client_state", [ + this.counterpartyIdentifiers.client, + blockNumber + ]); + } + + public async queryHeader(blockNumber: number): Promise { + return this.sdk.rpc.sendRpcRequest("ibc_compose_header", [blockNumber]); + } } async function waitForTx(sdk: SDK, txHash: H256) { diff --git a/ibc.ts/src/common/datagram/updateClient.ts b/ibc.ts/src/common/datagram/updateClient.ts new file mode 100644 index 0000000000..9ac55094c0 --- /dev/null +++ b/ibc.ts/src/common/datagram/updateClient.ts @@ -0,0 +1,19 @@ +const RLP = require("rlp"); + +export class UpdateClientDatagram { + private id: string; + private header: Buffer; + + public constructor({ id, header }: { id: string; header: Buffer }) { + this.id = id; + this.header = header; + } + + public rlpBytes(): Buffer { + return RLP.encode(this.toEncodeObject()); + } + + public toEncodeObject(): any[] { + return [2, this.id, this.header]; + } +} diff --git a/ibc.ts/src/common/foundry/types.ts b/ibc.ts/src/common/foundry/types.ts new file mode 100644 index 0000000000..f8761a90d6 --- /dev/null +++ b/ibc.ts/src/common/foundry/types.ts @@ -0,0 +1,4 @@ +export interface ClientState { + number: number; + next_validator_set_hash: string; +} diff --git a/ibc.ts/src/common/types.ts b/ibc.ts/src/common/types.ts new file mode 100644 index 0000000000..a969385519 --- /dev/null +++ b/ibc.ts/src/common/types.ts @@ -0,0 +1,6 @@ +export interface IBCQueryResult { + data: T | null; + proof: string; +} + +export type IBCHeader = string; diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts index 788168d9cc..d477d16d73 100644 --- a/ibc.ts/src/relayer/index.ts +++ b/ibc.ts/src/relayer/index.ts @@ -4,6 +4,7 @@ import { Datagram } from "../common/datagram/index"; import { delay } from "../common/util"; import { getConfig } from "./config"; import { PlatformAddress } from "codechain-primitives/lib"; +import { UpdateClientDatagram } from "../common/datagram/updateClient"; require("dotenv").config(); @@ -111,6 +112,27 @@ async function updateLightClient({ height: number; counterpartyChainHeight: number; }): Promise { - console.error("Not implemented"); - return []; + const datagrams = []; + const clientState = await chain.queryClient(height); + + if (clientState!.data == null) { + throw new Error( + `No client state found. Please create a light client with identifier: ${chain.counterpartyIdentifiers.client}` + ); + } + let currentBlockNumber = clientState!.data!.number; + while (currentBlockNumber < counterpartyChainHeight) { + const header = (await counterpartyChain.queryHeader( + currentBlockNumber + 1 + ))!; + datagrams.push( + new UpdateClientDatagram({ + id: chain.counterpartyIdentifiers.client, + header: Buffer.from(header, "hex") + }) + ); + currentBlockNumber += 1; + } + + return datagrams; }