Skip to content

Commit

Permalink
imp(ibc-testkit): Tendermint proof verifications via integration test (
Browse files Browse the repository at this point in the history
…#1146)

* test tmclient proof verification

* use ClientStateConfig directly

* rm MockClientConfig

* use basecoin store proofspec as default

* update tests

* use merkle storage in MockContext

* fix ProofSpecs size

* refactor MockIbcStore to perform begin_block and end_block

* simpler proof verification test

* use ValidationContext::commitment_prefix()

* nits

* refactor host related trait method

* tendermint host client integration test with proof verification

* rm raw test

* use typed relayer

* add todo for channel integration test

* core over std

* be semantically correct

* add comment for TypedRelayer

* integration test for all pairs

* fix semantic bug

* renames

* add channel management

* channel creation in RelayerContext

* add channel creation in integration test

* add test for channel close

* query client_id from connection and channel

* ibc_store_mut

* utils functions for packet relay

* add packet relay integration test

* add comments

* optimize integration utils functions

* serde feature for integration tests

* rm redundant chain_id

* sync clock only on a

* add comment

* imp: place router under MockGenericContext

* nit: add docstring for router

* nits

* rm redundant lint filters

* imp: ditch RelayerContext

* nit: simplify build_client_update_datagram

* refactor integration tests

* add doc strings for TypedRelayerOps

* doc strings for RelayerContext

* mv client_update_ping_pong to tests dir

* rename main_store to multi_store

* update TestHost trait

* update mock and tendermint hosts

* update relayer functions

* nits

* renames and comments

* add comments for return values in relayer ops

* imp: simplify into_header

---------

Co-authored-by: Farhad Shabani <[email protected]>
  • Loading branch information
rnbguy and Farhad-Shabani authored Apr 5, 2024
1 parent 05ea5fa commit 2acb2fa
Show file tree
Hide file tree
Showing 16 changed files with 1,984 additions and 342 deletions.
139 changes: 87 additions & 52 deletions ibc-testkit/src/context.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
use core::fmt::Debug;
use core::time::Duration;

use basecoin_store::avl::get_proof_spec as basecoin_proof_spec;
use basecoin_store::context::ProvableStore;
use basecoin_store::impls::{GrowingStore, InMemoryStore, RevertibleStore};
use ibc::core::channel::types::channel::ChannelEnd;
use ibc::core::channel::types::commitment::PacketCommitment;
use ibc::core::client::context::client_state::ClientStateValidation;
use ibc::core::client::context::ClientExecutionContext;
use ibc::core::client::context::{ClientExecutionContext, ClientValidationContext};
use ibc::core::client::types::Height;
use ibc::core::commitment_types::specs::ProofSpecs;
use ibc::core::connection::types::ConnectionEnd;
use ibc::core::entrypoint::dispatch;
use ibc::core::handler::types::events::IbcEvent;
Expand All @@ -20,16 +18,15 @@ use ibc::core::host::types::path::{
SeqAckPath, SeqRecvPath, SeqSendPath,
};
use ibc::core::host::{ExecutionContext, ValidationContext};
use ibc::core::router::router::Router;
use ibc::primitives::prelude::*;
use ibc::primitives::Timestamp;
use typed_builder::TypedBuilder;

use super::testapp::ibc::core::types::{LightClientState, MockIbcStore};
use crate::fixtures::core::context::MockContextConfig;
use crate::hosts::{HostClientState, TestBlock, TestHeader, TestHost};
use crate::relayer::error::RelayerError;
use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState};
use crate::testapp::ibc::core::router::MockRouter;
use crate::testapp::ibc::core::types::DEFAULT_BLOCK_TIME_SECS;

/// A context implementing the dependencies necessary for testing any IBC module.
Expand All @@ -40,34 +37,23 @@ where
H: TestHost,
HostClientState<H>: ClientStateValidation<MockIbcStore<S>>,
{
/// The multi store of the context.
/// This is where the IBC store root is stored at IBC commitment prefix.
pub multi_store: S,

/// The type of host chain underlying this mock context.
pub host: H,

/// An object that stores all IBC related data.
pub ibc_store: MockIbcStore<S>,

/// A router that can route messages to the appropriate IBC application.
pub ibc_router: MockRouter,
}

pub type MockStore = RevertibleStore<GrowingStore<InMemoryStore>>;
pub type MockContext<H> = MockGenericContext<MockStore, H>;

#[derive(Debug, TypedBuilder)]
pub struct MockClientConfig {
#[builder(default = Duration::from_secs(64000))]
pub trusting_period: Duration,
#[builder(default = Duration::from_millis(3000))]
pub max_clock_drift: Duration,
#[builder(default = Duration::from_secs(128_000))]
pub unbonding_period: Duration,
#[builder(default = vec![basecoin_proof_spec()].into())]
pub proof_specs: ProofSpecs,
}

impl Default for MockClientConfig {
fn default() -> Self {
Self::builder().build()
}
}

