synor/crates/synor-ibc/src/handler.rs

685 lines
21 KiB
Rust

//! 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<RwLock<LightClient>>,
/// Connection manager
connections: Arc<RwLock<ConnectionManager>>,
/// Channel manager
channels: Arc<RwLock<ChannelManager>>,
/// Packet handler
packets: Arc<RwLock<PacketHandler>>,
/// Current block height
current_height: Arc<RwLock<Height>>,
/// Current block timestamp
current_timestamp: Arc<RwLock<Timestamp>>,
}
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<ClientId> {
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<ClientState> {
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<Version>,
) -> IbcResult<ConnectionId> {
// 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<Version>,
proof_init: Vec<u8>,
proof_height: Height,
) -> IbcResult<ConnectionId> {
// 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<u8>,
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<u8>,
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<ConnectionEnd> {
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<ChannelId> {
// 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<u8>,
proof_height: Height,
) -> IbcResult<ChannelId> {
// 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<u8>,
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<u8>,
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<Channel> {
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<u8>,
timeout: Timeout,
) -> IbcResult<u64> {
// 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<u8>,
_proof_height: Height,
) -> IbcResult<Acknowledgement> {
// 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<u8>,
_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<u8>,
_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<u64> {
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);
}
}