diff --git a/Cargo.toml b/Cargo.toml index 6007c4f27..ac45f6c20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,3 +105,6 @@ tendermint-testgen = { version = "0.34.0", default-features = fals # parity dependencies parity-scale-codec = { version = "3.6.5", default-features = false, features = ["full"] } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } + +[patch.crates-io] +ibc-proto = { git = "https://github.com/cosmos/ibc-proto-rs", rev = "7643fa3" } diff --git a/ibc-core/ics23-commitment/types/src/commitment.rs b/ibc-core/ics23-commitment/types/src/commitment.rs index b24b60a88..da5580f86 100644 --- a/ibc-core/ics23-commitment/types/src/commitment.rs +++ b/ibc-core/ics23-commitment/types/src/commitment.rs @@ -144,11 +144,17 @@ impl<'a> TryFrom<&'a CommitmentProofBytes> for MerkleProof { )] #[cfg_attr(feature = "serde", derive(serde::Deserialize))] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] -#[derive(Clone, PartialEq, Eq, Hash, Default)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct CommitmentPrefix { bytes: Vec, } +impl Default for CommitmentPrefix { + fn default() -> Self { + Self { bytes: vec![0x00] } + } +} + impl CommitmentPrefix { pub fn as_bytes(&self) -> &[u8] { &self.bytes diff --git a/ibc-testkit/Cargo.toml b/ibc-testkit/Cargo.toml index 55546cf8c..94ccac0d3 100644 --- a/ibc-testkit/Cargo.toml +++ b/ibc-testkit/Cargo.toml @@ -31,6 +31,8 @@ typed-builder = { version = "0.18.0" } # ibc dependencies ibc = { workspace = true, features = ["std"] } ibc-proto = { workspace = true } +ibc-query = { version = "0.50.0", path = "../ibc-query" } +basecoin-store = { git = "https://github.com/informalsystems/basecoin-rs", rev = "dc3b43a" } # cosmos dependencies tendermint = { workspace = true } diff --git a/ibc-testkit/src/fixtures/core/context.rs b/ibc-testkit/src/fixtures/core/context.rs index a886777e7..f6522ce2b 100644 --- a/ibc-testkit/src/fixtures/core/context.rs +++ b/ibc-testkit/src/fixtures/core/context.rs @@ -1,22 +1,22 @@ -use alloc::sync::Arc; +use alloc::fmt::Debug; use core::cmp::min; use core::ops::{Add, Sub}; use core::time::Duration; +use basecoin_store::context::ProvableStore; use ibc::core::client::types::Height; use ibc::core::host::types::identifiers::ChainId; use ibc::core::primitives::prelude::*; use ibc::core::primitives::Timestamp; -use parking_lot::Mutex; use tendermint_testgen::Validator as TestgenValidator; use typed_builder::TypedBuilder; use crate::hosts::block::{HostBlock, HostType}; -use crate::testapp::ibc::core::types::{MockContext, MockIbcStore, DEFAULT_BLOCK_TIME_SECS}; +use crate::testapp::ibc::core::types::{MockGenericContext, MockIbcStore, DEFAULT_BLOCK_TIME_SECS}; /// Configuration of the `MockContext` type for generating dummy contexts. #[derive(Debug, TypedBuilder)] -#[builder(build_method(into = MockContext))] +#[builder(build_method(into))] pub struct MockContextConfig { #[builder(default = HostType::Mock)] host_type: HostType, @@ -41,7 +41,10 @@ pub struct MockContextConfig { latest_timestamp: Timestamp, } -impl From for MockContext { +impl From for MockGenericContext +where + S: ProvableStore + Debug + Default, +{ fn from(params: MockContextConfig) -> Self { assert_ne!( params.max_history_size, 0, @@ -115,13 +118,13 @@ impl From for MockContext { .collect() }; - MockContext { + MockGenericContext { host_chain_type: params.host_type, host_chain_id: params.host_id.clone(), max_history_size: params.max_history_size, history, block_time: params.block_time, - ibc_store: Arc::new(Mutex::new(MockIbcStore::default())), + ibc_store: MockIbcStore::default(), } } } diff --git a/ibc-testkit/src/fixtures/mod.rs b/ibc-testkit/src/fixtures/mod.rs index af14cadb9..709cb4206 100644 --- a/ibc-testkit/src/fixtures/mod.rs +++ b/ibc-testkit/src/fixtures/mod.rs @@ -12,7 +12,7 @@ pub enum Expect { Failure(Option), } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Fixture { pub ctx: MockContext, pub msg: M, diff --git a/ibc-testkit/src/relayer/context.rs b/ibc-testkit/src/relayer/context.rs index d41331345..c35cd080f 100644 --- a/ibc-testkit/src/relayer/context.rs +++ b/ibc-testkit/src/relayer/context.rs @@ -1,3 +1,6 @@ +use alloc::fmt::Debug; + +use basecoin_store::context::ProvableStore; use ibc::core::client::types::Height; use ibc::core::handler::types::error::ContextError; use ibc::core::host::types::identifiers::ClientId; @@ -6,7 +9,7 @@ use ibc::core::primitives::prelude::*; use ibc::core::primitives::Signer; use crate::testapp::ibc::clients::AnyClientState; -use crate::testapp::ibc::core::types::MockContext; +use crate::testapp::ibc::core::types::MockGenericContext; /// Trait capturing all dependencies (i.e., the context) which algorithms in ICS18 require to /// relay packets between chains. This trait comprises the dependencies towards a single chain. /// Most of the functions in this represent wrappers over the ABCI interface. @@ -24,7 +27,10 @@ pub trait RelayerContext { fn signer(&self) -> Signer; } -impl RelayerContext for MockContext { +impl RelayerContext for MockGenericContext +where + S: ProvableStore + Debug, +{ fn query_latest_height(&self) -> Result { ValidationContext::host_height(self) } @@ -59,7 +65,7 @@ mod tests { use crate::relayer::error::RelayerError; use crate::testapp::ibc::clients::mock::client_state::client_type as mock_client_type; use crate::testapp::ibc::core::router::MockRouter; - use crate::testapp::ibc::core::types::MockClientConfig; + use crate::testapp::ibc::core::types::{MockClientConfig, MockContext}; /// Builds a `ClientMsg::UpdateClient` for a client with id `client_id` running on the `dest` /// context, assuming that the latest header on the source context is `src_header`. @@ -126,30 +132,37 @@ mod tests { let mut ctx_a = MockContextConfig::builder() .host_id(chain_id_a.clone()) .latest_height(chain_a_start_height) - .build() - .with_client_config( - MockClientConfig::builder() - .client_chain_id(chain_id_b.clone()) - .client_id(client_on_a_for_b.clone()) - .latest_height(client_on_a_for_b_height) - .client_type(tm_client_type()) // The target host chain (B) is synthetic TM. - .build(), - ); - // dummy; not actually used in client updates - let mut router_a = MockRouter::new_with_transfer(); + .build::(); let mut ctx_b = MockContextConfig::builder() - .host_id(chain_id_b) + .host_id(chain_id_b.clone()) .host_type(HostType::SyntheticTendermint) .latest_height(chain_b_start_height) - .build() - .with_client_config( - MockClientConfig::builder() - .client_chain_id(chain_id_a) - .client_id(client_on_b_for_a.clone()) - .latest_height(client_on_b_for_a_height) - .build(), - ); + .latest_timestamp(ctx_a.timestamp_at(chain_a_start_height.decrement().unwrap())) // chain B is running slower than chain A + .build::(); + + ctx_a = ctx_a.with_client_config( + MockClientConfig::builder() + .client_chain_id(chain_id_b) + .client_id(client_on_a_for_b.clone()) + .latest_height(client_on_a_for_b_height) + .latest_timestamp(ctx_b.timestamp_at(client_on_a_for_b_height)) + .client_type(tm_client_type()) // The target host chain (B) is synthetic TM. + .build(), + ); + + ctx_b = ctx_b.with_client_config( + MockClientConfig::builder() + .client_chain_id(chain_id_a) + .client_id(client_on_b_for_a.clone()) + .latest_height(client_on_b_for_a_height) + .latest_timestamp(ctx_a.timestamp_at(client_on_b_for_a_height)) + .build(), + ); + + // dummy; not actually used in client updates + let mut router_a = MockRouter::new_with_transfer(); + // dummy; not actually used in client updates let mut router_b = MockRouter::new_with_transfer(); diff --git a/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs b/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs index f798960d3..70ed7466b 100644 --- a/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs +++ b/ibc-testkit/src/testapp/ibc/clients/mock/client_state.rs @@ -21,9 +21,7 @@ use crate::testapp::ibc::clients::mock::client_state::client_type as mock_client use crate::testapp::ibc::clients::mock::consensus_state::MockConsensusState; use crate::testapp::ibc::clients::mock::header::{MockHeader, MOCK_HEADER_TYPE_URL}; use crate::testapp::ibc::clients::mock::misbehaviour::{Misbehaviour, MOCK_MISBEHAVIOUR_TYPE_URL}; -use crate::testapp::ibc::clients::mock::proto::{ - ClientState as RawMockClientState, Header as RawMockHeader, -}; +use crate::testapp::ibc::clients::mock::proto::ClientState as RawMockClientState; pub const MOCK_CLIENT_STATE_TYPE_URL: &str = "/ibc.mock.ClientState"; pub const MOCK_CLIENT_TYPE: &str = "9999-mock"; @@ -39,14 +37,16 @@ pub fn client_type() -> ClientType { #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct MockClientState { pub header: MockHeader, - pub frozen_height: Option, + pub trusting_period: Duration, + pub frozen: bool, } impl MockClientState { pub fn new(header: MockHeader) -> Self { Self { header, - frozen_height: None, + trusting_period: Duration::from_nanos(0), + frozen: false, } } @@ -58,15 +58,22 @@ impl MockClientState { None } - pub fn with_frozen_height(self, frozen_height: Height) -> Self { + pub fn with_trusting_period(self, trusting_period: Duration) -> Self { Self { - frozen_height: Some(frozen_height), + trusting_period, + ..self + } + } + + pub fn with_frozen_height(self, _frozen_height: Height) -> Self { + Self { + frozen: true, ..self } } pub fn is_frozen(&self) -> bool { - self.frozen_height.is_some() + self.frozen } fn expired(&self, _elapsed: Duration) -> bool { @@ -80,17 +87,25 @@ impl TryFrom for MockClientState { type Error = ClientError; fn try_from(raw: RawMockClientState) -> Result { - Ok(Self::new(raw.header.expect("Never fails").try_into()?)) + Ok(Self { + header: raw + .header + .ok_or(ClientError::Other { + description: "header is not present".into(), + })? + .try_into()?, + trusting_period: Duration::from_nanos(raw.trusting_period), + frozen: raw.frozen, + }) } } impl From for RawMockClientState { fn from(value: MockClientState) -> Self { RawMockClientState { - header: Some(RawMockHeader { - height: Some(value.header.height().into()), - timestamp: value.header.timestamp.nanoseconds(), - }), + header: Some(value.header.into()), + trusting_period: value.trusting_period.as_nanos() as _, + frozen: value.frozen, } } } diff --git a/ibc-testkit/src/testapp/ibc/clients/mock/consensus_state.rs b/ibc-testkit/src/testapp/ibc/clients/mock/consensus_state.rs index 8a2e31e58..43db3edc4 100644 --- a/ibc-testkit/src/testapp/ibc/clients/mock/consensus_state.rs +++ b/ibc-testkit/src/testapp/ibc/clients/mock/consensus_state.rs @@ -6,9 +6,7 @@ use ibc::core::primitives::Timestamp; use ibc::primitives::proto::{Any, Protobuf}; use crate::testapp::ibc::clients::mock::header::MockHeader; -use crate::testapp::ibc::clients::mock::proto::{ - ConsensusState as RawMockConsensusState, Header as RawMockHeader, -}; +use crate::testapp::ibc::clients::mock::proto::ConsensusState as RawMockConsensusState; pub const MOCK_CONSENSUS_STATE_TYPE_URL: &str = "/ibc.mock.ConsensusState"; #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -40,7 +38,7 @@ impl TryFrom for MockConsensusState { let raw_header = raw.header.ok_or(ClientError::MissingRawConsensusState)?; Ok(Self { - header: MockHeader::try_from(raw_header)?, + header: raw_header.try_into()?, root: CommitmentRoot::from(vec![0]), }) } @@ -49,10 +47,7 @@ impl TryFrom for MockConsensusState { impl From for RawMockConsensusState { fn from(value: MockConsensusState) -> Self { RawMockConsensusState { - header: Some(RawMockHeader { - height: Some(value.header.height().into()), - timestamp: value.header.timestamp.nanoseconds(), - }), + header: Some(value.header.into()), } } } diff --git a/ibc-testkit/src/testapp/ibc/clients/mock/header.rs b/ibc-testkit/src/testapp/ibc/clients/mock/header.rs index 072b7bba7..ab4785054 100644 --- a/ibc-testkit/src/testapp/ibc/clients/mock/header.rs +++ b/ibc-testkit/src/testapp/ibc/clients/mock/header.rs @@ -45,11 +45,15 @@ impl TryFrom for MockHeader { Ok(MockHeader { height: raw .height - .and_then(|raw_height| raw_height.try_into().ok()) - .ok_or(ClientError::MissingClientMessage)?, - - timestamp: Timestamp::from_nanoseconds(raw.timestamp) - .map_err(ClientError::InvalidPacketTimestamp)?, + .ok_or(ClientError::Other { + description: "missing height".into(), + })? + .try_into()?, + timestamp: Timestamp::from_nanoseconds(raw.timestamp).map_err(|err| { + ClientError::Other { + description: err.to_string(), + } + })?, }) } } diff --git a/ibc-testkit/src/testapp/ibc/clients/mod.rs b/ibc-testkit/src/testapp/ibc/clients/mod.rs index e5f7ad7be..d897d5338 100644 --- a/ibc-testkit/src/testapp/ibc/clients/mod.rs +++ b/ibc-testkit/src/testapp/ibc/clients/mod.rs @@ -1,5 +1,8 @@ pub mod mock; +use alloc::fmt::Debug; + +use basecoin_store::context::ProvableStore; use derive_more::{From, TryInto}; use ibc::clients::tendermint::client_state::ClientState as TmClientState; use ibc::clients::tendermint::consensus_state::ConsensusState as TmConsensusState; @@ -17,11 +20,11 @@ use crate::testapp::ibc::clients::mock::client_state::{ use crate::testapp::ibc::clients::mock::consensus_state::{ MockConsensusState, MOCK_CONSENSUS_STATE_TYPE_URL, }; -use crate::testapp::ibc::core::types::MockContext; +use crate::testapp::ibc::core::types::MockGenericContext; #[derive(Debug, Clone, From, PartialEq, ClientState)] -#[validation(MockContext)] -#[execution(MockContext)] +#[validation(MockGenericContext)] +#[execution(MockGenericContext)] pub enum AnyClientState { Tendermint(TmClientState), Mock(MockClientState), diff --git a/ibc-testkit/src/testapp/ibc/core/client_ctx.rs b/ibc-testkit/src/testapp/ibc/core/client_ctx.rs index 227c647af..25728e54b 100644 --- a/ibc-testkit/src/testapp/ibc/core/client_ctx.rs +++ b/ibc-testkit/src/testapp/ibc/core/client_ctx.rs @@ -1,6 +1,9 @@ use alloc::collections::BTreeMap; use alloc::vec::Vec; +use core::fmt::Debug; +use basecoin_store::context::{ProvableStore, Store}; +use basecoin_store::types::Height as StoreHeight; use ibc::clients::tendermint::context::{ CommonContext as TmCommonContext, ValidationContext as TmValidationContext, }; @@ -9,13 +12,16 @@ use ibc::core::client::types::error::ClientError; use ibc::core::client::types::Height; use ibc::core::handler::types::error::ContextError; use ibc::core::host::types::identifiers::{ChannelId, ClientId, PortId}; -use ibc::core::host::types::path::{ClientConsensusStatePath, ClientStatePath}; +use ibc::core::host::types::path::{ + ClientConsensusStatePath, ClientStatePath, ClientUpdateHeightPath, ClientUpdateTimePath, Path, +}; use ibc::core::host::ValidationContext; use ibc::core::primitives::Timestamp; +use ibc::primitives::prelude::{format, *}; use crate::testapp::ibc::clients::mock::client_state::MockClientContext; use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState}; -use crate::testapp::ibc::core::types::MockContext; +use crate::testapp::ibc::core::types::MockGenericContext; pub type PortChannelIdMap = BTreeMap>; @@ -30,27 +36,10 @@ pub struct MockClientRecord { pub consensus_states: BTreeMap, } -impl MockClientContext for MockContext { - type ConversionError = &'static str; - type AnyConsensusState = AnyConsensusState; - - fn host_timestamp(&self) -> Result { - ValidationContext::host_timestamp(self) - } - - fn host_height(&self) -> Result { - ValidationContext::host_height(self) - } - - fn consensus_state( - &self, - client_cons_state_path: &ClientConsensusStatePath, - ) -> Result { - ValidationContext::consensus_state(self, client_cons_state_path) - } -} - -impl TmCommonContext for MockContext { +impl MockClientContext for MockGenericContext +where + S: ProvableStore + Debug, +{ type ConversionError = &'static str; type AnyConsensusState = AnyConsensusState; @@ -68,218 +57,290 @@ impl TmCommonContext for MockContext { ) -> Result { ValidationContext::consensus_state(self, client_cons_state_path) } - - fn consensus_state_heights(&self, client_id: &ClientId) -> Result, ContextError> { - let ibc_store = self.ibc_store.lock(); - let client_record = - ibc_store - .clients - .get(client_id) - .ok_or_else(|| ClientError::ClientStateNotFound { - client_id: client_id.clone(), - })?; - - let heights = client_record.consensus_states.keys().cloned().collect(); - - Ok(heights) - } } - -impl TmValidationContext for MockContext { - fn next_consensus_state( - &self, - client_id: &ClientId, - height: &Height, - ) -> Result, ContextError> { - let ibc_store = self.ibc_store.lock(); - let client_record = - ibc_store - .clients - .get(client_id) - .ok_or_else(|| ClientError::ClientStateNotFound { - client_id: client_id.clone(), - })?; - - // Get the consensus state heights and sort them in ascending order. - let mut heights: Vec = client_record.consensus_states.keys().cloned().collect(); - heights.sort(); - - // Search for next state. - for h in heights { - if h > *height { - // unwrap should never happen, as the consensus state for h must exist - return Ok(Some( - client_record - .consensus_states - .get(&h) - .expect("Never fails") - .clone(), - )); - } - } - Ok(None) - } - - fn prev_consensus_state( - &self, - client_id: &ClientId, - height: &Height, - ) -> Result, ContextError> { - let ibc_store = self.ibc_store.lock(); - let client_record = - ibc_store - .clients - .get(client_id) - .ok_or_else(|| ClientError::ClientStateNotFound { - client_id: client_id.clone(), - })?; - - // Get the consensus state heights and sort them in descending order. - let mut heights: Vec = client_record.consensus_states.keys().cloned().collect(); - heights.sort_by(|a, b| b.cmp(a)); - - // Search for previous state. - for h in heights { - if h < *height { - // unwrap should never happen, as the consensus state for h must exist - return Ok(Some( - client_record - .consensus_states - .get(&h) - .expect("Never fails") - .clone(), - )); - } - } - Ok(None) - } -} - -impl ClientValidationContext for MockContext { +impl ClientValidationContext for MockGenericContext +where + S: ProvableStore + Debug, +{ + /// Returns the time and height when the client state for the given + /// [`ClientId`] was updated with a header for the given [`Height`] fn update_meta( &self, client_id: &ClientId, height: &Height, ) -> Result<(Timestamp, Height), ContextError> { - let key = (client_id.clone(), *height); - (|| { - let ibc_store = self.ibc_store.lock(); - let time = ibc_store.client_processed_times.get(&key)?; - let height = ibc_store.client_processed_heights.get(&key)?; - Some((*time, *height)) - })() - .ok_or(ClientError::UpdateMetaDataNotFound { - client_id: key.0, - height: key.1, - }) - .map_err(ContextError::from) + let client_update_time_path = ClientUpdateTimePath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + ); + let processed_timestamp = self + .ibc_store + .client_processed_times + .get(StoreHeight::Pending, &client_update_time_path) + .ok_or(ClientError::UpdateMetaDataNotFound { + client_id: client_id.clone(), + height: *height, + })?; + let client_update_height_path = ClientUpdateHeightPath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + ); + let processed_height = self + .ibc_store + .client_processed_heights + .get(StoreHeight::Pending, &client_update_height_path) + .ok_or(ClientError::UpdateMetaDataNotFound { + client_id: client_id.clone(), + height: *height, + })?; + + Ok((processed_timestamp, processed_height)) } } -impl ClientExecutionContext for MockContext { +impl ClientExecutionContext for MockGenericContext +where + S: ProvableStore + Debug, +{ type V = Self; + type AnyClientState = AnyClientState; + type AnyConsensusState = AnyConsensusState; + /// Called upon successful client creation and update fn store_client_state( &mut self, client_state_path: ClientStatePath, client_state: Self::AnyClientState, ) -> Result<(), ContextError> { - let mut ibc_store = self.ibc_store.lock(); - - let client_id = client_state_path.0; - let client_record = ibc_store - .clients - .entry(client_id) - .or_insert(MockClientRecord { - consensus_states: Default::default(), - client_state: Default::default(), - }); - - client_record.client_state = Some(client_state); + self.ibc_store + .client_state_store + .set(client_state_path.clone(), client_state) + .map_err(|_| ClientError::Other { + description: "Client state store error".to_string(), + })?; Ok(()) } + /// Called upon successful client creation and update fn store_consensus_state( &mut self, consensus_state_path: ClientConsensusStatePath, consensus_state: Self::AnyConsensusState, ) -> Result<(), ContextError> { - let mut ibc_store = self.ibc_store.lock(); - - let client_record = ibc_store - .clients - .entry(consensus_state_path.client_id) - .or_insert(MockClientRecord { - consensus_states: Default::default(), - client_state: Default::default(), - }); - - let height = Height::new( - consensus_state_path.revision_number, - consensus_state_path.revision_height, - ) - .expect("Never fails"); - client_record - .consensus_states - .insert(height, consensus_state); - + self.ibc_store + .consensus_state_store + .set(consensus_state_path, consensus_state) + .map_err(|_| ClientError::Other { + description: "Consensus state store error".to_string(), + })?; Ok(()) } - fn delete_consensus_state( + /// Called upon successful client update. Implementations are expected to + /// use this to record the time and height at which this update (or header) + /// was processed. + fn store_update_meta( &mut self, - consensus_state_path: ClientConsensusStatePath, + client_id: ClientId, + height: Height, + host_timestamp: Timestamp, + host_height: Height, ) -> Result<(), ContextError> { - let mut ibc_store = self.ibc_store.lock(); - - let client_record = ibc_store - .clients - .entry(consensus_state_path.client_id) - .or_insert(MockClientRecord { - consensus_states: Default::default(), - client_state: Default::default(), - }); - - let height = Height::new( - consensus_state_path.revision_number, - consensus_state_path.revision_height, - ) - .expect("Never fails"); - - client_record.consensus_states.remove(&height); - + let client_update_time_path = ClientUpdateTimePath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + ); + self.ibc_store + .client_processed_times + .set(client_update_time_path, host_timestamp) + .map_err(|_| ClientError::Other { + description: "store update error".into(), + })?; + let client_update_height_path = ClientUpdateHeightPath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + ); + self.ibc_store + .client_processed_heights + .set(client_update_height_path, host_height) + .map_err(|_| ClientError::Other { + description: "store update error".into(), + })?; Ok(()) } + /// Delete the update metadata associated with the client at the specified + /// height. fn delete_update_meta( &mut self, client_id: ClientId, height: Height, ) -> Result<(), ContextError> { - let key = (client_id.clone(), height); - let mut ibc_store = self.ibc_store.lock(); - ibc_store.client_processed_times.remove(&key); - ibc_store.client_processed_heights.remove(&key); + let client_update_time_path = ClientUpdateTimePath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + ); + self.ibc_store + .client_processed_times + .delete(client_update_time_path); + let client_update_height_path = ClientUpdateHeightPath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + ); + self.ibc_store + .client_processed_heights + .delete(client_update_height_path); Ok(()) } - fn store_update_meta( + fn delete_consensus_state( &mut self, - client_id: ClientId, - height: Height, - host_timestamp: Timestamp, - host_height: Height, + consensus_state_path: ClientConsensusStatePath, ) -> Result<(), ContextError> { - let mut ibc_store = self.ibc_store.lock(); - ibc_store - .client_processed_times - .insert((client_id.clone(), height), host_timestamp); - ibc_store - .client_processed_heights - .insert((client_id, height), host_height); + self.ibc_store + .consensus_state_store + .delete(consensus_state_path); Ok(()) } } + +impl TmCommonContext for MockGenericContext +where + S: ProvableStore + Debug, +{ + type ConversionError = &'static str; + type AnyConsensusState = AnyConsensusState; + + fn host_timestamp(&self) -> Result { + ValidationContext::host_timestamp(self) + } + + fn host_height(&self) -> Result { + ValidationContext::host_height(self) + } + + fn consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result { + ValidationContext::consensus_state(self, client_cons_state_path) + } + + fn consensus_state_heights(&self, client_id: &ClientId) -> Result, ContextError> { + let path = format!("clients/{}/consensusStates", client_id) + .try_into() + .map_err(|_| ClientError::Other { + description: "Invalid consensus state path".into(), + })?; + + self.ibc_store + .consensus_state_store + .get_keys(&path) + .into_iter() + .flat_map(|path| { + if let Ok(Path::ClientConsensusState(consensus_path)) = path.try_into() { + Some(consensus_path) + } else { + None + } + }) + .map(|consensus_path| { + let height = Height::new( + consensus_path.revision_number, + consensus_path.revision_height, + )?; + Ok(height) + }) + .collect() + } +} + +impl TmValidationContext for MockGenericContext +where + S: ProvableStore + Debug, +{ + fn next_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError> { + let path = format!("clients/{client_id}/consensusStates") + .try_into() + .unwrap(); // safety - path must be valid since ClientId and height are valid Identifiers + + let keys = self.ibc_store.store.get_keys(&path); + let found_path = keys.into_iter().find_map(|path| { + if let Ok(Path::ClientConsensusState(path)) = path.try_into() { + if height + > &Height::new(path.revision_number, path.revision_height).expect("no error") + { + return Some(path); + } + } + None + }); + + if let Some(path) = found_path { + let consensus_state = self + .ibc_store + .consensus_state_store + .get(StoreHeight::Pending, &path) + .ok_or(ClientError::ConsensusStateNotFound { + client_id: client_id.clone(), + height: *height, + })?; + + Ok(Some(consensus_state)) + } else { + Ok(None) + } + } + + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: &Height, + ) -> Result, ContextError> { + let path = format!("clients/{client_id}/consensusStates") + .try_into() + .unwrap(); // safety - path must be valid since ClientId and height are valid Identifiers + + let keys = self.ibc_store.store.get_keys(&path); + let pos = keys.iter().position(|path| { + if let Ok(Path::ClientConsensusState(path)) = path.clone().try_into() { + height + >= &Height::new(path.revision_number, path.revision_height).expect("no error") + } else { + false + } + }); + + if let Some(pos) = pos { + if pos > 0 { + let prev_path = match keys[pos - 1].clone().try_into() { + Ok(Path::ClientConsensusState(p)) => p, + _ => unreachable!(), // safety - path retrieved from store + }; + let consensus_state = self + .ibc_store + .consensus_state_store + .get(StoreHeight::Pending, &prev_path) + .ok_or(ClientError::ConsensusStateNotFound { + client_id: client_id.clone(), + height: *height, + })?; + return Ok(Some(consensus_state)); + } + } + Ok(None) + } +} diff --git a/ibc-testkit/src/testapp/ibc/core/core_ctx.rs b/ibc-testkit/src/testapp/ibc/core/core_ctx.rs index 5c561d14e..aaf2df979 100644 --- a/ibc-testkit/src/testapp/ibc/core/core_ctx.rs +++ b/ibc-testkit/src/testapp/ibc/core/core_ctx.rs @@ -1,343 +1,282 @@ //! Implementation of a global context mock. Used in testing handlers of all IBC modules. -use core::ops::Add; +use alloc::fmt::Debug; use core::time::Duration; -use ibc::clients::tendermint::client_state::ClientState; -use ibc::core::channel::types::channel::ChannelEnd; +use basecoin_store::context::ProvableStore; +use basecoin_store::types::height::Height as StoreHeight; +use ibc::core::channel::types::channel::{ChannelEnd, IdentifiedChannelEnd}; use ibc::core::channel::types::commitment::{AcknowledgementCommitment, PacketCommitment}; use ibc::core::channel::types::error::{ChannelError, PacketError}; -use ibc::core::channel::types::packet::Receipt; +use ibc::core::channel::types::packet::{PacketState, Receipt}; +use ibc::core::client::context::consensus_state::ConsensusState; use ibc::core::client::types::error::ClientError; use ibc::core::client::types::Height; use ibc::core::commitment_types::commitment::CommitmentPrefix; use ibc::core::connection::types::error::ConnectionError; -use ibc::core::connection::types::ConnectionEnd; +use ibc::core::connection::types::version::Version as ConnectionVersion; +use ibc::core::connection::types::{ConnectionEnd, IdentifiedConnectionEnd}; use ibc::core::handler::types::error::ContextError; use ibc::core::handler::types::events::IbcEvent; use ibc::core::host::types::identifiers::{ClientId, ConnectionId, Sequence}; use ibc::core::host::types::path::{ - AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, CommitmentPath, - ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, + AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, + CommitmentPath, ConnectionPath, NextChannelSequencePath, NextClientSequencePath, + NextConnectionSequencePath, Path, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; use ibc::core::host::{ExecutionContext, ValidationContext}; use ibc::core::primitives::prelude::*; use ibc::core::primitives::{Signer, Timestamp}; use ibc::primitives::proto::Any; +use ibc::primitives::ToVec; +use ibc_query::core::context::{ProvableContext, QueryContext}; -use super::types::MockContext; -use crate::testapp::ibc::clients::mock::client_state::MockClientState; +use super::types::MockGenericContext; use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState}; +use crate::testapp::ibc::utils::blocks_since; -impl ValidationContext for MockContext { +impl ValidationContext for MockGenericContext +where + S: ProvableStore + Debug, +{ type V = Self; type E = Self; type AnyConsensusState = AnyConsensusState; type AnyClientState = AnyClientState; fn client_state(&self, client_id: &ClientId) -> Result { - match self.ibc_store.lock().clients.get(client_id) { - Some(client_record) => { - client_record - .client_state - .clone() - .ok_or_else(|| ClientError::ClientStateNotFound { - client_id: client_id.clone(), - }) - } - None => Err(ClientError::ClientStateNotFound { + Ok(self + .ibc_store + .client_state_store + .get(StoreHeight::Pending, &ClientStatePath(client_id.clone())) + .ok_or(ClientError::ClientStateNotFound { client_id: client_id.clone(), - }), - } - .map_err(ContextError::ClientError) + })?) } fn decode_client_state(&self, client_state: Any) -> Result { - if let Ok(client_state) = ClientState::try_from(client_state.clone()) { - client_state.inner().validate().map_err(ClientError::from)?; - Ok(client_state.into()) - } else if let Ok(client_state) = MockClientState::try_from(client_state.clone()) { - Ok(client_state.into()) - } else { - Err(ClientError::UnknownClientStateType { - client_state_type: client_state.type_url, - }) - } - .map_err(ContextError::ClientError) + Ok(AnyClientState::try_from(client_state)?) } fn consensus_state( &self, client_cons_state_path: &ClientConsensusStatePath, - ) -> Result { - let client_id = &client_cons_state_path.client_id; + ) -> Result { let height = Height::new( client_cons_state_path.revision_number, client_cons_state_path.revision_height, - )?; - match self.ibc_store.lock().clients.get(client_id) { - Some(client_record) => match client_record.consensus_states.get(&height) { - Some(consensus_state) => Ok(consensus_state.clone()), - None => Err(ClientError::ConsensusStateNotFound { - client_id: client_id.clone(), - height, - }), - }, - None => Err(ClientError::ConsensusStateNotFound { - client_id: client_id.clone(), + ) + .map_err(|_| ClientError::InvalidHeight)?; + let consensus_state = self + .ibc_store + .consensus_state_store + .get(StoreHeight::Pending, client_cons_state_path) + .ok_or(ClientError::ConsensusStateNotFound { + client_id: client_cons_state_path.client_id.clone(), height, - }), - } - .map_err(ContextError::ClientError) + })?; + + Ok(consensus_state) } fn host_height(&self) -> Result { - Ok(self.latest_height()) + // TODO(rano): height sync with block and merkle tree + Ok(self.history.last().expect("atleast one block").height()) } fn host_timestamp(&self) -> Result { - Ok(self - .history - .last() - .expect("history cannot be empty") - .timestamp() - .add(self.block_time) - .expect("Never fails")) + let host_height = self.host_height()?; + let host_cons_state = self.host_consensus_state(&host_height)?; + Ok(host_cons_state.timestamp()) } - fn host_consensus_state(&self, height: &Height) -> Result { - match self.host_block(height) { - Some(block_ref) => Ok(block_ref.clone().into()), - None => Err(ClientError::MissingLocalConsensusState { height: *height }), - } - .map_err(ConnectionError::Client) - .map_err(ContextError::ConnectionError) + fn host_consensus_state( + &self, + height: &Height, + ) -> Result { + // TODO(rano): height sync with block and merkle tree + let height_delta = blocks_since(self.host_height().expect("no error"), *height) + .expect("no error") as usize; + + let index = self + .history + .len() + .checked_sub(1 + height_delta) + .ok_or(ClientError::MissingLocalConsensusState { height: *height })?; + + let consensus_state = self.history[index].clone().into(); + Ok(consensus_state) } fn client_counter(&self) -> Result { - Ok(self.ibc_store.lock().client_ids_counter) + Ok(self + .ibc_store + .client_counter + .get(StoreHeight::Pending, &NextClientSequencePath) + .ok_or(ClientError::Other { + description: "client counter not found".into(), + })?) } - fn connection_end(&self, cid: &ConnectionId) -> Result { - match self.ibc_store.lock().connections.get(cid) { - Some(connection_end) => Ok(connection_end.clone()), - None => Err(ConnectionError::ConnectionNotFound { - connection_id: cid.clone(), - }), - } - .map_err(ContextError::ConnectionError) + fn connection_end(&self, conn_id: &ConnectionId) -> Result { + Ok(self + .ibc_store + .connection_end_store + .get(StoreHeight::Pending, &ConnectionPath::new(conn_id)) + .ok_or(ConnectionError::ConnectionNotFound { + connection_id: conn_id.clone(), + })?) } - fn validate_self_client( - &self, - client_state_of_host_on_counterparty: Any, - ) -> Result<(), ContextError> { - let mock_client_state = MockClientState::try_from(client_state_of_host_on_counterparty) - .map_err(|_| ConnectionError::InvalidClientState { - reason: "client must be a mock client".to_string(), - }) - .map_err(ContextError::ConnectionError)?; - - if mock_client_state.is_frozen() { - return Err(ClientError::ClientFrozen { - description: String::new(), - } - .into()); - } - - let self_chain_id = &self.host_chain_id; - let self_revision_number = self_chain_id.revision_number(); - if self_revision_number != mock_client_state.latest_height().revision_number() { - return Err(ContextError::ConnectionError( - ConnectionError::InvalidClientState { - reason: format!( - "client is not in the same revision as the chain. expected: {}, got: {}", - self_revision_number, - mock_client_state.latest_height().revision_number() - ), - }, - )); - } - - let host_current_height = self.latest_height().increment(); - if mock_client_state.latest_height() >= host_current_height { - return Err(ContextError::ConnectionError( - ConnectionError::InvalidClientState { - reason: format!( - "client has latest height {} greater than or equal to chain height {}", - mock_client_state.latest_height(), - host_current_height - ), - }, - )); - } - + fn validate_self_client(&self, _counterparty_client_state: Any) -> Result<(), ContextError> { Ok(()) } fn commitment_prefix(&self) -> CommitmentPrefix { - CommitmentPrefix::try_from(b"mock".to_vec()).expect("Never fails") + // this is prefix of ibc store + // using default, as in our mock context, we don't store any other data + CommitmentPrefix::default() } fn connection_counter(&self) -> Result { - Ok(self.ibc_store.lock().connection_ids_counter) + Ok(self + .ibc_store + .conn_counter + .get(StoreHeight::Pending, &NextConnectionSequencePath) + .ok_or(ConnectionError::Other { + description: "connection counter not found".into(), + })?) } - fn channel_end(&self, chan_end_path: &ChannelEndPath) -> Result { - let port_id = &chan_end_path.0; - let channel_id = &chan_end_path.1; + fn get_compatible_versions(&self) -> Vec { + vec![ConnectionVersion::default()] + } - match self + fn channel_end(&self, channel_end_path: &ChannelEndPath) -> Result { + let channel_end = self .ibc_store - .lock() - .channels - .get(port_id) - .and_then(|map| map.get(channel_id)) - { - Some(channel_end) => Ok(channel_end.clone()), - None => Err(ChannelError::ChannelNotFound { - port_id: port_id.clone(), - channel_id: channel_id.clone(), - }), - } - .map_err(ContextError::ChannelError) + .channel_end_store + .get( + StoreHeight::Pending, + &ChannelEndPath::new(&channel_end_path.0, &channel_end_path.1), + ) + .ok_or(ChannelError::MissingChannel)?; + Ok(channel_end) } fn get_next_sequence_send( &self, seq_send_path: &SeqSendPath, ) -> Result { - let port_id = &seq_send_path.0; - let channel_id = &seq_send_path.1; - - match self + let seq_send = self .ibc_store - .lock() - .next_sequence_send - .get(port_id) - .and_then(|map| map.get(channel_id)) - { - Some(sequence) => Ok(*sequence), - None => Err(PacketError::MissingNextSendSeq { - port_id: port_id.clone(), - channel_id: channel_id.clone(), - }), - } - .map_err(ContextError::PacketError) + .send_sequence_store + .get( + StoreHeight::Pending, + &SeqSendPath::new(&seq_send_path.0, &seq_send_path.1), + ) + .ok_or(PacketError::ImplementationSpecific)?; + Ok(seq_send) } fn get_next_sequence_recv( &self, seq_recv_path: &SeqRecvPath, ) -> Result { - let port_id = &seq_recv_path.0; - let channel_id = &seq_recv_path.1; - - match self + let seq_recv = self .ibc_store - .lock() - .next_sequence_recv - .get(port_id) - .and_then(|map| map.get(channel_id)) - { - Some(sequence) => Ok(*sequence), - None => Err(PacketError::MissingNextRecvSeq { - port_id: port_id.clone(), - channel_id: channel_id.clone(), - }), - } - .map_err(ContextError::PacketError) + .recv_sequence_store + .get( + StoreHeight::Pending, + &SeqRecvPath::new(&seq_recv_path.0, &seq_recv_path.1), + ) + .ok_or(PacketError::ImplementationSpecific)?; + Ok(seq_recv) } fn get_next_sequence_ack(&self, seq_ack_path: &SeqAckPath) -> Result { - let port_id = &seq_ack_path.0; - let channel_id = &seq_ack_path.1; - - match self + let seq_ack = self .ibc_store - .lock() - .next_sequence_ack - .get(port_id) - .and_then(|map| map.get(channel_id)) - { - Some(sequence) => Ok(*sequence), - None => Err(PacketError::MissingNextAckSeq { - port_id: port_id.clone(), - channel_id: channel_id.clone(), - }), - } - .map_err(ContextError::PacketError) + .ack_sequence_store + .get( + StoreHeight::Pending, + &SeqAckPath::new(&seq_ack_path.0, &seq_ack_path.1), + ) + .ok_or(PacketError::ImplementationSpecific)?; + Ok(seq_ack) } fn get_packet_commitment( &self, commitment_path: &CommitmentPath, ) -> Result { - let port_id = &commitment_path.port_id; - let channel_id = &commitment_path.channel_id; - let seq = &commitment_path.sequence; - - match self + let commitment = self .ibc_store - .lock() - .packet_commitment - .get(port_id) - .and_then(|map| map.get(channel_id)) - .and_then(|map| map.get(seq)) - { - Some(commitment) => Ok(commitment.clone()), - None => Err(PacketError::PacketCommitmentNotFound { sequence: *seq }), - } - .map_err(ContextError::PacketError) + .packet_commitment_store + .get( + StoreHeight::Pending, + &CommitmentPath::new( + &commitment_path.port_id, + &commitment_path.channel_id, + commitment_path.sequence, + ), + ) + .ok_or(PacketError::ImplementationSpecific)?; + Ok(commitment) } fn get_packet_receipt(&self, receipt_path: &ReceiptPath) -> Result { - let port_id = &receipt_path.port_id; - let channel_id = &receipt_path.channel_id; - let seq = &receipt_path.sequence; - - match self + let receipt = self .ibc_store - .lock() - .packet_receipt - .get(port_id) - .and_then(|map| map.get(channel_id)) - .and_then(|map| map.get(seq)) - { - Some(receipt) => Ok(receipt.clone()), - None => Err(PacketError::PacketReceiptNotFound { sequence: *seq }), - } - .map_err(ContextError::PacketError) + .packet_receipt_store + .is_path_set( + StoreHeight::Pending, + &ReceiptPath::new( + &receipt_path.port_id, + &receipt_path.channel_id, + receipt_path.sequence, + ), + ) + .then_some(Receipt::Ok) + .ok_or(PacketError::PacketReceiptNotFound { + sequence: receipt_path.sequence, + })?; + Ok(receipt) } fn get_packet_acknowledgement( &self, ack_path: &AckPath, ) -> Result { - let port_id = &ack_path.port_id; - let channel_id = &ack_path.channel_id; - let seq = &ack_path.sequence; - - match self + let ack = self .ibc_store - .lock() - .packet_acknowledgement - .get(port_id) - .and_then(|map| map.get(channel_id)) - .and_then(|map| map.get(seq)) - { - Some(ack) => Ok(ack.clone()), - None => Err(PacketError::PacketAcknowledgementNotFound { sequence: *seq }), - } - .map_err(ContextError::PacketError) - } - + .packet_ack_store + .get( + StoreHeight::Pending, + &AckPath::new(&ack_path.port_id, &ack_path.channel_id, ack_path.sequence), + ) + .ok_or(PacketError::PacketAcknowledgementNotFound { + sequence: ack_path.sequence, + })?; + Ok(ack) + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. fn channel_counter(&self) -> Result { - Ok(self.ibc_store.lock().channel_ids_counter) + Ok(self + .ibc_store + .channel_counter + .get(StoreHeight::Pending, &NextChannelSequencePath) + .ok_or(ChannelError::Other { + description: "channel counter not found".into(), + })?) } + /// Returns the maximum expected time per block fn max_expected_time_per_block(&self) -> Duration { - self.block_time + Duration::from_secs(8) } fn validate_message_signer(&self, _signer: &Signer) -> Result<(), ContextError> { @@ -349,44 +288,456 @@ impl ValidationContext for MockContext { } } -impl ExecutionContext for MockContext { - fn get_client_execution_context(&mut self) -> &mut Self::E { - self +/// Trait to provide proofs in gRPC service blanket implementations. +impl ProvableContext for MockGenericContext +where + S: ProvableStore + Debug, +{ + /// Returns the proof for the given [`Height`] and [`Path`] + fn get_proof(&self, height: Height, path: &Path) -> Option> { + self.ibc_store + .store + .get_proof(height.revision_height().into(), &path.to_string().into()) + .map(|p| p.to_vec()) + } +} + +/// Trait to complete the gRPC service blanket implementations. +impl QueryContext for MockGenericContext +where + S: ProvableStore + Debug, +{ + /// Returns the list of all client states. + fn client_states(&self) -> Result, ContextError> { + let path = "clients".to_owned().into(); + + self.ibc_store + .client_state_store + .get_keys(&path) + .into_iter() + .filter_map(|path| { + if let Ok(Path::ClientState(client_path)) = path.try_into() { + Some(client_path) + } else { + None + } + }) + .map(|client_state_path| { + let client_state = self + .ibc_store + .client_state_store + .get(StoreHeight::Pending, &client_state_path) + .ok_or_else(|| ClientError::ClientStateNotFound { + client_id: client_state_path.0.clone(), + })?; + Ok((client_state_path.0, client_state)) + }) + .collect() + } + + /// Returns the list of all consensus states of the given client. + fn consensus_states( + &self, + client_id: &ClientId, + ) -> Result, ContextError> { + let path = format!("clients/{}/consensusStates", client_id) + .try_into() + .map_err(|_| ClientError::Other { + description: "Invalid consensus state path".into(), + })?; + + self.ibc_store + .consensus_state_store + .get_keys(&path) + .into_iter() + .flat_map(|path| { + if let Ok(Path::ClientConsensusState(consensus_path)) = path.try_into() { + Some(consensus_path) + } else { + None + } + }) + .map(|consensus_path| { + let height = Height::new( + consensus_path.revision_number, + consensus_path.revision_height, + )?; + let client_state = self + .ibc_store + .consensus_state_store + .get(StoreHeight::Pending, &consensus_path) + .ok_or({ + ClientError::ConsensusStateNotFound { + client_id: consensus_path.client_id, + height, + } + })?; + Ok((height, client_state)) + }) + .collect() + } + + /// Returns the list of heights at which the consensus state of the given client was updated. + fn consensus_state_heights(&self, client_id: &ClientId) -> Result, ContextError> { + let path = format!("clients/{}/consensusStates", client_id) + .try_into() + .map_err(|_| ClientError::Other { + description: "Invalid consensus state path".into(), + })?; + + self.ibc_store + .consensus_state_store + .get_keys(&path) + .into_iter() + .flat_map(|path| { + if let Ok(Path::ClientConsensusState(consensus_path)) = path.try_into() { + Some(consensus_path) + } else { + None + } + }) + .map(|consensus_path| { + Ok(Height::new( + consensus_path.revision_number, + consensus_path.revision_height, + )?) + }) + .collect::, _>>() + } + + /// Connections queries all the IBC connections of a chain. + fn connection_ends(&self) -> Result, ContextError> { + let path = "connections".to_owned().into(); + + self.ibc_store + .connection_end_store + .get_keys(&path) + .into_iter() + .flat_map(|path| { + if let Ok(Path::Connection(connection_path)) = path.try_into() { + Some(connection_path) + } else { + None + } + }) + .map(|connection_path| { + let connection_end = self + .ibc_store + .connection_end_store + .get(StoreHeight::Pending, &connection_path) + .ok_or_else(|| ConnectionError::ConnectionNotFound { + connection_id: connection_path.0.clone(), + })?; + Ok(IdentifiedConnectionEnd { + connection_id: connection_path.0, + connection_end, + }) + }) + .collect() + } + + /// ClientConnections queries all the connection paths associated with a client. + fn client_connection_ends( + &self, + client_id: &ClientId, + ) -> Result, ContextError> { + let client_connection_path = ClientConnectionPath::new(client_id); + + Ok(self + .ibc_store + .connection_ids_store + .get(StoreHeight::Pending, &client_connection_path) + .unwrap_or_default()) + } + + /// Channels queries all the IBC channels of a chain. + fn channel_ends(&self) -> Result, ContextError> { + let path = "channelEnds".to_owned().into(); + + self.ibc_store + .channel_end_store + .get_keys(&path) + .into_iter() + .flat_map(|path| { + if let Ok(Path::ChannelEnd(channel_path)) = path.try_into() { + Some(channel_path) + } else { + None + } + }) + .map(|channel_path| { + let channel_end = self + .ibc_store + .channel_end_store + .get(StoreHeight::Pending, &channel_path) + .ok_or_else(|| ChannelError::ChannelNotFound { + port_id: channel_path.0.clone(), + channel_id: channel_path.1.clone(), + })?; + Ok(IdentifiedChannelEnd { + port_id: channel_path.0, + channel_id: channel_path.1, + channel_end, + }) + }) + .collect() + } + + /// PacketCommitments returns all the packet commitments associated with a channel. + fn packet_commitments( + &self, + channel_end_path: &ChannelEndPath, + ) -> Result, ContextError> { + let path = format!( + "commitments/ports/{}/channels/{}/sequences", + channel_end_path.0, channel_end_path.1 + ) + .try_into() + .map_err(|_| PacketError::Other { + description: "Invalid commitment path".into(), + })?; + + self.ibc_store + .packet_commitment_store + .get_keys(&path) + .into_iter() + .flat_map(|path| { + if let Ok(Path::Commitment(commitment_path)) = path.try_into() { + Some(commitment_path) + } else { + None + } + }) + .filter(|commitment_path| { + self.ibc_store + .packet_commitment_store + .get(StoreHeight::Pending, commitment_path) + .is_some() + }) + .map(|commitment_path| { + self.get_packet_commitment(&commitment_path) + .map(|packet| PacketState { + seq: commitment_path.sequence, + port_id: commitment_path.port_id, + chan_id: commitment_path.channel_id, + data: packet.as_ref().into(), + }) + }) + .collect::, _>>() + } + + /// PacketAcknowledgements returns all the packet acknowledgements associated with a channel. + /// Returns all the packet acknowledgements if sequences is empty. + fn packet_acknowledgements( + &self, + channel_end_path: &ChannelEndPath, + sequences: impl ExactSizeIterator, + ) -> Result, ContextError> { + let collected_paths: Vec<_> = if sequences.len() == 0 { + // if sequences is empty, return all the acks + let ack_path_prefix = format!( + "acks/ports/{}/channels/{}/sequences", + channel_end_path.0, channel_end_path.1 + ) + .try_into() + .map_err(|_| PacketError::Other { + description: "Invalid ack path".into(), + })?; + + self.ibc_store + .packet_ack_store + .get_keys(&ack_path_prefix) + .into_iter() + .flat_map(|path| { + if let Ok(Path::Ack(ack_path)) = path.try_into() { + Some(ack_path) + } else { + None + } + }) + .collect() + } else { + sequences + .into_iter() + .map(|seq| AckPath::new(&channel_end_path.0, &channel_end_path.1, seq)) + .collect() + }; + + collected_paths + .into_iter() + .filter(|ack_path| { + self.ibc_store + .packet_ack_store + .get(StoreHeight::Pending, ack_path) + .is_some() + }) + .map(|ack_path| { + self.get_packet_acknowledgement(&ack_path) + .map(|packet| PacketState { + seq: ack_path.sequence, + port_id: ack_path.port_id, + chan_id: ack_path.channel_id, + data: packet.as_ref().into(), + }) + }) + .collect::, _>>() + } + + /// UnreceivedPackets returns all the unreceived IBC packets associated with + /// a channel and sequences. + fn unreceived_packets( + &self, + channel_end_path: &ChannelEndPath, + sequences: impl ExactSizeIterator, + ) -> Result, ContextError> { + // QUESTION. Currently only works for unordered channels; ordered channels + // don't use receipts. However, ibc-go does it this way. Investigate if + // this query only ever makes sense on unordered channels. + + Ok(sequences + .into_iter() + .map(|seq| ReceiptPath::new(&channel_end_path.0, &channel_end_path.1, seq)) + .filter(|receipt_path| { + self.ibc_store + .packet_receipt_store + .get(StoreHeight::Pending, receipt_path) + .is_none() + }) + .map(|receipts_path| receipts_path.sequence) + .collect()) + } + + /// UnreceivedAcks returns all the unreceived IBC acknowledgements associated with a channel and sequences. + /// Returns all the unreceived acks if sequences is empty. + fn unreceived_acks( + &self, + channel_end_path: &ChannelEndPath, + sequences: impl ExactSizeIterator, + ) -> Result, ContextError> { + let collected_paths: Vec<_> = if sequences.len() == 0 { + // if sequences is empty, return all the acks + let commitment_path_prefix = format!( + "commitments/ports/{}/channels/{}/sequences", + channel_end_path.0, channel_end_path.1 + ) + .try_into() + .map_err(|_| PacketError::Other { + description: "Invalid commitment path".into(), + })?; + + self.ibc_store + .packet_commitment_store + .get_keys(&commitment_path_prefix) + .into_iter() + .flat_map(|path| { + if let Ok(Path::Commitment(commitment_path)) = path.try_into() { + Some(commitment_path) + } else { + None + } + }) + .collect() + } else { + sequences + .into_iter() + .map(|seq| CommitmentPath::new(&channel_end_path.0, &channel_end_path.1, seq)) + .collect() + }; + + Ok(collected_paths + .into_iter() + .filter(|commitment_path: &CommitmentPath| -> bool { + self.ibc_store + .packet_commitment_store + .get(StoreHeight::Pending, commitment_path) + .is_some() + }) + .map(|commitment_path| commitment_path.sequence) + .collect()) } +} +impl ExecutionContext for MockGenericContext +where + S: ProvableStore + Debug, +{ + /// Called upon client creation. + /// Increases the counter which keeps track of how many clients have been created. + /// Should never fail. fn increase_client_counter(&mut self) -> Result<(), ContextError> { - self.ibc_store.lock().client_ids_counter += 1; + let current_sequence = self + .ibc_store + .client_counter + .get(StoreHeight::Pending, &NextClientSequencePath) + .ok_or(ClientError::Other { + description: "client counter not found".into(), + })?; + + self.ibc_store + .client_counter + .set(NextClientSequencePath, current_sequence + 1) + .map_err(|e| ClientError::Other { + description: format!("client counter update failed: {e:?}"), + })?; + Ok(()) } + /// Stores the given connection_end at path fn store_connection( &mut self, connection_path: &ConnectionPath, connection_end: ConnectionEnd, ) -> Result<(), ContextError> { - let connection_id = connection_path.0.clone(); self.ibc_store - .lock() - .connections - .insert(connection_id, connection_end); + .connection_end_store + .set(connection_path.clone(), connection_end) + .map_err(|_| ConnectionError::Other { + description: "Connection end store error".to_string(), + })?; Ok(()) } + /// Stores the given connection_id at a path associated with the client_id. fn store_connection_to_client( &mut self, client_connection_path: &ClientConnectionPath, conn_id: ConnectionId, ) -> Result<(), ContextError> { - let client_id = client_connection_path.0.clone(); + let mut conn_ids: Vec = self + .ibc_store + .connection_ids_store + .get(StoreHeight::Pending, client_connection_path) + .unwrap_or_default(); + conn_ids.push(conn_id); self.ibc_store - .lock() - .client_connections - .insert(client_id, conn_id); + .connection_ids_store + .set(client_connection_path.clone(), conn_ids) + .map_err(|_| ConnectionError::Other { + description: "Connection ids store error".to_string(), + })?; Ok(()) } + /// Called upon connection identifier creation (Init or Try process). + /// Increases the counter which keeps track of how many connections have been created. + /// Should never fail. fn increase_connection_counter(&mut self) -> Result<(), ContextError> { - self.ibc_store.lock().connection_ids_counter += 1; + let current_sequence = self + .ibc_store + .conn_counter + .get(StoreHeight::Pending, &NextConnectionSequencePath) + .ok_or(ConnectionError::Other { + description: "connection counter not found".into(), + })?; + + self.ibc_store + .conn_counter + .set(NextConnectionSequencePath, current_sequence + 1) + .map_err(|e| ConnectionError::Other { + description: format!("connection counter update failed: {e:?}"), + })?; + Ok(()) } @@ -396,42 +747,26 @@ impl ExecutionContext for MockContext { commitment: PacketCommitment, ) -> Result<(), ContextError> { self.ibc_store - .lock() - .packet_commitment - .entry(commitment_path.port_id.clone()) - .or_default() - .entry(commitment_path.channel_id.clone()) - .or_default() - .insert(commitment_path.sequence, commitment); + .packet_commitment_store + .set(commitment_path.clone(), commitment) + .map_err(|_| PacketError::ImplementationSpecific)?; Ok(()) } - fn delete_packet_commitment( - &mut self, - commitment_path: &CommitmentPath, - ) -> Result<(), ContextError> { - self.ibc_store - .lock() - .packet_commitment - .get_mut(&commitment_path.port_id) - .and_then(|map| map.get_mut(&commitment_path.channel_id)) - .and_then(|map| map.remove(&commitment_path.sequence)); + fn delete_packet_commitment(&mut self, key: &CommitmentPath) -> Result<(), ContextError> { + self.ibc_store.packet_commitment_store.delete(key.clone()); Ok(()) } fn store_packet_receipt( &mut self, - path: &ReceiptPath, - receipt: Receipt, + receipt_path: &ReceiptPath, + _receipt: Receipt, ) -> Result<(), ContextError> { self.ibc_store - .lock() - .packet_receipt - .entry(path.port_id.clone()) - .or_default() - .entry(path.channel_id.clone()) - .or_default() - .insert(path.sequence, receipt); + .packet_receipt_store + .set_path(receipt_path.clone()) + .map_err(|_| PacketError::ImplementationSpecific)?; Ok(()) } @@ -440,49 +775,30 @@ impl ExecutionContext for MockContext { ack_path: &AckPath, ack_commitment: AcknowledgementCommitment, ) -> Result<(), ContextError> { - let port_id = ack_path.port_id.clone(); - let channel_id = ack_path.channel_id.clone(); - let seq = ack_path.sequence; - self.ibc_store - .lock() - .packet_acknowledgement - .entry(port_id) - .or_default() - .entry(channel_id) - .or_default() - .insert(seq, ack_commitment); + .packet_ack_store + .set(ack_path.clone(), ack_commitment) + .map_err(|_| PacketError::ImplementationSpecific)?; Ok(()) } fn delete_packet_acknowledgement(&mut self, ack_path: &AckPath) -> Result<(), ContextError> { - let port_id = ack_path.port_id.clone(); - let channel_id = ack_path.channel_id.clone(); - let sequence = ack_path.sequence; - - self.ibc_store - .lock() - .packet_acknowledgement - .get_mut(&port_id) - .and_then(|map| map.get_mut(&channel_id)) - .and_then(|map| map.remove(&sequence)); + self.ibc_store.packet_ack_store.delete(ack_path.clone()); Ok(()) } + /// Stores the given channel_end at a path associated with the port_id and channel_id. fn store_channel( &mut self, channel_end_path: &ChannelEndPath, channel_end: ChannelEnd, ) -> Result<(), ContextError> { - let port_id = channel_end_path.0.clone(); - let channel_id = channel_end_path.1.clone(); - self.ibc_store - .lock() - .channels - .entry(port_id) - .or_default() - .insert(channel_id, channel_end); + .channel_end_store + .set(channel_end_path.clone(), channel_end) + .map_err(|_| ChannelError::Other { + description: "Channel end store error".to_string(), + })?; Ok(()) } @@ -491,15 +807,10 @@ impl ExecutionContext for MockContext { seq_send_path: &SeqSendPath, seq: Sequence, ) -> Result<(), ContextError> { - let port_id = seq_send_path.0.clone(); - let channel_id = seq_send_path.1.clone(); - self.ibc_store - .lock() - .next_sequence_send - .entry(port_id) - .or_default() - .insert(channel_id, seq); + .send_sequence_store + .set(seq_send_path.clone(), seq) + .map_err(|_| PacketError::ImplementationSpecific)?; Ok(()) } @@ -508,15 +819,10 @@ impl ExecutionContext for MockContext { seq_recv_path: &SeqRecvPath, seq: Sequence, ) -> Result<(), ContextError> { - let port_id = seq_recv_path.0.clone(); - let channel_id = seq_recv_path.1.clone(); - self.ibc_store - .lock() - .next_sequence_recv - .entry(port_id) - .or_default() - .insert(channel_id, seq); + .recv_sequence_store + .set(seq_recv_path.clone(), seq) + .map_err(|_| PacketError::ImplementationSpecific)?; Ok(()) } @@ -525,30 +831,43 @@ impl ExecutionContext for MockContext { seq_ack_path: &SeqAckPath, seq: Sequence, ) -> Result<(), ContextError> { - let port_id = seq_ack_path.0.clone(); - let channel_id = seq_ack_path.1.clone(); - self.ibc_store - .lock() - .next_sequence_ack - .entry(port_id) - .or_default() - .insert(channel_id, seq); + .ack_sequence_store + .set(seq_ack_path.clone(), seq) + .map_err(|_| PacketError::ImplementationSpecific)?; Ok(()) } fn increase_channel_counter(&mut self) -> Result<(), ContextError> { - self.ibc_store.lock().channel_ids_counter += 1; + let current_sequence = self + .ibc_store + .channel_counter + .get(StoreHeight::Pending, &NextChannelSequencePath) + .ok_or(ChannelError::Other { + description: "channel counter not found".into(), + })?; + + self.ibc_store + .channel_counter + .set(NextChannelSequencePath, current_sequence + 1) + .map_err(|e| ChannelError::Other { + description: format!("channel counter update failed: {e:?}"), + })?; + Ok(()) } fn emit_ibc_event(&mut self, event: IbcEvent) -> Result<(), ContextError> { - self.ibc_store.lock().events.push(event); + self.ibc_store.events.lock().push(event); Ok(()) } fn log_message(&mut self, message: String) -> Result<(), ContextError> { - self.ibc_store.lock().logs.push(message); + self.ibc_store.logs.lock().push(message); Ok(()) } + + fn get_client_execution_context(&mut self) -> &mut Self::E { + self + } } diff --git a/ibc-testkit/src/testapp/ibc/core/types.rs b/ibc-testkit/src/testapp/ibc/core/types.rs index 603d6d00d..d635e8951 100644 --- a/ibc-testkit/src/testapp/ibc/core/types.rs +++ b/ibc-testkit/src/testapp/ibc/core/types.rs @@ -1,17 +1,20 @@ //! Implementation of a global context mock. Used in testing handlers of all IBC modules. -use alloc::collections::btree_map::BTreeMap; +use alloc::collections::BTreeMap; use alloc::sync::Arc; use core::cmp::min; use core::fmt::Debug; use core::ops::{Add, Sub}; use core::time::Duration; +use basecoin_store::context::ProvableStore; +use basecoin_store::impls::{GrowingStore, InMemoryStore, RevertibleStore, SharedStore}; +use basecoin_store::types::{BinStore, JsonStore, ProtobufStore, TypedSet, TypedStore}; use ibc::clients::tendermint::client_state::ClientState as TmClientState; use ibc::clients::tendermint::types::TENDERMINT_CLIENT_TYPE; use ibc::core::channel::types::channel::ChannelEnd; use ibc::core::channel::types::commitment::{AcknowledgementCommitment, PacketCommitment}; -use ibc::core::channel::types::packet::Receipt; +use ibc::core::client::context::ClientExecutionContext; use ibc::core::client::types::Height; use ibc::core::connection::types::ConnectionEnd; use ibc::core::entrypoint::dispatch; @@ -20,15 +23,24 @@ use ibc::core::handler::types::msgs::MsgEnvelope; use ibc::core::host::types::identifiers::{ ChainId, ChannelId, ClientId, ClientType, ConnectionId, PortId, Sequence, }; -use ibc::core::host::ValidationContext; +use ibc::core::host::types::path::{ + AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, + ClientUpdateHeightPath, ClientUpdateTimePath, CommitmentPath, ConnectionPath, + NextChannelSequencePath, NextClientSequencePath, NextConnectionSequencePath, ReceiptPath, + SeqAckPath, SeqRecvPath, SeqSendPath, +}; +use ibc::core::host::{ExecutionContext, ValidationContext}; use ibc::core::primitives::prelude::*; use ibc::core::primitives::Timestamp; use ibc::core::router::router::Router; +use ibc_proto::google::protobuf::Any; +use ibc_proto::ibc::core::channel::v1::Channel as RawChannelEnd; +use ibc_proto::ibc::core::client::v1::Height as RawHeight; +use ibc_proto::ibc::core::connection::v1::ConnectionEnd as RawConnectionEnd; use parking_lot::Mutex; use tendermint_testgen::Validator as TestgenValidator; use typed_builder::TypedBuilder; -use super::client_ctx::{MockClientRecord, PortChannelIdMap}; use crate::fixtures::clients::tendermint::ClientStateConfig as TmClientStateConfig; use crate::fixtures::core::context::MockContextConfig; use crate::hosts::block::{HostBlock, HostType}; @@ -39,69 +51,124 @@ use crate::testapp::ibc::clients::mock::client_state::{ use crate::testapp::ibc::clients::mock::consensus_state::MockConsensusState; use crate::testapp::ibc::clients::mock::header::MockHeader; use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState}; +use crate::testapp::ibc::utils::blocks_since; pub const DEFAULT_BLOCK_TIME_SECS: u64 = 3; /// An object that stores all IBC related data. -#[derive(Clone, Debug, Default)] -pub struct MockIbcStore { - /// The set of all clients, indexed by their id. - pub clients: BTreeMap, - - /// Tracks the processed time for clients header updates - pub client_processed_times: BTreeMap<(ClientId, Height), Timestamp>, - - /// Tracks the processed height for the clients - pub client_processed_heights: BTreeMap<(ClientId, Height), Height>, - - /// Counter for the client identifiers, necessary for `increase_client_counter` and the - /// `client_counter` methods. - pub client_ids_counter: u64, - - /// Association between client ids and connection ids. - pub client_connections: BTreeMap, - - /// All the connections in the store. - pub connections: BTreeMap, - - /// Counter for connection identifiers (see `increase_connection_counter`). - pub connection_ids_counter: u64, - - /// Association between connection ids and channel ids. - pub connection_channels: BTreeMap>, - - /// Counter for channel identifiers (see `increase_channel_counter`). - pub channel_ids_counter: u64, - - /// All the channels in the store. TODO Make new key PortId X ChannelId - pub channels: PortChannelIdMap, - - /// Tracks the sequence number for the next packet to be sent. - pub next_sequence_send: PortChannelIdMap, +#[derive(Debug)] +pub struct MockIbcStore +where + S: ProvableStore + Debug, +{ + /// Handle to store instance. + /// The module is guaranteed exclusive access to all paths in the store key-space. + pub store: SharedStore, + /// A typed-store for next client counter sequence + pub client_counter: JsonStore, NextClientSequencePath, u64>, + /// A typed-store for next connection counter sequence + pub conn_counter: JsonStore, NextConnectionSequencePath, u64>, + /// A typed-store for next channel counter sequence + pub channel_counter: JsonStore, NextChannelSequencePath, u64>, + /// Tracks the processed time for client updates + pub client_processed_times: JsonStore, ClientUpdateTimePath, Timestamp>, + /// A typed-store to track the processed height for client updates + pub client_processed_heights: + ProtobufStore, ClientUpdateHeightPath, Height, RawHeight>, + /// A typed-store for AnyClientState + pub client_state_store: ProtobufStore, ClientStatePath, AnyClientState, Any>, + /// A typed-store for AnyConsensusState + pub consensus_state_store: + ProtobufStore, ClientConsensusStatePath, AnyConsensusState, Any>, + /// A typed-store for ConnectionEnd + pub connection_end_store: + ProtobufStore, ConnectionPath, ConnectionEnd, RawConnectionEnd>, + /// A typed-store for ConnectionIds + pub connection_ids_store: JsonStore, ClientConnectionPath, Vec>, + /// A typed-store for ChannelEnd + pub channel_end_store: ProtobufStore, ChannelEndPath, ChannelEnd, RawChannelEnd>, + /// A typed-store for send sequences + pub send_sequence_store: JsonStore, SeqSendPath, Sequence>, + /// A typed-store for receive sequences + pub recv_sequence_store: JsonStore, SeqRecvPath, Sequence>, + /// A typed-store for ack sequences + pub ack_sequence_store: JsonStore, SeqAckPath, Sequence>, + /// A typed-store for packet commitments + pub packet_commitment_store: BinStore, CommitmentPath, PacketCommitment>, + /// A typed-store for packet receipts + pub packet_receipt_store: TypedSet, ReceiptPath>, + /// A typed-store for packet ack + pub packet_ack_store: BinStore, AckPath, AcknowledgementCommitment>, + /// Map of host consensus states + pub consensus_states: Arc>>, + /// IBC Events + pub events: Arc>>, + /// message logs + pub logs: Arc>>, +} - /// Tracks the sequence number for the next packet to be received. - pub next_sequence_recv: PortChannelIdMap, +impl MockIbcStore +where + S: ProvableStore + Debug, +{ + pub fn new(store: S) -> Self { + let shared_store = SharedStore::new(store); - /// Tracks the sequence number for the next packet to be acknowledged. - pub next_sequence_ack: PortChannelIdMap, + let mut client_counter = TypedStore::new(shared_store.clone()); + let mut conn_counter = TypedStore::new(shared_store.clone()); + let mut channel_counter = TypedStore::new(shared_store.clone()); - pub packet_acknowledgement: PortChannelIdMap>, + client_counter + .set(NextClientSequencePath, 0) + .expect("no error"); - /// Constant-size commitments to packets data fields - pub packet_commitment: PortChannelIdMap>, + conn_counter + .set(NextConnectionSequencePath, 0) + .expect("no error"); - /// Used by unordered channel - pub packet_receipt: PortChannelIdMap>, + channel_counter + .set(NextChannelSequencePath, 0) + .expect("no error"); - /// Emitted IBC events in order - pub events: Vec, + Self { + client_counter, + conn_counter, + channel_counter, + client_processed_times: TypedStore::new(shared_store.clone()), + client_processed_heights: TypedStore::new(shared_store.clone()), + consensus_states: Arc::new(Mutex::new(Default::default())), + client_state_store: TypedStore::new(shared_store.clone()), + consensus_state_store: TypedStore::new(shared_store.clone()), + connection_end_store: TypedStore::new(shared_store.clone()), + connection_ids_store: TypedStore::new(shared_store.clone()), + channel_end_store: TypedStore::new(shared_store.clone()), + send_sequence_store: TypedStore::new(shared_store.clone()), + recv_sequence_store: TypedStore::new(shared_store.clone()), + ack_sequence_store: TypedStore::new(shared_store.clone()), + packet_commitment_store: TypedStore::new(shared_store.clone()), + packet_receipt_store: TypedStore::new(shared_store.clone()), + packet_ack_store: TypedStore::new(shared_store.clone()), + events: Arc::new(Mutex::new(Vec::new())), + logs: Arc::new(Mutex::new(Vec::new())), + store: shared_store, + } + } +} - /// Logs of the IBC module - pub logs: Vec, +impl Default for MockIbcStore +where + S: ProvableStore + Debug + Default, +{ + fn default() -> Self { + Self::new(S::default()) + } } /// A context implementing the dependencies necessary for testing any IBC module. #[derive(Debug)] -pub struct MockContext { +pub struct MockGenericContext +where + S: ProvableStore + Debug, +{ /// The type of host chain underlying this mock context. pub host_chain_type: HostType, @@ -119,9 +186,11 @@ pub struct MockContext { pub block_time: Duration, /// An object that stores all IBC related data. - pub ibc_store: Arc>, + pub ibc_store: MockIbcStore, } +pub type MockContext = MockGenericContext>>; + #[derive(Debug, TypedBuilder)] pub struct MockClientConfig { #[builder(default = ChainId::new("mockZ-1").expect("no error"))] @@ -133,8 +202,8 @@ pub struct MockClientConfig { latest_height: Height, #[builder(default)] consensus_state_heights: Vec, - #[builder(default = Timestamp::now())] - latest_timestamp: Timestamp, + #[builder(default, setter(strip_option))] + latest_timestamp: Option, #[builder(default = Duration::from_secs(64000))] trusting_period: Duration, @@ -147,35 +216,21 @@ pub struct MockClientConfig { /// Returns a MockContext with bare minimum initialization: no clients, no connections and no channels are /// present, and the chain has Height(5). This should be used sparingly, mostly for testing the /// creation of new domain objects. -impl Default for MockContext { +impl Default for MockGenericContext +where + S: ProvableStore + Debug + Default, +{ fn default() -> Self { MockContextConfig::builder().build() } } -/// A manual clone impl is provided because the tests are oblivious to the fact that the `ibc_store` -/// is a shared ptr. -impl Clone for MockContext { - fn clone(&self) -> Self { - let ibc_store = { - let ibc_store = self.ibc_store.lock().clone(); - Arc::new(Mutex::new(ibc_store)) - }; - - Self { - host_chain_type: self.host_chain_type, - host_chain_id: self.host_chain_id.clone(), - max_history_size: self.max_history_size, - history: self.history.clone(), - block_time: self.block_time, - ibc_store, - } - } -} - /// Implementation of internal interface for use in testing. The methods in this interface should /// _not_ be accessible to any Ics handler. -impl MockContext { +impl MockGenericContext +where + S: ProvableStore + Debug, +{ /// Creates a mock context. Parameter `max_history_size` determines how many blocks will /// the chain maintain in its history, which also determines the pruning window. Parameter /// `latest_height` determines the current height of the chain. This context @@ -189,7 +244,10 @@ impl MockContext { host_type: HostType, max_history_size: u64, latest_height: Height, - ) -> Self { + ) -> Self + where + S: Default, + { assert_ne!( max_history_size, 0, "The chain must have a non-zero max_history_size" @@ -212,7 +270,7 @@ impl MockContext { let block_time = Duration::from_secs(DEFAULT_BLOCK_TIME_SECS); let next_block_timestamp = Timestamp::now().add(block_time).expect("Never fails"); - MockContext { + Self { host_chain_type: host_type, host_chain_id: host_id.clone(), max_history_size, @@ -232,7 +290,7 @@ impl MockContext { }) .collect(), block_time, - ibc_store: Arc::new(Mutex::new(MockIbcStore::default())), + ibc_store: MockIbcStore::default(), } } @@ -249,7 +307,10 @@ impl MockContext { host_type: HostType, validator_history: &[Vec], latest_height: Height, - ) -> Self { + ) -> Self + where + S: Default, + { let max_history_size = validator_history.len() as u64 - 1; assert_ne!( @@ -295,16 +356,20 @@ impl MockContext { }) .collect(); - MockContext { + Self { host_chain_type: host_type, host_chain_id: host_id.clone(), max_history_size, history, block_time, - ibc_store: Arc::new(Mutex::new(MockIbcStore::default())), + ibc_store: MockIbcStore::default(), } } + pub fn chain_revision_number(&self) -> u64 { + self.host_chain_id.revision_number() + } + /// Associates a client record to this context. /// Given a client id and a height, registers a new client in the context and also associates /// to this client a mock client state and a mock consensus state for height `height`. The type @@ -443,20 +508,45 @@ impl MockContext { ) } - pub fn with_client_config(self, client: MockClientConfig) -> Self { + pub fn with_client_state(mut self, client_id: &ClientId, client_state: AnyClientState) -> Self { + let client_state_path = ClientStatePath::new(client_id); + self.store_client_state(client_state_path, client_state) + .expect("error writing to store"); + self + } + + pub fn with_consensus_state( + mut self, + client_id: &ClientId, + height: Height, + consensus_state: AnyConsensusState, + ) -> Self { + let consensus_state_path = ClientConsensusStatePath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + ); + self.store_consensus_state(consensus_state_path, consensus_state) + .expect("error writing to store"); + + self + } + + pub fn with_client_config(mut self, client: MockClientConfig) -> Self { let cs_heights = if client.consensus_state_heights.is_empty() { vec![client.latest_height] } else { client.consensus_state_heights }; - fn blocks_since(a: Height, b: Height) -> Option { - (a.revision_number() == b.revision_number() - && a.revision_height() >= b.revision_height()) - .then(|| a.revision_height() - b.revision_height()) - } + let client_latest_timestamp = client + .latest_timestamp + .unwrap_or_else(|| self.latest_timestamp()); - let (client_state, consensus_states) = match client.client_type.as_str() { + let (client_state, consensus_states): ( + AnyClientState, + BTreeMap, + ) = match client.client_type.as_str() { MOCK_CLIENT_TYPE => { let blocks: Vec<_> = cs_heights .into_iter() @@ -466,8 +556,7 @@ impl MockContext { ( cs_height, MockHeader::new(cs_height).with_timestamp( - client - .latest_timestamp + client_latest_timestamp .sub(self.block_time * (n_blocks as u32)) .expect("never fails"), ), @@ -476,7 +565,7 @@ impl MockContext { .collect(); let client_state = MockClientState::new( - MockHeader::new(client.latest_height).with_timestamp(client.latest_timestamp), + MockHeader::new(client.latest_height).with_timestamp(client_latest_timestamp), ); let cs_states = blocks @@ -497,8 +586,7 @@ impl MockContext { HostBlock::generate_tm_block( client.client_chain_id.clone(), cs_height.revision_height(), - client - .latest_timestamp + client_latest_timestamp .sub(self.block_time * (n_blocks as u32)) .expect("never fails"), ), @@ -528,89 +616,73 @@ impl MockContext { _ => panic!("unknown client type"), }; - let client_record = MockClientRecord { - client_state: Some(client_state), - consensus_states, - }; + self = self.with_client_state(&client.client_id, client_state); + + for (height, consensus_state) in consensus_states { + self = self.with_consensus_state(&client.client_id, height, consensus_state); + } - self.ibc_store - .lock() - .clients - .insert(client.client_id.clone(), client_record); self } /// Associates a connection to this context. pub fn with_connection( - self, + mut self, connection_id: ConnectionId, connection_end: ConnectionEnd, ) -> Self { - self.ibc_store - .lock() - .connections - .insert(connection_id, connection_end); + let connection_path = ConnectionPath::new(&connection_id); + self.store_connection(&connection_path, connection_end) + .expect("error writing to store"); self } /// Associates a channel (in an arbitrary state) to this context. pub fn with_channel( - self, + mut self, port_id: PortId, chan_id: ChannelId, channel_end: ChannelEnd, ) -> Self { - let mut channels = self.ibc_store.lock().channels.clone(); - channels - .entry(port_id) - .or_default() - .insert(chan_id, channel_end); - self.ibc_store.lock().channels = channels; + let channel_end_path = ChannelEndPath::new(&port_id, &chan_id); + self.store_channel(&channel_end_path, channel_end) + .expect("error writing to store"); self } pub fn with_send_sequence( - self, + mut self, port_id: PortId, chan_id: ChannelId, seq_number: Sequence, ) -> Self { - let mut next_sequence_send = self.ibc_store.lock().next_sequence_send.clone(); - next_sequence_send - .entry(port_id) - .or_default() - .insert(chan_id, seq_number); - self.ibc_store.lock().next_sequence_send = next_sequence_send; + let seq_send_path = SeqSendPath::new(&port_id, &chan_id); + self.store_next_sequence_send(&seq_send_path, seq_number) + .expect("error writing to store"); self } pub fn with_recv_sequence( - self, + mut self, port_id: PortId, chan_id: ChannelId, seq_number: Sequence, ) -> Self { - let mut next_sequence_recv = self.ibc_store.lock().next_sequence_recv.clone(); - next_sequence_recv - .entry(port_id) - .or_default() - .insert(chan_id, seq_number); - self.ibc_store.lock().next_sequence_recv = next_sequence_recv; + let seq_recv_path = SeqRecvPath::new(&port_id, &chan_id); + self.store_next_sequence_recv(&seq_recv_path, seq_number) + .expect("error writing to store"); self } pub fn with_ack_sequence( - self, + mut self, port_id: PortId, chan_id: ChannelId, seq_number: Sequence, ) -> Self { - let mut next_sequence_ack = self.ibc_store.lock().next_sequence_send.clone(); - next_sequence_ack - .entry(port_id) - .or_default() - .insert(chan_id, seq_number); - self.ibc_store.lock().next_sequence_ack = next_sequence_ack; + let seq_ack_path = SeqAckPath::new(&port_id, &chan_id); + self.store_next_sequence_ack(&seq_ack_path, seq_number) + .expect("error writing to store"); self } @@ -636,20 +708,15 @@ impl MockContext { } pub fn with_packet_commitment( - self, + mut self, port_id: PortId, chan_id: ChannelId, seq: Sequence, data: PacketCommitment, ) -> Self { - let mut packet_commitment = self.ibc_store.lock().packet_commitment.clone(); - packet_commitment - .entry(port_id) - .or_default() - .entry(chan_id) - .or_default() - .insert(seq, data); - self.ibc_store.lock().packet_commitment = packet_commitment; + let commitment_path = CommitmentPath::new(&port_id, &chan_id, seq); + self.store_packet_commitment(&commitment_path, data) + .expect("error writing to store"); self } @@ -734,11 +801,8 @@ impl MockContext { } pub fn latest_client_states(&self, client_id: &ClientId) -> AnyClientState { - self.ibc_store.lock().clients[client_id] - .client_state - .as_ref() - .expect("Never fails") - .clone() + self.client_state(client_id) + .expect("error reading from store") } pub fn latest_consensus_states( @@ -746,35 +810,42 @@ impl MockContext { client_id: &ClientId, height: &Height, ) -> AnyConsensusState { - self.ibc_store.lock().clients[client_id] - .consensus_states - .get(height) - .expect("Never fails") - .clone() + self.consensus_state(&ClientConsensusStatePath::new( + client_id.clone(), + height.revision_number(), + height.revision_height(), + )) + .expect("error reading from store") } pub fn latest_height(&self) -> Height { - self.history - .last() - .expect("history cannot be empty") - .height() + self.host_height().expect("Never fails") } - pub fn ibc_store_share(&self) -> Arc> { - self.ibc_store.clone() + pub fn latest_timestamp(&self) -> Timestamp { + self.host_block(&self.latest_height()) + .expect("Never fails") + .timestamp() + } + + pub fn timestamp_at(&self, height: Height) -> Timestamp { + let n_blocks = blocks_since(self.latest_height(), height).expect("less or equal height"); + self.latest_timestamp() + .sub(self.block_time * (n_blocks as u32)) + .expect("Never fails") } pub fn query_latest_header(&self) -> Option { - let block_ref = self.host_block(&self.host_height().expect("Never fails")); + let block_ref = self.host_block(&self.latest_height()); block_ref.cloned() } pub fn get_events(&self) -> Vec { - self.ibc_store.lock().events.clone() + self.ibc_store.events.lock().clone() } pub fn get_logs(&self) -> Vec { - self.ibc_store.lock().logs.clone() + self.ibc_store.logs.lock().clone() } } diff --git a/ibc-testkit/src/testapp/ibc/mod.rs b/ibc-testkit/src/testapp/ibc/mod.rs index f13a3c645..fb9937e58 100644 --- a/ibc-testkit/src/testapp/ibc/mod.rs +++ b/ibc-testkit/src/testapp/ibc/mod.rs @@ -1,3 +1,4 @@ pub mod applications; pub mod clients; pub mod core; +pub mod utils; diff --git a/ibc-testkit/src/testapp/ibc/utils.rs b/ibc-testkit/src/testapp/ibc/utils.rs new file mode 100644 index 000000000..d5032cae1 --- /dev/null +++ b/ibc-testkit/src/testapp/ibc/utils.rs @@ -0,0 +1,6 @@ +use ibc::core::client::types::Height; + +pub fn blocks_since(a: Height, b: Height) -> Option { + (a.revision_number() == b.revision_number() && a.revision_height() >= b.revision_height()) + .then(|| a.revision_height() - b.revision_height()) +} diff --git a/ibc-testkit/tests/core/ics02_client/update_client.rs b/ibc-testkit/tests/core/ics02_client/update_client.rs index fc60fcbab..e098194e5 100644 --- a/ibc-testkit/tests/core/ics02_client/update_client.rs +++ b/ibc-testkit/tests/core/ics02_client/update_client.rs @@ -135,7 +135,7 @@ fn test_consensus_state_pruning() { .latest_height(client_height) .latest_timestamp(Timestamp::now()) .max_history_size(u64::MAX) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(chain_id.clone()) @@ -232,7 +232,7 @@ fn test_update_synthetic_tendermint_client_adjacent_ok() { let mut ctx = MockContextConfig::builder() .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(chain_id_b.clone()) @@ -248,7 +248,7 @@ fn test_update_synthetic_tendermint_client_adjacent_ok() { .host_id(chain_id_b) .host_type(HostType::SyntheticTendermint) .latest_height(update_height) - .build(); + .build::(); let signer = dummy_account_id(); @@ -288,7 +288,7 @@ fn test_update_synthetic_tendermint_client_validator_change_ok() { let mut ctx_a = MockContextConfig::builder() .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) - .build() + .build::() .with_client_config( // client state initialized with client_height, and // [{id: 1, power: 50}, {id: 2, power: 50}] for validator set and next validator set. @@ -335,7 +335,7 @@ fn test_update_synthetic_tendermint_client_validator_change_ok() { .latest_height(update_height) .max_history_size(ctx_b_val_history.len() as u64 - 1) .validator_set_history(ctx_b_val_history) - .build(); + .build::(); let signer = dummy_account_id(); @@ -380,7 +380,7 @@ fn test_update_synthetic_tendermint_client_validator_change_fail() { let ctx_a = MockContextConfig::builder() .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) - .build() + .build::() .with_client_config( // client state initialized with client_height, and // [{id: 1, power: 50}, {id: 2, power: 50}] for validator set and next validator set. @@ -428,7 +428,7 @@ fn test_update_synthetic_tendermint_client_validator_change_fail() { .latest_height(update_height) .max_history_size(ctx_b_val_history.len() as u64 - 1) .validator_set_history(ctx_b_val_history) - .build(); + .build::(); let signer = dummy_account_id(); @@ -465,7 +465,7 @@ fn test_update_synthetic_tendermint_client_non_adjacent_ok() { let mut ctx = MockContextConfig::builder() .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(chain_id_b.clone()) @@ -485,7 +485,7 @@ fn test_update_synthetic_tendermint_client_non_adjacent_ok() { .host_id(chain_id_b) .host_type(HostType::SyntheticTendermint) .latest_height(update_height) - .build(); + .build::(); let signer = dummy_account_id(); @@ -530,7 +530,7 @@ fn test_update_synthetic_tendermint_client_duplicate_ok() { let mut ctx_a = MockContextConfig::builder() .host_id(ctx_a_chain_id) .latest_height(start_height) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(ctx_b_chain_id.clone()) @@ -547,7 +547,7 @@ fn test_update_synthetic_tendermint_client_duplicate_ok() { .host_id(ctx_b_chain_id) .host_type(HostType::SyntheticTendermint) .latest_height(client_height) - .build(); + .build::(); let signer = dummy_account_id(); @@ -617,14 +617,9 @@ fn test_update_synthetic_tendermint_client_duplicate_ok() { ClientState::from(client_state).into() }; - let mut ibc_store = ctx_a.ibc_store.lock(); - let client_record = ibc_store.clients.get_mut(&client_id).unwrap(); + ctx_a = ctx_a.with_client_state(&client_id, client_state); - client_record - .consensus_states - .insert(client_height, consensus_state); - - client_record.client_state = Some(client_state); + ctx_a = ctx_a.with_consensus_state(&client_id, client_height, consensus_state); } let latest_header_height = block.height(); @@ -663,7 +658,7 @@ fn test_update_synthetic_tendermint_client_lower_height() { let ctx = MockContextConfig::builder() .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(chain_start_height) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_id(client_id.clone()) @@ -678,7 +673,7 @@ fn test_update_synthetic_tendermint_client_lower_height() { .host_id(ChainId::new("mockgaiaB-1").unwrap()) .host_type(HostType::SyntheticTendermint) .latest_height(client_height) - .build(); + .build::(); let signer = dummy_account_id(); @@ -825,7 +820,7 @@ fn test_misbehaviour_synthetic_tendermint_equivocation() { let mut ctx_a = MockContextConfig::builder() .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(chain_id_b.clone()) @@ -842,7 +837,7 @@ fn test_misbehaviour_synthetic_tendermint_equivocation() { .host_id(chain_id_b.clone()) .host_type(HostType::SyntheticTendermint) .latest_height(misbehaviour_height) - .build(); + .build::(); // Get chain-B's header at `misbehaviour_height` let header1: TmHeader = { @@ -887,7 +882,7 @@ fn test_misbehaviour_synthetic_tendermint_bft_time() { let mut ctx_a = MockContextConfig::builder() .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(chain_id_b.clone()) @@ -957,7 +952,7 @@ fn test_expired_client() { .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) .latest_timestamp(timestamp) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(chain_id_b.clone()) @@ -995,7 +990,7 @@ fn test_client_update_max_clock_drift() { .host_id(ChainId::new("mockgaiaA-1").unwrap()) .latest_height(Height::new(1, 1).unwrap()) .latest_timestamp(timestamp) - .build() + .build::() .with_client_config( MockClientConfig::builder() .client_chain_id(chain_id_b.clone()) @@ -1015,7 +1010,7 @@ fn test_client_update_max_clock_drift() { .latest_height(client_height) .latest_timestamp(timestamp) .max_history_size(u64::MAX) - .build(); + .build::(); while ctx_b.host_timestamp().expect("no error") < (ctx_a.host_timestamp().expect("no error") + max_clock_drift).expect("no error") diff --git a/ibc-testkit/tests/core/ics02_client/upgrade_client.rs b/ibc-testkit/tests/core/ics02_client/upgrade_client.rs index 84d5b1fbd..7b58f98e6 100644 --- a/ibc-testkit/tests/core/ics02_client/upgrade_client.rs +++ b/ibc-testkit/tests/core/ics02_client/upgrade_client.rs @@ -34,7 +34,7 @@ fn msg_upgrade_client_fixture(ctx_variant: Ctx, msg_variant: Msg) -> Fixture Fixture { let ctx_new = MockContextConfig::builder() .host_id(ChainId::new(&format!("mockgaia-{}", latest_height.revision_number())).unwrap()) .latest_height(latest_height) - .build(); + .build::(); let ctx = match ctx { Ctx::New => ctx_new, Ctx::NewWithConnection => ctx_new diff --git a/ibc-testkit/tests/core/ics03_connection/conn_open_init.rs b/ibc-testkit/tests/core/ics03_connection/conn_open_init.rs index 007be597e..c3f978419 100644 --- a/ibc-testkit/tests/core/ics03_connection/conn_open_init.rs +++ b/ibc-testkit/tests/core/ics03_connection/conn_open_init.rs @@ -128,7 +128,7 @@ fn conn_open_init_no_context() { fn conn_open_init_no_version() { let mut fxt = conn_open_init_fixture(Ctx::WithClient, Msg::NoVersion); conn_open_init_validate(&fxt, Expect::Success); - let expected_version = ValidationContext::get_compatible_versions(&fxt.ctx.clone()); + let expected_version = ValidationContext::get_compatible_versions(&fxt.ctx); conn_open_init_execute(&mut fxt, Expect::Success, expected_version); } #[test] diff --git a/ibc-testkit/tests/core/ics03_connection/conn_open_try.rs b/ibc-testkit/tests/core/ics03_connection/conn_open_try.rs index 5afca6581..9800aa789 100644 --- a/ibc-testkit/tests/core/ics03_connection/conn_open_try.rs +++ b/ibc-testkit/tests/core/ics03_connection/conn_open_try.rs @@ -53,7 +53,7 @@ fn conn_open_try_fixture(ctx_variant: Ctx, msg_variant: Msg) -> Fixture(); let ctx = match ctx_variant { Ctx::Default => MockContext::default(), Ctx::WithClient => ctx_new.with_client_config( diff --git a/ibc-testkit/tests/core/ics04_channel/send_packet.rs b/ibc-testkit/tests/core/ics04_channel/send_packet.rs index 333563ee0..7963eda89 100644 --- a/ibc-testkit/tests/core/ics04_channel/send_packet.rs +++ b/ibc-testkit/tests/core/ics04_channel/send_packet.rs @@ -26,8 +26,6 @@ fn send_packet_processing() { want_pass: bool, } - let context = MockContext::default(); - let chan_end_on_a = ChannelEnd::new( State::Open, Order::default(), @@ -84,14 +82,13 @@ fn send_packet_processing() { let tests: Vec = vec![ Test { name: "Processing fails because no channel exists in the context".to_string(), - ctx: context.clone(), + ctx: MockContext::default(), packet: packet.clone(), want_pass: false, }, Test { name: "Good parameters".to_string(), - ctx: context - .clone() + ctx: MockContext::default() .with_client_config( MockClientConfig::builder() .latest_height(client_height) @@ -109,8 +106,7 @@ fn send_packet_processing() { }, Test { name: "Packet timeout height same as destination chain height".to_string(), - ctx: context - .clone() + ctx: MockContext::default() .with_client_config( MockClientConfig::builder() .latest_height(client_height) @@ -128,8 +124,7 @@ fn send_packet_processing() { }, Test { name: "Packet timeout height one more than destination chain height".to_string(), - ctx: context - .clone() + ctx: MockContext::default() .with_client_config( MockClientConfig::builder() .latest_height(client_height) @@ -147,7 +142,7 @@ fn send_packet_processing() { }, Test { name: "Packet timeout due to timestamp".to_string(), - ctx: context + ctx: MockContext::default() .with_client_config( MockClientConfig::builder() .latest_height(client_height) @@ -173,7 +168,7 @@ fn send_packet_processing() { "send_packet: test passed but was supposed to fail for test: {}, \nparams {:?} {:?}", test.name, test.packet.clone(), - test.ctx.clone() + test.ctx ); let ibc_events = test.ctx.get_events(); @@ -194,7 +189,7 @@ fn send_packet_processing() { "send_packet: did not pass test: {}, \nparams {:?} {:?} error: {:?}", test.name, test.packet.clone(), - test.ctx.clone(), + test.ctx, e, ); } diff --git a/ibc-testkit/tests/core/ics04_channel/timeout.rs b/ibc-testkit/tests/core/ics04_channel/timeout.rs index 55d964b68..aeb3d2c0a 100644 --- a/ibc-testkit/tests/core/ics04_channel/timeout.rs +++ b/ibc-testkit/tests/core/ics04_channel/timeout.rs @@ -43,9 +43,10 @@ fn fixture() -> Fixture { let router = MockRouter::new_with_transfer(); + // in case of timeout, timeout timestamp should be less than host's timestamp + let timeout_timestamp = ctx.latest_timestamp().nanoseconds() - 1; let msg_proof_height = 2; let msg_timeout_height = 5; - let timeout_timestamp = Timestamp::now().nanoseconds(); let msg = MsgTimeout::try_from(dummy_raw_msg_timeout( msg_proof_height, diff --git a/ibc-testkit/tests/core/router.rs b/ibc-testkit/tests/core/router.rs index 70756530f..fd6cd00e1 100644 --- a/ibc-testkit/tests/core/router.rs +++ b/ibc-testkit/tests/core/router.rs @@ -1,3 +1,5 @@ +use std::ops::Add; + use ibc::apps::transfer::handler::send_transfer; use ibc::apps::transfer::types::error::TokenTransferError; use ibc::apps::transfer::types::msgs::transfer::MsgTransfer; @@ -33,6 +35,7 @@ use ibc_testkit::fixtures::core::connection::{ dummy_msg_conn_open_ack, dummy_msg_conn_open_init, dummy_msg_conn_open_init_with_client_id, dummy_msg_conn_open_try, msg_conn_open_try_with_client_id, }; +use ibc_testkit::fixtures::core::context::MockContextConfig; use ibc_testkit::fixtures::core::signer::dummy_account_id; use ibc_testkit::testapp::ibc::applications::transfer::types::DummyTransferModule; use ibc_testkit::testapp::ibc::clients::mock::client_state::MockClientState; @@ -87,14 +90,23 @@ fn routing_module_and_keepers() { let upgrade_client_height_second = Height::new(1, 1).unwrap(); // We reuse this same context across all tests. Nothing in particular needs parametrizing. - let mut ctx = MockContext::default(); + let mut ctx = MockContextConfig::builder() + // a future timestamp, so that submitted packets are considered from past + // not more than 5 secs, as later dummy_raw_msg_timeout_on_close(*, 5) is used + .latest_timestamp( + Timestamp::now() + .add(core::time::Duration::from_secs(4)) + .unwrap(), + ) + .build::(); let mut router = MockRouter::new_with_transfer(); + let header = MockHeader::new(start_client_height).with_current_timestamp(); + let create_client_msg = MsgCreateClient::new( - MockClientState::new(MockHeader::new(start_client_height).with_current_timestamp()).into(), - MockConsensusState::new(MockHeader::new(start_client_height).with_current_timestamp()) - .into(), + MockClientState::new(header).into(), + MockConsensusState::new(header).into(), default_signer.clone(), );