Implements full Inter-Blockchain Communication (IBC) protocol: synor-ibc crate (new): - Light client management (create, update, verify headers) - Connection handshake (4-way: Init, Try, Ack, Confirm) - Channel handshake (4-way: Init, Try, Ack, Confirm) - Packet handling (send, receive, acknowledge, timeout) - Merkle commitment proofs for state verification - ICS-20 fungible token transfer support - Atomic swap engine with HTLC (hashlock + timelock) IBC Bridge Contract (contracts/ibc-bridge): - Token locking/unlocking for cross-chain transfers - Relayer whitelist management - Channel registration and sequence tracking - HTLC atomic swap (create, claim, refund) - Event emission for indexing - 52KB optimized WASM binary Test coverage: 40 tests passing
768 lines
21 KiB
Rust
768 lines
21 KiB
Rust
//! IBC Channel Management
|
|
//!
|
|
//! Channels provide the transport layer for IBC packets. Each channel
|
|
//! is bound to a specific port and connection.
|
|
//!
|
|
//! # Channel Types
|
|
//!
|
|
//! - **Ordered**: Packets must be received in order (sequence numbers)
|
|
//! - **Unordered**: Packets can be received in any order
|
|
//!
|
|
//! # Channel Handshake (4-way)
|
|
//!
|
|
//! Similar to connection handshake:
|
|
//! 1. ChanOpenInit
|
|
//! 2. ChanOpenTry
|
|
//! 3. ChanOpenAck
|
|
//! 4. ChanOpenConfirm
|
|
|
|
use crate::connection::ConnectionId;
|
|
use crate::error::{IbcError, IbcResult};
|
|
use crate::types::Height;
|
|
use borsh::{BorshDeserialize, BorshSerialize};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
use std::fmt;
|
|
|
|
/// Port identifier
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
|
pub struct PortId(pub String);
|
|
|
|
impl PortId {
|
|
/// Create a new port ID
|
|
pub fn new(id: impl Into<String>) -> Self {
|
|
Self(id.into())
|
|
}
|
|
|
|
/// Transfer port
|
|
pub fn transfer() -> Self {
|
|
Self("transfer".to_string())
|
|
}
|
|
|
|
/// Interchain accounts host port
|
|
pub fn ica_host() -> Self {
|
|
Self("icahost".to_string())
|
|
}
|
|
|
|
/// Interchain accounts controller port
|
|
pub fn ica_controller(owner: &str) -> Self {
|
|
Self(format!("icacontroller-{}", owner))
|
|
}
|
|
|
|
/// Validate port ID
|
|
pub fn validate(&self) -> IbcResult<()> {
|
|
if self.0.is_empty() {
|
|
return Err(IbcError::InvalidIdentifier("port ID cannot be empty".to_string()));
|
|
}
|
|
|
|
if self.0.len() > 128 {
|
|
return Err(IbcError::InvalidIdentifier("port ID too long".to_string()));
|
|
}
|
|
|
|
// Alphanumeric and limited special characters
|
|
if !self.0.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_' || c == '.') {
|
|
return Err(IbcError::InvalidIdentifier(
|
|
"port ID contains invalid characters".to_string(),
|
|
));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for PortId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
/// Channel identifier
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
|
pub struct ChannelId(pub String);
|
|
|
|
impl ChannelId {
|
|
/// Create a new channel ID
|
|
pub fn new(sequence: u64) -> Self {
|
|
Self(format!("channel-{}", sequence))
|
|
}
|
|
|
|
/// Parse sequence from ID
|
|
pub fn sequence(&self) -> Option<u64> {
|
|
self.0.strip_prefix("channel-")?.parse().ok()
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ChannelId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
/// Channel ordering
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum ChannelOrder {
|
|
/// Packets can arrive in any order
|
|
Unordered,
|
|
/// Packets must arrive in sequence order
|
|
Ordered,
|
|
}
|
|
|
|
impl ChannelOrder {
|
|
/// Get order name
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
ChannelOrder::Unordered => "ORDER_UNORDERED",
|
|
ChannelOrder::Ordered => "ORDER_ORDERED",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ChannelOrder {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.as_str())
|
|
}
|
|
}
|
|
|
|
/// Channel state
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum ChannelState {
|
|
/// Uninitialized (default)
|
|
Uninitialized,
|
|
/// Init: ChanOpenInit sent
|
|
Init,
|
|
/// TryOpen: ChanOpenTry sent
|
|
TryOpen,
|
|
/// Open: Channel fully established
|
|
Open,
|
|
/// Closed: Channel closed
|
|
Closed,
|
|
}
|
|
|
|
impl ChannelState {
|
|
/// Get state name
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
ChannelState::Uninitialized => "UNINITIALIZED",
|
|
ChannelState::Init => "INIT",
|
|
ChannelState::TryOpen => "TRYOPEN",
|
|
ChannelState::Open => "OPEN",
|
|
ChannelState::Closed => "CLOSED",
|
|
}
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ChannelState {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.as_str())
|
|
}
|
|
}
|
|
|
|
/// Channel counterparty
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ChannelCounterparty {
|
|
/// Counterparty port ID
|
|
pub port_id: PortId,
|
|
/// Counterparty channel ID
|
|
pub channel_id: Option<ChannelId>,
|
|
}
|
|
|
|
impl ChannelCounterparty {
|
|
/// Create a new counterparty
|
|
pub fn new(port_id: PortId, channel_id: Option<ChannelId>) -> Self {
|
|
Self { port_id, channel_id }
|
|
}
|
|
}
|
|
|
|
/// Channel end state
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Channel {
|
|
/// Channel state
|
|
pub state: ChannelState,
|
|
/// Channel ordering
|
|
pub ordering: ChannelOrder,
|
|
/// Counterparty information
|
|
pub counterparty: ChannelCounterparty,
|
|
/// Connection hops (usually single connection)
|
|
pub connection_hops: Vec<ConnectionId>,
|
|
/// Channel version (app-specific)
|
|
pub version: String,
|
|
}
|
|
|
|
impl Channel {
|
|
/// Create a new channel in Init state
|
|
pub fn new_init(
|
|
ordering: ChannelOrder,
|
|
counterparty: ChannelCounterparty,
|
|
connection_hops: Vec<ConnectionId>,
|
|
version: String,
|
|
) -> Self {
|
|
Self {
|
|
state: ChannelState::Init,
|
|
ordering,
|
|
counterparty,
|
|
connection_hops,
|
|
version,
|
|
}
|
|
}
|
|
|
|
/// Create a new channel in TryOpen state
|
|
pub fn new_try_open(
|
|
ordering: ChannelOrder,
|
|
counterparty: ChannelCounterparty,
|
|
connection_hops: Vec<ConnectionId>,
|
|
version: String,
|
|
) -> Self {
|
|
Self {
|
|
state: ChannelState::TryOpen,
|
|
ordering,
|
|
counterparty,
|
|
connection_hops,
|
|
version,
|
|
}
|
|
}
|
|
|
|
/// Check if channel is open
|
|
pub fn is_open(&self) -> bool {
|
|
self.state == ChannelState::Open
|
|
}
|
|
|
|
/// Check if channel is closed
|
|
pub fn is_closed(&self) -> bool {
|
|
self.state == ChannelState::Closed
|
|
}
|
|
|
|
/// Set state to TryOpen
|
|
pub fn set_try_open(&mut self) {
|
|
self.state = ChannelState::TryOpen;
|
|
}
|
|
|
|
/// Set state to Open
|
|
pub fn set_open(&mut self) {
|
|
self.state = ChannelState::Open;
|
|
}
|
|
|
|
/// Set state to Closed
|
|
pub fn set_closed(&mut self) {
|
|
self.state = ChannelState::Closed;
|
|
}
|
|
|
|
/// Set counterparty channel ID
|
|
pub fn set_counterparty_channel_id(&mut self, id: ChannelId) {
|
|
self.counterparty.channel_id = Some(id);
|
|
}
|
|
|
|
/// Validate the channel
|
|
pub fn validate(&self) -> IbcResult<()> {
|
|
if self.connection_hops.is_empty() {
|
|
return Err(IbcError::InvalidChannelState {
|
|
expected: "at least one connection hop".to_string(),
|
|
actual: "none".to_string(),
|
|
});
|
|
}
|
|
|
|
self.counterparty.port_id.validate()?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Channel key (port, channel)
|
|
pub type ChannelKey = (PortId, ChannelId);
|
|
|
|
/// Sequence counters for a channel
|
|
#[derive(Debug, Clone, Default)]
|
|
pub struct ChannelSequences {
|
|
/// Next sequence to send
|
|
pub next_send: u64,
|
|
/// Next sequence to receive
|
|
pub next_recv: u64,
|
|
/// Next sequence to acknowledge
|
|
pub next_ack: u64,
|
|
}
|
|
|
|
/// Channel manager
|
|
pub struct ChannelManager {
|
|
/// Channels by (port, channel)
|
|
channels: HashMap<ChannelKey, Channel>,
|
|
/// Sequence counters by (port, channel)
|
|
sequences: HashMap<ChannelKey, ChannelSequences>,
|
|
/// Port bindings (port -> module name)
|
|
port_bindings: HashMap<PortId, String>,
|
|
/// Next channel sequence per port
|
|
next_channel_sequence: HashMap<PortId, u64>,
|
|
}
|
|
|
|
impl ChannelManager {
|
|
/// Create a new channel manager
|
|
pub fn new() -> Self {
|
|
Self {
|
|
channels: HashMap::new(),
|
|
sequences: HashMap::new(),
|
|
port_bindings: HashMap::new(),
|
|
next_channel_sequence: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Bind a port to a module
|
|
pub fn bind_port(&mut self, port_id: PortId, module: String) -> IbcResult<()> {
|
|
port_id.validate()?;
|
|
|
|
if self.port_bindings.contains_key(&port_id) {
|
|
return Err(IbcError::PortAlreadyBound(port_id.to_string()));
|
|
}
|
|
|
|
self.port_bindings.insert(port_id, module);
|
|
Ok(())
|
|
}
|
|
|
|
/// Release a port binding
|
|
pub fn release_port(&mut self, port_id: &PortId) -> IbcResult<()> {
|
|
if self.port_bindings.remove(port_id).is_none() {
|
|
return Err(IbcError::PortNotBound(port_id.to_string()));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if port is bound
|
|
pub fn is_port_bound(&self, port_id: &PortId) -> bool {
|
|
self.port_bindings.contains_key(port_id)
|
|
}
|
|
|
|
/// Generate next channel ID for a port
|
|
fn next_channel_id(&mut self, port_id: &PortId) -> ChannelId {
|
|
let seq = self.next_channel_sequence.entry(port_id.clone()).or_insert(0);
|
|
let id = ChannelId::new(*seq);
|
|
*seq += 1;
|
|
id
|
|
}
|
|
|
|
/// Get a channel
|
|
pub fn get_channel(&self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<&Channel> {
|
|
self.channels
|
|
.get(&(port_id.clone(), channel_id.clone()))
|
|
.ok_or_else(|| IbcError::ChannelNotFound {
|
|
port: port_id.to_string(),
|
|
channel: channel_id.to_string(),
|
|
})
|
|
}
|
|
|
|
/// Get mutable channel
|
|
fn get_channel_mut(
|
|
&mut self,
|
|
port_id: &PortId,
|
|
channel_id: &ChannelId,
|
|
) -> IbcResult<&mut Channel> {
|
|
self.channels
|
|
.get_mut(&(port_id.clone(), channel_id.clone()))
|
|
.ok_or_else(|| IbcError::ChannelNotFound {
|
|
port: port_id.to_string(),
|
|
channel: channel_id.to_string(),
|
|
})
|
|
}
|
|
|
|
/// Get sequences for a channel
|
|
pub fn get_sequences(&self, port_id: &PortId, channel_id: &ChannelId) -> ChannelSequences {
|
|
self.sequences
|
|
.get(&(port_id.clone(), channel_id.clone()))
|
|
.cloned()
|
|
.unwrap_or_default()
|
|
}
|
|
|
|
/// Increment send sequence and return previous value
|
|
pub fn increment_send_sequence(
|
|
&mut self,
|
|
port_id: &PortId,
|
|
channel_id: &ChannelId,
|
|
) -> u64 {
|
|
let key = (port_id.clone(), channel_id.clone());
|
|
let seq = self.sequences.entry(key).or_default();
|
|
let current = seq.next_send;
|
|
seq.next_send += 1;
|
|
current
|
|
}
|
|
|
|
/// Increment receive sequence
|
|
pub fn increment_recv_sequence(&mut self, port_id: &PortId, channel_id: &ChannelId) {
|
|
let key = (port_id.clone(), channel_id.clone());
|
|
let seq = self.sequences.entry(key).or_default();
|
|
seq.next_recv += 1;
|
|
}
|
|
|
|
/// Increment ack sequence
|
|
pub fn increment_ack_sequence(&mut self, port_id: &PortId, channel_id: &ChannelId) {
|
|
let key = (port_id.clone(), channel_id.clone());
|
|
let seq = self.sequences.entry(key).or_default();
|
|
seq.next_ack += 1;
|
|
}
|
|
|
|
/// Channel Open Init
|
|
pub fn chan_open_init(
|
|
&mut self,
|
|
port_id: PortId,
|
|
ordering: ChannelOrder,
|
|
connection_hops: Vec<ConnectionId>,
|
|
counterparty_port: PortId,
|
|
version: String,
|
|
) -> IbcResult<ChannelId> {
|
|
// Validate port
|
|
port_id.validate()?;
|
|
counterparty_port.validate()?;
|
|
|
|
// Check port is bound
|
|
if !self.is_port_bound(&port_id) {
|
|
return Err(IbcError::PortNotBound(port_id.to_string()));
|
|
}
|
|
|
|
// Generate channel ID
|
|
let channel_id = self.next_channel_id(&port_id);
|
|
|
|
// Create channel
|
|
let counterparty = ChannelCounterparty::new(counterparty_port, None);
|
|
let channel = Channel::new_init(ordering, counterparty, connection_hops, version);
|
|
channel.validate()?;
|
|
|
|
// Store channel
|
|
let key = (port_id.clone(), channel_id.clone());
|
|
self.channels.insert(key.clone(), channel);
|
|
self.sequences.insert(key, ChannelSequences {
|
|
next_send: 1,
|
|
next_recv: 1,
|
|
next_ack: 1,
|
|
});
|
|
|
|
tracing::info!(
|
|
port = %port_id,
|
|
channel = %channel_id,
|
|
"Channel init complete"
|
|
);
|
|
|
|
Ok(channel_id)
|
|
}
|
|
|
|
/// Channel Open Try
|
|
pub fn chan_open_try(
|
|
&mut self,
|
|
port_id: PortId,
|
|
ordering: ChannelOrder,
|
|
connection_hops: Vec<ConnectionId>,
|
|
counterparty_port: PortId,
|
|
counterparty_channel: ChannelId,
|
|
version: String,
|
|
_counterparty_version: String,
|
|
proof_init: Vec<u8>,
|
|
_proof_height: Height,
|
|
) -> IbcResult<ChannelId> {
|
|
// Validate
|
|
port_id.validate()?;
|
|
|
|
// Check port is bound
|
|
if !self.is_port_bound(&port_id) {
|
|
return Err(IbcError::PortNotBound(port_id.to_string()));
|
|
}
|
|
|
|
// Verify proof (simplified)
|
|
if proof_init.is_empty() {
|
|
return Err(IbcError::MissingProof("proof_init required".to_string()));
|
|
}
|
|
|
|
// Generate channel ID
|
|
let channel_id = self.next_channel_id(&port_id);
|
|
|
|
// Create channel
|
|
let counterparty = ChannelCounterparty::new(counterparty_port, Some(counterparty_channel));
|
|
let channel = Channel::new_try_open(ordering, counterparty, connection_hops, version);
|
|
channel.validate()?;
|
|
|
|
// Store channel
|
|
let key = (port_id.clone(), channel_id.clone());
|
|
self.channels.insert(key.clone(), channel);
|
|
self.sequences.insert(key, ChannelSequences {
|
|
next_send: 1,
|
|
next_recv: 1,
|
|
next_ack: 1,
|
|
});
|
|
|
|
tracing::info!(
|
|
port = %port_id,
|
|
channel = %channel_id,
|
|
"Channel try open complete"
|
|
);
|
|
|
|
Ok(channel_id)
|
|
}
|
|
|
|
/// Channel Open Ack
|
|
pub fn chan_open_ack(
|
|
&mut self,
|
|
port_id: &PortId,
|
|
channel_id: &ChannelId,
|
|
counterparty_channel: ChannelId,
|
|
counterparty_version: String,
|
|
proof_try: Vec<u8>,
|
|
_proof_height: Height,
|
|
) -> IbcResult<()> {
|
|
let channel = self.get_channel_mut(port_id, channel_id)?;
|
|
|
|
// Verify state
|
|
if channel.state != ChannelState::Init {
|
|
return Err(IbcError::InvalidChannelState {
|
|
expected: ChannelState::Init.to_string(),
|
|
actual: channel.state.to_string(),
|
|
});
|
|
}
|
|
|
|
// Verify proof (simplified)
|
|
if proof_try.is_empty() {
|
|
return Err(IbcError::MissingProof("proof_try required".to_string()));
|
|
}
|
|
|
|
// Update channel
|
|
channel.set_counterparty_channel_id(counterparty_channel);
|
|
channel.version = counterparty_version;
|
|
channel.set_open();
|
|
|
|
tracing::info!(
|
|
port = %port_id,
|
|
channel = %channel_id,
|
|
"Channel ack complete - channel OPEN"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Channel Open Confirm
|
|
pub fn chan_open_confirm(
|
|
&mut self,
|
|
port_id: &PortId,
|
|
channel_id: &ChannelId,
|
|
proof_ack: Vec<u8>,
|
|
_proof_height: Height,
|
|
) -> IbcResult<()> {
|
|
let channel = self.get_channel_mut(port_id, channel_id)?;
|
|
|
|
// Verify state
|
|
if channel.state != ChannelState::TryOpen {
|
|
return Err(IbcError::InvalidChannelState {
|
|
expected: ChannelState::TryOpen.to_string(),
|
|
actual: channel.state.to_string(),
|
|
});
|
|
}
|
|
|
|
// Verify proof (simplified)
|
|
if proof_ack.is_empty() {
|
|
return Err(IbcError::MissingProof("proof_ack required".to_string()));
|
|
}
|
|
|
|
// Update state
|
|
channel.set_open();
|
|
|
|
tracing::info!(
|
|
port = %port_id,
|
|
channel = %channel_id,
|
|
"Channel confirm complete - channel OPEN"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Channel Close Init
|
|
pub fn chan_close_init(&mut self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<()> {
|
|
let channel = self.get_channel_mut(port_id, channel_id)?;
|
|
|
|
if !channel.is_open() {
|
|
return Err(IbcError::InvalidChannelState {
|
|
expected: ChannelState::Open.to_string(),
|
|
actual: channel.state.to_string(),
|
|
});
|
|
}
|
|
|
|
channel.set_closed();
|
|
|
|
tracing::info!(
|
|
port = %port_id,
|
|
channel = %channel_id,
|
|
"Channel close init - channel CLOSED"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Channel Close Confirm
|
|
pub fn chan_close_confirm(
|
|
&mut self,
|
|
port_id: &PortId,
|
|
channel_id: &ChannelId,
|
|
proof_init: Vec<u8>,
|
|
_proof_height: Height,
|
|
) -> IbcResult<()> {
|
|
let channel = self.get_channel_mut(port_id, channel_id)?;
|
|
|
|
if channel.is_closed() {
|
|
return Ok(()); // Already closed
|
|
}
|
|
|
|
// Verify proof (simplified)
|
|
if proof_init.is_empty() {
|
|
return Err(IbcError::MissingProof("proof_init required".to_string()));
|
|
}
|
|
|
|
channel.set_closed();
|
|
|
|
tracing::info!(
|
|
port = %port_id,
|
|
channel = %channel_id,
|
|
"Channel close confirm - channel CLOSED"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get all channels
|
|
pub fn all_channels(&self) -> impl Iterator<Item = (&ChannelKey, &Channel)> {
|
|
self.channels.iter()
|
|
}
|
|
|
|
/// Get channels for a port
|
|
pub fn port_channels(&self, port_id: &PortId) -> Vec<(ChannelId, Channel)> {
|
|
self.channels
|
|
.iter()
|
|
.filter(|((p, _), _)| p == port_id)
|
|
.map(|((_, c), ch)| (c.clone(), ch.clone()))
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
impl Default for ChannelManager {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_port_id() {
|
|
let port = PortId::transfer();
|
|
assert_eq!(port.to_string(), "transfer");
|
|
assert!(port.validate().is_ok());
|
|
|
|
let invalid = PortId::new("");
|
|
assert!(invalid.validate().is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn test_channel_id() {
|
|
let id = ChannelId::new(5);
|
|
assert_eq!(id.to_string(), "channel-5");
|
|
assert_eq!(id.sequence(), Some(5));
|
|
}
|
|
|
|
#[test]
|
|
fn test_port_binding() {
|
|
let mut manager = ChannelManager::new();
|
|
let port = PortId::transfer();
|
|
|
|
assert!(manager.bind_port(port.clone(), "transfer".to_string()).is_ok());
|
|
assert!(manager.is_port_bound(&port));
|
|
|
|
// Can't bind same port twice
|
|
assert!(manager.bind_port(port.clone(), "other".to_string()).is_err());
|
|
|
|
// Release and rebind
|
|
assert!(manager.release_port(&port).is_ok());
|
|
assert!(!manager.is_port_bound(&port));
|
|
}
|
|
|
|
#[test]
|
|
fn test_channel_handshake() {
|
|
let mut manager_a = ChannelManager::new();
|
|
let mut manager_b = ChannelManager::new();
|
|
|
|
let port = PortId::transfer();
|
|
let conn = ConnectionId::new(0);
|
|
|
|
// Bind ports
|
|
manager_a.bind_port(port.clone(), "transfer".to_string()).unwrap();
|
|
manager_b.bind_port(port.clone(), "transfer".to_string()).unwrap();
|
|
|
|
// Chain A: Init
|
|
let chan_a = manager_a
|
|
.chan_open_init(
|
|
port.clone(),
|
|
ChannelOrder::Unordered,
|
|
vec![conn.clone()],
|
|
port.clone(),
|
|
"ics20-1".to_string(),
|
|
)
|
|
.unwrap();
|
|
|
|
// Chain B: TryOpen
|
|
let chan_b = manager_b
|
|
.chan_open_try(
|
|
port.clone(),
|
|
ChannelOrder::Unordered,
|
|
vec![conn.clone()],
|
|
port.clone(),
|
|
chan_a.clone(),
|
|
"ics20-1".to_string(),
|
|
"ics20-1".to_string(),
|
|
vec![1, 2, 3],
|
|
Height::from_height(100),
|
|
)
|
|
.unwrap();
|
|
|
|
// Chain A: Ack
|
|
manager_a
|
|
.chan_open_ack(
|
|
&port,
|
|
&chan_a,
|
|
chan_b.clone(),
|
|
"ics20-1".to_string(),
|
|
vec![1, 2, 3],
|
|
Height::from_height(101),
|
|
)
|
|
.unwrap();
|
|
|
|
assert!(manager_a.get_channel(&port, &chan_a).unwrap().is_open());
|
|
|
|
// Chain B: Confirm
|
|
manager_b
|
|
.chan_open_confirm(&port, &chan_b, vec![1, 2, 3], Height::from_height(102))
|
|
.unwrap();
|
|
|
|
assert!(manager_b.get_channel(&port, &chan_b).unwrap().is_open());
|
|
}
|
|
|
|
#[test]
|
|
fn test_sequence_management() {
|
|
let mut manager = ChannelManager::new();
|
|
let port = PortId::transfer();
|
|
let conn = ConnectionId::new(0);
|
|
|
|
manager.bind_port(port.clone(), "transfer".to_string()).unwrap();
|
|
|
|
let channel = manager
|
|
.chan_open_init(
|
|
port.clone(),
|
|
ChannelOrder::Ordered,
|
|
vec![conn],
|
|
port.clone(),
|
|
"ics20-1".to_string(),
|
|
)
|
|
.unwrap();
|
|
|
|
// Initial sequences should be 1
|
|
let seq = manager.get_sequences(&port, &channel);
|
|
assert_eq!(seq.next_send, 1);
|
|
assert_eq!(seq.next_recv, 1);
|
|
|
|
// Increment send
|
|
let send_seq = manager.increment_send_sequence(&port, &channel);
|
|
assert_eq!(send_seq, 1);
|
|
|
|
let seq = manager.get_sequences(&port, &channel);
|
|
assert_eq!(seq.next_send, 2);
|
|
}
|
|
}
|