diff --git a/shared/src/ledger/ibc/handler.rs b/shared/src/ledger/ibc/handler.rs index 0759077e22..23f4f224c2 100644 --- a/shared/src/ledger/ibc/handler.rs +++ b/shared/src/ledger/ibc/handler.rs @@ -675,9 +675,17 @@ pub trait IbcActions { msg: &MsgRecvPacket, ) -> std::result::Result<(), Self::Error> { // check the packet data - if let Ok(data) = serde_json::from_slice(&msg.packet.data) { - self.receive_token(&msg.packet, &data)?; - } + let packet_ack = + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + match self.receive_token(&msg.packet, &data) { + Ok(_) => PacketAck::result_success(), + Err(_) => PacketAck::result_error( + "receiving a token failed".to_string(), + ), + } + } else { + PacketAck::result_error("unknown packet data".to_string()) + }; // store the receipt let receipt_key = storage::receipt_key( @@ -693,7 +701,7 @@ pub trait IbcActions { &msg.packet.destination_channel, msg.packet.sequence, ); - let ack = PacketAck::default().encode_to_vec(); + let ack = packet_ack.encode_to_vec(); let ack_commitment = sha2::Sha256::digest(&ack).to_vec(); self.write_ibc_data(&ack_key, ack_commitment)?; @@ -718,6 +726,14 @@ pub trait IbcActions { &mut self, msg: &MsgAcknowledgement, ) -> std::result::Result<(), Self::Error> { + let ack = PacketAck::try_from(msg.acknowledgement.clone()) + .map_err(Error::IbcData)?; + if !ack.is_success() { + if let Ok(data) = serde_json::from_slice(&msg.packet.data) { + self.refund_token(&msg.packet, &data)?; + } + } + let commitment_key = storage::commitment_key( &msg.packet.source_port, &msg.packet.source_channel, @@ -725,6 +741,14 @@ pub trait IbcActions { ); self.delete_ibc_data(&commitment_key)?; + // get and increment the next sequence ack + let port_channel_id = port_channel_id( + msg.packet.source_port.clone(), + msg.packet.source_channel, + ); + let seq_key = storage::next_sequence_ack_key(&port_channel_id); + self.get_and_inc_sequence(&seq_key)?; + let event = make_ack_event(msg.packet.clone()).try_into().unwrap(); self.emit_ibc_event(event)?; @@ -917,13 +941,12 @@ pub trait IbcActions { data.sender, e )) })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::SendingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; + let token_str = data.denom.split('/').last().ok_or_else(|| { + Error::SendingToken(format!( + "No token was specified: {}", + data.denom + )) + })?; let token = Address::decode(token_str).map_err(|e| { Error::SendingToken(format!( "Invalid token address: token {}, error {}", @@ -937,13 +960,13 @@ pub trait IbcActions { )) })?; - // check the denomination field + // check the denom field let prefix = format!( "{}/{}/", msg.source_port.clone(), msg.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { + if data.denom.starts_with(&prefix) { // sink zone let burn = Address::Internal(InternalAddress::IbcBurn); self.transfer_token(&source, &burn, &token, amount)?; @@ -982,13 +1005,12 @@ pub trait IbcActions { data.receiver, e )) })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::ReceivingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; + let token_str = data.denom.split('/').last().ok_or_else(|| { + Error::ReceivingToken(format!( + "No token was specified: {}", + data.denom + )) + })?; let token = Address::decode(token_str).map_err(|e| { Error::ReceivingToken(format!( "Invalid token address: token {}, error {}", @@ -1007,7 +1029,7 @@ pub trait IbcActions { packet.source_port.clone(), packet.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { + if data.denom.starts_with(&prefix) { // unescrow the token because this chain is the source let escrow = Address::Internal(InternalAddress::ibc_escrow_address( @@ -1035,13 +1057,12 @@ pub trait IbcActions { data.sender, e )) })?; - let token_str = - data.denomination.split('/').last().ok_or_else(|| { - Error::ReceivingToken(format!( - "No token was specified: {}", - data.denomination - )) - })?; + let token_str = data.denom.split('/').last().ok_or_else(|| { + Error::ReceivingToken(format!( + "No token was specified: {}", + data.denom + )) + })?; let token = Address::decode(token_str).map_err(|e| { Error::ReceivingToken(format!( "Invalid token address: token {}, error {}", @@ -1060,7 +1081,7 @@ pub trait IbcActions { packet.source_port.clone(), packet.source_channel.clone() ); - if data.denomination.starts_with(&prefix) { + if data.denom.starts_with(&prefix) { // mint the token because the sender chain is the sink zone let mint = Address::Internal(InternalAddress::IbcMint); self.transfer_token(&mint, &dest, &token, amount)?; diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index b5dec0293c..fa500d5e43 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -1570,7 +1570,7 @@ mod tests { .write(&key, PacketReceipt::default().as_bytes().to_vec()) .expect("write failed"); let key = ack_key(&get_port_id(), &get_channel_id(), sequence); - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); write_log.write(&key, ack).expect("write failed"); let tx_code = vec![]; @@ -1648,7 +1648,7 @@ mod tests { write_log.commit_block(&mut storage).expect("commit failed"); // prepare data - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); let proof_packet = CommitmentProofBytes::try_from(vec![0]).unwrap(); let proofs = Proofs::new(proof_packet, None, None, None, Height::new(0, 1)) @@ -1837,7 +1837,7 @@ mod tests { &msg.packet.destination_channel, msg.packet.sequence, ); - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); write_log.write(&ack_key, ack).expect("write failed"); write_log.commit_tx(); @@ -1886,7 +1886,7 @@ mod tests { .expect("write failed"); let ack_key = ack_key(&get_port_id(), &get_channel_id(), Sequence::from(1)); - let ack = PacketAck::default().encode_to_vec(); + let ack = PacketAck::result_success().encode_to_vec(); write_log.write(&ack_key, ack).expect("write failed"); write_log.commit_tx(); diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index 06181bdd45..c4af113550 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -102,6 +102,9 @@ where Ics26Envelope::Ics4PacketMsg(PacketMsg::RecvPacket(msg)) => { self.validate_receiving_token(&msg.packet) } + Ics26Envelope::Ics4PacketMsg(PacketMsg::AckPacket(msg)) => { + self.validate_refunding_token(&msg.packet) + } Ics26Envelope::Ics4PacketMsg(PacketMsg::ToPacket(msg)) => { self.validate_refunding_token(&msg.packet) } @@ -121,18 +124,17 @@ where { fn validate_sending_token(&self, msg: &MsgTransfer) -> Result { let data = FungibleTokenPacketData::from(msg.clone()); - let token_str = - data.denomination.split('/').last().ok_or(Error::NoToken)?; + let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; - // check the denomination field + // check the denom field let prefix = format!( "{}/{}/", msg.source_port.clone(), msg.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let change = if data.denom.starts_with(&prefix) { // sink zone let target = Address::Internal(InternalAddress::IbcBurn); let target_key = token::balance_key(&token, &target); @@ -174,8 +176,7 @@ where let data: FungibleTokenPacketData = serde_json::from_slice(&packet.data) .map_err(Error::DecodingPacketData)?; - let token_str = - data.denomination.split('/').last().ok_or(Error::NoToken)?; + let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; @@ -184,7 +185,7 @@ where packet.source_port.clone(), packet.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let change = if data.denom.starts_with(&prefix) { // this chain is the source let source = Address::Internal(InternalAddress::ibc_escrow_address( @@ -226,18 +227,17 @@ where let data: FungibleTokenPacketData = serde_json::from_slice(&packet.data) .map_err(Error::DecodingPacketData)?; - let token_str = - data.denomination.split('/').last().ok_or(Error::NoToken)?; + let token_str = data.denom.split('/').last().ok_or(Error::NoToken)?; let token = Address::decode(token_str).map_err(Error::Address)?; let amount = Amount::from_str(&data.amount).map_err(Error::Amount)?; - // check the denomination field + // check the denom field let prefix = format!( "{}/{}/", packet.source_port.clone(), packet.source_channel.clone() ); - let change = if data.denomination.starts_with(&prefix) { + let change = if data.denom.starts_with(&prefix) { // sink zone: mint the token for the refund let source = Address::Internal(InternalAddress::IbcMint); let source_key = token::balance_key(&token, &source); diff --git a/shared/src/types/ibc/data.rs b/shared/src/types/ibc/data.rs index 4c5d5f2469..947142ab97 100644 --- a/shared/src/types/ibc/data.rs +++ b/shared/src/types/ibc/data.rs @@ -16,7 +16,9 @@ use crate::ibc::core::ics03_connection::msgs::conn_open_confirm::MsgConnectionOp use crate::ibc::core::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use crate::ibc::core::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; use crate::ibc::core::ics03_connection::msgs::ConnectionMsg; -use crate::ibc::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; +use crate::ibc::core::ics04_channel::msgs::acknowledgement::{ + Acknowledgement, MsgAcknowledgement, +}; use crate::ibc::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; use crate::ibc::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; use crate::ibc::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; @@ -32,14 +34,14 @@ use crate::ibc::core::ics26_routing::error::Error as Ics26Error; use crate::ibc::core::ics26_routing::msgs::Ics26Envelope; use crate::ibc::downcast; use crate::ibc_proto::google::protobuf::Any; -use crate::ibc_proto::ibc::core::channel::v1::acknowledgement::Response; -use crate::ibc_proto::ibc::core::channel::v1::Acknowledgement; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { #[error("Decoding IBC data error: {0}")] DecodingData(prost::DecodeError), + #[error("Decoding Json data error: {0}")] + DecodingJsonData(serde_json::Error), #[error("Decoding message error: {0}")] DecodingMessage(Ics26Error), #[error("Downcast error: {0}")] @@ -326,39 +328,70 @@ impl Default for PacketReceipt { } /// Acknowledgement for a packet -#[derive(Clone, Debug)] -pub struct PacketAck(pub Acknowledgement); +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum PacketAck { + /// Success Acknowledgement + Result(String), + /// Error Acknowledgement + Error(String), +} + +/// Success acknowledgement +const ACK_SUCCESS_B64: &str = "AQ=="; +/// Error acknowledgement +const ACK_ERR_STR: &str = + "error handling packet on destination chain: see events for details"; // TODO temporary type. add a new type for ack to ibc-rs impl PacketAck { + /// Success acknowledgement + pub fn result_success() -> Self { + Self::Result(ACK_SUCCESS_B64.to_string()) + } + + /// Acknowledgement with an error + pub fn result_error(err: String) -> Self { + Self::Error(format!("{}: {}", ACK_ERR_STR, err)) + } + + /// Check if the ack is for success + pub fn is_success(&self) -> bool { + match self { + Self::Result(_) => true, + Self::Error(_) => false, + } + } + /// Encode the ack pub fn encode_to_vec(&self) -> Vec { - serde_json::to_vec(&self.0) + serde_json::to_vec(&self) .expect("Encoding acknowledgement shouldn't fail") } } -impl Default for PacketAck { - fn default() -> Self { - Self(Acknowledgement { - response: Some(Response::Result(vec![1_u8])), - }) +impl TryFrom for PacketAck { + type Error = Error; + + fn try_from(ack: Acknowledgement) -> Result { + serde_json::from_slice(&ack.into_bytes()) + .map_err(Error::DecodingJsonData) } } // for the string to be used by the current reader impl Display for PacketAck { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", serde_json::to_string(&self.0).unwrap()) + write!(f, "{}", serde_json::to_string(&self).unwrap()) } } -// TODO temporary type. add a new type for ack to ibc-rs +// TODO temporary type. add a new type for packet data to ibc-rs /// Data to transfer a token #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct FungibleTokenPacketData { /// the token denomination to be transferred - pub denomination: String, + pub denom: String, /// the token amount to be transferred pub amount: String, /// the sender address @@ -372,7 +405,7 @@ impl From for FungibleTokenPacketData { // TODO validation let token = msg.token.unwrap(); Self { - denomination: token.denom, + denom: token.denom, amount: token.amount, sender: msg.sender.to_string(), receiver: msg.receiver.to_string(), diff --git a/shared/src/types/token.rs b/shared/src/types/token.rs index 787a8855dc..2831b532fa 100644 --- a/shared/src/types/token.rs +++ b/shared/src/types/token.rs @@ -411,11 +411,8 @@ impl TryFrom for Transfer { Address::decode(&data.sender).map_err(TransferError::Address)?; let target = Address::decode(&data.receiver).map_err(TransferError::Address)?; - let token_str = data - .denomination - .split('/') - .last() - .ok_or(TransferError::NoToken)?; + let token_str = + data.denom.split('/').last().ok_or(TransferError::NoToken)?; let token = Address::decode(token_str).map_err(TransferError::Address)?; let amount = diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 9a8ead4be7..c9f03d3450 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -64,7 +64,7 @@ use namada::ledger::tx_env::TxEnv; use namada::proto::Tx; use namada::tendermint_proto::Protobuf; use namada::types::address::{self, Address, InternalAddress}; -use namada::types::ibc::data::FungibleTokenPacketData; +use namada::types::ibc::data::{FungibleTokenPacketData, PacketAck}; use namada::types::storage::{self, BlockHash, BlockHeight}; use namada::types::token::{self, Amount}; use namada::vm::{wasm, WasmCacheRwAccess}; @@ -584,7 +584,7 @@ pub fn msg_packet_recv(packet: Packet) -> MsgRecvPacket { pub fn msg_packet_ack(packet: Packet) -> MsgAcknowledgement { MsgAcknowledgement { packet, - acknowledgement: vec![0].into(), + acknowledgement: PacketAck::result_success().encode_to_vec().into(), proofs: dummy_proofs(), signer: Signer::new("test"), } @@ -601,7 +601,7 @@ pub fn received_packet( let timeout_timestamp = (Timestamp::now() + Duration::from_secs(100)).unwrap(); let data = FungibleTokenPacketData { - denomination: token, + denom: token, amount: 100u64.to_string(), sender: address::testing::gen_established_address().to_string(), receiver: receiver.to_string(), diff --git a/wasm/checksums.json b/wasm/checksums.json index 01140354ba..6037526d05 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,18 +1,18 @@ { - "tx_bond.wasm": "tx_bond.38c037a51f9215c2be9c1b01f647251ffdc96a02a0c958c5d3db4ee36ccde43b.wasm", - "tx_ibc.wasm": "tx_ibc.5f86477029d987073ebfec66019dc991b0bb8b80717d4885b860f910916cbcdd.wasm", - "tx_init_account.wasm": "tx_init_account.8d901bce15d1ab63a591def00421183a651d4d5e09ace4291bf0a9044692741d.wasm", - "tx_init_nft.wasm": "tx_init_nft.1991808f44c1c24d4376a3d46b602bed27575f6c0359095c53f37b9225050ffc.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.716cd08d59b26bd75815511f03e141e6ac27bc0b7d7be10a71b04559244722c2.wasm", - "tx_init_validator.wasm": "tx_init_validator.611edff2746f71cdaa7547a84a96676b555821f00af8375a28f8dab7ae9fc9fa.wasm", - "tx_mint_nft.wasm": "tx_mint_nft.3f20f1a86da43cc475ccc127428944bd177d40fbe2d2d1588c6fadd069cbe4b2.wasm", - "tx_transfer.wasm": "tx_transfer.5653340103a32e6685f9668ec24855f65ae17bcc43035c2559a13f5c47bb67af.wasm", - "tx_unbond.wasm": "tx_unbond.71e66ac6f792123a2aaafd60b3892d74a7d0e7a03c3ea34f15fea9089010b810.wasm", - "tx_update_vp.wasm": "tx_update_vp.6d291dadb43545a809ba33fe26582b7984c67c65f05e363a93dbc62e06a33484.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.ff3def7b4bb0c46635bd6d544ac1745362757ce063feb8142d2ed9ab207f2a12.wasm", - "tx_withdraw.wasm": "tx_withdraw.ba1a743cf8914a353d7706777e0b1a37e20cd271b16e022fd3b50ad28971291f.wasm", - "vp_nft.wasm": "vp_nft.4471284b5c5f3e28c973f0a2ad2dde52ebe4a1dcd5dc15e93b380706fd0e35ea.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.7d7eb09cddc7ae348417da623e21ec4a4f8c78f15ae12de5abe7087eeab1e0db.wasm", - "vp_token.wasm": "vp_token.4a5436f7519de15c80103557add57e8d06e766e1ec1f7a642ffca252be01c5d0.wasm", - "vp_user.wasm": "vp_user.729b18aab60e8ae09b75b5f067658f30459a5ccfcd34f909b88da96523681019.wasm" + "tx_bond.wasm": "tx_bond.625486ac966bb5b3c4d1ffdd8791525a599309cd25db0952dd47c380353a5c9e.wasm", + "tx_ibc.wasm": "tx_ibc.bf644196888f53514c7abb40ed10796495d7a8995e6f6afcbcbf306a9b4f162f.wasm", + "tx_init_account.wasm": "tx_init_account.69223aac4969585d38ffeb52892a6d03d22d2109ac96bd7303c73469702dc2e3.wasm", + "tx_init_nft.wasm": "tx_init_nft.46a65c572e9e2c0fe775dd10731e06dae8efcac705d4ae036248ba5b2a6308a8.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.843a9a82f03e7a9b47e83a3821a27695d8a2b54a99c21d59aca63a0428ea5c74.wasm", + "tx_init_validator.wasm": "tx_init_validator.602b9ca6d94081bfe4e4dd2e1c2cfcd60b9cc781367f490da869069efb61eb3d.wasm", + "tx_mint_nft.wasm": "tx_mint_nft.2682d8d347511cd94cc1c7846f059e11128d07ca383932ded313ef0fbd6a1d86.wasm", + "tx_transfer.wasm": "tx_transfer.8d633c7b64c5e359a382a49d3f119ae2d3f24cc8885b6e9a13e8e59b3b519804.wasm", + "tx_unbond.wasm": "tx_unbond.d6c5059b086d8157362457e4e68738be43116a0234f35f22e09da1375884a089.wasm", + "tx_update_vp.wasm": "tx_update_vp.92521511f70a211f5a828f2e30598dc8aefe31ee350ce9793d3b5f11581153d4.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ba9d56edd22af26223a7b3e2c0490b41ed602d1596de00e2448ae08893f5e8eb.wasm", + "tx_withdraw.wasm": "tx_withdraw.4e37765b4550aac6e57485d3fcfd1c920dd517c49eb5a1f47621ce96e4902285.wasm", + "vp_nft.wasm": "vp_nft.7f2fb73bab5b185e7dc68a03d4e3f357f851a89177bee80d396d450550ce6ddd.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.39dc6d8fecfa6122a458373fe1ae77ea3499aa3b725ef3dee302c3919ae5fb70.wasm", + "vp_token.wasm": "vp_token.46caf3e8ab02bedc79003f06e4183654b1ee63a06ca44abaa5f5eff491e6fc31.wasm", + "vp_user.wasm": "vp_user.b899008d3b67d5d98230b0a51285a20a45b18f32c9b59d5328fdf5fd2683e57f.wasm" } \ No newline at end of file