diff --git a/core/src/ibc/channel_04/manager.rs b/core/src/ibc/channel_04/manager.rs index 9530d2039b..dae07f3d79 100644 --- a/core/src/ibc/channel_04/manager.rs +++ b/core/src/ibc/channel_04/manager.rs @@ -54,12 +54,17 @@ impl<'a> Manager<'a> { } // Utility functions - fn check_connection_opened(&self, id: IdentifierSlice) -> Result { + fn query_connection(&self, connection_id: IdentifierSlice) -> Result { let kv_store = self.ctx.get_kv_store(); - let connection_end: ConnectionEnd = - rlp::decode(&kv_store.get(&connection_path(&id)).ok_or_else(|| "Connection doesn't exist".to_owned())?) - .expect("Illformed ConnectionEnd stored in the DB"); + let connection_end: ConnectionEnd = rlp::decode( + &kv_store.get(&connection_path(&connection_id)).ok_or_else(|| "Connection doesn't exist".to_owned())?, + ) + .expect("Illformed ConnectionEnd stored in the DB"); + Ok(connection_end) + } + fn check_connection_opened(&self, id: IdentifierSlice) -> Result { + let connection_end = self.query_connection(id)?; if connection_end.state != ConnectionState::OPEN { return Err("Connection not opened".to_owned()) } @@ -203,6 +208,7 @@ impl<'a> Manager<'a> { } let client_identifier = self.check_connection_opened(&connection)?; + let connection_end = self.query_connection(&connection)?; let expected = ChannelEnd { state: ChannelState::INIT, @@ -210,7 +216,7 @@ impl<'a> Manager<'a> { counterparty_port_identifier: DEFAULT_PORT.to_string(), counterparty_channel_identifier: channel_identifier.clone(), // Note: the array should be reversed in the future where `connection` becomes an array. - connection_hops: vec![connection.clone()], + connection_hops: vec![connection_end.counterparty_connection_identifier], version: counterparty_version, }; @@ -284,9 +290,8 @@ impl<'a> Manager<'a> { counterparty_port_identifier: DEFAULT_PORT.to_string(), counterparty_channel_identifier: channel_identifier.clone(), connection_hops: { - let mut x = previous.connection_hops.clone(); - x.reverse(); - x + let connection_end = self.query_connection(&previous.connection_hops[0])?; + vec![connection_end.counterparty_connection_identifier] }, version: counterparty_version.clone(), }; @@ -330,9 +335,8 @@ impl<'a> Manager<'a> { counterparty_port_identifier: DEFAULT_PORT.to_string(), counterparty_channel_identifier: channel_identifier.clone(), connection_hops: { - let mut x = previous.connection_hops.clone(); - x.reverse(); - x + let connection_end = self.query_connection(&previous.connection_hops[0])?; + vec![connection_end.counterparty_connection_identifier] }, version: previous.version.clone(), }; @@ -392,9 +396,8 @@ impl<'a> Manager<'a> { counterparty_port_identifier: DEFAULT_PORT.to_string(), counterparty_channel_identifier: channel_identifier.clone(), connection_hops: { - let mut x = previous.connection_hops.clone(); - x.reverse(); - x + let connection_end = self.query_connection(&previous.connection_hops[0])?; + vec![connection_end.counterparty_connection_identifier] }, version: previous.version.clone(), }; diff --git a/core/src/ibc/context.rs b/core/src/ibc/context.rs index 49404b8de0..e8a807aa59 100644 --- a/core/src/ibc/context.rs +++ b/core/src/ibc/context.rs @@ -81,8 +81,13 @@ impl<'a> KVStore for TopLevelKVStore<'a> { } fn insert(&mut self, path: Path, value: &[u8]) -> Option { + // FIXME: the update_ibc_data returns the previous data. + // When the previous data is empty, it should return None. + // Currently it is returning an empty RLP array. + let prev = self.get(path); let key = TopLevelKVStore::key(path); - self.state.update_ibc_data(&key, value.to_vec()).expect("Set in IBC KVStore").map(Bytes::from) + self.state.update_ibc_data(&key, value.to_vec()).expect("Set in IBC KVStore"); + prev } fn remove(&mut self, path: Path) -> Option { diff --git a/core/src/ibc/transaction_handler/datagrams.rs b/core/src/ibc/transaction_handler/datagrams.rs index a236b41a76..fdf5be6d9b 100644 --- a/core/src/ibc/transaction_handler/datagrams.rs +++ b/core/src/ibc/transaction_handler/datagrams.rs @@ -14,13 +14,14 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +use crate::ibc::channel_04::types::ChannelOrder; use crate::ibc::channel_04::types::Packet; use primitives::Bytes; use rlp::{Decodable, DecoderError, Encodable, Rlp, RlpStream}; #[repr(u8)] -#[derive(Clone, Copy)] -enum DatagramTag { +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum DatagramTag { CreateClient = 1, UpdateClient = 2, ConnOpenInit = 3, @@ -72,7 +73,8 @@ impl Decodable for DatagramTag { // This is because of RLP macro's weak support. #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct ChanOpenInit { - pub order: u8, + pub tag: DatagramTag, + pub order: ChannelOrder, pub connection: String, pub channel_identifier: String, pub counterparty_channel_identifier: String, @@ -80,7 +82,8 @@ pub struct ChanOpenInit { } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct ChanOpenTry { - pub order: u8, + pub tag: DatagramTag, + pub order: ChannelOrder, pub connection: String, pub channel_identifier: String, pub counterparty_channel_identifier: String, @@ -91,6 +94,7 @@ pub struct ChanOpenTry { } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct ChanOpenAck { + pub tag: DatagramTag, pub channel_identifier: String, pub counterparty_version: String, pub proof_try: Bytes, @@ -98,26 +102,31 @@ pub struct ChanOpenAck { } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct ChanOpenConfirm { + pub tag: DatagramTag, pub channel_identifier: String, pub proof_ack: Bytes, pub proof_height: u64, } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct ChanCloseInit { + pub tag: DatagramTag, pub channel_identifier: String, } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct ChanCloseConfirm { + pub tag: DatagramTag, pub channel_identifier: String, pub proof_init: Bytes, pub proof_height: u64, } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct SendPacket { + pub tag: DatagramTag, pub packet: Packet, } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct RecvPacket { + pub tag: DatagramTag, pub packet: Packet, pub proof: Bytes, pub proof_height: u64, @@ -125,6 +134,7 @@ pub struct RecvPacket { } #[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)] pub struct AcknowledgePacket { + pub tag: DatagramTag, pub packet: Packet, pub ack: Bytes, pub proof: Bytes, @@ -288,47 +298,47 @@ impl Encodable for Datagram { Datagram::ChanOpenInit { raw, } => { - s.append(&DatagramTag::ChanOpenInit).append(raw); + s.append_single_value(raw); } Datagram::ChanOpenTry { raw, } => { - s.append(&DatagramTag::ChanOpenTry).append(raw); + s.append_single_value(raw); } Datagram::ChanOpenAck { raw, } => { - s.append(&DatagramTag::ChanOpenAck).append(raw); + s.append_single_value(raw); } Datagram::ChanOpenConfirm { raw, } => { - s.append(&DatagramTag::ChanOpenConfirm).append(raw); + s.append_single_value(raw); } Datagram::ChanCloseInit { raw, } => { - s.append(&DatagramTag::ChanCloseInit).append(raw); + s.append_single_value(raw); } Datagram::ChanCloseConfirm { raw, } => { - s.append(&DatagramTag::ChanCloseConfirm).append(raw); + s.append_single_value(raw); } Datagram::SendPacket { raw, } => { - s.append(&DatagramTag::SendPacket).append(raw); + s.append_single_value(raw); } Datagram::RecvPacket { raw, } => { - s.append(&DatagramTag::RecvPacket).append(raw); + s.append_single_value(raw); } Datagram::AcknowledgePacket { raw, } => { - s.append(&DatagramTag::AcknowledgePacket).append(raw); + s.append_single_value(raw); } }; } @@ -517,4 +527,19 @@ mod tests { }; rlp_encode_and_decode_test!(conn_open_confirm); } + + #[test] + fn chann_open_init() { + let chan_open_init = Datagram::ChanOpenInit { + raw: ChanOpenInit { + tag: DatagramTag::ChanOpenInit, + order: ChannelOrder::ORDERED, + connection: "connection".to_owned(), + channel_identifier: "channel".to_owned(), + counterparty_channel_identifier: "counterparty_channel".to_owned(), + version: "version".to_owned(), + }, + }; + rlp_encode_and_decode_test!(chan_open_init); + } } diff --git a/core/src/ibc/transaction_handler/mod.rs b/core/src/ibc/transaction_handler/mod.rs index bd1f20347a..7c8638342a 100644 --- a/core/src/ibc/transaction_handler/mod.rs +++ b/core/src/ibc/transaction_handler/mod.rs @@ -29,6 +29,22 @@ use ibc::context as ibc_context; use rlp::{Decodable, Rlp}; pub fn execute( + bytes: &[u8], + state: &mut TopLevelState, + fee_payer: &Address, + sender_public: &Public, + current_block_number: u64, +) -> StateResult<()> { + let result = execute_inner(bytes, state, fee_payer, sender_public, current_block_number); + + if let Err(err) = &result { + cwarn!(IBC, "Executing datagram failed: {:?}", err); + } + + result +} + +fn execute_inner( bytes: &[u8], state: &mut TopLevelState, _fee_payer: &Address, @@ -49,7 +65,7 @@ pub fn execute( } } - let result = match datagram { + match datagram { Datagram::CreateClient { id, kind, @@ -151,13 +167,7 @@ pub fn execute( let mut channel_manager = ibc_channel::Manager::new(&mut context); channel_manager .chan_open_init( - { - if raw.order == 1 { - ibc::channel_04::types::ChannelOrder::ORDERED - } else { - ibc::channel_04::types::ChannelOrder::UNORDERED - } - }, + raw.order, raw.connection, raw.channel_identifier, raw.counterparty_channel_identifier, @@ -172,13 +182,7 @@ pub fn execute( let mut channel_manager = ibc_channel::Manager::new(&mut context); channel_manager .chan_open_try( - { - if raw.order == 1 { - ibc::channel_04::types::ChannelOrder::ORDERED - } else { - ibc::channel_04::types::ChannelOrder::UNORDERED - } - }, + raw.order, raw.connection, raw.channel_identifier, raw.counterparty_channel_identifier, @@ -260,11 +264,5 @@ pub fn execute( .map_err(|err| RuntimeError::IBC(format!("AcknowledgePacket: {}", err)))?; Ok(()) } - }; - - if let Err(err) = &result { - cwarn!(IBC, "Executing datagram failed: {:?}", err); } - - result } diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts index 95f52f124a..99025b967a 100644 --- a/ibc.ts/src/common/chain.ts +++ b/ibc.ts/src/common/chain.ts @@ -5,7 +5,7 @@ import { IBC } from "./foundry/transaction"; import { delay } from "./util"; import Debug from "debug"; import { ClientState } from "./foundry/types"; -import { IBCHeader, IBCQueryResult, ConnectionEnd } from "./types"; +import { IBCHeader, IBCQueryResult, ConnectionEnd, ChannelEnd } from "./types"; const debug = Debug("common:tx"); @@ -126,6 +126,16 @@ export class Chain { blockNumber ]); } + + public async queryChannel( + blockNumber?: number + ): Promise | null> { + return this.sdk.rpc.sendRpcRequest("ibc_query_channel_end", [ + "DEFAULT_PORT", + this.counterpartyIdentifiers.channel, + blockNumber + ]); + } } async function waitForTx(sdk: SDK, txHash: H256) { diff --git a/ibc.ts/src/common/datagram/chanOpenAck.ts b/ibc.ts/src/common/datagram/chanOpenAck.ts new file mode 100644 index 0000000000..9d8f90f121 --- /dev/null +++ b/ibc.ts/src/common/datagram/chanOpenAck.ts @@ -0,0 +1,39 @@ +const RLP = require("rlp"); + +export class ChanOpenAckDatagram { + private channelIdentifier: string; + private counterpartyVersion: string; + private proofTry: Buffer; + private proofHeight: number; + + public constructor({ + channelIdentifier, + counterpartyVersion, + proofTry, + proofHeight + }: { + channelIdentifier: string; + counterpartyVersion: string; + proofTry: Buffer; + proofHeight: number; + }) { + this.channelIdentifier = channelIdentifier; + this.counterpartyVersion = counterpartyVersion; + this.proofTry = proofTry; + this.proofHeight = proofHeight; + } + + public rlpBytes(): Buffer { + return RLP.encode(this.toEncodeObject()); + } + + public toEncodeObject(): any[] { + return [ + 9, + this.channelIdentifier, + this.counterpartyVersion, + this.proofTry, + this.proofHeight + ]; + } +} diff --git a/ibc.ts/src/common/datagram/chanOpenConfirm.ts b/ibc.ts/src/common/datagram/chanOpenConfirm.ts new file mode 100644 index 0000000000..8632745794 --- /dev/null +++ b/ibc.ts/src/common/datagram/chanOpenConfirm.ts @@ -0,0 +1,29 @@ +const RLP = require("rlp"); + +export class ChanOpenConfirmDatagram { + private channelIdentifier: string; + private proofAck: Buffer; + private proofHeight: number; + + public constructor({ + channelIdentifier, + proofAck, + proofHeight + }: { + channelIdentifier: string; + proofAck: Buffer; + proofHeight: number; + }) { + this.channelIdentifier = channelIdentifier; + this.proofAck = proofAck; + this.proofHeight = proofHeight; + } + + public rlpBytes(): Buffer { + return RLP.encode(this.toEncodeObject()); + } + + public toEncodeObject(): any[] { + return [10, this.channelIdentifier, this.proofAck, this.proofHeight]; + } +} diff --git a/ibc.ts/src/common/datagram/chanOpenInit.ts b/ibc.ts/src/common/datagram/chanOpenInit.ts new file mode 100644 index 0000000000..259242e2e3 --- /dev/null +++ b/ibc.ts/src/common/datagram/chanOpenInit.ts @@ -0,0 +1,44 @@ +const RLP = require("rlp"); + +export class ChanOpenInitDatagram { + private order: number; + private connection: string; + private channelIdentifier: string; + private counterpartyChannelIdentifier: string; + private version: string; + + public constructor({ + order, + connection, + channelIdentifier, + counterpartyChannelIdentifier, + version + }: { + order: number; + connection: string; + channelIdentifier: string; + counterpartyChannelIdentifier: string; + version: string; + }) { + this.order = order; + this.connection = connection; + this.channelIdentifier = channelIdentifier; + this.counterpartyChannelIdentifier = counterpartyChannelIdentifier; + this.version = version; + } + + public rlpBytes(): Buffer { + return RLP.encode(this.toEncodeObject()); + } + + public toEncodeObject(): any[] { + return [ + 7, + this.order, + this.connection, + this.channelIdentifier, + this.counterpartyChannelIdentifier, + this.version + ]; + } +} diff --git a/ibc.ts/src/common/datagram/chanOpenTry.ts b/ibc.ts/src/common/datagram/chanOpenTry.ts new file mode 100644 index 0000000000..2fa91bd14a --- /dev/null +++ b/ibc.ts/src/common/datagram/chanOpenTry.ts @@ -0,0 +1,59 @@ +const RLP = require("rlp"); + +export class ChanOpenTryDatagram { + private order: number; + private connection: string; + private channelIdentifier: string; + private counterpartyChannelIdentifier: string; + private version: string; + private counterpartyVersion: string; + private proofInit: Buffer; + private proofHeight: number; + + public constructor({ + order, + connection, + channelIdentifier, + counterpartyChannelIdentifier, + version, + counterpartyVersion, + proofInit, + proofHeight + }: { + order: number; + connection: string; + channelIdentifier: string; + counterpartyChannelIdentifier: string; + version: string; + counterpartyVersion: string; + proofInit: Buffer; + proofHeight: number; + }) { + this.order = order; + this.connection = connection; + this.channelIdentifier = channelIdentifier; + this.counterpartyChannelIdentifier = counterpartyChannelIdentifier; + this.version = version; + this.counterpartyVersion = counterpartyVersion; + this.proofInit = proofInit; + this.proofHeight = proofHeight; + } + + public rlpBytes(): Buffer { + return RLP.encode(this.toEncodeObject()); + } + + public toEncodeObject(): any[] { + return [ + 8, + this.order, + this.connection, + this.channelIdentifier, + this.counterpartyChannelIdentifier, + this.version, + this.counterpartyVersion, + this.proofInit, + this.proofHeight + ]; + } +} diff --git a/ibc.ts/src/common/datagram/index.ts b/ibc.ts/src/common/datagram/index.ts index 4b10241349..e84d0d1847 100644 --- a/ibc.ts/src/common/datagram/index.ts +++ b/ibc.ts/src/common/datagram/index.ts @@ -2,3 +2,6 @@ export interface Datagram { rlpBytes(): Buffer; toEncodeObject(): any[]; } + +export const ChannelOrdered = 0; +export const ChannelUnordered = 1; diff --git a/ibc.ts/src/common/types.ts b/ibc.ts/src/common/types.ts index 256937212c..9023607a81 100644 --- a/ibc.ts/src/common/types.ts +++ b/ibc.ts/src/common/types.ts @@ -12,3 +12,12 @@ export interface ConnectionEnd { clientIdentifier: string; counterpartyClientIdentifier: string; } + +export interface ChannelEnd { + state: "INIT" | "TRYOPEN" | "OPEN" | "CLOSED"; + ordering: "ORDERED" | "UNORDERED"; + counterpartyPortIdentifier: string; + counterpartyChannelIdentifier: string; + connectionHops: string[]; + version: string; +} diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts index 36c6284262..201c0b588e 100644 --- a/ibc.ts/src/relayer/index.ts +++ b/ibc.ts/src/relayer/index.ts @@ -1,6 +1,6 @@ import Debug from "debug"; import { Chain } from "../common/chain"; -import { Datagram } from "../common/datagram/index"; +import { Datagram, ChannelOrdered } from "../common/datagram/index"; import { delay } from "../common/util"; import { getConfig } from "../common/config"; import { PlatformAddress } from "codechain-primitives/lib"; @@ -9,6 +9,9 @@ import { strict as assert } from "assert"; import { ConnOpenTryDatagram } from "../common/datagram/connOpenTry"; import { ConnOpenAckDatagram } from "../common/datagram/connOpenAck"; import { ConnOpenConfirmDatagram } from "../common/datagram/connOpenConfirm"; +import { ChanOpenTryDatagram } from "../common/datagram/chanOpenTry"; +import { ChanOpenAckDatagram } from "../common/datagram/chanOpenAck"; +import { ChanOpenConfirmDatagram } from "../common/datagram/chanOpenConfirm"; require("dotenv").config(); @@ -41,7 +44,15 @@ async function main() { while (true) { debug("Run relay"); - await relay(chainA, chainB); + try { + await relay(chainA, chainB); + } catch (err) { + if (err.message === "NoClient") { + console.log("Light client does not exist"); + } else { + throw err; + } + } await delay(1000); } } @@ -119,6 +130,21 @@ async function pendingDatagrams({ counterpartyDatagramsForConnection ); + const { + localDatagrams: localDatagramsForChannel, + counterpartyDatagrams: counterpartyDatagramsForChannel + } = await buildChannel({ + chain, + counterpartyChain, + height, + counterpartyChainHeight + }); + + localDatagrams = localDatagrams.concat(localDatagramsForChannel); + counterpartyDatagrams = counterpartyDatagrams.concat( + counterpartyDatagramsForChannel + ); + return { localDatagrams, counterpartyDatagrams }; } @@ -137,9 +163,7 @@ async function updateLightClient({ 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}` - ); + throw new Error("NoClient"); } let currentBlockNumber = clientState!.data!.number; // FIXME: We can't get the best block's IBC header @@ -250,3 +274,74 @@ async function buildConnection({ return { localDatagrams, counterpartyDatagrams }; } + +async function buildChannel({ + chain, + counterpartyChain, + height, + counterpartyChainHeight +}: { + chain: Chain; + counterpartyChain: Chain; + height: number; + counterpartyChainHeight: number; +}) { + const localDatagrams: Datagram[] = []; + const counterpartyDatagrams = []; + + const channel = await chain.queryChannel(height); + assert.notEqual(channel, null, "Block at height exists"); + + const counterpartyChannel = await counterpartyChain.queryChannel( + counterpartyChainHeight + ); + assert.notEqual(counterpartyChannel, null, "Block at height exists"); + + if (channel!.data?.state === "INIT" && counterpartyChannel!.data == null) { + counterpartyDatagrams.push( + new ChanOpenTryDatagram({ + order: ChannelOrdered, + connection: + counterpartyChain.counterpartyIdentifiers.connection, + channelIdentifier: + counterpartyChain.counterpartyIdentifiers.channel, + counterpartyChannelIdentifier: + chain.counterpartyIdentifiers.channel, + version: "", + counterpartyVersion: "", + proofInit: Buffer.from(channel!.proof, "hex"), + proofHeight: height + }) + ); + } + if ( + channel!.data?.state === "INIT" && + counterpartyChannel!.data?.state === "TRYOPEN" + ) { + localDatagrams.push( + new ChanOpenAckDatagram({ + channelIdentifier: chain.counterpartyIdentifiers.channel, + counterpartyVersion: "", + proofTry: Buffer.from(counterpartyChannel!.proof, "hex"), + proofHeight: counterpartyChainHeight + }) + ); + } + if ( + channel!.data?.state === "OPEN" && + counterpartyChannel!.data?.state === "TRYOPEN" + ) { + counterpartyDatagrams.push( + new ChanOpenConfirmDatagram({ + channelIdentifier: + counterpartyChain.counterpartyIdentifiers.channel, + proofAck: Buffer.from(channel!.proof, "hex"), + proofHeight: height + }) + ); + } + return { + localDatagrams, + counterpartyDatagrams + }; +} diff --git a/ibc.ts/src/scenario/index.ts b/ibc.ts/src/scenario/index.ts index de794d23c3..c2041ac92b 100644 --- a/ibc.ts/src/scenario/index.ts +++ b/ibc.ts/src/scenario/index.ts @@ -5,6 +5,8 @@ import { PlatformAddress } from "codechain-primitives/lib"; import { CreateClientDatagram } from "../common/datagram/createClient"; import { strict as assert } from "assert"; import { ConnOpenInitDatagram } from "../common/datagram/connOpenInit"; +import { ChanOpenInitDatagram } from "../common/datagram/chanOpenInit"; +import { ChannelOrdered } from "../common/datagram"; const { Select } = require("enquirer"); require("dotenv").config(); @@ -95,6 +97,44 @@ async function main() { break; } } + + const channelPrompt = new Select({ + name: "channel", + message: "Will you create a channel?", + choices: ["yes", "skip", "exit"] + }); + const channelAnswer = await channelPrompt.run(); + + if (channelAnswer === "exit") { + return; + } + + if (channelAnswer === "yes") { + console.log("Create a channel"); + await createChannel({ chainA, chainB }); + } + + while (true) { + const channelCheckPrompt = new Select({ + name: "channel check", + message: "Will you check the channel?", + choices: ["yes", "skip", "exit"] + }); + const channelCheckAnsser = await channelCheckPrompt.run(); + + if (channelCheckAnsser === "exit") { + return; + } + + if (channelCheckAnsser === "yes") { + console.log("Check a channel"); + await checkChannels({ chainA, chainB }); + } + + if (channelCheckAnsser === "skip") { + break; + } + } } main().catch(console.error); @@ -175,3 +215,35 @@ async function checkConnections({ const connectionB = await chainB.queryConnection(); console.log(`Connection in B ${JSON.stringify(connectionB)}`); } + +async function createChannel({ + chainA, + chainB +}: { + chainA: Chain; + chainB: Chain; +}) { + await chainA.submitDatagram( + new ChanOpenInitDatagram({ + order: ChannelOrdered, + connection: chainA.counterpartyIdentifiers.connection, + channelIdentifier: chainA.counterpartyIdentifiers.channel, + counterpartyChannelIdentifier: + chainB.counterpartyIdentifiers.channel, + version: "" + }) + ); +} + +async function checkChannels({ + chainA, + chainB +}: { + chainA: Chain; + chainB: Chain; +}) { + const channelA = await chainA.queryChannel(); + console.log(`Channel in A ${JSON.stringify(channelA)}`); + const channelB = await chainB.queryChannel(); + console.log(`Channel in B ${JSON.stringify(channelB)}`); +}