diff --git a/.changelog/unreleased/improvements/3317-store-ibc-trace-when-minting.md b/.changelog/unreleased/improvements/3317-store-ibc-trace-when-minting.md new file mode 100644 index 0000000000..7622b65086 --- /dev/null +++ b/.changelog/unreleased/improvements/3317-store-ibc-trace-when-minting.md @@ -0,0 +1,2 @@ +- Store IBC denom when minting the IBC token + ([\#3317](https://github.com/anoma/namada/issues/3317)) \ No newline at end of file diff --git a/crates/ibc/src/actions.rs b/crates/ibc/src/actions.rs index 24a13a35bf..4676721c49 100644 --- a/crates/ibc/src/actions.rs +++ b/crates/ibc/src/actions.rs @@ -13,7 +13,7 @@ use namada_core::ibc::core::channel::types::timeout::TimeoutHeight; use namada_core::ibc::MsgTransfer; use namada_core::tendermint::Time as TmTime; use namada_core::token::Amount; -use namada_events::{EmitEvents, EventTypeBuilder}; +use namada_events::EmitEvents; use namada_governance::storage::proposal::PGFIbcTarget; use namada_parameters::read_epoch_duration_parameter; use namada_state::{ @@ -124,23 +124,6 @@ where Ok(()) } - /// Get IBC events - fn get_ibc_events( - &self, - event_type: impl AsRef, - ) -> Result, StorageError> { - let event_type = EventTypeBuilder::new_of::() - .with_segment(event_type) - .build(); - - Ok(self - .state - .write_log() - .lookup_events_with_prefix(&event_type) - .filter_map(|event| IbcEvent::try_from(event).ok()) - .collect()) - } - /// Transfer token fn transfer_token( &mut self, diff --git a/crates/ibc/src/context/nft_transfer.rs b/crates/ibc/src/context/nft_transfer.rs index ef06b05ed7..2ac019bf6f 100644 --- a/crates/ibc/src/context/nft_transfer.rs +++ b/crates/ibc/src/context/nft_transfer.rs @@ -89,6 +89,26 @@ where .store_withdraw(token, added_withdraw) .map_err(NftTransferError::from) } + + fn store_ibc_trace( + &self, + owner: &Address, + class_id: &PrefixedClassId, + token_id: &TokenId, + ) -> Result<(), NftTransferError> { + let ibc_trace = format!("{class_id}/{token_id}"); + let trace_hash = storage::calc_hash(&ibc_trace); + + self.inner + .borrow_mut() + .store_ibc_trace(owner.to_string(), &trace_hash, &ibc_trace) + .map_err(NftTransferError::from)?; + + self.inner + .borrow_mut() + .store_ibc_trace(token_id, &trace_hash, &ibc_trace) + .map_err(NftTransferError::from) + } } impl NftTransferValidationContext for NftTransferContext @@ -342,6 +362,10 @@ where self.update_mint_amount(&ibc_token, true)?; self.add_deposit(&ibc_token)?; + // Store the IBC trace with the token hash to be able to retrieve it + // later + self.store_ibc_trace(account, class_id, token_id)?; + self.inner .borrow_mut() .mint_token(account, &ibc_token, Amount::from_u64(1)) diff --git a/crates/ibc/src/context/storage.rs b/crates/ibc/src/context/storage.rs index 4db283e567..a33b2621fd 100644 --- a/crates/ibc/src/context/storage.rs +++ b/crates/ibc/src/context/storage.rs @@ -12,12 +12,6 @@ pub trait IbcStorageContext: StorageRead + StorageWrite { /// Emit an IBC event fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Error>; - /// Get IBC events - fn get_ibc_events( - &self, - event_type: impl AsRef, - ) -> Result, Error>; - /// Transfer token fn transfer_token( &mut self, diff --git a/crates/ibc/src/context/token_transfer.rs b/crates/ibc/src/context/token_transfer.rs index 94410c5673..1fc9700f93 100644 --- a/crates/ibc/src/context/token_transfer.rs +++ b/crates/ibc/src/context/token_transfer.rs @@ -141,6 +141,32 @@ where .store_withdraw(token, added_withdraw) .map_err(TokenTransferError::from) } + + fn maybe_store_ibc_denom( + &self, + owner: &Address, + coin: &PrefixedCoin, + ) -> Result<(), TokenTransferError> { + if coin.denom.trace_path.is_empty() { + // It isn't an IBC denom + return Ok(()); + } + let ibc_denom = coin.denom.to_string(); + let trace_hash = storage::calc_hash(&ibc_denom); + + self.inner + .borrow_mut() + .store_ibc_trace(owner.to_string(), &trace_hash, &ibc_denom) + .map_err(TokenTransferError::from)?; + + let base_token = Address::decode(coin.denom.base_denom.as_str()) + .map(|a| a.to_string()) + .unwrap_or(coin.denom.base_denom.to_string()); + self.inner + .borrow_mut() + .store_ibc_trace(base_token, &trace_hash, &ibc_denom) + .map_err(TokenTransferError::from) + } } impl TokenTransferValidationContext for TokenTransferContext @@ -277,6 +303,10 @@ where self.insert_verifier(&ibc_token); } + // Store the IBC denom with the token hash to be able to retrieve it + // later + self.maybe_store_ibc_denom(account, coin)?; + self.inner .borrow_mut() .mint_token(account, &ibc_token, amount) diff --git a/crates/ibc/src/event.rs b/crates/ibc/src/event.rs index 7bd613e881..5e19292686 100644 --- a/crates/ibc/src/event.rs +++ b/crates/ibc/src/event.rs @@ -19,7 +19,7 @@ use namada_core::ibc::core::host::types::identifiers::{ use namada_core::ibc::primitives::Timestamp; use namada_core::tendermint::abci::Event as AbciEvent; use namada_events::extend::{ - event_domain_of, AttributesMap, EventAttributeEntry, ExtendAttributesMap, + event_domain_of, AttributesMap, EventAttributeEntry, ReadFromEventAttributes as _, }; use namada_events::{ @@ -140,63 +140,6 @@ pub struct IbcEvent { pub attributes: HashMap, } -fn validate_ibc_event_type( - namada_event: &Event, -) -> Result { - if namada_event.kind().domain() != IbcEvent::DOMAIN { - return Err(EventError::InvalidEventType); - } - - let event_type = namada_event.kind().sub_domain(); - - // TODO(namada#3229): validate IBC event types. eg: - // - // ```ignore - // if !matches!( - // event_type, - // "update_client" | "send_packet" | "write_acknowledgement" - // ) { - // return Err(EventError::InvalidEventType); - // } - // ``` - - Ok(IbcEventType(event_type.to_owned())) -} - -impl TryFrom<&Event> for IbcEvent { - type Error = EventError; - - fn try_from( - namada_event: &Event, - ) -> std::result::Result { - Ok(Self { - event_type: validate_ibc_event_type(namada_event)?, - #[allow(deprecated)] - attributes: namada_event - .attributes() - .iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - }) - } -} - -impl TryFrom for IbcEvent { - type Error = EventError; - - fn try_from(namada_event: Event) -> std::result::Result { - Ok(Self { - event_type: validate_ibc_event_type(&namada_event)?, - attributes: { - let mut attrs: HashMap<_, _> = - namada_event.into_attributes().into_iter().collect(); - attrs.with_attribute(event_domain_of::()); - attrs - }, - }) - } -} - impl std::cmp::PartialOrd for IbcEvent { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) diff --git a/crates/ibc/src/lib.rs b/crates/ibc/src/lib.rs index fc79cfd566..65be208679 100644 --- a/crates/ibc/src/lib.rs +++ b/crates/ibc/src/lib.rs @@ -39,12 +39,11 @@ pub use context::token_transfer::TokenTransferContext; pub use context::transfer_mod::{ModuleWrapper, TransferModule}; use context::IbcContext; pub use context::ValidationParams; -use namada_core::address::{Address, MASP}; +use namada_core::address::Address; use namada_core::ibc::apps::nft_transfer::handler::{ send_nft_transfer_execute, send_nft_transfer_validate, }; use namada_core::ibc::apps::nft_transfer::types::error::NftTransferError; -use namada_core::ibc::apps::nft_transfer::types::packet::PacketData as NftPacketData; use namada_core::ibc::apps::nft_transfer::types::{ is_receiver_chain_source as is_nft_receiver_chain_source, PrefixedClassId, TokenId, TracePrefix as NftTracePrefix, @@ -53,13 +52,13 @@ use namada_core::ibc::apps::transfer::handler::{ send_transfer_execute, send_transfer_validate, }; use namada_core::ibc::apps::transfer::types::error::TokenTransferError; -use namada_core::ibc::apps::transfer::types::packet::PacketData; use namada_core::ibc::apps::transfer::types::{ - is_receiver_chain_source, TracePrefix, + ack_success_b64, is_receiver_chain_source, TracePrefix, }; use namada_core::ibc::core::channel::types::acknowledgement::{ Acknowledgement, AcknowledgementStatus, }; +use namada_core::ibc::core::channel::types::commitment::compute_ack_commitment; use namada_core::ibc::core::channel::types::msgs::{ MsgRecvPacket as IbcMsgRecvPacket, PacketMsg, }; @@ -72,8 +71,6 @@ use namada_core::ibc::core::host::types::identifiers::{ChannelId, PortId}; use namada_core::ibc::core::router::types::error::RouterError; use namada_core::ibc::primitives::proto::Any; pub use namada_core::ibc::*; -use namada_core::masp::PaymentAddress; -use namada_events::extend::{ReadFromEventAttributes, Success as SuccessAttr}; use namada_token::Transfer; use prost::Message; use thiserror::Error; @@ -175,10 +172,7 @@ where MsgEnvelope::Packet(PacketMsg::Recv(msg.message.clone())); execute(&mut self.ctx, &mut self.router, envelope) .map_err(|e| Error::Context(Box::new(e)))?; - let transfer = if self.is_receiving_success()? { - // the current ibc-rs execution doesn't store the denom - // for the token hash when transfer with MsgRecvPacket - self.store_trace(&msg.message)?; + let transfer = if self.is_receiving_success(&msg.message)? { // For receiving the token to a shielded address msg.transfer.clone() } else { @@ -211,147 +205,31 @@ where IbcMessage::Envelope(envelope) => { execute(&mut self.ctx, &mut self.router, *envelope.clone()) .map_err(|e| Error::Context(Box::new(e)))?; - if let MsgEnvelope::Packet(PacketMsg::Recv(msg)) = &**envelope { - if self.is_receiving_success()? { - // the current ibc-rs execution doesn't store the denom - // for the token hash when transfer with MsgRecvPacket - self.store_trace(msg)?; - } - } Ok(None) } } } - /// Store the trace path when transfer with MsgRecvPacket - fn store_trace(&mut self, msg: &IbcMsgRecvPacket) -> Result<(), Error> { - // Get the IBC trace, and the receiver from the packet data - let minted_token_info = if let Ok(data) = - serde_json::from_slice::(&msg.packet.data) - { - let ibc_denom = received_ibc_trace( - data.token.denom.to_string(), - &msg.packet.port_id_on_a, - &msg.packet.chan_id_on_a, - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - )?; - if !ibc_denom.contains('/') { - // Skip to store it because the token has been redeemed - return Ok(()); - } - let receiver = - if data.receiver.as_ref().parse::().is_ok() { - MASP.to_string() - } else { - data.receiver.to_string() - }; - Some((vec![ibc_denom], receiver)) - } else if let Ok(data) = - serde_json::from_slice::(&msg.packet.data) - { - let ibc_traces: Result, _> = data - .token_ids - .0 - .iter() - .map(|id| { - let trace = format!("{}/{id}", data.class_id); - received_ibc_trace( - trace, - &msg.packet.port_id_on_a, - &msg.packet.chan_id_on_a, - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - ) - }) - .collect(); - let receiver = - if data.receiver.as_ref().parse::().is_ok() { - MASP.to_string() - } else { - data.receiver.to_string() - }; - Some((ibc_traces?, receiver)) - } else { - None - }; - - if let Some((ibc_traces, receiver)) = minted_token_info { - // If the trace event has the trace hash and the IBC denom or NFT - // IDs, a token has been minted. The raw IBC trace including the - // port ID, the channel ID and the base token is stored to be - // restored from the trace hash. - for ibc_trace in ibc_traces { - let trace_hash = storage::calc_hash(&ibc_trace); - self.ctx - .inner - .borrow_mut() - .store_ibc_trace(&receiver, &trace_hash, &ibc_trace) - .map_err(|e| { - Error::Trace(format!( - "Writing the IBC trace failed: {}", - e - )) - })?; - let base_token = if let Some((_, base_token)) = - is_ibc_denom(&ibc_trace) - { - base_token - } else if let Some((_, _, token_id)) = is_nft_trace(&ibc_trace) - { - token_id - } else { - // non-prefixed denom - continue; - }; - self.ctx - .inner - .borrow_mut() - .store_ibc_trace(base_token, trace_hash, &ibc_trace) - .map_err(|e| { - Error::Trace(format!( - "Writing the IBC trace failed: {}", - e - )) - })?; - } - } - Ok(()) - } - - /// Check the result of receiving the packet from IBC events - fn is_receiving_success(&self) -> Result { - let mut receive_event = self + /// Check the result of receiving the packet by checking the packet + /// acknowledgement + fn is_receiving_success( + &self, + msg: &IbcMsgRecvPacket, + ) -> Result { + let packet_ack = self .ctx .inner .borrow() - .get_ibc_events(EVENT_TYPE_PACKET) - .map_err(|_| { - Error::Trace("Reading the IBC event failed".to_string()) - })?; - if receive_event.is_empty() { - // check the packet is for an NFT - receive_event = self - .ctx - .inner - .borrow() - .get_ibc_events(EVENT_TYPE_NFT_PACKET) - .map_err(|_| { - Error::Trace("Reading the IBC event failed".to_string()) - })?; - } - receive_event.first().as_ref().map_or_else( - || Ok(false), - |event| { - let success = SuccessAttr::read_opt_from_event_attributes( - &event.attributes, - ) - .map_err(|err| { - Error::Trace(format!("Reading the IBC event failed: {err}")) - })?; - Ok(success.unwrap_or(false)) - }, - ) + .packet_ack( + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + ) + .map_err(|e| Error::Context(Box::new(e)))?; + let success_ack_commitment = compute_ack_commitment( + &AcknowledgementStatus::success(ack_success_b64()).into(), + ); + Ok(packet_ack == success_ack_commitment) } /// Validate according to the message in IBC VP diff --git a/crates/namada/src/ledger/native_vp/ibc/context.rs b/crates/namada/src/ledger/native_vp/ibc/context.rs index ab6832c92e..0baaad84c1 100644 --- a/crates/namada/src/ledger/native_vp/ibc/context.rs +++ b/crates/namada/src/ledger/native_vp/ibc/context.rs @@ -9,8 +9,7 @@ use namada_core::storage::Epochs; use namada_gas::MEMORY_ACCESS_GAS_PER_BYTE; use namada_ibc::event::IbcEvent; use namada_ibc::{IbcCommonContext, IbcStorageContext}; -use namada_sdk::events::log::dumb_queries; -use namada_sdk::events::{Event, EventTypeBuilder}; +use namada_sdk::events::Event; use namada_state::{StateRead, StorageError, StorageRead, StorageWrite}; use namada_vp_env::VpEnv; @@ -195,28 +194,6 @@ where Ok(()) } - fn get_ibc_events( - &self, - event_type: impl AsRef, - ) -> Result> { - let matcher = dumb_queries::QueryMatcher::with_prefix( - EventTypeBuilder::new_of::() - .with_segment(event_type) - .build(), - ); - Ok(self - .event - .iter() - .filter_map(|event| { - if matcher.matches(event) { - IbcEvent::try_from(event).ok() - } else { - None - } - }) - .collect()) - } - fn transfer_token( &mut self, src: &Address, @@ -376,13 +353,6 @@ where unimplemented!("Validation doesn't emit an event") } - fn get_ibc_events( - &self, - _event_type: impl AsRef, - ) -> Result> { - unimplemented!("Validation doesn't get an event") - } - fn transfer_token( &mut self, _src: &Address, diff --git a/crates/tx_prelude/src/ibc.rs b/crates/tx_prelude/src/ibc.rs index 2a5ca7b1ca..678e27ccf8 100644 --- a/crates/tx_prelude/src/ibc.rs +++ b/crates/tx_prelude/src/ibc.rs @@ -6,7 +6,6 @@ use std::rc::Rc; use namada_core::address::Address; use namada_core::token::Amount; -use namada_events::EventTypeBuilder; pub use namada_ibc::event::{IbcEvent, IbcEventType}; pub use namada_ibc::storage::{ burn_tokens, ibc_token, is_ibc_key, mint_limit_key, mint_tokens, @@ -43,20 +42,6 @@ impl IbcStorageContext for Ctx { ::emit_event(self, event) } - fn get_ibc_events( - &self, - event_type: impl AsRef, - ) -> Result, Error> { - let event_type = EventTypeBuilder::new_of::() - .with_segment(event_type.as_ref()) - .build(); - - Ok(::get_events(self, &event_type)? - .into_iter() - .filter_map(|event| IbcEvent::try_from(event).ok()) - .collect()) - } - fn transfer_token( &mut self, src: &Address,