/// 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.
Expand All @@ -94,6 +80,10 @@ where
&self.ibc_store
}

pub fn ibc_store_mut(&mut self) -> &mut MockIbcStore<S> {
&mut self.ibc_store
}

pub fn host_block(&self, target_height: &Height) -> Option<H::Block> {
self.host.get_block(target_height)
}
Expand All @@ -102,6 +92,13 @@ where
self.host.get_block(&self.latest_height())
}

pub fn light_client_latest_height(&self, client_id: &ClientId) -> Height {
self.ibc_store
.client_state(client_id)
.expect("client state exists")
.latest_height()
}

pub fn advance_block_up_to(mut self, target_height: Height) -> Self {
let latest_height = self.host.latest_height();
if target_height.revision_number() != latest_height.revision_number() {
Expand All @@ -118,38 +115,79 @@ where
}

pub fn generate_genesis_block(&mut self, genesis_time: Timestamp, params: &H::BlockParams) {
// commit store
let app_hash = self.ibc_store.commit().expect("no error");
self.end_block();

// generate and push genesis block
let genesis_block = self.host.generate_block(app_hash, 1, genesis_time, params);
self.host.push_block(genesis_block);
// commit multi store
let multi_store_commitment = self.multi_store.commit().expect("no error");

// store it in ibc context as host consensus state
self.ibc_store.store_host_consensus_state(
// generate a genesis block
let genesis_block =
self.host
.latest_block()
.into_header()
.into_consensus_state()
.into(),
.generate_block(multi_store_commitment, 1, genesis_time, params);

// push the genesis block to the host
self.host.push_block(genesis_block);

self.begin_block();
}

pub fn begin_block(&mut self) {
let consensus_state = self
.host
.latest_block()
.into_header()
.into_consensus_state()
.into();

let ibc_commitment_proof = self
.multi_store
.get_proof(
self.host.latest_height().revision_height().into(),
&self
.ibc_store
.commitment_prefix()
.as_bytes()
.try_into()
.expect("valid utf8 prefix"),
)
.expect("no error");

self.ibc_store.begin_block(
self.host.latest_height().revision_height(),
consensus_state,
ibc_commitment_proof,
);
}

pub fn advance_with_block_params(&mut self, block_time: Duration, params: &H::BlockParams) {
// commit store
let app_hash = self.ibc_store.commit().expect("no error");
pub fn end_block(&mut self) {
// commit ibc store
let ibc_store_commitment = self.ibc_store.end_block().expect("no error");

// commit ibc store commitment in multi store
self.multi_store
.set(
self.ibc_store
.commitment_prefix()
.as_bytes()
.try_into()
.expect("valid utf8 prefix"),
ibc_store_commitment,
)
.expect("no error");
}

pub fn produce_block(&mut self, block_time: Duration, params: &H::BlockParams) {
// commit the multi store
let multi_store_commitment = self.multi_store.commit().expect("no error");
// generate a new block
self.host.advance_block(app_hash, block_time, params);
self.host
.advance_block(multi_store_commitment, block_time, params);
}

// store it in ibc context as host consensus state
self.ibc_store.store_host_consensus_state(
self.host
.latest_block()
.into_header()
.into_consensus_state()
.into(),
);
pub fn advance_with_block_params(&mut self, block_time: Duration, params: &H::BlockParams) {
self.end_block();
self.produce_block(block_time, params);
self.begin_block();
}

pub fn advance_block(&mut self) {
Expand Down Expand Up @@ -355,12 +393,9 @@ where
/// A datagram passes from the relayer to the IBC module (on host chain).
/// Alternative method to `Ics18Context::send` that does not exercise any serialization.
/// Used in testing the Ics18 algorithms, hence this may return a Ics18Error.
pub fn deliver(
&mut self,
router: &mut impl Router,
msg: MsgEnvelope,
) -> Result<(), RelayerError> {
dispatch(&mut self.ibc_store, router, msg).map_err(RelayerError::TransactionFailed)?;
pub fn deliver(&mut self, msg: MsgEnvelope) -> Result<(), RelayerError> {
dispatch(&mut self.ibc_store, &mut self.ibc_router, msg)
.map_err(RelayerError::TransactionFailed)?;
// Create a new block.
self.advance_block();
Ok(())
Expand Down
51 changes: 29 additions & 22 deletions ibc-testkit/src/fixtures/clients/tendermint.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use core::str::FromStr;
use core::time::Duration;

use basecoin_store::avl::get_proof_spec as basecoin_proof_spec;
use ibc::clients::tendermint::client_state::ClientState as TmClientState;
use ibc::clients::tendermint::types::error::{Error as ClientError, Error};
use ibc::clients::tendermint::types::proto::v1::{ClientState as RawTmClientState, Fraction};
Expand All @@ -15,6 +16,7 @@ use ibc::core::commitment_types::specs::ProofSpecs;
use ibc::core::host::types::identifiers::ChainId;
use ibc::core::primitives::prelude::*;
use tendermint::block::Header as TmHeader;
use typed_builder::TypedBuilder;

/// Returns a dummy tendermint `ClientState` by given `frozen_height`, for testing purposes only!
pub fn dummy_tm_client_state_from_raw(frozen_height: RawHeight) -> Result<TmClientState, Error> {
Expand Down Expand Up @@ -64,43 +66,48 @@ pub fn dummy_raw_tm_client_state(frozen_height: RawHeight) -> RawTmClientState {
}
}

#[derive(typed_builder::TypedBuilder, Debug)]
#[derive(TypedBuilder, Debug)]
pub struct ClientStateConfig {
pub chain_id: ChainId,
#[builder(default = TrustThreshold::ONE_THIRD)]
pub trust_level: TrustThreshold,
#[builder(default = Duration::from_secs(64000))]
pub trusting_period: Duration,
#[builder(default = Duration::from_secs(128_000))]
pub unbonding_period: Duration,
#[builder(default = Duration::from_millis(3000))]
max_clock_drift: Duration,
pub latest_height: Height,
#[builder(default = ProofSpecs::cosmos())]
pub max_clock_drift: Duration,
#[builder(default = vec![basecoin_proof_spec(); 2].into())]
pub proof_specs: ProofSpecs,
#[builder(default)]
pub upgrade_path: Vec<String>,
#[builder(default = AllowUpdate { after_expiry: false, after_misbehaviour: false })]
allow_update: AllowUpdate,
}

impl TryFrom<ClientStateConfig> for TmClientState {
type Error = ClientError;

fn try_from(config: ClientStateConfig) -> Result<Self, Self::Error> {
let client_state = ClientStateType::new(
config.chain_id,
config.trust_level,
config.trusting_period,
config.unbonding_period,
config.max_clock_drift,
config.latest_height,
config.proof_specs,
config.upgrade_path,
config.allow_update,
)?;

Ok(client_state.into())
impl Default for ClientStateConfig {
fn default() -> Self {
Self::builder().build()
}
}

impl ClientStateConfig {
pub fn into_client_state(
self,
chain_id: ChainId,
latest_height: Height,
) -> Result<TmClientState, ClientError> {
Ok(ClientStateType::new(
chain_id,
self.trust_level,
self.trusting_period,
self.unbonding_period,
self.max_clock_drift,
latest_height,
self.proof_specs,
self.upgrade_path,
self.allow_update,
)?
.into())
}
}

Expand Down
7 changes: 5 additions & 2 deletions ibc-testkit/src/fixtures/core/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use typed_builder::TypedBuilder;

use crate::context::MockGenericContext;
use crate::hosts::{HostClientState, TestBlock, TestHost};
use crate::testapp::ibc::core::router::MockRouter;
use crate::testapp::ibc::core::types::{MockIbcStore, DEFAULT_BLOCK_TIME_SECS};
use crate::utils::year_2023;

Expand All @@ -21,7 +22,7 @@ where
H: TestHost,
{
#[builder(default)]
pub host: H,
host: H,

#[builder(default = Duration::from_secs(DEFAULT_BLOCK_TIME_SECS))]
block_time: Duration,
Expand Down Expand Up @@ -56,11 +57,13 @@ where
.expect("no underflow");

let mut context = Self {
multi_store: Default::default(),
host: params.host,
ibc_store: MockIbcStore::new(
params.latest_height.revision_number(),
Default::default(),
),
host: params.host,
ibc_router: MockRouter::new_with_transfer(),
};

// store is a height 0; no block
Expand Down
4 changes: 4 additions & 0 deletions ibc-testkit/src/hosts/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ impl TestBlock for MockHeader {
fn timestamp(&self) -> Timestamp {
self.timestamp
}

fn into_header_with_trusted(self, _: &Self) -> Self::Header {
self
}
}

impl From<MockHeader> for MockConsensusState {
Expand Down
10 changes: 7 additions & 3 deletions ibc-testkit/src/hosts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,21 @@ pub trait TestHost: Default + Debug + Sized {
/// TestBlock is a trait that defines the interface for a block produced by a host blockchain.
pub trait TestBlock: Clone + Debug {
/// The type of header can be extracted from the block.
type Header: TestHeader + From<Self>;
type Header: TestHeader;

/// The height of the block.
fn height(&self) -> Height;

/// The timestamp of the block.
fn timestamp(&self) -> Timestamp;

/// Extract the header from the block.
/// Extract the IBC header using the target and trusted blocks.
fn into_header_with_trusted(self, trusted_block: &Self) -> Self::Header;

/// Extract the IBC header only using the target block (sets the trusted
/// block to itself).
fn into_header(self) -> Self::Header {
self.into()
self.clone().into_header_with_trusted(&self)
}
}

Expand Down
Loading

0 comments on commit 2acb2fa

Please sign in to comment.