//! 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) -> 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 { 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, } impl ChannelCounterparty { /// Create a new counterparty pub fn new(port_id: PortId, channel_id: Option) -> 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, /// 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, 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, 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, /// Sequence counters by (port, channel) sequences: HashMap, /// Port bindings (port -> module name) port_bindings: HashMap, /// Next channel sequence per port next_channel_sequence: HashMap, } 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, counterparty_port: PortId, version: String, ) -> IbcResult { // 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, counterparty_port: PortId, counterparty_channel: ChannelId, version: String, _counterparty_version: String, proof_init: Vec, _proof_height: Height, ) -> IbcResult { // 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, _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, _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, _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 { 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); } }