diff --git a/.changelog/unreleased/improvements/2062-ibc-refactoring.md b/.changelog/unreleased/improvements/2062-ibc-refactoring.md new file mode 100644 index 0000000000..f0dd83909a --- /dev/null +++ b/.changelog/unreleased/improvements/2062-ibc-refactoring.md @@ -0,0 +1,2 @@ +- refactoring IBC and remove IBC token denomination + ([\#2062](https://github.com/anoma/namada/issues/2062)) \ No newline at end of file diff --git a/core/src/ledger/ibc/context/common.rs b/core/src/ledger/ibc/context/common.rs index a6018d231d..666de8ea30 100644 --- a/core/src/ledger/ibc/context/common.rs +++ b/core/src/ledger/ibc/context/common.rs @@ -1,7 +1,7 @@ //! IbcCommonContext implementation for IBC -use borsh::BorshDeserialize; -use borsh_ext::BorshSerializeExt; +use core::time::Duration; + use prost::Message; use sha2::Digest; @@ -11,16 +11,18 @@ use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as Tm use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::consensus_state::ConsensusState; use crate::ibc::core::ics02_client::error::ClientError; +use crate::ibc::core::ics02_client::height::Height; use crate::ibc::core::ics03_connection::connection::ConnectionEnd; use crate::ibc::core::ics03_connection::error::ConnectionError; use crate::ibc::core::ics04_channel::channel::ChannelEnd; -use crate::ibc::core::ics04_channel::commitment::PacketCommitment; +use crate::ibc::core::ics04_channel::commitment::{ + AcknowledgementCommitment, PacketCommitment, +}; use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; -use crate::ibc::core::ics04_channel::packet::Sequence; +use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; -use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; -use crate::ibc::core::ics24_host::path::{ - ChannelEndPath, ClientConsensusStatePath, CommitmentPath, Path, SeqSendPath, +use crate::ibc::core::ics24_host::identifier::{ + ChannelId, ClientId, ConnectionId, PortId, }; use crate::ibc::core::timestamp::Timestamp; use crate::ibc::core::ContextError; @@ -31,9 +33,15 @@ use crate::ibc::mock::consensus_state::MockConsensusState; use crate::ibc_proto::google::protobuf::Any; use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; -use crate::types::address::Address; -use crate::types::storage::Key; -use crate::types::token; +use crate::ledger::parameters::storage::get_max_expected_time_per_block_key; +use crate::ledger::storage_api; +use crate::tendermint::Time as TmTime; +use crate::tendermint_proto::Protobuf as TmProtobuf; +use crate::types::storage::{BlockHeight, Key}; +use crate::types::time::DurationSecs; + +/// Result of IBC common function call +pub type Result = std::result::Result; /// Context to handle typical IBC data pub trait IbcCommonContext: IbcStorageContext { @@ -41,139 +49,397 @@ pub trait IbcCommonContext: IbcStorageContext { fn client_state( &self, client_id: &ClientId, - ) -> Result, ContextError> { + ) -> Result> { let key = storage::client_state_key(client_id); - match self.read(&key) { - Ok(Some(value)) => { - let any = Any::decode(&value[..]).map_err(|e| { - ContextError::ClientError(ClientError::Decode(e)) - })?; + match self.read_bytes(&key)? { + Some(value) => { + let any = + Any::decode(&value[..]).map_err(ClientError::Decode)?; self.decode_client_state(any) } - Ok(None) => Err(ContextError::ClientError( - ClientError::ClientStateNotFound { - client_id: client_id.clone(), - }, - )), - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!( - "Reading the client state failed: ID {}", - client_id, - ), - })), + None => Err(ClientError::ClientStateNotFound { + client_id: client_id.clone(), + } + .into()), } } + /// Store the ClientState + fn store_client_state( + &mut self, + client_id: &ClientId, + client_state: Box, + ) -> Result<()> { + let key = storage::client_state_key(client_id); + let bytes = client_state.encode_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) + } + + /// Decode ClientState from Any + fn decode_client_state( + &self, + client_state: Any, + ) -> Result> { + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockClientState::try_from(client_state.clone()) { + return Ok(cs.into_box()); + } + + if let Ok(cs) = TmClientState::try_from(client_state) { + return Ok(cs.into_box()); + } + + Err(ClientError::ClientSpecific { + description: "Unknown client state".to_string(), + } + .into()) + } + /// Get the ConsensusState fn consensus_state( &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result, ContextError> { - let path = Path::ClientConsensusState(client_cons_state_path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - match self.read(&key) { - Ok(Some(value)) => { - let any = Any::decode(&value[..]).map_err(|e| { - ContextError::ClientError(ClientError::Decode(e)) - })?; + client_id: &ClientId, + height: Height, + ) -> Result> { + let key = storage::consensus_state_key(client_id, height); + match self.read_bytes(&key)? { + Some(value) => { + let any = + Any::decode(&value[..]).map_err(ClientError::Decode)?; self.decode_consensus_state(any) } - Ok(None) => { - let client_id = storage::client_id(&key).expect("invalid key"); - let height = - storage::consensus_height(&key).expect("invalid key"); - Err(ContextError::ClientError( - ClientError::ConsensusStateNotFound { client_id, height }, - )) + None => Err(ClientError::ConsensusStateNotFound { + client_id: client_id.clone(), + height, } - Err(_) => Err(ContextError::ClientError(ClientError::Other { + .into()), + } + } + + /// Store the ConsensusState + fn store_consensus_state( + &mut self, + client_id: &ClientId, + height: Height, + consensus_state: Box, + ) -> Result<()> { + let key = storage::consensus_state_key(client_id, height); + let bytes = consensus_state.encode_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) + } + + /// Decode ConsensusState from Any + fn decode_consensus_state( + &self, + consensus_state: Any, + ) -> Result> { + #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] + if let Ok(cs) = MockConsensusState::try_from(consensus_state.clone()) { + return Ok(cs.into_box()); + } + + if let Ok(cs) = TmConsensusState::try_from(consensus_state) { + return Ok(cs.into_box()); + } + + Err(ClientError::ClientSpecific { + description: "Unknown consensus state".to_string(), + } + .into()) + } + + /// Decode ConsensusState from bytes + fn decode_consensus_state_value( + &self, + consensus_state: Vec, + ) -> Result> { + let any = + Any::decode(&consensus_state[..]).map_err(ClientError::Decode)?; + self.decode_consensus_state(any) + } + + /// Get the client update time + fn client_update_time(&self, client_id: &ClientId) -> Result { + let key = storage::client_update_timestamp_key(client_id); + let value = + self.read_bytes(&key)?.ok_or(ClientError::ClientSpecific { description: format!( - "Reading the consensus state failed: Key {}", - key, + "The client update time doesn't exist: ID {client_id}", ), - })), + })?; + let time = + TmTime::decode_vec(&value).map_err(|_| ClientError::Other { + description: format!( + "Decoding the client update time failed: ID {client_id}", + ), + })?; + Ok(time.into()) + } + + /// Store the client update time + fn store_update_time( + &mut self, + client_id: &ClientId, + timestamp: Timestamp, + ) -> Result<()> { + let key = storage::client_update_timestamp_key(client_id); + let time = timestamp.into_tm_time().ok_or(ClientError::Other { + description: format!( + "The client timestamp is invalid: ID {client_id}", + ), + })?; + self.write_bytes( + &key, + time.encode_vec().expect("encoding shouldn't fail"), + ) + .map_err(ContextError::from) + } + + /// Get the client update height + fn client_update_height(&self, client_id: &ClientId) -> Result { + let key = storage::client_update_height_key(client_id); + let value = self.read_bytes(&key)?.ok_or({ + ClientError::ClientSpecific { + description: format!( + "The client update height doesn't exist: ID {client_id}", + ), + } + })?; + Height::decode_vec(&value).map_err(|_| { + ClientError::Other { + description: format!( + "Decoding the client update height failed: ID {client_id}", + ), + } + .into() + }) + } + + /// Get the timestamp on this chain + fn host_timestamp(&self) -> Result { + let height = self.get_block_height()?; + let header = self.get_block_header(height)?.ok_or_else(|| { + ContextError::from(ClientError::Other { + description: "No host header".to_string(), + }) + })?; + let time = TmTime::try_from(header.time).map_err(|_| { + ContextError::ClientError(ClientError::Other { + description: "Converting to Tendermint time failed".to_string(), + }) + })?; + Ok(time.into()) + } + + /// Get the consensus state of this chain + fn host_consensus_state( + &self, + height: &Height, + ) -> Result> { + let height = BlockHeight(height.revision_height()); + let header = self.get_block_header(height)?.ok_or_else(|| { + ContextError::from(ClientError::Other { + description: "No host header".to_string(), + }) + })?; + let commitment_root = header.hash.to_vec().into(); + let time = header + .time + .try_into() + .expect("The time should be converted"); + let next_validators_hash = header + .next_validators_hash + .try_into() + .expect("The hash should be converted"); + let consensus_state = + TmConsensusState::new(commitment_root, time, next_validators_hash); + Ok(consensus_state.into_box()) + } + + /// Get the max expected time per block + fn max_expected_time_per_block(&self) -> Result { + let key = get_max_expected_time_per_block_key(); + match self.read::(&key)? { + Some(duration) => Ok(duration.into()), + None => unreachable!("The parameter should be initialized"), } } + /// Store the client update height + fn store_update_height( + &mut self, + client_id: &ClientId, + host_height: Height, + ) -> Result<()> { + let key = storage::client_update_height_key(client_id); + let bytes = host_height.encode_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) + } + /// Get the ConnectionEnd - fn connection_end( - &self, + fn connection_end(&self, conn_id: &ConnectionId) -> Result { + let key = storage::connection_key(conn_id); + let value = self.read_bytes(&key)?.ok_or( + ConnectionError::ConnectionNotFound { + connection_id: conn_id.clone(), + }, + )?; + ConnectionEnd::decode_vec(&value).map_err(|_| { + ConnectionError::Other { + description: format!( + "Decoding the connection end failed: ID {conn_id}", + ), + } + .into() + }) + } + + /// Store the ConnectionEnd + fn store_connection( + &mut self, connection_id: &ConnectionId, - ) -> Result { + connection_end: ConnectionEnd, + ) -> Result<()> { let key = storage::connection_key(connection_id); - match self.read(&key) { - Ok(Some(value)) => { - ConnectionEnd::decode_vec(&value).map_err(|_| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Decoding the connection end failed: ID {}", - connection_id, - ), - }) - }) - } - Ok(None) => Err(ContextError::ConnectionError( - ConnectionError::ConnectionNotFound { - connection_id: connection_id.clone(), - }, - )), - Err(_) => { - Err(ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Reading the connection end failed: ID {}", - connection_id, - ), - })) - } - } + let bytes = connection_end.encode_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) + } + + /// Append the connection ID to the connection list of the client + fn append_connection( + &mut self, + client_id: &ClientId, + conn_id: ConnectionId, + ) -> Result<()> { + let key = storage::client_connections_key(client_id); + let list = match self.read::(&key)? { + Some(list) => format!("{list},{conn_id}"), + None => conn_id.to_string(), + }; + self.write(&key, list).map_err(ContextError::from) } /// Get the ChannelEnd fn channel_end( &self, - channel_end_path: &ChannelEndPath, - ) -> Result { - let path = Path::ChannelEnd(channel_end_path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - match self.read(&key) { - Ok(Some(value)) => ChannelEnd::decode_vec(&value).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Decoding the channel end failed: Key {}", - key, - ), - }) - }), - Ok(None) => { - let (port_id, channel_id) = - storage::port_channel_id(&key).expect("invalid key"); - Err(ContextError::ChannelError(ChannelError::ChannelNotFound { - channel_id, - port_id, - })) - } - Err(_) => Err(ContextError::ChannelError(ChannelError::Other { + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + let key = storage::channel_key(port_id, channel_id); + let value = + self.read_bytes(&key)? + .ok_or(ChannelError::ChannelNotFound { + port_id: port_id.clone(), + channel_id: channel_id.clone(), + })?; + ChannelEnd::decode_vec(&value).map_err(|_| { + ChannelError::Other { description: format!( - "Reading the channel end failed: Key {}", - key, + "Decoding the channel end failed: Key {key}", ), - })), - } + } + .into() + }) + } + + /// Store the ChannelEnd + fn store_channel( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + channel_end: ChannelEnd, + ) -> Result<()> { + let key = storage::channel_key(port_id, channel_id); + let bytes = channel_end.encode_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) } /// Get the NextSequenceSend fn get_next_sequence_send( &self, - path: &SeqSendPath, - ) -> Result { - let path = Path::SeqSend(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + let key = storage::next_sequence_send_key(port_id, channel_id); self.read_sequence(&key) } + /// Store the NextSequenceSend + fn store_next_sequence_send( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + seq: Sequence, + ) -> Result<()> { + let key = storage::next_sequence_send_key(port_id, channel_id); + self.store_sequence(&key, seq) + } + + /// Get the NextSequenceRecv + fn get_next_sequence_recv( + &self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + let key = storage::next_sequence_recv_key(port_id, channel_id); + self.read_sequence(&key) + } + + /// Store the NextSequenceRecv + fn store_next_sequence_recv( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + seq: Sequence, + ) -> Result<()> { + let key = storage::next_sequence_recv_key(port_id, channel_id); + self.store_sequence(&key, seq) + } + + /// Get the NextSequenceAck + fn get_next_sequence_ack( + &self, + port_id: &PortId, + channel_id: &ChannelId, + ) -> Result { + let key = storage::next_sequence_ack_key(port_id, channel_id); + self.read_sequence(&key) + } + + /// Store the NextSequenceAck + fn store_next_sequence_ack( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + seq: Sequence, + ) -> Result<()> { + let key = storage::next_sequence_ack_key(port_id, channel_id); + self.store_sequence(&key, seq) + } + + /// Read a sequence + fn read_sequence(&self, key: &Key) -> Result { + match self.read_bytes(key)? { + Some(value) => { + let value: [u8; 8] = + value.try_into().map_err(|_| ChannelError::Other { + description: format!( + "The sequence value wasn't u64: Key {key}", + ), + })?; + Ok(u64::from_be_bytes(value).into()) + } + // when the sequence has never been used, returns the initial value + None => Ok(1.into()), + } + } + + /// Store the sequence + fn store_sequence(&mut self, key: &Key, sequence: Sequence) -> Result<()> { + let bytes = u64::from(sequence).to_be_bytes().to_vec(); + self.write_bytes(key, bytes).map_err(ContextError::from) + } + /// Calculate the hash fn hash(value: &[u8]) -> Vec { sha2::Sha256::digest(value).to_vec() @@ -203,160 +469,130 @@ pub trait IbcCommonContext: IbcStorageContext { Self::hash(&hash_input).into() } - /// Decode ClientState from Any - fn decode_client_state( + /// Get the packet commitment + fn packet_commitment( &self, - client_state: Any, - ) -> Result, ContextError> { - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockClientState::try_from(client_state.clone()) { - return Ok(cs.into_box()); + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + ) -> Result { + let key = storage::commitment_key(port_id, channel_id, sequence); + match self.read_bytes(&key)? { + Some(value) => Ok(value.into()), + None => { + Err(PacketError::PacketCommitmentNotFound { sequence }.into()) + } } + } - if let Ok(cs) = TmClientState::try_from(client_state) { - return Ok(cs.into_box()); - } + /// Store the packet commitment + fn store_packet_commitment( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + commitment: PacketCommitment, + ) -> Result<()> { + let key = storage::commitment_key(port_id, channel_id, sequence); + let bytes = commitment.into_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) + } - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: "Unknown client state".to_string(), - })) + /// Delete the packet commitment + fn delete_packet_commitment( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + ) -> Result<()> { + let key = storage::commitment_key(port_id, channel_id, sequence); + self.delete(&key).map_err(ContextError::from) } - /// Decode ConsensusState from Any - fn decode_consensus_state( + /// Get the packet receipt + fn packet_receipt( &self, - consensus_state: Any, - ) -> Result, ContextError> { - #[cfg(any(feature = "ibc-mocks-abcipp", feature = "ibc-mocks"))] - if let Ok(cs) = MockConsensusState::try_from(consensus_state.clone()) { - return Ok(cs.into_box()); - } - - if let Ok(cs) = TmConsensusState::try_from(consensus_state) { - return Ok(cs.into_box()); + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + ) -> Result { + let key = storage::receipt_key(port_id, channel_id, sequence); + match self.read_bytes(&key)? { + Some(_) => Ok(Receipt::Ok), + None => Err(PacketError::PacketReceiptNotFound { sequence }.into()), } - - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: "Unknown consensus state".to_string(), - })) } - /// Read a counter - fn read_counter(&self, key: &Key) -> Result { - match self.read(key) { - Ok(Some(value)) => { - let value: [u8; 8] = value.try_into().map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "The counter value wasn't u64: Key {}", - key - ), - }) - })?; - Ok(u64::from_be_bytes(value)) - } - Ok(None) => unreachable!("the counter should be initialized"), - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!("Reading the counter failed: Key {}", key), - })), - } + /// Store the packet receipt + fn store_packet_receipt( + &mut self, + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + ) -> Result<()> { + let key = storage::receipt_key(port_id, channel_id, sequence); + // the value is the same as ibc-go + let bytes = [1_u8].to_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) } - /// Read a sequence - fn read_sequence(&self, key: &Key) -> Result { - match self.read(key) { - Ok(Some(value)) => { - let value: [u8; 8] = value.try_into().map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "The counter value wasn't u64: Key {}", - key - ), - }) - })?; - Ok(u64::from_be_bytes(value).into()) - } - // when the sequence has never been used, returns the initial value - Ok(None) => Ok(1.into()), - Err(_) => { - let sequence = storage::port_channel_sequence_id(key) - .expect("The key should have sequence") - .2; - Err(ContextError::ChannelError(ChannelError::Other { - description: format!( - "Reading the next sequence send failed: Sequence {}", - sequence - ), - })) + /// Get the packet acknowledgement + fn packet_ack( + &self, + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + ) -> Result { + let key = storage::ack_key(port_id, channel_id, sequence); + match self.read_bytes(&key)? { + Some(value) => Ok(value.into()), + None => { + Err(PacketError::PacketAcknowledgementNotFound { sequence } + .into()) } } } - /// Write the packet commitment - fn store_packet_commitment( + /// Store the packet acknowledgement + fn store_packet_ack( &mut self, - path: &CommitmentPath, - commitment: PacketCommitment, - ) -> Result<(), ContextError> { - let path = Path::Commitment(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = commitment.into_vec(); - self.write(&key, bytes).map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Writing the packet commitment failed: Key {}", - key - ), - }, - )) - }) + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + ack_commitment: AcknowledgementCommitment, + ) -> Result<()> { + let key = storage::ack_key(port_id, channel_id, sequence); + let bytes = ack_commitment.into_vec(); + self.write_bytes(&key, bytes).map_err(ContextError::from) } - /// Write the NextSequenceSend - fn store_next_sequence_send( + /// Delete the packet acknowledgement + fn delete_packet_ack( &mut self, - path: &SeqSendPath, - seq: Sequence, - ) -> Result<(), ContextError> { - let path = Path::SeqSend(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.store_sequence(&key, seq) + port_id: &PortId, + channel_id: &ChannelId, + sequence: Sequence, + ) -> Result<()> { + let key = storage::ack_key(port_id, channel_id, sequence); + self.delete(&key).map_err(ContextError::from) } - /// Increment and write the counter - fn increase_counter(&mut self, key: &Key) -> Result<(), ContextError> { - let count = self.read_counter(key)?; - self.write(key, (count + 1).to_be_bytes().to_vec()) - .map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the counter failed: Key {}", - key - ), - }) - }) + /// Read a counter + fn read_counter(&self, key: &Key) -> Result { + match self.read::(key)? { + Some(counter) => Ok(counter), + None => unreachable!("the counter should be initialized"), + } } - /// Write the sequence - fn store_sequence( - &mut self, - key: &Key, - sequence: Sequence, - ) -> Result<(), ContextError> { - self.write(key, u64::from(sequence).to_be_bytes().to_vec()) - .map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Writing the counter failed: Key {}", - key - ), - }, - )) - }) + /// Increment the counter + fn increment_counter(&mut self, key: &Key) -> Result<()> { + let count = self.read_counter(key)?; + let count = + u64::checked_add(count, 1).ok_or_else(|| ClientError::Other { + description: format!("The counter overflow: Key {key}"), + })?; + self.write(key, count).map_err(ContextError::from) } /// Write the IBC denom. The given address could be a non-Namada token. @@ -365,84 +601,32 @@ pub trait IbcCommonContext: IbcStorageContext { addr: impl AsRef, trace_hash: impl AsRef, denom: impl AsRef, - ) -> Result<(), ContextError> { + ) -> Result<()> { let key = storage::ibc_denom_key(addr, trace_hash.as_ref()); - let has_key = self.has_key(&key).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Reading the IBC denom failed: Key {}", - key, - ), - }) + let has_key = self.has_key(&key).map_err(|_| ChannelError::Other { + description: format!("Reading the IBC denom failed: Key {key}"), })?; if !has_key { - let bytes = denom.as_ref().serialize_to_vec(); - self.write(&key, bytes).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { + self.write(&key, denom.as_ref()).map_err(|_| { + ChannelError::Other { description: format!( - "Writing the denom failed: Key {}", - key + "Writing the denom failed: Key {key}", ), - }) + } })?; } Ok(()) } +} - /// Read the token denom - fn read_token_denom( - &self, - token: &Address, - ) -> Result, ContextError> { - let key = token::denom_key(token); - let bytes = self.read(&key).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Reading the token denom failed: Key {}", - key - ), - }) - })?; - bytes - .map(|b| token::Denomination::try_from_slice(&b)) - .transpose() - .map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Decoding the token denom failed: Token {}", - token - ), - }) - }) - } - - /// Write the IBC denom - fn store_token_denom( - &mut self, - token: &Address, - ) -> Result<(), ContextError> { - let key = token::denom_key(token); - let has_key = self.has_key(&key).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Reading the token denom failed: Key {}", - key - ), - }) - })?; - if !has_key { - // IBC denomination should be zero for U256 - let denom = token::Denomination::from(0); - let bytes = denom.serialize_to_vec(); - self.write(&key, bytes).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Writing the token denom failed: Key {}", - key - ), - }) - })?; +/// Convert `storage_api::Error` into `ContextError`. +/// It always returns `ClientError::Other` though the storage error could happen +/// in any storage access. +impl From for ContextError { + fn from(error: storage_api::Error) -> Self { + ClientError::Other { + description: format!("Storage error: {error}"), } - Ok(()) + .into() } } diff --git a/core/src/ledger/ibc/context/execution.rs b/core/src/ledger/ibc/context/execution.rs index 0160cb1d29..05fbdb0130 100644 --- a/core/src/ledger/ibc/context/execution.rs +++ b/core/src/ledger/ibc/context/execution.rs @@ -1,33 +1,25 @@ //! ExecutionContext implementation for IBC -use borsh::BorshDeserialize; -use borsh_ext::BorshSerializeExt; - use super::super::{IbcActions, IbcCommonContext}; use crate::ibc::core::events::IbcEvent; use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::consensus_state::ConsensusState; -use crate::ibc::core::ics02_client::error::ClientError; use crate::ibc::core::ics03_connection::connection::ConnectionEnd; -use crate::ibc::core::ics03_connection::error::ConnectionError; use crate::ibc::core::ics04_channel::channel::ChannelEnd; use crate::ibc::core::ics04_channel::commitment::{ AcknowledgementCommitment, PacketCommitment, }; -use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; use crate::ibc::core::ics24_host::identifier::{ClientId, ConnectionId}; use crate::ibc::core::ics24_host::path::{ AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, - ClientStatePath, CommitmentPath, ConnectionPath, Path, ReceiptPath, - SeqAckPath, SeqRecvPath, SeqSendPath, + ClientStatePath, CommitmentPath, ConnectionPath, ReceiptPath, SeqAckPath, + SeqRecvPath, SeqSendPath, }; use crate::ibc::core::timestamp::Timestamp; -use crate::ibc::core::{ContextError, ExecutionContext, ValidationContext}; +use crate::ibc::core::{ContextError, ExecutionContext}; use crate::ibc::Height; -use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; -use crate::tendermint_proto::Protobuf as TmProtobuf; impl ExecutionContext for IbcActions<'_, C> where @@ -38,18 +30,9 @@ where client_state_path: ClientStatePath, client_state: Box, ) -> Result<(), ContextError> { - let path = Path::ClientState(client_state_path); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = client_state.encode_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the client state failed: Key {}", - key - ), - }) - }) + self.ctx + .borrow_mut() + .store_client_state(&client_state_path.0, client_state) } fn store_consensus_state( @@ -57,27 +40,24 @@ where consensus_state_path: ClientConsensusStatePath, consensus_state: Box, ) -> Result<(), ContextError> { - let path = Path::ClientConsensusState(consensus_state_path); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = consensus_state.encode_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }) + let client_id = consensus_state_path.client_id; + let height = Height::new( + consensus_state_path.epoch, + consensus_state_path.height, + )?; + self.ctx.borrow_mut().store_consensus_state( + &client_id, + height, + consensus_state, + ) } fn increase_client_counter(&mut self) { let key = storage::client_counter_key(); - let count = self.client_counter().expect("read failed"); self.ctx .borrow_mut() - .write(&key, (count + 1).to_be_bytes().to_vec()) - .expect("write failed"); + .increment_counter(&key) + .expect("Error cannot be returned"); } fn store_update_time( @@ -86,30 +66,9 @@ where _height: Height, timestamp: Timestamp, ) -> Result<(), ContextError> { - let key = storage::client_update_timestamp_key(&client_id); - match timestamp.into_tm_time() { - Some(time) => self - .ctx - .borrow_mut() - .write( - &key, - time.encode_vec().expect("encoding shouldn't fail"), - ) - .map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }), - None => Err(ContextError::ClientError(ClientError::Other { - description: format!( - "The client timestamp is invalid: ID {}", - client_id - ), - })), - } + self.ctx + .borrow_mut() + .store_update_time(&client_id, timestamp) } fn store_update_height( @@ -118,16 +77,9 @@ where _height: Height, host_height: Height, ) -> Result<(), ContextError> { - let key = storage::client_update_height_key(&client_id); - let bytes = host_height.encode_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Writing the consensus state failed: Key {}", - key - ), - }) - }) + self.ctx + .borrow_mut() + .store_update_height(&client_id, host_height) } fn store_connection( @@ -135,18 +87,9 @@ where connection_path: &ConnectionPath, connection_end: ConnectionEnd, ) -> Result<(), ContextError> { - let path = Path::Connection(connection_path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = connection_end.encode_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Writing the connection end failed: Key {}", - key - ), - }) - }) + self.ctx + .borrow_mut() + .store_connection(&connection_path.0, connection_end) } fn store_connection_to_client( @@ -154,48 +97,16 @@ where client_connection_path: &ClientConnectionPath, conn_id: ConnectionId, ) -> Result<(), ContextError> { - let path = Path::ClientConnection(client_connection_path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let list = match self.ctx.borrow().read(&key) { - Ok(Some(value)) => { - let list = String::try_from_slice(&value).map_err(|e| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Decoding the connection list failed: Key {}, \ - error {}", - key, e - ), - }) - })?; - format!("{},{}", list, conn_id) - } - Ok(None) => conn_id.to_string(), - Err(_) => { - Err(ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Reading the connection list of failed: Key {}", - key, - ), - }))? - } - }; - let bytes = list.serialize_to_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::ConnectionError(ConnectionError::Other { - description: format!( - "Writing the list of connection IDs failed: Key {}", - key - ), - }) - }) + self.ctx + .borrow_mut() + .append_connection(&client_connection_path.0, conn_id) } fn increase_connection_counter(&mut self) { let key = storage::connection_counter_key(); self.ctx .borrow_mut() - .increase_counter(&key) + .increment_counter(&key) .expect("Error cannot be returned"); } @@ -204,28 +115,23 @@ where path: &CommitmentPath, commitment: PacketCommitment, ) -> Result<(), ContextError> { - self.ctx - .borrow_mut() - .store_packet_commitment(path, commitment) + self.ctx.borrow_mut().store_packet_commitment( + &path.port_id, + &path.channel_id, + path.sequence, + commitment, + ) } fn delete_packet_commitment( &mut self, path: &CommitmentPath, ) -> Result<(), ContextError> { - let path = Path::Commitment(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.ctx.borrow_mut().delete(&key).map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Deleting the packet commitment failed: Key {}", - key - ), - }, - )) - }) + self.ctx.borrow_mut().delete_packet_commitment( + &path.port_id, + &path.channel_id, + path.sequence, + ) } fn store_packet_receipt( @@ -233,21 +139,11 @@ where path: &ReceiptPath, _receipt: Receipt, ) -> Result<(), ContextError> { - let path = Path::Receipt(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - // the value is the same as ibc-go - let bytes = [1_u8].to_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Writing the receipt failed: Key {}", - key - ), - }, - )) - }) + self.ctx.borrow_mut().store_packet_receipt( + &path.port_id, + &path.channel_id, + path.sequence, + ) } fn store_packet_acknowledgement( @@ -255,39 +151,23 @@ where path: &AckPath, ack_commitment: AcknowledgementCommitment, ) -> Result<(), ContextError> { - let path = Path::Ack(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = ack_commitment.into_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Writing the packet ack failed: Key {}", - key - ), - }, - )) - }) + self.ctx.borrow_mut().store_packet_ack( + &path.port_id, + &path.channel_id, + path.sequence, + ack_commitment, + ) } fn delete_packet_acknowledgement( &mut self, path: &AckPath, ) -> Result<(), ContextError> { - let path = Path::Ack(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.ctx.borrow_mut().delete(&key).map_err(|_| { - ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Deleting the packet ack failed: Key {}", - key - ), - }, - )) - }) + self.ctx.borrow_mut().delete_packet_ack( + &path.port_id, + &path.channel_id, + path.sequence, + ) } fn store_channel( @@ -295,18 +175,9 @@ where path: &ChannelEndPath, channel_end: ChannelEnd, ) -> Result<(), ContextError> { - let path = Path::ChannelEnd(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - let bytes = channel_end.encode_vec(); - self.ctx.borrow_mut().write(&key, bytes).map_err(|_| { - ContextError::ChannelError(ChannelError::Other { - description: format!( - "Writing the channel end failed: Key {}", - key - ), - }) - }) + self.ctx + .borrow_mut() + .store_channel(&path.0, &path.1, channel_end) } fn store_next_sequence_send( @@ -314,7 +185,9 @@ where path: &SeqSendPath, seq: Sequence, ) -> Result<(), ContextError> { - self.ctx.borrow_mut().store_next_sequence_send(path, seq) + self.ctx + .borrow_mut() + .store_next_sequence_send(&path.0, &path.1, seq) } fn store_next_sequence_recv( @@ -322,10 +195,9 @@ where path: &SeqRecvPath, seq: Sequence, ) -> Result<(), ContextError> { - let path = Path::SeqRecv(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.ctx.borrow_mut().store_sequence(&key, seq) + self.ctx + .borrow_mut() + .store_next_sequence_recv(&path.0, &path.1, seq) } fn store_next_sequence_ack( @@ -333,17 +205,16 @@ where path: &SeqAckPath, seq: Sequence, ) -> Result<(), ContextError> { - let path = Path::SeqAck(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.ctx.borrow_mut().store_sequence(&key, seq) + self.ctx + .borrow_mut() + .store_next_sequence_ack(&path.0, &path.1, seq) } fn increase_channel_counter(&mut self) { let key = storage::channel_counter_key(); self.ctx .borrow_mut() - .increase_counter(&key) + .increment_counter(&key) .expect("Error cannot be returned"); } diff --git a/core/src/ledger/ibc/context/storage.rs b/core/src/ledger/ibc/context/storage.rs index ef24cd94f2..facbb2a5c2 100644 --- a/core/src/ledger/ibc/context/storage.rs +++ b/core/src/ledger/ibc/context/storage.rs @@ -1,65 +1,22 @@ //! IBC storage context -use std::fmt::Debug; - pub use ics23::ProofSpec; -use super::super::Error; -use crate::ledger::storage_api; +use crate::ledger::storage_api::{Error, StorageRead, StorageWrite}; use crate::types::address::Address; use crate::types::ibc::{IbcEvent, IbcShieldedTransfer}; -use crate::types::storage::{BlockHeight, Header, Key}; use crate::types::token::DenominatedAmount; -// This is needed to use `ibc::Handler::Error` with `IbcActions` in -// `tx_prelude/src/ibc.rs` -impl From for storage_api::Error { - fn from(err: Error) -> Self { - storage_api::Error::new(err) - } -} - /// IBC context trait to be implemented in integration that can read and write -pub trait IbcStorageContext { - /// IBC storage error - type Error: From + Debug; - /// Storage read prefix iterator - type PrefixIter<'iter> - where - Self: 'iter; - - /// Read IBC-related data - fn read(&self, key: &Key) -> Result>, Self::Error>; - - /// Check if the given key is present - fn has_key(&self, key: &Key) -> Result; - - /// Read IBC-related data with a prefix - fn iter_prefix<'iter>( - &'iter self, - prefix: &Key, - ) -> Result, Self::Error>; - - /// next key value pair - fn iter_next<'iter>( - &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error>; - - /// Write IBC-related data - fn write(&mut self, key: &Key, value: Vec) -> Result<(), Self::Error>; - - /// Delete IBC-related data - fn delete(&mut self, key: &Key) -> Result<(), Self::Error>; - +pub trait IbcStorageContext: StorageRead + StorageWrite { /// Emit an IBC event - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error>; + fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Error>; /// Get IBC events fn get_ibc_events( &self, event_type: impl AsRef, - ) -> Result, Self::Error>; + ) -> Result, Error>; /// Transfer token fn transfer_token( @@ -68,13 +25,13 @@ pub trait IbcStorageContext { dest: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error>; + ) -> Result<(), Error>; /// Handle masp tx fn handle_masp_tx( &mut self, shielded: &IbcShieldedTransfer, - ) -> Result<(), Self::Error>; + ) -> Result<(), Error>; /// Mint token fn mint_token( @@ -82,7 +39,7 @@ pub trait IbcStorageContext { target: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error>; + ) -> Result<(), Error>; /// Burn token fn burn_token( @@ -90,16 +47,7 @@ pub trait IbcStorageContext { target: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error>; - - /// Get the current height of this chain - fn get_height(&self) -> Result; - - /// Get the block header of this chain - fn get_header( - &self, - height: BlockHeight, - ) -> Result, Self::Error>; + ) -> Result<(), Error>; /// Logging fn log_string(&self, message: String); diff --git a/core/src/ledger/ibc/context/transfer_mod.rs b/core/src/ledger/ibc/context/transfer_mod.rs index 28a24f72e0..9c56e93369 100644 --- a/core/src/ledger/ibc/context/transfer_mod.rs +++ b/core/src/ledger/ibc/context/transfer_mod.rs @@ -45,8 +45,9 @@ use crate::ibc::core::ics24_host::path::{ }; use crate::ibc::core::router::{Module, ModuleExtras, ModuleId}; use crate::ibc::core::ContextError; -use crate::ibc::Signer; +use crate::ibc::{Height, Signer}; use crate::ledger::ibc::storage; +use crate::ledger::storage_api::token::read_denom; use crate::types::address::{Address, InternalAddress}; use crate::types::token; use crate::types::uint::Uint; @@ -96,22 +97,20 @@ where }; // Convert IBC amount to Namada amount for the token - let denom = self - .ctx - .borrow() - .read_token_denom(&token)? + let denom = read_denom(&*self.ctx.borrow(), &token) + .map_err(ContextError::from)? .unwrap_or(token::Denomination(0)); let uint_amount = Uint(primitive_types::U256::from(coin.amount).0); let amount = token::Amount::from_uint(uint_amount, denom).map_err(|e| { - TokenTransferError::ContextError(ContextError::ChannelError( + TokenTransferError::ContextError( ChannelError::Other { description: format!( - "The IBC amount is invalid: Coin {}, Error {}", - coin, e + "The IBC amount is invalid: Coin {coin}, Error {e}", ), - }, - )) + } + .into(), + ) })?; let amount = token::DenominatedAmount { amount, denom }; @@ -375,7 +374,9 @@ where &self, channel_end_path: &ChannelEndPath, ) -> Result { - self.ctx.borrow().channel_end(channel_end_path) + self.ctx + .borrow() + .channel_end(&channel_end_path.0, &channel_end_path.1) } fn connection_end( @@ -396,14 +397,22 @@ where &self, client_cons_state_path: &ClientConsensusStatePath, ) -> Result, ContextError> { - self.ctx.borrow().consensus_state(client_cons_state_path) + let height = Height::new( + client_cons_state_path.epoch, + client_cons_state_path.height, + )?; + self.ctx + .borrow() + .consensus_state(&client_cons_state_path.client_id, height) } fn get_next_sequence_send( &self, seq_send_path: &SeqSendPath, ) -> Result { - self.ctx.borrow().get_next_sequence_send(seq_send_path) + self.ctx + .borrow() + .get_next_sequence_send(&seq_send_path.0, &seq_send_path.1) } } @@ -483,16 +492,7 @@ where self.ctx .borrow_mut() .transfer_token(from, to, &ibc_token, amount) - .map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Sending a coin failed: from {}, to {}, amount {}", - from, to, amount, - ), - }, - )) - }) + .map_err(|e| ContextError::from(e).into()) } fn mint_coins_execute( @@ -506,16 +506,7 @@ where self.ctx .borrow_mut() .mint_token(account, &ibc_token, amount) - .map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Minting a coin failed: account {}, amount {}", - account, amount, - ), - }, - )) - }) + .map_err(|e| ContextError::from(e).into()) } fn burn_coins_execute( @@ -529,16 +520,7 @@ where self.ctx .borrow_mut() .burn_token(account, &ibc_token, amount) - .map_err(|_| { - TokenTransferError::ContextError(ContextError::ChannelError( - ChannelError::Other { - description: format!( - "Burning a coin failed: account {}, amount {}", - account, amount, - ), - }, - )) - }) + .map_err(|e| ContextError::from(e).into()) } } @@ -551,9 +533,11 @@ where seq_send_path: &SeqSendPath, seq: Sequence, ) -> Result<(), ContextError> { - self.ctx - .borrow_mut() - .store_next_sequence_send(seq_send_path, seq) + self.ctx.borrow_mut().store_next_sequence_send( + &seq_send_path.0, + &seq_send_path.1, + seq, + ) } fn store_packet_commitment( @@ -561,9 +545,12 @@ where commitment_path: &CommitmentPath, commitment: PacketCommitment, ) -> Result<(), ContextError> { - self.ctx - .borrow_mut() - .store_packet_commitment(commitment_path, commitment) + self.ctx.borrow_mut().store_packet_commitment( + &commitment_path.port_id, + &commitment_path.channel_id, + commitment_path.sequence, + commitment, + ) } fn emit_ibc_event(&mut self, event: IbcEvent) { diff --git a/core/src/ledger/ibc/context/validation.rs b/core/src/ledger/ibc/context/validation.rs index 9ad3dd2654..21144c7f48 100644 --- a/core/src/ledger/ibc/context/validation.rs +++ b/core/src/ledger/ibc/context/validation.rs @@ -1,18 +1,13 @@ //! ValidationContext implementation for IBC -use prost::Message; - use super::super::{IbcActions, IbcCommonContext}; -use crate::ibc::clients::ics07_tendermint::consensus_state::ConsensusState as TmConsensusState; use crate::ibc::core::ics02_client::client_state::ClientState; use crate::ibc::core::ics02_client::consensus_state::ConsensusState; -use crate::ibc::core::ics02_client::error::ClientError; use crate::ibc::core::ics03_connection::connection::ConnectionEnd; use crate::ibc::core::ics04_channel::channel::ChannelEnd; use crate::ibc::core::ics04_channel::commitment::{ AcknowledgementCommitment, PacketCommitment, }; -use crate::ibc::core::ics04_channel::error::{ChannelError, PacketError}; use crate::ibc::core::ics04_channel::packet::{Receipt, Sequence}; use crate::ibc::core::ics23_commitment::commitment::CommitmentPrefix; use crate::ibc::core::ics23_commitment::specs::ProofSpecs; @@ -20,7 +15,7 @@ use crate::ibc::core::ics24_host::identifier::{ ChainId, ClientId, ConnectionId, }; use crate::ibc::core::ics24_host::path::{ - AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, Path, + AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use crate::ibc::core::timestamp::Timestamp; @@ -30,13 +25,8 @@ use crate::ibc::hosts::tendermint::ValidateSelfClientContext; use crate::ibc::mock::client_state::MockClientState; use crate::ibc::{Height, Signer}; use crate::ibc_proto::google::protobuf::Any; -use crate::ibc_proto::protobuf::Protobuf; use crate::ledger::ibc::storage; -use crate::ledger::parameters::storage::get_max_expected_time_per_block_key; -use crate::tendermint::Time as TmTime; -use crate::tendermint_proto::Protobuf as TmProtobuf; -use crate::types::storage::{BlockHeight, Key}; -use crate::types::time::DurationSecs; +use crate::types::storage::Key; const COMMITMENT_PREFIX: &[u8] = b"ibc"; @@ -62,7 +52,13 @@ where &self, client_cons_state_path: &ClientConsensusStatePath, ) -> Result, ContextError> { - self.ctx.borrow().consensus_state(client_cons_state_path) + let height = Height::new( + client_cons_state_path.epoch, + client_cons_state_path.height, + )?; + self.ctx + .borrow() + .consensus_state(&client_cons_state_path.client_id, height) } fn next_consensus_state( @@ -71,27 +67,11 @@ where height: &Height, ) -> Result>, ContextError> { let prefix = storage::consensus_state_prefix(client_id); - // for iterator + // or iterator let ctx = self.ctx.borrow(); - let mut iter = ctx.iter_prefix(&prefix).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Reading the consensus state failed: ID {}, height {}", - client_id, height, - ), - }) - })?; + let mut iter = ctx.iter_prefix(&prefix)?; let mut lowest_height_value = None; - while let Some((key, value)) = - ctx.iter_next(&mut iter).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Iterating consensus states failed: ID {}, height {}", - client_id, height, - ), - }) - })? - { + while let Some((key, value)) = ctx.iter_next(&mut iter)? { let key = Key::parse(key).expect("the key should be parsable"); let consensus_height = storage::consensus_height(&key) .expect("the key should have a height"); @@ -105,16 +85,9 @@ where }; } } - match lowest_height_value { - Some((_, value)) => { - let any = Any::decode(&value[..]).map_err(|e| { - ContextError::ClientError(ClientError::Decode(e)) - })?; - let cs = self.ctx.borrow().decode_consensus_state(any)?; - Ok(Some(cs)) - } - None => Ok(None), - } + lowest_height_value + .map(|(_, value)| ctx.decode_consensus_state_value(value)) + .transpose() } fn prev_consensus_state( @@ -125,25 +98,9 @@ where let prefix = storage::consensus_state_prefix(client_id); // for iterator let ctx = self.ctx.borrow(); - let mut iter = ctx.iter_prefix(&prefix).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Reading the consensus state failed: ID {}, height {}", - client_id, height, - ), - }) - })?; + let mut iter = ctx.iter_prefix(&prefix)?; let mut highest_height_value = None; - while let Some((key, value)) = - ctx.iter_next(&mut iter).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Iterating consensus states failed: ID {}, height {}", - client_id, height, - ), - }) - })? - { + while let Some((key, value)) = ctx.iter_next(&mut iter)? { let key = Key::parse(key).expect("the key should be parsable"); let consensus_height = storage::consensus_height(&key) .expect("the key should have the height"); @@ -157,87 +114,26 @@ where }; } } - match highest_height_value { - Some((_, value)) => { - let any = Any::decode(&value[..]).map_err(|e| { - ContextError::ClientError(ClientError::Decode(e)) - })?; - let cs = self.ctx.borrow().decode_consensus_state(any)?; - Ok(Some(cs)) - } - None => Ok(None), - } + highest_height_value + .map(|(_, value)| ctx.decode_consensus_state_value(value)) + .transpose() } fn host_height(&self) -> Result { - let height = self.ctx.borrow().get_height().map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: "Getting the host height failed".to_string(), - }) - })?; + let height = self.ctx.borrow().get_block_height()?; // the revision number is always 0 Height::new(0, height.0).map_err(ContextError::ClientError) } fn host_timestamp(&self) -> Result { - let height = self.host_height()?; - let height = BlockHeight(height.revision_height()); - let header = self - .ctx - .borrow() - .get_header(height) - .map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: "Getting the host header failed".to_string(), - }) - })? - .ok_or_else(|| { - ContextError::ClientError(ClientError::Other { - description: "No host header".to_string(), - }) - })?; - let time = TmTime::try_from(header.time).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: "Converting to Tenderming time failed".to_string(), - }) - })?; - Ok(time.into()) + self.ctx.borrow().host_timestamp() } fn host_consensus_state( &self, height: &Height, ) -> Result, ContextError> { - let height = BlockHeight(height.revision_height()); - let header = self - .ctx - .borrow() - .get_header(height) - .map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Getting the header on this chain failed: Height {}", - height - ), - }) - })? - .ok_or_else(|| { - ContextError::ClientError(ClientError::Other { - description: "No host header".to_string(), - }) - })?; - let commitment_root = header.hash.to_vec().into(); - let time = header - .time - .try_into() - .expect("The time should be converted"); - let next_validators_hash = header - .next_validators_hash - .try_into() - .expect("The hash should be converted"); - let consensus_state = - TmConsensusState::new(commitment_root, time, next_validators_hash); - Ok(consensus_state.into_box()) + self.ctx.borrow().host_consensus_state(height) } fn client_counter(&self) -> Result { @@ -283,126 +179,63 @@ where fn channel_end( &self, - channel_end_path: &ChannelEndPath, + path: &ChannelEndPath, ) -> Result { - self.ctx.borrow().channel_end(channel_end_path) + self.ctx.borrow().channel_end(&path.0, &path.1) } fn get_next_sequence_send( &self, path: &SeqSendPath, ) -> Result { - self.ctx.borrow().get_next_sequence_send(path) + self.ctx.borrow().get_next_sequence_send(&path.0, &path.1) } fn get_next_sequence_recv( &self, path: &SeqRecvPath, ) -> Result { - let path = Path::SeqRecv(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.ctx.borrow().read_sequence(&key) + self.ctx.borrow().get_next_sequence_recv(&path.0, &path.1) } fn get_next_sequence_ack( &self, path: &SeqAckPath, ) -> Result { - let path = Path::SeqAck(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - self.ctx.borrow().read_sequence(&key) + self.ctx.borrow().get_next_sequence_ack(&path.0, &path.1) } fn get_packet_commitment( &self, path: &CommitmentPath, ) -> Result { - let path = Path::Commitment(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.borrow().read(&key) { - Ok(Some(value)) => Ok(value.into()), - Ok(None) => { - let port_channel_sequence_id = - storage::port_channel_sequence_id(&key) - .expect("invalid key"); - Err(ContextError::PacketError( - PacketError::PacketCommitmentNotFound { - sequence: port_channel_sequence_id.2, - }, - )) - } - Err(_) => Err(ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Reading commitment failed: Key {}", - key, - ), - }, - ))), - } + self.ctx.borrow().packet_commitment( + &path.port_id, + &path.channel_id, + path.sequence, + ) } fn get_packet_receipt( &self, path: &ReceiptPath, ) -> Result { - let path = Path::Receipt(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.borrow().read(&key) { - Ok(Some(_)) => Ok(Receipt::Ok), - Ok(None) => { - let port_channel_sequence_id = - storage::port_channel_sequence_id(&key) - .expect("invalid key"); - Err(ContextError::PacketError( - PacketError::PacketReceiptNotFound { - sequence: port_channel_sequence_id.2, - }, - )) - } - Err(_) => Err(ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Reading the receipt failed: Key {}", - key, - ), - }, - ))), - } + self.ctx.borrow().packet_receipt( + &path.port_id, + &path.channel_id, + path.sequence, + ) } fn get_packet_acknowledgement( &self, path: &AckPath, ) -> Result { - let path = Path::Ack(path.clone()); - let key = storage::ibc_key(path.to_string()) - .expect("Creating a key for the client state shouldn't fail"); - match self.ctx.borrow().read(&key) { - Ok(Some(value)) => Ok(value.into()), - Ok(None) => { - let port_channel_sequence_id = - storage::port_channel_sequence_id(&key) - .expect("invalid key"); - Err(ContextError::PacketError( - PacketError::PacketAcknowledgementNotFound { - sequence: port_channel_sequence_id.2, - }, - )) - } - Err(_) => Err(ContextError::PacketError(PacketError::Channel( - ChannelError::Other { - description: format!( - "Reading the ack commitment failed: Key {}", - key - ), - }, - ))), - } + self.ctx.borrow().packet_ack( + &path.port_id, + &path.channel_id, + path.sequence, + ) } fn client_update_time( @@ -410,34 +243,7 @@ where client_id: &ClientId, _height: &Height, ) -> Result { - let key = storage::client_update_timestamp_key(client_id); - match self.ctx.borrow().read(&key) { - Ok(Some(value)) => { - let time = TmTime::decode_vec(&value).map_err(|_| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Decoding the client update time failed: ID {}", - client_id - ), - }) - })?; - Ok(time.into()) - } - Ok(None) => { - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!( - "The client update time doesn't exist: ID {}", - client_id - ), - })) - } - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!( - "Reading the client update time failed: ID {}", - client_id, - ), - })), - } + self.ctx.borrow().client_update_time(client_id) } fn client_update_height( @@ -445,31 +251,7 @@ where client_id: &ClientId, _height: &Height, ) -> Result { - let key = storage::client_update_height_key(client_id); - match self.ctx.borrow().read(&key) { - Ok(Some(value)) => Height::decode_vec(&value).map_err(|e| { - ContextError::ClientError(ClientError::Other { - description: format!( - "Decoding the height failed: Key {}, error {}", - key, e - ), - }) - }), - Ok(None) => { - Err(ContextError::ClientError(ClientError::ClientSpecific { - description: format!( - "The client update height doesn't exist: ID {}", - client_id - ), - })) - } - Err(_) => Err(ContextError::ClientError(ClientError::Other { - description: format!( - "Reading the client update height failed: ID {}", - client_id, - ), - })), - } + self.ctx.borrow().client_update_height(client_id) } fn channel_counter(&self) -> Result { @@ -478,15 +260,10 @@ where } fn max_expected_time_per_block(&self) -> core::time::Duration { - let key = get_max_expected_time_per_block_key(); - match self.ctx.borrow().read(&key) { - Ok(Some(value)) => { - crate::ledger::storage::types::decode::(value) - .expect("Decoding max_expected_time_per_block failed") - .into() - } - _ => unreachable!("The parameter should be initialized"), - } + self.ctx + .borrow() + .max_expected_time_per_block() + .expect("Error cannot be returned") } fn validate_message_signer( @@ -510,7 +287,7 @@ where let height = self .ctx .borrow() - .get_height() + .get_block_height() .expect("The height should exist"); Height::new(0, height.0).expect("The conversion shouldn't fail") } diff --git a/core/src/ledger/ibc/mod.rs b/core/src/ledger/ibc/mod.rs index c78afeabd6..b9550e6b03 100644 --- a/core/src/ledger/ibc/mod.rs +++ b/core/src/ledger/ibc/mod.rs @@ -147,52 +147,38 @@ where /// Store the denom when transfer with MsgRecvPacket fn store_denom(&mut self, envelope: &MsgEnvelope) -> Result<(), Error> { - match envelope { - MsgEnvelope::Packet(PacketMsg::Recv(_)) => { - if let Some((trace_hash, ibc_denom, receiver)) = - self.get_minted_token_info()? - { - // If the denomination trace event has the trace hash and - // the IBC denom, a token has been minted. The raw IBC denom - // including the port ID, the channel ID and the base token - // is stored to be restored from the trace hash. The amount - // denomination is also set for the minting. + if let MsgEnvelope::Packet(PacketMsg::Recv(_)) = envelope { + if let Some((trace_hash, ibc_denom, receiver)) = + self.get_minted_token_info()? + { + // If the denomination trace event has the trace hash and + // the IBC denom, a token has been minted. The raw IBC denom + // including the port ID, the channel ID and the base token + // is stored to be restored from the trace hash. The amount + // denomination is also set for the minting. + self.ctx + .borrow_mut() + .store_ibc_denom(&receiver, &trace_hash, &ibc_denom) + .map_err(|e| { + Error::Denom(format!( + "Writing the IBC denom failed: {}", + e + )) + })?; + if let Some((_, base_token)) = is_ibc_denom(&ibc_denom) { self.ctx .borrow_mut() - .store_ibc_denom(&receiver, &trace_hash, &ibc_denom) + .store_ibc_denom(base_token, trace_hash, &ibc_denom) .map_err(|e| { Error::Denom(format!( "Writing the IBC denom failed: {}", e )) })?; - if let Some((_, base_token)) = is_ibc_denom(&ibc_denom) { - self.ctx - .borrow_mut() - .store_ibc_denom(base_token, trace_hash, &ibc_denom) - .map_err(|e| { - Error::Denom(format!( - "Writing the IBC denom failed: {}", - e - )) - })?; - } - let token = storage::ibc_token(ibc_denom); - self.ctx.borrow_mut().store_token_denom(&token).map_err( - |e| { - Error::Denom(format!( - "Writing the token denom failed: {}", - e - )) - }, - ) - } else { - Ok(()) } } - // other messages - _ => Ok(()), } + Ok(()) } /// Get the minted IBC denom, the trace hash, and the receiver from IBC diff --git a/core/src/ledger/storage_api/token.rs b/core/src/ledger/storage_api/token.rs index 5ea915d034..c372c0bfc1 100644 --- a/core/src/ledger/storage_api/token.rs +++ b/core/src/ledger/storage_api/token.rs @@ -55,7 +55,7 @@ where (token::denom_key(&token), true) } Address::Internal(InternalAddress::IbcToken(_)) => { - (token::denom_key(token), true) + return Ok(Some(0u8.into())); } token => (token::denom_key(token), false), }; diff --git a/shared/src/ledger/native_vp/ibc/context.rs b/shared/src/ledger/native_vp/ibc/context.rs index 7926f3d838..6104eaf1cb 100644 --- a/shared/src/ledger/native_vp/ibc/context.rs +++ b/shared/src/ledger/native_vp/ibc/context.rs @@ -4,25 +4,27 @@ use std::collections::{BTreeSet, HashMap, HashSet}; use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; -use namada_core::ledger::ibc::storage::is_ibc_key; use namada_core::ledger::ibc::{IbcCommonContext, IbcStorageContext}; -use namada_core::ledger::storage::write_log::StorageModification; -use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; -use namada_core::ledger::storage_api::StorageRead; -use namada_core::types::address::{self, Address, InternalAddress}; -use namada_core::types::ibc::{IbcEvent, IbcShieldedTransfer}; -use namada_core::types::storage::{ - BlockHeight, Epoch, Header, Key, KeySeg, TxIndex, + +use crate::ledger::ibc::storage::is_ibc_key; +use crate::ledger::native_vp::CtxPreStorageRead; +use crate::ledger::storage::write_log::StorageModification; +use crate::ledger::storage::{self as ledger_storage, StorageHasher}; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::address::{self, Address, InternalAddress}; +use crate::types::ibc::{IbcEvent, IbcShieldedTransfer}; +use crate::types::storage::{ + BlockHash, BlockHeight, Epoch, Header, Key, KeySeg, TxIndex, }; -use namada_core::types::token::{ +use crate::types::token::{ self, Amount, DenominatedAmount, Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; - -use super::Error; -use crate::ledger::native_vp::CtxPreStorageRead; use crate::vm::WasmCacheAccess; +/// Result of a storage API call. +pub type Result = std::result::Result; + #[derive(Debug)] pub struct PseudoExecutionContext<'view, 'a, DB, H, CA> where @@ -61,17 +63,16 @@ where } } -impl<'view, 'a, DB, H, CA> IbcStorageContext +impl<'view, 'a, DB, H, CA> StorageRead for PseudoExecutionContext<'view, 'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type Error = Error; type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; - fn read(&self, key: &Key) -> Result>, Self::Error> { + fn read_bytes(&self, key: &Key) -> Result>> { match self.store.get(key) { Some(StorageModification::Write { ref value }) => { Ok(Some(value.clone())) @@ -83,43 +84,94 @@ where Some(StorageModification::InitAccount { .. }) => { unreachable!("InitAccount shouldn't be inserted") } - None => self.ctx.read_bytes(key).map_err(Error::NativeVpError), + None => self.ctx.read_bytes(key), } } - fn has_key(&self, key: &Key) -> Result { - Ok(self.store.contains_key(key) - || self.ctx.has_key(key).map_err(Error::NativeVpError)?) + fn has_key(&self, key: &Key) -> Result { + Ok(self.store.contains_key(key) || self.ctx.has_key(key)?) } fn iter_prefix<'iter>( &'iter self, prefix: &Key, - ) -> Result, Self::Error> { + ) -> Result> { // NOTE: Read only the previous state since the updated state isn't // needed for the caller - self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) + self.ctx.iter_prefix(prefix) } fn iter_next<'iter>( &'iter self, iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error> { - self.ctx.iter_next(iter).map_err(Error::NativeVpError) + ) -> Result)>> { + self.ctx.iter_next(iter) + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id() + } + + fn get_block_height(&self) -> Result { + self.ctx.get_block_height() + } + + fn get_block_header(&self, height: BlockHeight) -> Result> { + self.ctx.get_block_header(height) + } + + fn get_block_hash(&self) -> Result { + self.ctx.get_block_hash() + } + + fn get_block_epoch(&self) -> Result { + self.ctx.get_block_epoch() + } + + fn get_tx_index(&self) -> Result { + self.ctx.get_tx_index() + } + + fn get_native_token(&self) -> Result
{ + self.ctx.get_native_token() } +} - fn write(&mut self, key: &Key, value: Vec) -> Result<(), Self::Error> { - self.store - .insert(key.clone(), StorageModification::Write { value }); +impl<'view, 'a, DB, H, CA> StorageWrite + for PseudoExecutionContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + fn write_bytes( + &mut self, + key: &Key, + value: impl AsRef<[u8]>, + ) -> Result<()> { + self.store.insert( + key.clone(), + StorageModification::Write { + value: value.as_ref().to_vec(), + }, + ); Ok(()) } - fn delete(&mut self, key: &Key) -> Result<(), Self::Error> { + fn delete(&mut self, key: &Key) -> Result<()> { self.store.insert(key.clone(), StorageModification::Delete); Ok(()) } +} - fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), Self::Error> { +impl<'view, 'a, DB, H, CA> IbcStorageContext + for PseudoExecutionContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<()> { self.event.insert(event); Ok(()) } @@ -127,7 +179,7 @@ where fn get_ibc_events( &self, event_type: impl AsRef, - ) -> Result, Self::Error> { + ) -> Result> { Ok(self .event .iter() @@ -142,28 +194,21 @@ where dest: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<()> { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); - let src_bal: Option = - self.ctx.read(&src_key).map_err(Error::NativeVpError)?; + let src_bal: Option = self.ctx.read(&src_key)?; let mut src_bal = src_bal.expect("The source has no balance"); src_bal.spend(&amount.amount); - let mut dest_bal: Amount = self - .ctx - .read(&dest_key) - .map_err(Error::NativeVpError)? - .unwrap_or_default(); + let mut dest_bal: Amount = + self.ctx.read(&dest_key)?.unwrap_or_default(); dest_bal.receive(&amount.amount); self.write(&src_key, src_bal.serialize_to_vec())?; self.write(&dest_key, dest_bal.serialize_to_vec()) } - fn handle_masp_tx( - &mut self, - shielded: &IbcShieldedTransfer, - ) -> Result<(), Self::Error> { + fn handle_masp_tx(&mut self, shielded: &IbcShieldedTransfer) -> Result<()> { let masp_addr = address::masp(); let head_tx_key = Key::from(masp_addr.to_db_key()) .push(&HEAD_TX_KEY.to_owned()) @@ -177,9 +222,9 @@ where // so that clients do not have to separately look these // up let record: (Epoch, BlockHeight, TxIndex, Transfer, Transaction) = ( - self.ctx.get_block_epoch().map_err(Error::NativeVpError)?, - self.ctx.get_block_height().map_err(Error::NativeVpError)?, - self.ctx.get_tx_index().map_err(Error::NativeVpError)?, + self.ctx.get_block_epoch()?, + self.ctx.get_block_height()?, + self.ctx.get_tx_index()?, shielded.transfer.clone(), shielded.masp_tx.clone(), ); @@ -200,21 +245,15 @@ where target: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<()> { let target_key = token::balance_key(token, target); - let mut target_bal: Amount = self - .ctx - .read(&target_key) - .map_err(Error::NativeVpError)? - .unwrap_or_default(); + let mut target_bal: Amount = + self.ctx.read(&target_key)?.unwrap_or_default(); target_bal.receive(&amount.amount); let minted_key = token::minted_balance_key(token); - let mut minted_bal: Amount = self - .ctx - .read(&minted_key) - .map_err(Error::NativeVpError)? - .unwrap_or_default(); + let mut minted_bal: Amount = + self.ctx.read(&minted_key)?.unwrap_or_default(); minted_bal.receive(&amount.amount); self.write(&target_key, target_bal.serialize_to_vec())?; @@ -232,44 +271,23 @@ where target: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<()> { let target_key = token::balance_key(token, target); - let mut target_bal: Amount = self - .ctx - .read(&target_key) - .map_err(Error::NativeVpError)? - .unwrap_or_default(); + let mut target_bal: Amount = + self.ctx.read(&target_key)?.unwrap_or_default(); target_bal.spend(&amount.amount); let minted_key = token::minted_balance_key(token); - let mut minted_bal: Amount = self - .ctx - .read(&minted_key) - .map_err(Error::NativeVpError)? - .unwrap_or_default(); + let mut minted_bal: Amount = + self.ctx.read(&minted_key)?.unwrap_or_default(); minted_bal.spend(&amount.amount); self.write(&target_key, target_bal.serialize_to_vec())?; self.write(&minted_key, minted_bal.serialize_to_vec()) } - /// Get the current height of this chain - fn get_height(&self) -> Result { - self.ctx.get_block_height().map_err(Error::NativeVpError) - } - - /// Get the block header of this chain - fn get_header( - &self, - height: BlockHeight, - ) -> Result, Self::Error> { - self.ctx - .get_block_header(height) - .map_err(Error::NativeVpError) - } - fn log_string(&self, message: String) { - tracing::debug!("{} in the pseudo execution for IBC VP", message); + tracing::debug!("{message} in the pseudo execution for IBC VP"); } } @@ -304,54 +322,101 @@ where } } -impl<'view, 'a, DB, H, CA> IbcStorageContext +impl<'view, 'a, DB, H, CA> StorageRead for VpValidationContext<'view, 'a, DB, H, CA> where DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - type Error = Error; type PrefixIter<'iter> = ledger_storage::PrefixIter<'iter, DB> where Self: 'iter; - fn read(&self, key: &Key) -> Result>, Self::Error> { - self.ctx.read_bytes(key).map_err(Error::NativeVpError) + fn read_bytes(&self, key: &Key) -> Result>> { + self.ctx.read_bytes(key) } - fn has_key(&self, key: &Key) -> Result { - self.ctx.has_key(key).map_err(Error::NativeVpError) + fn has_key(&self, key: &Key) -> Result { + self.ctx.has_key(key) } fn iter_prefix<'iter>( &'iter self, prefix: &Key, - ) -> Result, Self::Error> { - self.ctx.iter_prefix(prefix).map_err(Error::NativeVpError) + ) -> Result> { + self.ctx.iter_prefix(prefix) } fn iter_next<'iter>( &'iter self, iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error> { - self.ctx.iter_next(iter).map_err(Error::NativeVpError) + ) -> Result)>> { + self.ctx.iter_next(iter) + } + + fn get_chain_id(&self) -> Result { + self.ctx.get_chain_id() + } + + fn get_block_height(&self) -> Result { + self.ctx.get_block_height() + } + + fn get_block_header(&self, height: BlockHeight) -> Result> { + self.ctx.get_block_header(height) + } + + fn get_block_hash(&self) -> Result { + self.ctx.get_block_hash() + } + + fn get_block_epoch(&self) -> Result { + self.ctx.get_block_epoch() } - fn write(&mut self, _key: &Key, _data: Vec) -> Result<(), Self::Error> { + fn get_tx_index(&self) -> Result { + self.ctx.get_tx_index() + } + + fn get_native_token(&self) -> Result
{ + self.ctx.get_native_token() + } +} + +impl<'view, 'a, DB, H, CA> StorageWrite + for VpValidationContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + fn write_bytes( + &mut self, + _key: &Key, + _val: impl AsRef<[u8]>, + ) -> Result<()> { unimplemented!("Validation doesn't write any data") } - fn delete(&mut self, _key: &Key) -> Result<(), Self::Error> { + fn delete(&mut self, _key: &Key) -> Result<()> { unimplemented!("Validation doesn't delete any data") } +} - fn emit_ibc_event(&mut self, _event: IbcEvent) -> Result<(), Self::Error> { +impl<'view, 'a, DB, H, CA> IbcStorageContext + for VpValidationContext<'view, 'a, DB, H, CA> +where + DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, + H: 'static + StorageHasher, + CA: 'static + WasmCacheAccess, +{ + fn emit_ibc_event(&mut self, _event: IbcEvent) -> Result<()> { unimplemented!("Validation doesn't emit an event") } fn get_ibc_events( &self, _event_type: impl AsRef, - ) -> Result, Self::Error> { + ) -> Result> { unimplemented!("Validation doesn't get an event") } @@ -361,14 +426,14 @@ where _dest: &Address, _token: &Address, _amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<()> { unimplemented!("Validation doesn't transfer") } fn handle_masp_tx( &mut self, _shielded: &IbcShieldedTransfer, - ) -> Result<(), Self::Error> { + ) -> Result<()> { unimplemented!("Validation doesn't handle a masp tx") } @@ -377,7 +442,7 @@ where _target: &Address, _token: &Address, _amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<()> { unimplemented!("Validation doesn't mint") } @@ -386,26 +451,13 @@ where _target: &Address, _token: &Address, _amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<()> { unimplemented!("Validation doesn't burn") } - fn get_height(&self) -> Result { - self.ctx.get_block_height().map_err(Error::NativeVpError) - } - - fn get_header( - &self, - height: BlockHeight, - ) -> Result, Self::Error> { - self.ctx - .get_block_header(height) - .map_err(Error::NativeVpError) - } - /// Logging fn log_string(&self, message: String) { - tracing::debug!("{} for validation in IBC VP", message); + tracing::debug!("{message} for validation in IBC VP"); } } diff --git a/shared/src/ledger/native_vp/ibc/mod.rs b/shared/src/ledger/native_vp/ibc/mod.rs index 902bef0a5b..24ef99169f 100644 --- a/shared/src/ledger/native_vp/ibc/mod.rs +++ b/shared/src/ledger/native_vp/ibc/mod.rs @@ -295,6 +295,7 @@ mod tests { use std::convert::TryFrom; use std::str::FromStr; + use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; use namada_core::ledger::gas::TxGasMeter; use namada_core::ledger::governance::parameters::GovernanceParameters; @@ -388,6 +389,7 @@ mod tests { get_epoch_duration_storage_key, get_max_expected_time_per_block_key, }; use crate::ledger::parameters::EpochDuration; + use crate::ledger::storage_api::StorageRead; use crate::ledger::{ibc, pos}; use crate::proto::{Code, Data, Section, Signature, Tx}; use crate::tendermint::time::Time as TmTime; @@ -571,8 +573,8 @@ mod tests { } } - fn increment_counter(wl_storage: &mut TestWlStorage, key: &Key) { - let count = match wl_storage.storage.read(key).expect("read failed").0 { + fn increment_sequence(wl_storage: &mut TestWlStorage, key: &Key) { + let count = match wl_storage.read_bytes(key).expect("read failed") { Some(value) => { let count: [u8; 8] = value.try_into().expect("decoding a count failed"); @@ -586,6 +588,19 @@ mod tests { .expect("write failed"); } + fn increment_counter(wl_storage: &mut TestWlStorage, key: &Key) { + let count = match wl_storage.read_bytes(key).expect("read failed") { + Some(value) => { + u64::try_from_slice(&value).expect("invalid counter value") + } + None => unreachable!("The counter should be initialized"), + }; + wl_storage + .write_log + .write(key, (count + 1).serialize_to_vec()) + .expect("write failed"); + } + fn dummy_proof() -> CommitmentProofBytes { CommitmentProofBytes::try_from(vec![0]).unwrap() } @@ -1558,13 +1573,13 @@ mod tests { let channel_id = get_channel_id(); let port_id = msg.port_id_on_a.clone(); let send_key = next_sequence_send_key(&port_id, &channel_id); - increment_counter(&mut wl_storage, &send_key); + increment_sequence(&mut wl_storage, &send_key); keys_changed.insert(send_key); let recv_key = next_sequence_recv_key(&port_id, &channel_id); - increment_counter(&mut wl_storage, &recv_key); + increment_sequence(&mut wl_storage, &recv_key); keys_changed.insert(recv_key); let ack_key = next_sequence_ack_key(&port_id, &channel_id); - increment_counter(&mut wl_storage, &ack_key); + increment_sequence(&mut wl_storage, &ack_key); keys_changed.insert(ack_key); // event let event = RawIbcEvent::OpenInitChannel(ChanOpenInit::new( @@ -1681,13 +1696,13 @@ mod tests { let channel_id = get_channel_id(); let port_id = msg.port_id_on_a.clone(); let send_key = next_sequence_send_key(&port_id, &channel_id); - increment_counter(&mut wl_storage, &send_key); + increment_sequence(&mut wl_storage, &send_key); keys_changed.insert(send_key); let recv_key = next_sequence_recv_key(&port_id, &channel_id); - increment_counter(&mut wl_storage, &recv_key); + increment_sequence(&mut wl_storage, &recv_key); keys_changed.insert(recv_key); let ack_key = next_sequence_ack_key(&port_id, &channel_id); - increment_counter(&mut wl_storage, &ack_key); + increment_sequence(&mut wl_storage, &ack_key); keys_changed.insert(ack_key); // event let event = RawIbcEvent::OpenTryChannel(ChanOpenTry::new( diff --git a/shared/src/ledger/native_vp/ibc/token.rs b/shared/src/ledger/native_vp/ibc/token.rs deleted file mode 100644 index 1c9cc5bc1d..0000000000 --- a/shared/src/ledger/native_vp/ibc/token.rs +++ /dev/null @@ -1,375 +0,0 @@ -//! IBC token transfer validation as a native validity predicate - -use std::collections::{BTreeSet, HashMap, HashSet}; - -use borsh::BorshDeserialize; -use prost::Message; -use thiserror::Error; - -use crate::ibc::applications::transfer::coin::PrefixedCoin; -use crate::ibc::applications::transfer::error::TokenTransferError; -use crate::ibc::applications::transfer::msgs::transfer::{ - MsgTransfer, TYPE_URL as MSG_TRANSFER_TYPE_URL, -}; -use crate::ibc::applications::transfer::packet::PacketData; -use crate::ibc::applications::transfer::{ - is_receiver_chain_source, is_sender_chain_source, -}; -use crate::ibc::core::ics04_channel::msgs::PacketMsg; -use crate::ibc::core::ics04_channel::packet::Packet; -use crate::ibc::core::ics26_routing::error::RouterError; -use crate::ibc::core::ics26_routing::msgs::MsgEnvelope; -use crate::ibc_proto::google::protobuf::Any; -use crate::ledger::ibc::storage as ibc_storage; -use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; -use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::proto::Tx; -use crate::types::address::{Address, InternalAddress}; -use crate::types::storage::Key; -use crate::types::token::{self, Amount, AmountParseError}; -use crate::vm::WasmCacheAccess; - -#[allow(missing_docs)] -#[derive(Error, Debug)] -pub enum Error { - #[error("Native VP error: {0}")] - NativeVpError(native_vp::Error), - #[error("IBC message error: {0}")] - IbcMessage(RouterError), - #[error("Invalid message")] - InvalidMessage, - #[error("Parsing amount error: {0}")] - Amount(AmountParseError), - #[error("Decoding error: {0}")] - Decoding(std::io::Error), - #[error("Decoding IBC data error: {0}")] - DecodingIbcData(prost::DecodeError), - #[error("Decoding PacketData error: {0}")] - DecodingPacketData(serde_json::Error), - #[error("IBC message is required as transaction data")] - NoTxData, - #[error("Invalid denom: {0}")] - Denom(String), - #[error("Invalid MsgTransfer: {0}")] - MsgTransfer(TokenTransferError), - #[error("Invalid token transfer: {0}")] - TokenTransfer(String), -} - -/// Result for IBC token VP -pub type Result = std::result::Result; - -/// IBC token VP to validate the transfer for an IBC-specific account. The -/// account is a sub-prefixed account with an IBC token hash, or a normal -/// account for `IbcEscrow`, `IbcBurn`, or `IbcMint`. -pub struct IbcToken<'a, DB, H, CA> -where - DB: ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: StorageHasher, - CA: 'static + WasmCacheAccess, -{ - /// Context to interact with the host structures. - pub ctx: Ctx<'a, DB, H, CA>, -} - -impl<'a, DB, H, CA> NativeVp for IbcToken<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - type Error = Error; - - fn validate_tx( - &self, - tx_data: &Tx, - keys_changed: &BTreeSet, - _verifiers: &BTreeSet
, - ) -> Result { - let signed = tx_data; - let tx_data = signed.data().ok_or(Error::NoTxData)?; - - // Check the non-onwer balance updates - let ibc_keys_changed: HashSet = keys_changed - .iter() - .filter(|k| { - matches!( - token::is_any_token_balance_key(k), - Some([ - _, - Address::Internal( - InternalAddress::IbcEscrow - | InternalAddress::IbcBurn - | InternalAddress::IbcMint - ) - ]) - ) - }) - .cloned() - .collect(); - if ibc_keys_changed.is_empty() { - // some multitoken balances are changed - let mut changes = HashMap::new(); - for key in keys_changed { - if let Some((sub_prefix, _)) = - token::is_any_multitoken_balance_key(key) - { - if !ibc_storage::is_ibc_sub_prefix(&sub_prefix) { - continue; - } - let pre: token::Amount = - self.ctx.read_pre(key)?.unwrap_or_default(); - let post: token::Amount = - self.ctx.read_post(key)?.unwrap_or_default(); - let this_change = post.change() - pre.change(); - let change: token::Change = - changes.get(&sub_prefix).cloned().unwrap_or_default(); - changes.insert(sub_prefix, change + this_change); - } - } - if changes.iter().all(|(_, c)| c.is_zero()) { - return Ok(true); - } else { - return Err(Error::TokenTransfer( - "Invalid transfer between different origin accounts" - .to_owned(), - )); - } - } else if ibc_keys_changed.len() > 1 { - // a transaction can update at most 1 special IBC account for now - return Err(Error::TokenTransfer( - "Invalid transfer for multiple non-owner balances".to_owned(), - )); - } - - // Check the message - let ibc_msg = - Any::decode(&tx_data[..]).map_err(Error::DecodingIbcData)?; - match ibc_msg.type_url.as_str() { - MSG_TRANSFER_TYPE_URL => { - let msg = MsgTransfer::try_from(ibc_msg) - .map_err(Error::MsgTransfer)?; - self.validate_sending_token(&msg) - } - _ => { - let envelope: MsgEnvelope = - ibc_msg.try_into().map_err(Error::IbcMessage)?; - match envelope { - MsgEnvelope::Packet(PacketMsg::Recv(msg)) => { - self.validate_receiving_token(&msg.packet) - } - MsgEnvelope::Packet(PacketMsg::Ack(msg)) => { - self.validate_refunding_token(&msg.packet) - } - MsgEnvelope::Packet(PacketMsg::Timeout(msg)) => { - self.validate_refunding_token(&msg.packet) - } - MsgEnvelope::Packet(PacketMsg::TimeoutOnClose(msg)) => { - self.validate_refunding_token(&msg.packet) - } - _ => Err(Error::InvalidMessage), - } - } - } - } -} - -impl<'a, DB, H, CA> IbcToken<'a, DB, H, CA> -where - DB: 'static + ledger_storage::DB + for<'iter> ledger_storage::DBIter<'iter>, - H: 'static + StorageHasher, - CA: 'static + WasmCacheAccess, -{ - fn validate_sending_token(&self, msg: &MsgTransfer) -> Result { - let mut coin = msg.token.clone(); - // lookup the original denom with the IBC token hash - if let Some(token_hash) = - ibc_storage::token_hash_from_denom(&coin.denom).map_err(|e| { - Error::Denom(format!("Invalid denom: error {}", e)) - })? - { - let denom_key = ibc_storage::ibc_denom_key(token_hash); - coin.denom = match self.ctx.read_bytes_pre(&denom_key) { - Ok(Some(v)) => String::from_utf8(v).map_err(|e| { - Error::Denom(format!( - "Decoding the denom string failed: {}", - e - )) - })?, - _ => { - return Err(Error::Denom(format!( - "No original denom: denom_key {}", - denom_key - ))); - } - }; - } - let coin = PrefixedCoin::try_from(coin).map_err(Error::MsgTransfer)?; - let token = ibc_storage::token(coin.denom.to_string()) - .map_err(|e| Error::Denom(e.to_string()))?; - let amount = Amount::try_from(coin.amount).map_err(Error::Amount)?; - - // check the denomination field - let change = if is_sender_chain_source( - msg.port_id_on_a.clone(), - msg.chan_id_on_a.clone(), - &coin.denom, - ) { - // source zone - // check the amount of the token has been escrowed - let target_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let pre = - try_decode_token_amount(self.ctx.read_bytes_pre(&target_key)?)? - .unwrap_or_default(); - let post = try_decode_token_amount( - self.ctx.read_bytes_post(&target_key)?, - )? - .unwrap_or_default(); - post.change() - pre.change() - } else { - // sink zone - // check the amount of the token has been burned - let target_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcBurn), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&target_key)?, - )? - .unwrap_or_default(); - // the previous balance of the burn address should be zero - post.change() - }; - - if change == amount.change() { - Ok(true) - } else { - Err(Error::TokenTransfer(format!( - "Sending the token is invalid: coin {}", - coin, - ))) - } - } - - fn validate_receiving_token(&self, packet: &Packet) -> Result { - let data = serde_json::from_slice::(&packet.data) - .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(data.token.denom.to_string()) - .map_err(|e| Error::Denom(e.to_string()))?; - let amount = - Amount::try_from(data.token.amount).map_err(Error::Amount)?; - - let change = if is_receiver_chain_source( - packet.port_id_on_a.clone(), - packet.chan_id_on_a.clone(), - &data.token.denom, - ) { - // this chain is the source - // check the amount of the token has been unescrowed - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let pre = - try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? - .unwrap_or_default(); - let post = try_decode_token_amount( - self.ctx.read_bytes_post(&source_key)?, - )? - .unwrap_or_default(); - pre.change() - post.change() - } else { - // the sender is the source - // check the amount of the token has been minted - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcMint), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&source_key)?, - )? - .unwrap_or_default(); - // the previous balance of the mint address should be the maximum - Amount::max_signed().change() - post.change() - }; - - if change == amount.change() { - Ok(true) - } else { - Err(Error::TokenTransfer(format!( - "Receivinging the token is invalid: coin {}", - data.token - ))) - } - } - - fn validate_refunding_token(&self, packet: &Packet) -> Result { - let data = serde_json::from_slice::(&packet.data) - .map_err(Error::DecodingPacketData)?; - let token = ibc_storage::token(data.token.denom.to_string()) - .map_err(|e| Error::Denom(e.to_string()))?; - let amount = - Amount::try_from(data.token.amount).map_err(Error::Amount)?; - - // check the denom field - let change = if is_sender_chain_source( - packet.port_id_on_a.clone(), - packet.chan_id_on_a.clone(), - &data.token.denom, - ) { - // source zone: unescrow the token for the refund - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcEscrow), - ); - let pre = - try_decode_token_amount(self.ctx.read_bytes_pre(&source_key)?)? - .unwrap_or_default(); - let post = try_decode_token_amount( - self.ctx.read_bytes_post(&source_key)?, - )? - .unwrap_or_default(); - pre.change() - post.change() - } else { - // sink zone: mint the token for the refund - let source_key = token::balance_key( - &token, - &Address::Internal(InternalAddress::IbcMint), - ); - let post = try_decode_token_amount( - self.ctx.read_bytes_temp(&source_key)?, - )? - .unwrap_or_default(); - // the previous balance of the mint address should be the maximum - Amount::max_signed().change() - post.change() - }; - - if change == amount.change() { - Ok(true) - } else { - Err(Error::TokenTransfer(format!( - "Refunding the token is invalid: coin {}", - data.token, - ))) - } - } -} - -impl From for Error { - fn from(err: native_vp::Error) -> Self { - Self::NativeVpError(err) - } -} - -fn try_decode_token_amount( - bytes: Option>, -) -> Result> { - if let Some(bytes) = bytes { - let tokens = Amount::try_from_slice(&bytes).map_err(Error::Decoding)?; - return Ok(Some(tokens)); - } - Ok(None) -} diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 5f701b7e1f..4aa1581348 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -4,7 +4,7 @@ use std::collections::BTreeSet; use std::convert::TryInto; use std::num::TryFromIntError; -use borsh::{BorshDeserialize, BorshSerialize}; +use borsh::BorshDeserialize; use borsh_ext::BorshSerializeExt; use masp_primitives::transaction::Transaction; use namada_core::ledger::gas::{GasMetering, TxGasMeter}; @@ -19,6 +19,7 @@ use super::WasmCacheAccess; use crate::ledger::gas::{self, VpGasMeter, STORAGE_ACCESS_GAS_PER_BYTE}; use crate::ledger::storage::write_log::{self, WriteLog}; use crate::ledger::storage::{self, Storage, StorageHasher}; +use crate::ledger::storage_api::{self, ResultExt}; use crate::ledger::vp_host_fns; use crate::proto::Tx; use crate::types::address::{self, Address}; @@ -2086,21 +2087,22 @@ where } // Temp. workaround for -impl<'a, DB, H, CA> namada_core::ledger::ibc::IbcStorageContext - for TxCtx<'a, DB, H, CA> +use namada_core::ledger::storage_api::StorageRead; + +use crate::types::storage::BlockHash; +impl<'a, DB, H, CA> StorageRead for TxCtx<'a, DB, H, CA> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, CA: WasmCacheAccess, { - type Error = TxRuntimeError; // type PrefixIter<'iter> = KeyValIterator<(String, Vec)>; type PrefixIter<'iter> = u64 where Self: 'iter; - fn read( + fn read_bytes( &self, key: &Key, - ) -> std::result::Result>, Self::Error> { + ) -> std::result::Result>, storage_api::Error> { let write_log = unsafe { self.write_log.get() }; let (log_val, gas) = write_log.read(key); ibc_tx_charge_gas(self, gas)?; @@ -2118,15 +2120,14 @@ where None => { // when not found in write log, try to read from the storage let storage = unsafe { self.storage.get() }; - let (value, gas) = - storage.read(key).map_err(TxRuntimeError::StorageError)?; + let (value, gas) = storage.read(key).into_storage_result()?; ibc_tx_charge_gas(self, gas)?; value } }) } - fn has_key(&self, key: &Key) -> Result { + fn has_key(&self, key: &Key) -> Result { // try to read from the write log first let write_log = unsafe { self.write_log.get() }; let (log_val, gas) = write_log.read(key); @@ -2139,31 +2140,18 @@ where None => { // when not found in write log, try to check the storage let storage = unsafe { self.storage.get() }; - let (present, gas) = storage - .has_key(key) - .map_err(TxRuntimeError::StorageError)?; + let (present, gas) = + storage.has_key(key).into_storage_result()?; ibc_tx_charge_gas(self, gas)?; present } }) } - fn write( - &mut self, - key: &Key, - data: Vec, - ) -> std::result::Result<(), Self::Error> { - let write_log = unsafe { self.write_log.get() }; - let (gas, _size_diff) = write_log - .write(key, data) - .map_err(TxRuntimeError::StorageModificationError)?; - ibc_tx_charge_gas(self, gas) - } - fn iter_prefix<'iter>( &'iter self, prefix: &Key, - ) -> Result, Self::Error> { + ) -> Result, storage_api::Error> { let write_log = unsafe { self.write_log.get() }; let storage = unsafe { self.storage.get() }; let (iter, gas) = storage::iter_prefix_post(write_log, storage, prefix); @@ -2176,15 +2164,13 @@ where fn iter_next<'iter>( &'iter self, iter_id: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error> { + ) -> Result)>, storage_api::Error> { let write_log = unsafe { self.write_log.get() }; let iterators = unsafe { self.iterators.get() }; let iter_id = PrefixIteratorId::new(*iter_id); while let Some((key, val, iter_gas)) = iterators.next(iter_id) { - let (log_val, log_gas) = write_log.read( - &Key::parse(key.clone()) - .map_err(TxRuntimeError::StorageDataError)?, - ); + let (log_val, log_gas) = + write_log.read(&Key::parse(key.clone()).into_storage_result()?); ibc_tx_charge_gas(self, iter_gas + log_gas)?; match log_val { Some(write_log::StorageModification::Write { ref value }) => { @@ -2211,22 +2197,110 @@ where Ok(None) } - fn delete(&mut self, key: &Key) -> std::result::Result<(), Self::Error> { + fn get_chain_id(&self) -> Result { + let storage = unsafe { self.storage.get() }; + let (chain_id, gas) = storage.get_chain_id(); + ibc_tx_charge_gas(self, gas)?; + Ok(chain_id) + } + + fn get_block_height(&self) -> Result { + let storage = unsafe { self.storage.get() }; + let (height, gas) = storage.get_block_height(); + ibc_tx_charge_gas(self, gas)?; + Ok(height) + } + + fn get_block_header( + &self, + height: BlockHeight, + ) -> Result, storage_api::Error> + { + let storage = unsafe { self.storage.get() }; + let (header, gas) = storage + .get_block_header(Some(height)) + .into_storage_result()?; + ibc_tx_charge_gas(self, gas)?; + Ok(header) + } + + fn get_block_hash(&self) -> Result { + let storage = unsafe { self.storage.get() }; + let (hash, gas) = storage.get_block_hash(); + ibc_tx_charge_gas(self, gas)?; + Ok(hash) + } + + fn get_block_epoch(&self) -> Result { + let storage = unsafe { self.storage.get() }; + let (epoch, gas) = storage.get_current_epoch(); + ibc_tx_charge_gas(self, gas)?; + Ok(epoch) + } + + fn get_tx_index(&self) -> Result { + let tx_index = unsafe { self.tx_index.get() }; + ibc_tx_charge_gas( + self, + crate::vm::host_env::gas::STORAGE_ACCESS_GAS_PER_BYTE, + )?; + Ok(TxIndex(tx_index.0)) + } + + fn get_native_token(&self) -> Result { + let storage = unsafe { self.storage.get() }; + let native_token = storage.native_token.clone(); + ibc_tx_charge_gas( + self, + crate::vm::host_env::gas::STORAGE_ACCESS_GAS_PER_BYTE, + )?; + Ok(native_token) + } +} + +// Temp. workaround for +use namada_core::ledger::storage_api::StorageWrite; +impl<'a, DB, H, CA> StorageWrite for TxCtx<'a, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ + fn write_bytes( + &mut self, + key: &Key, + data: impl AsRef<[u8]>, + ) -> Result<(), storage_api::Error> { + let write_log = unsafe { self.write_log.get() }; + let (gas, _size_diff) = write_log + .write(key, data.as_ref().to_vec()) + .into_storage_result()?; + ibc_tx_charge_gas(self, gas) + } + + fn delete(&mut self, key: &Key) -> Result<(), storage_api::Error> { if key.is_validity_predicate().is_some() { - return Err(TxRuntimeError::CannotDeleteVp); + return Err(TxRuntimeError::CannotDeleteVp).into_storage_result(); } let write_log = unsafe { self.write_log.get() }; - let (gas, _size_diff) = write_log - .delete(key) - .map_err(TxRuntimeError::StorageModificationError)?; + let (gas, _size_diff) = write_log.delete(key).into_storage_result()?; ibc_tx_charge_gas(self, gas) } +} +// Temp. workaround for +impl<'a, DB, H, CA> namada_core::ledger::ibc::IbcStorageContext + for TxCtx<'a, DB, H, CA> +where + DB: storage::DB + for<'iter> storage::DBIter<'iter>, + H: StorageHasher, + CA: WasmCacheAccess, +{ fn emit_ibc_event( &mut self, event: IbcEvent, - ) -> std::result::Result<(), Self::Error> { + ) -> Result<(), storage_api::Error> { let write_log = unsafe { self.write_log.get() }; let gas = write_log.emit_ibc_event(event); ibc_tx_charge_gas(self, gas) @@ -2235,7 +2309,7 @@ where fn get_ibc_events( &self, event_type: impl AsRef, - ) -> Result, Self::Error> { + ) -> Result, storage_api::Error> { let write_log = unsafe { self.write_log.get() }; Ok(write_log .get_ibc_events() @@ -2251,24 +2325,23 @@ where dest: &Address, token: &Address, amount: namada_core::types::token::DenominatedAmount, - ) -> std::result::Result<(), Self::Error> { + ) -> Result<(), storage_api::Error> { use namada_core::types::token; if amount.amount != token::Amount::default() && src != dest { let src_key = token::balance_key(token, src); let dest_key = token::balance_key(token, dest); - let src_bal: Option = - ibc_read_borsh(self, &src_key)?; + let src_bal = self.read::(&src_key)?; let mut src_bal = src_bal.unwrap_or_else(|| { self.log_string(format!("src {} has no balance", src_key)); unreachable!() }); src_bal.spend(&amount.amount); - let mut dest_bal: token::Amount = - ibc_read_borsh(self, &dest_key)?.unwrap_or_default(); + let mut dest_bal = + self.read::(&dest_key)?.unwrap_or_default(); dest_bal.receive(&amount.amount); - ibc_write_borsh(self, &src_key, &src_bal)?; - ibc_write_borsh(self, &dest_key, &dest_bal)?; + self.write(&src_key, src_bal)?; + self.write(&dest_key, dest_bal)?; } Ok(()) } @@ -2276,14 +2349,13 @@ where fn handle_masp_tx( &mut self, shielded: &IbcShieldedTransfer, - ) -> Result<(), Self::Error> { + ) -> Result<(), storage_api::Error> { let masp_addr = address::masp(); let head_tx_key = Key::from(masp_addr.to_db_key()) .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); - let current_tx_idx: u64 = ibc_read_borsh(self, &head_tx_key) - .unwrap_or(None) - .unwrap_or(0); + let current_tx_idx = + self.read::(&head_tx_key).unwrap_or(None).unwrap_or(0); let current_tx_key = Key::from(masp_addr.to_db_key()) .push(&(TX_KEY_PREFIX.to_owned() + ¤t_tx_idx.to_string())) .expect("Cannot obtain a storage key"); @@ -2291,20 +2363,20 @@ where // so that clients do not have to separately look these // up let record: (Epoch, BlockHeight, TxIndex, Transfer, Transaction) = ( - ibc_get_block_epoch(self)?, - self.get_height()?, - ibc_get_tx_index(self)?, + self.get_block_epoch()?, + self.get_block_height()?, + self.get_tx_index()?, shielded.transfer.clone(), shielded.masp_tx.clone(), ); - ibc_write_borsh(self, ¤t_tx_key, &record)?; - ibc_write_borsh(self, &head_tx_key, &(current_tx_idx + 1))?; + self.write(¤t_tx_key, record)?; + self.write(&head_tx_key, current_tx_idx + 1)?; // If storage key has been supplied, then pin this transaction to it if let Some(key) = &shielded.transfer.key { let pin_key = Key::from(masp_addr.to_db_key()) .push(&(PIN_KEY_PREFIX.to_owned() + key)) .expect("Cannot obtain a storage key"); - ibc_write_borsh(self, &pin_key, ¤t_tx_idx)?; + self.write(&pin_key, current_tx_idx)?; } Ok(()) } @@ -2314,30 +2386,27 @@ where target: &Address, token: &Address, amount: namada_core::types::token::DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<(), storage_api::Error> { use namada_core::types::token; let target_key = token::balance_key(token, target); - let mut target_bal: token::Amount = - ibc_read_borsh(self, &target_key)?.unwrap_or_default(); + let mut target_bal = + self.read::(&target_key)?.unwrap_or_default(); target_bal.receive(&amount.amount); let minted_key = token::minted_balance_key(token); - let mut minted_bal: token::Amount = - ibc_read_borsh(self, &minted_key)?.unwrap_or_default(); + let mut minted_bal = + self.read::(&minted_key)?.unwrap_or_default(); minted_bal.receive(&amount.amount); - ibc_write_borsh(self, &target_key, &target_bal)?; - ibc_write_borsh(self, &minted_key, &minted_bal)?; + self.write(&target_key, target_bal)?; + self.write(&minted_key, minted_bal)?; let minter_key = token::minter_key(token); - ibc_write_borsh( - self, + self.write( &minter_key, - &Address::Internal(address::InternalAddress::Ibc), - )?; - - Ok(()) + Address::Internal(address::InternalAddress::Ibc), + ) } fn burn_token( @@ -2345,46 +2414,22 @@ where target: &Address, token: &Address, amount: namada_core::types::token::DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<(), storage_api::Error> { use namada_core::types::token; let target_key = token::balance_key(token, target); - let mut target_bal: token::Amount = - ibc_read_borsh(self, &target_key)?.unwrap_or_default(); + let mut target_bal = + self.read::(&target_key)?.unwrap_or_default(); target_bal.spend(&amount.amount); // burn the minted amount let minted_key = token::minted_balance_key(token); - let mut minted_bal: token::Amount = - ibc_read_borsh(self, &minted_key)?.unwrap_or_default(); + let mut minted_bal = + self.read::(&minted_key)?.unwrap_or_default(); minted_bal.spend(&amount.amount); - ibc_write_borsh(self, &target_key, &target_bal)?; - ibc_write_borsh(self, &minted_key, &minted_bal)?; - - Ok(()) - } - - fn get_height(&self) -> std::result::Result { - let storage = unsafe { self.storage.get() }; - let (height, gas) = storage.get_block_height(); - ibc_tx_charge_gas(self, gas)?; - Ok(height) - } - - fn get_header( - &self, - height: BlockHeight, - ) -> std::result::Result< - Option, - Self::Error, - > { - let storage = unsafe { self.storage.get() }; - let (header, gas) = storage - .get_block_header(Some(height)) - .map_err(TxRuntimeError::StorageError)?; - ibc_tx_charge_gas(self, gas)?; - Ok(header) + self.write(&target_key, target_bal)?; + self.write(&minted_key, minted_bal) } fn log_string(&self, message: String) { @@ -2397,7 +2442,7 @@ where fn ibc_tx_charge_gas<'a, DB, H, CA>( ctx: &TxCtx<'a, DB, H, CA>, used_gas: u64, -) -> TxResult<()> +) -> Result<(), storage_api::Error> where DB: storage::DB + for<'iter> storage::DBIter<'iter>, H: StorageHasher, @@ -2405,9 +2450,7 @@ where { let gas_meter = unsafe { ctx.gas_meter.get() }; // if we run out of gas, we need to stop the execution - let result = gas_meter - .consume(used_gas) - .map_err(TxRuntimeError::OutOfGas); + let result = gas_meter.consume(used_gas).into_storage_result(); if let Err(err) = &result { tracing::info!( "Stopping transaction execution because of gas error: {}", @@ -2417,81 +2460,6 @@ where result } -/// Read borsh encoded val by key. -// Temp helper for ibc tx workaround. -fn ibc_read_borsh<'a, T, DB, H, CA>( - ctx: &TxCtx<'a, DB, H, CA>, - key: &Key, -) -> TxResult> -where - T: BorshDeserialize, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, - CA: WasmCacheAccess, -{ - let bytes = namada_core::ledger::ibc::IbcStorageContext::read(ctx, key)?; - match bytes { - Some(bytes) => { - let val = T::try_from_slice(&bytes) - .map_err(TxRuntimeError::EncodingError)?; - Ok(Some(val)) - } - None => Ok(None), - } -} - -/// Write borsh encoded val by key. -// Temp helper for ibc tx workaround. -fn ibc_write_borsh<'a, T, DB, H, CA>( - ctx: &mut TxCtx<'a, DB, H, CA>, - key: &Key, - val: &T, -) -> TxResult<()> -where - T: BorshSerialize, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, - CA: WasmCacheAccess, -{ - let bytes = borsh::to_vec(val).map_err(TxRuntimeError::EncodingError)?; - namada_core::ledger::ibc::IbcStorageContext::write(ctx, key, bytes)?; - Ok(()) -} - -/// Get the current epoch. -// Temp helper for ibc tx workaround. -fn ibc_get_block_epoch<'a, DB, H, CA>( - ctx: &TxCtx<'a, DB, H, CA>, -) -> TxResult -where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, - CA: WasmCacheAccess, -{ - let storage = unsafe { ctx.storage.get() }; - let (epoch, gas) = storage.get_current_epoch(); - ibc_tx_charge_gas(ctx, gas)?; - Ok(epoch) -} - -/// Get the tx index. -// Temp helper for ibc tx workaround. -fn ibc_get_tx_index<'a, DB, H, CA>( - ctx: &TxCtx<'a, DB, H, CA>, -) -> TxResult -where - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, - CA: WasmCacheAccess, -{ - let tx_index = unsafe { ctx.tx_index.get() }; - ibc_tx_charge_gas( - ctx, - crate::vm::host_env::gas::STORAGE_ACCESS_GAS_PER_BYTE, - )?; - Ok(TxIndex(tx_index.0)) -} - // Temp. workaround for impl<'a, DB, H, CA> namada_core::ledger::ibc::IbcCommonContext for TxCtx<'a, DB, H, CA> diff --git a/tx_prelude/src/ibc.rs b/tx_prelude/src/ibc.rs index 2f19c24a82..733e084a0a 100644 --- a/tx_prelude/src/ibc.rs +++ b/tx_prelude/src/ibc.rs @@ -4,18 +4,15 @@ use std::cell::RefCell; use std::rc::Rc; pub use namada_core::ledger::ibc::{ - Error, IbcActions, IbcCommonContext, IbcStorageContext, ProofSpec, - TransferModule, + IbcActions, IbcCommonContext, IbcStorageContext, ProofSpec, TransferModule, }; -use namada_core::ledger::storage_api::{StorageRead, StorageWrite}; use namada_core::ledger::tx_env::TxEnv; use namada_core::types::address::{Address, InternalAddress}; pub use namada_core::types::ibc::{IbcEvent, IbcShieldedTransfer}; -use namada_core::types::storage::{BlockHeight, Header, Key}; use namada_core::types::token::DenominatedAmount; use crate::token::{burn, handle_masp_tx, mint, transfer}; -use crate::{Ctx, KeyValIterator}; +use crate::{Ctx, Error}; /// IBC actions to handle an IBC message pub fn ibc_actions(ctx: &mut Ctx) -> IbcActions { @@ -27,58 +24,17 @@ pub fn ibc_actions(ctx: &mut Ctx) -> IbcActions { } impl IbcStorageContext for Ctx { - type Error = crate::Error; - type PrefixIter<'iter> = KeyValIterator<(String, Vec)>; - - fn read( - &self, - key: &Key, - ) -> std::result::Result>, Self::Error> { - self.read_bytes(key) - } - - fn has_key(&self, key: &Key) -> Result { - ::has_key(self, key) - } - - fn write( - &mut self, - key: &Key, - data: Vec, - ) -> std::result::Result<(), Self::Error> { - self.write_bytes(key, data)?; - Ok(()) - } - - fn iter_prefix<'iter>( - &'iter self, - prefix: &Key, - ) -> Result, Self::Error> { - StorageRead::iter_prefix(self, prefix) - } - - fn iter_next<'iter>( - &'iter self, - iter: &mut Self::PrefixIter<'iter>, - ) -> Result)>, Self::Error> { - StorageRead::iter_next(self, iter) - } - - fn delete(&mut self, key: &Key) -> std::result::Result<(), Self::Error> { - StorageWrite::delete(self, key) - } - fn emit_ibc_event( &mut self, event: IbcEvent, - ) -> std::result::Result<(), Self::Error> { + ) -> std::result::Result<(), Error> { ::emit_ibc_event(self, &event) } fn get_ibc_events( &self, event_type: impl AsRef, - ) -> Result, Self::Error> { + ) -> Result, Error> { ::get_ibc_events(self, &event_type) } @@ -88,14 +44,14 @@ impl IbcStorageContext for Ctx { dest: &Address, token: &Address, amount: DenominatedAmount, - ) -> std::result::Result<(), Self::Error> { + ) -> std::result::Result<(), Error> { transfer(self, src, dest, token, amount) } fn handle_masp_tx( &mut self, shielded: &IbcShieldedTransfer, - ) -> Result<(), Self::Error> { + ) -> Result<(), Error> { handle_masp_tx(self, &shielded.transfer, &shielded.masp_tx) } @@ -104,7 +60,7 @@ impl IbcStorageContext for Ctx { target: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<(), Error> { mint( self, &Address::Internal(InternalAddress::Ibc), @@ -119,21 +75,10 @@ impl IbcStorageContext for Ctx { target: &Address, token: &Address, amount: DenominatedAmount, - ) -> Result<(), Self::Error> { + ) -> Result<(), Error> { burn(self, target, token, amount.amount) } - fn get_height(&self) -> std::result::Result { - self.get_block_height() - } - - fn get_header( - &self, - height: BlockHeight, - ) -> std::result::Result, Self::Error> { - self.get_block_header(height) - } - fn log_string(&self, message: String) { super::log_string(message); }