//! IBC Handler - Main interface for IBC operations //! //! The handler coordinates light clients, connections, channels, and packets. use crate::channel::{Channel, ChannelId, ChannelManager, ChannelOrder, PortId}; use crate::client::{ClientId, ClientState, ConsensusState, Header, LightClient}; use crate::connection::{ConnectionEnd, ConnectionId, ConnectionManager, Counterparty}; use crate::error::{IbcError, IbcResult}; use crate::packet::{Acknowledgement, FungibleTokenPacketData, Packet, PacketHandler, Timeout}; use crate::types::{Height, Timestamp, Version}; use parking_lot::RwLock; use std::sync::Arc; /// IBC handler configuration #[derive(Debug, Clone)] pub struct IbcConfig { /// Chain ID pub chain_id: String, /// Default connection delay period (nanoseconds) pub default_delay_period: u64, /// Enable packet timeout pub enable_timeout: bool, /// Maximum packet data size pub max_packet_size: usize, } impl Default for IbcConfig { fn default() -> Self { Self { chain_id: "synor-1".to_string(), default_delay_period: 0, enable_timeout: true, max_packet_size: 1024 * 1024, // 1 MB } } } /// Main IBC handler pub struct IbcHandler { /// Configuration config: IbcConfig, /// Light client manager light_client: Arc>, /// Connection manager connections: Arc>, /// Channel manager channels: Arc>, /// Packet handler packets: Arc>, /// Current block height current_height: Arc>, /// Current block timestamp current_timestamp: Arc>, } impl IbcHandler { /// Create a new IBC handler pub fn new(config: IbcConfig) -> Self { Self { config, light_client: Arc::new(RwLock::new(LightClient::new())), connections: Arc::new(RwLock::new(ConnectionManager::new())), channels: Arc::new(RwLock::new(ChannelManager::new())), packets: Arc::new(RwLock::new(PacketHandler::new())), current_height: Arc::new(RwLock::new(Height::from_height(1))), current_timestamp: Arc::new(RwLock::new(Timestamp::now())), } } /// Update current block height and timestamp pub fn update_block(&self, height: Height, timestamp: Timestamp) { *self.current_height.write() = height; *self.current_timestamp.write() = timestamp; } /// Get current height pub fn current_height(&self) -> Height { *self.current_height.read() } /// Get current timestamp pub fn current_timestamp(&self) -> Timestamp { *self.current_timestamp.read() } // ======================================================================== // Client Operations // ======================================================================== /// Create a new light client pub fn create_client( &self, client_state: ClientState, consensus_state: ConsensusState, ) -> IbcResult { let mut client = self.light_client.write(); let client_id = client.create_client(client_state, consensus_state)?; tracing::info!( client_id = %client_id, "Created IBC client" ); Ok(client_id) } /// Update a light client with a new header pub fn update_client(&self, client_id: &ClientId, header: Header) -> IbcResult<()> { let mut client = self.light_client.write(); client.update_client(client_id, header)?; tracing::info!( client_id = %client_id, "Updated IBC client" ); Ok(()) } /// Get client state pub fn get_client_state(&self, client_id: &ClientId) -> IbcResult { let client = self.light_client.read(); Ok(client.get_client_state(client_id)?.clone()) } // ======================================================================== // Connection Operations // ======================================================================== /// Initialize a connection (ConnOpenInit) pub fn connection_open_init( &self, client_id: ClientId, counterparty_client_id: ClientId, version: Option, ) -> IbcResult { // Verify client exists let _ = self.get_client_state(&client_id)?; let counterparty = Counterparty::new( counterparty_client_id, None, crate::types::CommitmentPrefix::default(), ); let mut connections = self.connections.write(); connections.conn_open_init( client_id, counterparty, version, self.config.default_delay_period, ) } /// Try to open a connection (ConnOpenTry) pub fn connection_open_try( &self, client_id: ClientId, counterparty_client_id: ClientId, counterparty_conn_id: ConnectionId, counterparty_versions: Vec, proof_init: Vec, proof_height: Height, ) -> IbcResult { // Verify client exists let _ = self.get_client_state(&client_id)?; let counterparty = Counterparty::new( counterparty_client_id, Some(counterparty_conn_id), crate::types::CommitmentPrefix::default(), ); let mut connections = self.connections.write(); connections.conn_open_try( client_id, counterparty, counterparty_versions, self.config.default_delay_period, proof_init, proof_height, ) } /// Acknowledge a connection (ConnOpenAck) pub fn connection_open_ack( &self, conn_id: &ConnectionId, counterparty_conn_id: ConnectionId, version: Version, proof_try: Vec, proof_height: Height, ) -> IbcResult<()> { let mut connections = self.connections.write(); connections.conn_open_ack(conn_id, counterparty_conn_id, version, proof_try, proof_height) } /// Confirm a connection (ConnOpenConfirm) pub fn connection_open_confirm( &self, conn_id: &ConnectionId, proof_ack: Vec, proof_height: Height, ) -> IbcResult<()> { let mut connections = self.connections.write(); connections.conn_open_confirm(conn_id, proof_ack, proof_height) } /// Get connection pub fn get_connection(&self, conn_id: &ConnectionId) -> IbcResult { let connections = self.connections.read(); Ok(connections.get_connection(conn_id)?.clone()) } // ======================================================================== // Channel Operations // ======================================================================== /// Bind a port to a module pub fn bind_port(&self, port_id: PortId, module: String) -> IbcResult<()> { let mut channels = self.channels.write(); channels.bind_port(port_id, module) } /// Initialize a channel (ChanOpenInit) pub fn channel_open_init( &self, port_id: PortId, connection_id: ConnectionId, counterparty_port: PortId, ordering: ChannelOrder, version: String, ) -> IbcResult { // Verify connection exists and is open let connection = self.get_connection(&connection_id)?; if !connection.is_open() { return Err(IbcError::InvalidConnectionState { expected: "OPEN".to_string(), actual: connection.state.to_string(), }); } let mut channels = self.channels.write(); channels.chan_open_init( port_id, ordering, vec![connection_id], counterparty_port, version, ) } /// Try to open a channel (ChanOpenTry) pub fn channel_open_try( &self, port_id: PortId, connection_id: ConnectionId, counterparty_port: PortId, counterparty_channel: ChannelId, ordering: ChannelOrder, version: String, counterparty_version: String, proof_init: Vec, proof_height: Height, ) -> IbcResult { // Verify connection let connection = self.get_connection(&connection_id)?; if !connection.is_open() { return Err(IbcError::InvalidConnectionState { expected: "OPEN".to_string(), actual: connection.state.to_string(), }); } let mut channels = self.channels.write(); channels.chan_open_try( port_id, ordering, vec![connection_id], counterparty_port, counterparty_channel, version, counterparty_version, proof_init, proof_height, ) } /// Acknowledge a channel (ChanOpenAck) pub fn channel_open_ack( &self, port_id: &PortId, channel_id: &ChannelId, counterparty_channel: ChannelId, counterparty_version: String, proof_try: Vec, proof_height: Height, ) -> IbcResult<()> { let mut channels = self.channels.write(); channels.chan_open_ack( port_id, channel_id, counterparty_channel, counterparty_version, proof_try, proof_height, ) } /// Confirm a channel (ChanOpenConfirm) pub fn channel_open_confirm( &self, port_id: &PortId, channel_id: &ChannelId, proof_ack: Vec, proof_height: Height, ) -> IbcResult<()> { let mut channels = self.channels.write(); channels.chan_open_confirm(port_id, channel_id, proof_ack, proof_height) } /// Close a channel pub fn channel_close_init(&self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult<()> { let mut channels = self.channels.write(); channels.chan_close_init(port_id, channel_id) } /// Get channel pub fn get_channel(&self, port_id: &PortId, channel_id: &ChannelId) -> IbcResult { let channels = self.channels.read(); Ok(channels.get_channel(port_id, channel_id)?.clone()) } // ======================================================================== // Packet Operations // ======================================================================== /// Send a packet pub fn send_packet( &self, source_port: PortId, source_channel: ChannelId, data: Vec, timeout: Timeout, ) -> IbcResult { // Validate data size if data.len() > self.config.max_packet_size { return Err(IbcError::PacketDataTooLarge { size: data.len(), max: self.config.max_packet_size, }); } // Get channel and verify it's open let channel = self.get_channel(&source_port, &source_channel)?; if !channel.is_open() { return Err(IbcError::InvalidChannelState { expected: "OPEN".to_string(), actual: channel.state.to_string(), }); } let counterparty = &channel.counterparty; let dest_port = counterparty.port_id.clone(); let dest_channel = counterparty.channel_id.clone().ok_or_else(|| { IbcError::InvalidChannelState { expected: "counterparty channel set".to_string(), actual: "none".to_string(), } })?; // Get sequence number let mut channels = self.channels.write(); let sequence = channels.increment_send_sequence(&source_port, &source_channel); // Create packet let packet = Packet::new( sequence, source_port.clone(), source_channel.clone(), dest_port, dest_channel, data, timeout.height, timeout.timestamp, ); packet.validate()?; // Store commitment let mut packets = self.packets.write(); packets.store_packet_commitment(&packet); tracing::info!( port = %source_port, channel = %source_channel, sequence = sequence, "Packet sent" ); Ok(sequence) } /// Receive a packet pub fn recv_packet( &self, packet: Packet, proof_commitment: Vec, _proof_height: Height, ) -> IbcResult { // Validate packet packet.validate()?; // Check timeout let current_height = self.current_height(); let current_time = self.current_timestamp(); if packet.is_timed_out(current_height, current_time) { return Err(IbcError::PacketTimeout { height: current_height.revision_height, timestamp: current_time.nanoseconds(), }); } // Get channel and verify let channel = self.get_channel(&packet.dest_port, &packet.dest_channel)?; if !channel.is_open() { return Err(IbcError::InvalidChannelState { expected: "OPEN".to_string(), actual: channel.state.to_string(), }); } // Verify proof (simplified) if proof_commitment.is_empty() { return Err(IbcError::MissingProof("proof_commitment required".to_string())); } // Handle based on channel ordering let mut packets = self.packets.write(); let mut channels = self.channels.write(); match channel.ordering { ChannelOrder::Unordered => { // Verify no receipt exists packets.verify_no_receipt(&packet)?; } ChannelOrder::Ordered => { // Verify sequence let expected = channels.get_sequences(&packet.dest_port, &packet.dest_channel).next_recv; packets.verify_ordered_sequence(&packet, expected)?; channels.increment_recv_sequence(&packet.dest_port, &packet.dest_channel); } } // Store receipt packets.store_packet_receipt(&packet, current_height, None); // Generate acknowledgement (success) let ack = Acknowledgement::success(vec![]); // Store ack commitment packets.store_ack_commitment( &packet.dest_port, &packet.dest_channel, packet.sequence, &ack, ); tracing::info!( port = %packet.dest_port, channel = %packet.dest_channel, sequence = packet.sequence, "Packet received" ); Ok(ack) } /// Acknowledge a packet pub fn acknowledge_packet( &self, packet: Packet, acknowledgement: Acknowledgement, proof_ack: Vec, _proof_height: Height, ) -> IbcResult<()> { // Get channel let channel = self.get_channel(&packet.source_port, &packet.source_channel)?; // Verify proof (simplified) if proof_ack.is_empty() { return Err(IbcError::MissingProof("proof_ack required".to_string())); } let mut packets = self.packets.write(); // Verify commitment exists if packets .get_packet_commitment(&packet.source_port, &packet.source_channel, packet.sequence) .is_none() { return Err(IbcError::PacketNotFound(packet.sequence)); } // For ordered channels, verify sequence if channel.ordering == ChannelOrder::Ordered { let mut channels = self.channels.write(); let expected = channels.get_sequences(&packet.source_port, &packet.source_channel).next_ack; if packet.sequence != expected { return Err(IbcError::InvalidPacketSequence { expected, actual: packet.sequence, }); } channels.increment_ack_sequence(&packet.source_port, &packet.source_channel); } // Delete commitment packets.delete_packet_commitment(&packet.source_port, &packet.source_channel, packet.sequence); tracing::info!( port = %packet.source_port, channel = %packet.source_channel, sequence = packet.sequence, success = acknowledgement.is_success(), "Packet acknowledged" ); Ok(()) } /// Timeout a packet pub fn timeout_packet( &self, packet: Packet, proof_unreceived: Vec, _proof_height: Height, _next_sequence_recv: u64, ) -> IbcResult<()> { // Verify the packet timed out let _current_height = self.current_height(); let _current_time = self.current_timestamp(); // Note: For timeout, we check if DESTINATION chain's height/time exceeds timeout // This is a simplification - real implementation would verify against proof_height // Verify proof (simplified) if proof_unreceived.is_empty() { return Err(IbcError::MissingProof("proof_unreceived required".to_string())); } let mut packets = self.packets.write(); packets.timeout_packet(&packet)?; tracing::info!( port = %packet.source_port, channel = %packet.source_channel, sequence = packet.sequence, "Packet timed out" ); Ok(()) } // ======================================================================== // Token Transfer (ICS-20) // ======================================================================== /// Send tokens via IBC transfer pub fn transfer( &self, source_port: PortId, source_channel: ChannelId, denom: String, amount: String, sender: String, receiver: String, timeout: Timeout, ) -> IbcResult { let packet_data = FungibleTokenPacketData::new(denom, amount, sender, receiver); let data = packet_data.encode(); self.send_packet(source_port, source_channel, data, timeout) } // ======================================================================== // Stats and Info // ======================================================================== /// Get connection count pub fn connection_count(&self) -> usize { self.connections.read().connection_count() } /// Get chain ID pub fn chain_id(&self) -> &str { &self.config.chain_id } } impl Default for IbcHandler { fn default() -> Self { Self::new(IbcConfig::default()) } } #[cfg(test)] mod tests { use super::*; use crate::types::ChainId; fn setup_handler() -> IbcHandler { let handler = IbcHandler::new(IbcConfig::default()); // Create a client let client_state = ClientState::new_synor( ChainId::synor_testnet(), Height::from_height(100), ); let consensus_state = ConsensusState::new( Timestamp::now(), vec![0u8; 32], vec![0u8; 32], ); handler.create_client(client_state, consensus_state).unwrap(); handler.bind_port(PortId::transfer(), "transfer".to_string()).unwrap(); handler } #[test] fn test_handler_creation() { let handler = IbcHandler::default(); assert_eq!(handler.chain_id(), "synor-1"); assert_eq!(handler.connection_count(), 0); } #[test] fn test_client_creation() { let handler = IbcHandler::default(); let client_state = ClientState::new_synor( ChainId::synor_testnet(), Height::from_height(100), ); let consensus_state = ConsensusState::new( Timestamp::now(), vec![0u8; 32], vec![0u8; 32], ); let client_id = handler.create_client(client_state, consensus_state).unwrap(); assert!(handler.get_client_state(&client_id).is_ok()); } #[test] fn test_connection_init() { let handler = setup_handler(); let client_id = ClientId::new("synor", 0); let counterparty_client = ClientId::new("07-tendermint", 0); let conn_id = handler.connection_open_init( client_id, counterparty_client, None, ).unwrap(); let conn = handler.get_connection(&conn_id).unwrap(); assert!(!conn.is_open()); // Still in Init state } #[test] fn test_update_block() { let handler = IbcHandler::default(); let new_height = Height::from_height(100); let new_time = Timestamp::from_seconds(1000); handler.update_block(new_height, new_time); assert_eq!(handler.current_height(), new_height); assert_eq!(handler.current_timestamp(), new_time); } }