diff --git a/core/src/ibc/channel_04/log.rs b/core/src/ibc/channel_04/log.rs
index b8ec7a086e..1d6f4c03e4 100644
--- a/core/src/ibc/channel_04/log.rs
+++ b/core/src/ibc/channel_04/log.rs
@@ -23,16 +23,21 @@ use super::types::Packet;
use crate::ibc;
use ibc::IdentifierSlice;
-pub fn set_packet<'a>(ctx: &'a mut dyn ibc::Context, packet: &Packet, tag: &str) {
+pub fn set_packet<'a>(
+ ctx: &'a mut dyn ibc::Context,
+ port: IdentifierSlice,
+ channel: IdentifierSlice,
+ packet: &Packet,
+ tag: &str,
+) {
let value = rlp::encode(packet);
- let path = format!("nastylogs/{}/{}/{}/latest", packet.source_port, packet.source_channel, tag);
+ let path = format!("nastylogs/{}/{}/{}/latest", port, channel, tag);
let result = ctx.get_kv_store_mut().insert(&path, &value);
if result.is_some() {
panic!("Packet already exists.");
}
}
-#[allow(dead_code)]
pub fn get_packet<'a>(
ctx: &'a mut dyn ibc::Context,
port: IdentifierSlice,
diff --git a/core/src/ibc/channel_04/manager.rs b/core/src/ibc/channel_04/manager.rs
index dae07f3d79..efc2f62594 100644
--- a/core/src/ibc/channel_04/manager.rs
+++ b/core/src/ibc/channel_04/manager.rs
@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use super::log::{remove_packet, set_packet};
+use super::log::{get_packet, remove_packet, set_packet};
use super::types::{
Acknowledgement, ChannelEnd, ChannelOrder, ChannelState, Packet, PacketCommitment, PacketCommitmentHash, Sequence,
};
@@ -445,7 +445,10 @@ impl<'a> Manager<'a> {
let mut next_sequence_send = self.get_sequence_send(&packet.source_port, &packet.source_channel)?;
if packet.sequence != next_sequence_send {
- return Err("Packet carries invalid sequence".to_owned())
+ return Err(format!(
+ "Packet carries invalid sequence. expected: {:?}, actual: {:?}",
+ next_sequence_send, packet.sequence
+ ))
}
next_sequence_send.raw += 1;
@@ -467,7 +470,7 @@ impl<'a> Manager<'a> {
);
// check log.rs to understand this statement
- set_packet(self.ctx, &packet, "send");
+ set_packet(self.ctx, &packet.source_port, &packet.source_channel, &packet, "send");
Ok(())
}
@@ -522,7 +525,10 @@ impl<'a> Manager<'a> {
);
if packet.sequence != next_sequence_recv {
- return Err("Packet carries invalid sequence".to_owned())
+ return Err(format!(
+ "Packet carries invalid sequence. expected: {:?} actal: {:?}",
+ next_sequence_recv, packet.sequence
+ ))
}
next_sequence_recv.raw += 1;
kv_store.insert(
@@ -535,8 +541,10 @@ impl<'a> Manager<'a> {
// check log.rs to understand this statement
// Unlike send, we just overwrite an old event.
- remove_packet(self.ctx, &packet.dest_port, &packet.dest_channel, "recv");
- set_packet(self.ctx, &packet, "recv");
+ if get_packet(self.ctx, &packet.dest_port, &packet.dest_channel, "recv").is_some() {
+ remove_packet(self.ctx, &packet.dest_port, &packet.dest_channel, "recv");
+ }
+ set_packet(self.ctx, &packet.dest_port, &packet.dest_channel, &packet, "recv");
Ok(packet)
}
diff --git a/core/src/ibc/channel_04/types.rs b/core/src/ibc/channel_04/types.rs
index b7613e9c53..617ad42adc 100644
--- a/core/src/ibc/channel_04/types.rs
+++ b/core/src/ibc/channel_04/types.rs
@@ -20,11 +20,25 @@ use primitives::{Bytes, H256};
use rlp;
use rlp::{DecoderError, Rlp, RlpStream};
-#[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)]
+#[derive(PartialEq, Debug)]
pub struct Sequence {
pub raw: u64,
}
+impl rlp::Encodable for Sequence {
+ fn rlp_append(&self, s: &mut RlpStream) {
+ s.append_single_value(&self.raw);
+ }
+}
+
+impl rlp::Decodable for Sequence {
+ fn decode(rlp: &Rlp) -> Result {
+ Ok(Self {
+ raw: rlp.as_val()?,
+ })
+ }
+}
+
#[repr(u8)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ChannelState {
@@ -100,12 +114,12 @@ pub struct Packet {
/// Acknowledgement and PacketCommitment's behaviors are somewhat different from other ICS data:
/// They are not saved directly in the state, but the hash PacketCommitmentHash will be.
-#[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)]
+#[derive(RlpEncodableWrapper, RlpDecodableWrapper, PartialEq, Debug)]
pub struct AcknowledgementHash {
pub raw: H256,
}
-#[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)]
+#[derive(RlpEncodableWrapper, RlpDecodableWrapper, PartialEq, Debug)]
pub struct Acknowledgement {
pub raw: Bytes,
}
@@ -118,7 +132,7 @@ impl Acknowledgement {
}
}
-#[derive(RlpEncodable, RlpDecodable, PartialEq, Debug)]
+#[derive(RlpEncodableWrapper, RlpDecodableWrapper, PartialEq, Debug)]
pub struct PacketCommitmentHash {
pub raw: H256,
}
diff --git a/core/src/ibc/transaction_handler/datagrams.rs b/core/src/ibc/transaction_handler/datagrams.rs
index fdf5be6d9b..a8bd8ae634 100644
--- a/core/src/ibc/transaction_handler/datagrams.rs
+++ b/core/src/ibc/transaction_handler/datagrams.rs
@@ -476,6 +476,7 @@ impl Decodable for Datagram {
#[cfg(test)]
mod tests {
use super::*;
+ use crate::ibc::channel_04::types::Sequence;
use rlp::{self, rlp_encode_and_decode_test};
#[test]
@@ -542,4 +543,25 @@ mod tests {
};
rlp_encode_and_decode_test!(chan_open_init);
}
+
+ #[test]
+ fn send_packet() {
+ let send_packet = Datagram::SendPacket {
+ raw: SendPacket {
+ tag: DatagramTag::SendPacket,
+ packet: Packet {
+ sequence: Sequence {
+ raw: 1,
+ },
+ timeout_height: 32,
+ source_port: "source_port".to_owned(),
+ source_channel: "source_channel".to_owned(),
+ dest_port: "dest_port".to_owned(),
+ dest_channel: "dest_channel".to_owned(),
+ data: b"data".to_vec(),
+ },
+ },
+ };
+ rlp_encode_and_decode_test!(send_packet);
+ }
}
diff --git a/ibc.ts/chainA/chainA.schem.json b/ibc.ts/chainA/chainA.schem.json
index 0b8a19425d..5846ba0b5e 100644
--- a/ibc.ts/chainA/chainA.schem.json
+++ b/ibc.ts/chainA/chainA.schem.json
@@ -6,7 +6,7 @@
"validators": [
"0xc7db8f558a87a45d3b4d2e7422abb48a27e6eeee00a0f884ca244d1d783d6ead2ca445e9ac8f08b311d78b4b4b69d48a9daf0c98d2a19e87e482f8060138ba88"
],
- "timeoutPropose": 10000,
+ "timeoutPropose": 3000,
"timeoutProposeDelta": 500,
"timeoutPrevote": 1000,
"timeoutPrevoteDelta": 500,
diff --git a/ibc.ts/chainB/chainB.schem.json b/ibc.ts/chainB/chainB.schem.json
index c33c47f003..d05a72993d 100644
--- a/ibc.ts/chainB/chainB.schem.json
+++ b/ibc.ts/chainB/chainB.schem.json
@@ -6,7 +6,7 @@
"validators": [
"0x521d103d95b5b684f9be089ccc5cffd6b4ff2997ce535a7053e44d811b84f38abf2f35ebfeb5458e86a0b7d7293e71bcb3a59eddeb8b63801c2c4a8081fc67ab"
],
- "timeoutPropose": 10000,
+ "timeoutPropose": 3000,
"timeoutProposeDelta": 500,
"timeoutPrevote": 1000,
"timeoutPrevoteDelta": 500,
diff --git a/ibc.ts/src/common/chain.ts b/ibc.ts/src/common/chain.ts
index 99025b967a..ec1f2fe472 100644
--- a/ibc.ts/src/common/chain.ts
+++ b/ibc.ts/src/common/chain.ts
@@ -1,4 +1,4 @@
-import { Datagram } from "./datagram/index";
+import { Datagram, PacketJSON } from "./datagram/index";
import { SDK } from "codechain-sdk";
import { H256, PlatformAddress } from "codechain-primitives";
import { IBC } from "./foundry/transaction";
@@ -136,6 +136,60 @@ export class Chain {
blockNumber
]);
}
+
+ public async queryCommitment(
+ sequence: number,
+ blockNumber?: number
+ ): Promise | null> {
+ return this.sdk.rpc.sendRpcRequest("ibc_query_packet_commitment", [
+ "DEFAULT_PORT",
+ this.counterpartyIdentifiers.channel,
+ sequence,
+ blockNumber
+ ]);
+ }
+
+ public async queryAcknowledgement(
+ sequence: number,
+ blockNumber?: number
+ ): Promise | null> {
+ return this.sdk.rpc.sendRpcRequest("ibc_query_packet_acknowledgement", [
+ "DEFAULT_PORT",
+ this.counterpartyIdentifiers.channel,
+ sequence,
+ blockNumber
+ ]);
+ }
+
+ public async queryLatestSendPacket(
+ blockNumber?: number
+ ): Promise {
+ return this.sdk.rpc.sendRpcRequest("ibc_query_latest_send_packet", [
+ "DEFAULT_PORT",
+ this.counterpartyIdentifiers.channel,
+ blockNumber
+ ]);
+ }
+
+ public async queryLatestRecvPacket(
+ blockNumber?: number
+ ): Promise {
+ return this.sdk.rpc.sendRpcRequest("ibc_query_latest_recv_packet", [
+ "DEFAULT_PORT",
+ this.counterpartyIdentifiers.channel,
+ blockNumber
+ ]);
+ }
+
+ public async queryNextSequenceRecv(
+ blockNumber?: number
+ ): Promise | null> {
+ return this.sdk.rpc.sendRpcRequest("ibc_query_next_sequence_recv", [
+ "DEFAULT_PORT",
+ this.counterpartyIdentifiers.channel,
+ blockNumber
+ ]);
+ }
}
async function waitForTx(sdk: SDK, txHash: H256) {
diff --git a/ibc.ts/src/common/datagram/acknowledgePacket.ts b/ibc.ts/src/common/datagram/acknowledgePacket.ts
new file mode 100644
index 0000000000..7e475213f5
--- /dev/null
+++ b/ibc.ts/src/common/datagram/acknowledgePacket.ts
@@ -0,0 +1,41 @@
+import { Packet } from ".";
+
+const RLP = require("rlp");
+
+export class AcknowledgePacketDatagram {
+ private packet: Packet;
+ private ack: Buffer;
+ private proof: Buffer;
+ private proofHeight: number;
+
+ public constructor({
+ packet,
+ ack,
+ proof,
+ proofHeight
+ }: {
+ packet: Packet;
+ ack: Buffer;
+ proof: Buffer;
+ proofHeight: number;
+ }) {
+ this.packet = packet;
+ this.ack = ack;
+ this.proof = proof;
+ this.proofHeight = proofHeight;
+ }
+
+ public rlpBytes(): Buffer {
+ return RLP.encode(this.toEncodeObject());
+ }
+
+ public toEncodeObject(): any[] {
+ return [
+ 15,
+ this.packet.toEncodeObject(),
+ this.ack,
+ this.proof,
+ this.proofHeight
+ ];
+ }
+}
diff --git a/ibc.ts/src/common/datagram/index.ts b/ibc.ts/src/common/datagram/index.ts
index e84d0d1847..b6d87e8f98 100644
--- a/ibc.ts/src/common/datagram/index.ts
+++ b/ibc.ts/src/common/datagram/index.ts
@@ -5,3 +5,53 @@ export interface Datagram {
export const ChannelOrdered = 0;
export const ChannelUnordered = 1;
+
+export interface PacketJSON {
+ sequence: number;
+ timeoutHeight: number;
+ sourcePort: string;
+ sourceChannel: string;
+ destPort: string;
+ destChannel: string;
+ data: string;
+}
+
+export class Packet {
+ public readonly sequence: number;
+ public readonly timeoutHeight: number;
+ public readonly sourcePort: string;
+ public readonly sourceChannel: string;
+ public readonly destPort: string;
+ public readonly destChannel: string;
+ public readonly data: Buffer;
+
+ public constructor({
+ sequence,
+ timeoutHeight,
+ sourcePort,
+ sourceChannel,
+ destPort,
+ destChannel,
+ data
+ }: PacketJSON) {
+ this.sequence = sequence;
+ this.timeoutHeight = timeoutHeight;
+ this.sourcePort = sourcePort;
+ this.sourceChannel = sourceChannel;
+ this.destPort = destPort;
+ this.destChannel = destChannel;
+ this.data = Buffer.from(data, "hex");
+ }
+
+ public toEncodeObject(): any[] {
+ return [
+ this.sequence,
+ this.timeoutHeight,
+ this.sourcePort,
+ this.sourceChannel,
+ this.destPort,
+ this.destChannel,
+ this.data
+ ];
+ }
+}
diff --git a/ibc.ts/src/common/datagram/recvPacket.ts b/ibc.ts/src/common/datagram/recvPacket.ts
new file mode 100644
index 0000000000..9d192f1c02
--- /dev/null
+++ b/ibc.ts/src/common/datagram/recvPacket.ts
@@ -0,0 +1,41 @@
+import { Packet } from ".";
+
+const RLP = require("rlp");
+
+export class RecvPacketDatagram {
+ private packet: Packet;
+ private proof: Buffer;
+ private proofHeight: number;
+ private ack: Buffer;
+
+ public constructor({
+ packet,
+ proof,
+ proofHeight,
+ ack
+ }: {
+ packet: Packet;
+ proof: Buffer;
+ proofHeight: number;
+ ack: Buffer;
+ }) {
+ this.packet = packet;
+ this.proof = proof;
+ this.proofHeight = proofHeight;
+ this.ack = ack;
+ }
+
+ public rlpBytes(): Buffer {
+ return RLP.encode(this.toEncodeObject());
+ }
+
+ public toEncodeObject(): any[] {
+ return [
+ 14,
+ this.packet.toEncodeObject(),
+ this.proof,
+ this.proofHeight,
+ this.ack
+ ];
+ }
+}
diff --git a/ibc.ts/src/common/datagram/sendPacket.ts b/ibc.ts/src/common/datagram/sendPacket.ts
new file mode 100644
index 0000000000..6d5637e764
--- /dev/null
+++ b/ibc.ts/src/common/datagram/sendPacket.ts
@@ -0,0 +1,19 @@
+import { Packet } from ".";
+
+const RLP = require("rlp");
+
+export class SendPacketDatagram {
+ private packet: Packet;
+
+ public constructor({ packet }: { packet: Packet }) {
+ this.packet = packet;
+ }
+
+ public rlpBytes(): Buffer {
+ return RLP.encode(this.toEncodeObject());
+ }
+
+ public toEncodeObject(): any[] {
+ return [13, this.packet.toEncodeObject()];
+ }
+}
diff --git a/ibc.ts/src/relayer/index.ts b/ibc.ts/src/relayer/index.ts
index 201c0b588e..c1957073f4 100644
--- a/ibc.ts/src/relayer/index.ts
+++ b/ibc.ts/src/relayer/index.ts
@@ -1,9 +1,9 @@
import Debug from "debug";
import { Chain } from "../common/chain";
-import { Datagram, ChannelOrdered } from "../common/datagram/index";
+import { Datagram, ChannelOrdered, Packet } from "../common/datagram/index";
import { delay } from "../common/util";
import { getConfig } from "../common/config";
-import { PlatformAddress } from "codechain-primitives/lib";
+import { PlatformAddress, blake256 } from "codechain-primitives/lib";
import { UpdateClientDatagram } from "../common/datagram/updateClient";
import { strict as assert } from "assert";
import { ConnOpenTryDatagram } from "../common/datagram/connOpenTry";
@@ -12,6 +12,8 @@ import { ConnOpenConfirmDatagram } from "../common/datagram/connOpenConfirm";
import { ChanOpenTryDatagram } from "../common/datagram/chanOpenTry";
import { ChanOpenAckDatagram } from "../common/datagram/chanOpenAck";
import { ChanOpenConfirmDatagram } from "../common/datagram/chanOpenConfirm";
+import { RecvPacketDatagram } from "../common/datagram/recvPacket";
+import { AcknowledgePacketDatagram } from "../common/datagram/acknowledgePacket";
require("dotenv").config();
@@ -145,6 +147,15 @@ async function pendingDatagrams({
counterpartyDatagramsForChannel
);
+ counterpartyDatagrams = counterpartyDatagrams.concat(
+ await relayPacket({
+ chain,
+ counterpartyChain,
+ height,
+ counterpartyChainHeight
+ })
+ );
+
return { localDatagrams, counterpartyDatagrams };
}
@@ -345,3 +356,95 @@ async function buildChannel({
counterpartyDatagrams
};
}
+
+async function relayPacket({
+ chain,
+ counterpartyChain,
+ height,
+ counterpartyChainHeight
+}: {
+ chain: Chain;
+ counterpartyChain: Chain;
+ height: number;
+ counterpartyChainHeight: number;
+}): Promise {
+ const counterpartyDatagrams = [];
+
+ // FIXME: what will be acknowledgement?
+ const acknowledgementValue = Buffer.from("acknowledgement", "utf8");
+
+ const sendPacket = await chain.queryLatestSendPacket(height);
+ if (sendPacket != null) {
+ const counterpartyAcknowledgement = await counterpartyChain.queryAcknowledgement(
+ sendPacket.sequence,
+ counterpartyChainHeight
+ );
+ assert.notEqual(
+ counterpartyAcknowledgement,
+ null,
+ `block at ${counterpartyChainHeight} exists`
+ );
+ if (counterpartyAcknowledgement!.data == null) {
+ const commitment = await chain.queryCommitment(
+ sendPacket.sequence,
+ height
+ );
+ assert.notEqual(
+ commitment?.data,
+ null,
+ `block at ${height} exists`
+ );
+
+ counterpartyDatagrams.push(
+ new RecvPacketDatagram({
+ packet: new Packet(sendPacket),
+ proof: Buffer.from(commitment!.proof, "hex"),
+ proofHeight: height,
+ ack: acknowledgementValue
+ })
+ );
+ }
+ }
+
+ const recvPacket = await chain.queryLatestRecvPacket(height);
+ if (recvPacket != null) {
+ const acknowledgement = await chain.queryAcknowledgement(
+ recvPacket.sequence,
+ height
+ );
+ assert.notEqual(
+ acknowledgement?.data,
+ null,
+ "(Recivecd packet != null) -> (ack !== null)"
+ );
+ assert.strictEqual(
+ acknowledgement!.data!,
+ "0x" + blake256(acknowledgementValue),
+ "Acknowledgement should be the same"
+ );
+ const counterpartyCommitment = await counterpartyChain.queryCommitment(
+ recvPacket.sequence,
+ counterpartyChainHeight
+ );
+ assert.notEqual(
+ counterpartyCommitment,
+ null,
+ `block at ${counterpartyChainHeight} exists`
+ );
+ if (counterpartyCommitment!.data == null) {
+ debug("Counterparty commitment does not exist");
+ }
+ if (counterpartyCommitment!.data != null) {
+ counterpartyDatagrams.push(
+ new AcknowledgePacketDatagram({
+ packet: new Packet(recvPacket),
+ ack: acknowledgementValue,
+ proof: Buffer.from(acknowledgement!.proof, "hex"),
+ proofHeight: height
+ })
+ );
+ }
+ }
+
+ return counterpartyDatagrams;
+}
diff --git a/ibc.ts/src/scenario/index.ts b/ibc.ts/src/scenario/index.ts
index c2041ac92b..9df09c8a58 100644
--- a/ibc.ts/src/scenario/index.ts
+++ b/ibc.ts/src/scenario/index.ts
@@ -6,13 +6,17 @@ 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";
+import { ChannelOrdered, Packet } from "../common/datagram";
+import { SendPacketDatagram } from "../common/datagram/sendPacket";
const { Select } = require("enquirer");
require("dotenv").config();
const debug = Debug("scenario:main");
+// Fix the packet's sequence in the PoC scenario.
+const SEQUENCE = 1;
+
async function main() {
const config = getConfig();
const chainA = new Chain({
@@ -42,96 +46,45 @@ async function main() {
keystorePath: config.chainB.keystorePath
});
- const lightclientPrompt = new Select({
- name: "light client",
- message: "Will you create light clients?",
- choices: ["yes", "skip", "exit"]
- });
- const lightclientAnswer = await lightclientPrompt.run();
-
- if (lightclientAnswer === "exit") {
+ if ("exit" === (await runLightClientCreationPrompt({ chainA, chainB }))) {
return;
}
- if (lightclientAnswer === "yes") {
- 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 });
- }
-
- const connectionPrompt = new Select({
- name: "connection",
- message: "Will you create connection?",
- choices: ["yes", "skip", "exit"]
- });
- const connectionAnswer = await connectionPrompt.run();
-
- if (connectionAnswer === "exit") {
+ if ("exit" === (await runConnectionCreationPrompt({ chainA, chainB }))) {
return;
}
- if (connectionAnswer === "yes") {
- console.log("Create a connection");
- await createConnection({ chainA, chainB });
- }
-
while (true) {
- const connectionCheckPrompt = new Select({
- name: "connection check",
- message: "Will you check connection?",
- choices: ["yes", "skip", "exit"]
- });
- const connectionCheckAnswer = await connectionCheckPrompt.run();
-
- if (connectionCheckAnswer === "exit") {
+ const result = await runConnectionCheckPrompt({ chainA, chainB });
+ if (result === "exit") {
return;
- }
-
- if (connectionCheckAnswer === "yes") {
- console.log("Check a connection");
- await checkConnections({ chainA, chainB });
- }
-
- if (connectionCheckAnswer === "skip") {
+ } else if (result === "break") {
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") {
+ if ("exit" === (await runChannelCreationPrompt({ chainA, chainB }))) {
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") {
+ const result = await runChannelCheckPrompt({ chainA, chainB });
+ if (result === "exit") {
return;
+ } else if (result === "break") {
+ break;
}
+ }
- if (channelCheckAnsser === "yes") {
- console.log("Check a channel");
- await checkChannels({ chainA, chainB });
- }
+ if ("exit" === (await runSendPacketPrompt({ chainA, chainB }))) {
+ return;
+ }
- if (channelCheckAnsser === "skip") {
+ while (true) {
+ const result = await runPacketCheckPrompt({ chainA, chainB });
+ if (result === "exit") {
+ return;
+ } else if (result === "break") {
break;
}
}
@@ -247,3 +200,245 @@ async function checkChannels({
const channelB = await chainB.queryChannel();
console.log(`Channel in B ${JSON.stringify(channelB)}`);
}
+
+async function sendPacket({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}) {
+ const chainBHeight = await chainB.latestHeight();
+ await chainA.submitDatagram(
+ new SendPacketDatagram({
+ packet: new Packet({
+ sequence: SEQUENCE,
+ timeoutHeight: chainBHeight + 1000,
+ sourcePort: "DEFAULT_PORT",
+ sourceChannel: chainA.counterpartyIdentifiers.channel,
+ destPort: "DEFAULT_PORT",
+ destChannel: chainB.counterpartyIdentifiers.channel,
+ data: Buffer.from("PING", "utf8").toString("hex")
+ })
+ })
+ );
+}
+
+async function checkPackets({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}) {
+ const commitment = await chainA.queryCommitment(SEQUENCE);
+ console.log(`Chain A's commitment: ${JSON.stringify(commitment)}`);
+ const recvSequence = await chainB.queryNextSequenceRecv();
+ console.log(`Chain B's recvSequence ${JSON.stringify(recvSequence)}`);
+ const acknowledgement = await chainB.queryAcknowledgement(SEQUENCE);
+ console.log(
+ `Chain B's acknowledgement: ${JSON.stringify(acknowledgement)}`
+ );
+ const chainASendPacket = await chainA.queryLatestSendPacket();
+ console.log(`Chain A's sendPacket: ${JSON.stringify(chainASendPacket)}`);
+ const chainBRecvPacket = await chainB.queryLatestRecvPacket();
+ console.log(`Chain B's recvPacket: ${JSON.stringify(chainBRecvPacket)}`);
+}
+
+async function runLightClientCreationPrompt({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}): Promise<"exit" | null> {
+ const lightclientPrompt = new Select({
+ name: "light client",
+ message: "Will you create light clients?",
+ choices: ["yes", "skip", "exit"]
+ });
+ const lightclientAnswer = await lightclientPrompt.run();
+
+ if (lightclientAnswer === "exit") {
+ return "exit";
+ }
+
+ if (lightclientAnswer === "yes") {
+ 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 });
+ }
+
+ return null;
+}
+
+async function runConnectionCreationPrompt({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}): Promise<"exit" | null> {
+ const connectionPrompt = new Select({
+ name: "connection",
+ message: "Will you create connection?",
+ choices: ["yes", "skip", "exit"]
+ });
+ const connectionAnswer = await connectionPrompt.run();
+
+ if (connectionAnswer === "exit") {
+ return "exit";
+ }
+
+ if (connectionAnswer === "yes") {
+ console.log("Create a connection");
+ await createConnection({ chainA, chainB });
+ }
+
+ return null;
+}
+
+async function runConnectionCheckPrompt({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}): Promise<"exit" | "break" | null> {
+ const connectionCheckPrompt = new Select({
+ name: "connection check",
+ message: "Will you check connection?",
+ choices: ["yes", "skip", "exit"]
+ });
+ const connectionCheckAnswer = await connectionCheckPrompt.run();
+
+ if (connectionCheckAnswer === "exit") {
+ return "exit";
+ }
+
+ if (connectionCheckAnswer === "yes") {
+ console.log("Check a connection");
+ await checkConnections({ chainA, chainB });
+ }
+
+ if (connectionCheckAnswer === "skip") {
+ return "break";
+ }
+
+ return null;
+}
+
+async function runChannelCreationPrompt({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}): Promise<"exit" | null> {
+ 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 "exit";
+ }
+
+ if (channelAnswer === "yes") {
+ console.log("Create a channel");
+ await createChannel({ chainA, chainB });
+ }
+
+ return null;
+}
+
+async function runChannelCheckPrompt({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}): Promise<"exit" | "break" | null> {
+ 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 "exit";
+ }
+
+ if (channelCheckAnsser === "yes") {
+ console.log("Check a channel");
+ await checkChannels({ chainA, chainB });
+ }
+
+ if (channelCheckAnsser === "skip") {
+ return "break";
+ }
+
+ return null;
+}
+
+async function runSendPacketPrompt({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}): Promise<"exit" | null> {
+ const packetPrompt = new Select({
+ name: "packet",
+ message: "Will you send a packet?",
+ choices: ["yes", "skip", "exit"]
+ });
+
+ const packetAnswer = await packetPrompt.run();
+
+ if (packetAnswer === "exit") {
+ return "exit";
+ }
+
+ if (packetAnswer === "yes") {
+ console.log("Send a packet");
+ await sendPacket({ chainA, chainB });
+ }
+
+ return null;
+}
+
+async function runPacketCheckPrompt({
+ chainA,
+ chainB
+}: {
+ chainA: Chain;
+ chainB: Chain;
+}): Promise<"exit" | "break" | null> {
+ const packetCheckPrompt = new Select({
+ name: "packet check",
+ message: "Will you check the packet status?",
+ choices: ["yes", "skip", "exit"]
+ });
+
+ const packetCheckAnswer = await packetCheckPrompt.run();
+
+ if (packetCheckAnswer === "exit") {
+ return "exit";
+ }
+
+ if (packetCheckAnswer === "yes") {
+ console.log("Check packets");
+ await checkPackets({ chainA, chainB });
+ }
+
+ if (packetCheckAnswer === "skip") {
+ return "break";
+ }
+
+ return null;
+}
diff --git a/rpc/src/v1/impls/ibc.rs b/rpc/src/v1/impls/ibc.rs
index 1c77d7fa27..16462017f3 100644
--- a/rpc/src/v1/impls/ibc.rs
+++ b/rpc/src/v1/impls/ibc.rs
@@ -198,12 +198,18 @@ where
query_common(&self.client, &path, block_number)
}
- fn query_latest_send_packet(&self, port_id: String, channel_id: String) -> Result