From 13ac2244f7c9aa9549b38e0b2e1f7d74afa16cee Mon Sep 17 00:00:00 2001 From: Dhruv D Jain Date: Fri, 13 Oct 2023 23:35:10 +0530 Subject: [PATCH] added mock client and consensus state (#27) * added mock client and consensus state * added tendermint patch and updated program id * fix code format * fix issues in contract * cargo +nightly fmt * fix formatting * reduce diff noise * storing height while storing consensus state and returning current timestamp when host_timestamp is called * rm channel counter update * remove local ibc crate * added mocks feature * fix formatting * reduce diff noise --------- Co-authored-by: Michal Nazarewicz --- .gitignore | 9 +- Anchor.toml | 4 +- Cargo.toml | 2 +- .../solana-ibc/programs/solana-ibc/Cargo.toml | 1 + .../programs/solana-ibc/src/client_state.rs | 129 +++++++++++++++++- .../solana-ibc/src/consensus_state.rs | 62 +++++++++ .../solana-ibc/src/execution_context.rs | 11 +- .../solana-ibc/programs/solana-ibc/src/lib.rs | 19 +-- .../programs/solana-ibc/src/tests.rs | 10 +- .../solana-ibc/src/validation_context.rs | 11 +- 10 files changed, 230 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index ea93e8e7..695277c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ +*.rs.bk +.DS_Store .anchor -Cargo.lock /dist/ -.DS_Store -*.rs.bk +Cargo.lock +local-ibc node_modules package-lock.json target -test-ledger \ No newline at end of file +test-ledger diff --git a/Anchor.toml b/Anchor.toml index 56e4887f..f32d2c57 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -2,9 +2,9 @@ seeds = false skip-lint = false [programs.localnet] -solana_ibc = "7MEuaEwNMsjVCJy9N31ZgvQf1dFkRNXYFREaAjMsoE5g" +solana_ibc = "EnfDJsAK7BGgetnmKzBx86CsgC5kfSPcsktFCQ4YLC81" [programs.devnet] -solana_ibc = "7MEuaEwNMsjVCJy9N31ZgvQf1dFkRNXYFREaAjMsoE5g" +solana_ibc = "EnfDJsAK7BGgetnmKzBx86CsgC5kfSPcsktFCQ4YLC81" [registry] url = "https://api.apr.dev" diff --git a/Cargo.toml b/Cargo.toml index ff707ef3..88318e68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ base64 = { version = "0.21", default-features = false, features = ["alloc"] } bincode = "1.3.3" borsh = { version = "0.10.3", default-features = false } derive_more = "0.99.17" -ibc = { version = "0.45.0", default-features = false, features = ["serde"] } +ibc = { version = "0.45.0", default-features = false, features = ["serde", "mocks", "std"] } ibc-proto = { version = "0.35.0", default-features = false, features = ["serde"] } pretty_assertions = "1.4.0" rand = { version = "0.8.5" } diff --git a/solana/solana-ibc/programs/solana-ibc/Cargo.toml b/solana/solana-ibc/programs/solana-ibc/Cargo.toml index 6714b97d..bb4690d7 100644 --- a/solana/solana-ibc/programs/solana-ibc/Cargo.toml +++ b/solana/solana-ibc/programs/solana-ibc/Cargo.toml @@ -13,6 +13,7 @@ no-entrypoint = [] no-idl = [] no-log-ix-name = [] cpi = ["no-entrypoint"] +mocks = [] [dependencies] anchor-lang.workspace = true diff --git a/solana/solana-ibc/programs/solana-ibc/src/client_state.rs b/solana/solana-ibc/programs/solana-ibc/src/client_state.rs index 617a4865..35d855b4 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/client_state.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/client_state.rs @@ -1,3 +1,4 @@ +use anchor_lang::solana_program::msg; use ibc::clients::ics07_tendermint::client_state::ClientState as TmClientState; use ibc::core::ics02_client::client_state::{ ClientStateCommon, ClientStateExecution, ClientStateValidation, UpdateKind, @@ -11,8 +12,14 @@ use ibc::core::ics24_host::identifier::ClientId; use ibc::core::ics24_host::path::{ClientConsensusStatePath, Path}; use ibc::core::timestamp::Timestamp; use ibc::core::{ContextError, ValidationContext}; +#[cfg(any(test, feature = "mocks"))] +use ibc::mock::client_state::{ + MockClientContext, MockClientState, MOCK_CLIENT_STATE_TYPE_URL, +}; use ibc::{Any, Height}; use ibc_proto::ibc::lightclients::tendermint::v1::ClientState as RawTmClientState; +#[cfg(any(test, feature = "mocks"))] +use ibc_proto::ibc::mock::ClientState as RawMockClientState; use ibc_proto::protobuf::Protobuf; use serde::{Deserialize, Serialize}; @@ -25,6 +32,8 @@ const TENDERMINT_CLIENT_STATE_TYPE_URL: &str = #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] pub enum AnyClientState { Tendermint(TmClientState), + #[cfg(any(test, feature = "mocks"))] + Mock(MockClientState), } impl Protobuf for AnyClientState {} @@ -41,6 +50,13 @@ impl TryFrom for AnyClientState { }, )?, )), + #[cfg(any(test, feature = "mocks"))] + MOCK_CLIENT_STATE_TYPE_URL => Ok(AnyClientState::Mock( + Protobuf::::decode_vec(&raw.value) + .map_err(|e| ClientError::ClientSpecific { + description: e.to_string(), + })?, + )), _ => Err(ClientError::UnknownClientStateType { client_state_type: raw.type_url, }), @@ -55,6 +71,13 @@ impl From for Any { type_url: TENDERMINT_CLIENT_STATE_TYPE_URL.to_string(), value: Protobuf::::encode_vec(&client_state), }, + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(mock_client_state) => Any { + type_url: MOCK_CLIENT_STATE_TYPE_URL.to_string(), + value: Protobuf::::encode_vec( + &mock_client_state, + ), + }, } } } @@ -75,6 +98,14 @@ impl ClientStateValidation for AnyClientState { client_message, update_kind, ), + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(mock_client_state) => mock_client_state + .verify_client_message( + ctx, + client_id, + client_message, + update_kind, + ), } } @@ -93,6 +124,14 @@ impl ClientStateValidation for AnyClientState { client_message, update_kind, ), + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(mock_client_state) => mock_client_state + .check_for_misbehaviour( + ctx, + client_id, + client_message, + update_kind, + ), } } @@ -115,6 +154,10 @@ impl ClientStateCommon for AnyClientState { AnyClientState::Tendermint(client_state) => { client_state.verify_consensus_state(consensus_state) } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(mock_client_state) => { + mock_client_state.verify_consensus_state(consensus_state) + } } } @@ -123,15 +166,30 @@ impl ClientStateCommon for AnyClientState { AnyClientState::Tendermint(client_state) => { client_state.client_type() } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(mock_client_state) => { + mock_client_state.client_type() + } } } fn latest_height(&self) -> Height { - match self { + msg!("Fetching the height"); + let height = match self { AnyClientState::Tendermint(client_state) => { client_state.latest_height() } - } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(mock_client_state) => { + msg!( + "This is latest height {:?}", + mock_client_state.latest_height() + ); + mock_client_state.latest_height() + } + }; + msg!("This was the height {}", height); + height } fn validate_proof_height( @@ -142,6 +200,10 @@ impl ClientStateCommon for AnyClientState { AnyClientState::Tendermint(client_state) => { client_state.validate_proof_height(proof_height) } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => { + client_state.validate_proof_height(proof_height) + } } } @@ -162,6 +224,15 @@ impl ClientStateCommon for AnyClientState { proof_upgrade_consensus_state, root, ), + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => client_state + .verify_upgrade_client( + upgraded_client_state, + upgraded_consensus_state, + proof_upgrade_client, + proof_upgrade_consensus_state, + root, + ), } } @@ -177,6 +248,10 @@ impl ClientStateCommon for AnyClientState { AnyClientState::Tendermint(client_state) => { client_state.verify_membership(prefix, proof, root, path, value) } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => { + client_state.verify_membership(prefix, proof, root, path, value) + } } } @@ -191,6 +266,10 @@ impl ClientStateCommon for AnyClientState { AnyClientState::Tendermint(client_state) => { client_state.verify_non_membership(prefix, proof, root, path) } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => { + client_state.verify_non_membership(prefix, proof, root, path) + } } } } @@ -199,6 +278,11 @@ impl From for AnyClientState { fn from(value: TmClientState) -> Self { AnyClientState::Tendermint(value) } } +#[cfg(any(test, feature = "mocks"))] +impl From for AnyClientState { + fn from(value: MockClientState) -> Self { AnyClientState::Mock(value) } +} + impl ClientStateExecution for AnyClientState { fn initialise( &self, @@ -210,6 +294,10 @@ impl ClientStateExecution for AnyClientState { AnyClientState::Tendermint(client_state) => { client_state.initialise(ctx, client_id, consensus_state) } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => { + client_state.initialise(ctx, client_id, consensus_state) + } } } @@ -223,6 +311,10 @@ impl ClientStateExecution for AnyClientState { AnyClientState::Tendermint(client_state) => { client_state.update_state(ctx, client_id, header) } + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => { + client_state.update_state(ctx, client_id, header) + } } } @@ -241,6 +333,14 @@ impl ClientStateExecution for AnyClientState { client_message, update_kind, ), + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => client_state + .update_state_on_misbehaviour( + ctx, + client_id, + client_message, + update_kind, + ), } } @@ -259,6 +359,14 @@ impl ClientStateExecution for AnyClientState { upgraded_client_state, upgraded_consensus_state, ), + #[cfg(any(test, feature = "mocks"))] + AnyClientState::Mock(client_state) => client_state + .update_state_on_upgrade( + ctx, + client_id, + upgraded_client_state, + upgraded_consensus_state, + ), } } } @@ -276,6 +384,23 @@ impl ibc::clients::ics07_tendermint::CommonContext for SolanaIbcStorage { } } +#[cfg(any(test, feature = "mocks"))] +impl MockClientContext for SolanaIbcStorage { + type ConversionError = ClientError; + type AnyConsensusState = AnyConsensusState; + + fn consensus_state( + &self, + client_cons_state_path: &ClientConsensusStatePath, + ) -> Result { + ValidationContext::consensus_state(self, client_cons_state_path) + } + + fn host_timestamp(&self) -> Result { + ValidationContext::host_timestamp(self) + } +} + impl ibc::clients::ics07_tendermint::ValidationContext for SolanaIbcStorage { fn host_timestamp(&self) -> Result { ValidationContext::host_timestamp(self) diff --git a/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs b/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs index 4ef0f862..36405dd4 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs @@ -3,8 +3,14 @@ use ibc::core::ics02_client::consensus_state::ConsensusState; use ibc::core::ics02_client::error::ClientError; use ibc::core::ics23_commitment::commitment::CommitmentRoot; use ibc::core::timestamp::Timestamp; +#[cfg(any(test, feature = "mocks"))] +use ibc::mock::consensus_state::{ + MockConsensusState, MOCK_CONSENSUS_STATE_TYPE_URL, +}; use ibc_proto::google::protobuf::Any; use ibc_proto::ibc::lightclients::tendermint::v1::ConsensusState as RawTmConsensusState; +#[cfg(any(test, feature = "mocks"))] +use ibc_proto::ibc::mock::ConsensusState as RawMockConsensusState; use ibc_proto::protobuf::Protobuf; use serde::{Deserialize, Serialize}; @@ -15,6 +21,8 @@ const TENDERMINT_CONSENSUS_STATE_TYPE_URL: &str = #[serde(tag = "type")] pub enum AnyConsensusState { Tendermint(TmConsensusState), + #[cfg(any(test, feature = "mocks"))] + Mock(MockConsensusState), } impl Protobuf for AnyConsensusState {} @@ -32,6 +40,13 @@ impl TryFrom for AnyConsensusState { })?, )) } + #[cfg(any(test, feature = "mocks"))] + MOCK_CONSENSUS_STATE_TYPE_URL => Ok(AnyConsensusState::Mock( + Protobuf::::decode_vec(&value.value) + .map_err(|e| ClientError::ClientSpecific { + description: e.to_string(), + })?, + )), _ => Err(ClientError::UnknownConsensusStateType { consensus_state_type: value.type_url.clone(), }), @@ -46,6 +61,11 @@ impl From for Any { type_url: TENDERMINT_CONSENSUS_STATE_TYPE_URL.to_string(), value: Protobuf::::encode_vec(&value), }, + #[cfg(any(test, feature = "mocks"))] + AnyConsensusState::Mock(value) => Any { + type_url: MOCK_CONSENSUS_STATE_TYPE_URL.to_string(), + value: Protobuf::::encode_vec(&value), + }, } } } @@ -56,16 +76,27 @@ impl From for AnyConsensusState { } } +#[cfg(any(test, feature = "mocks"))] +impl From for AnyConsensusState { + fn from(value: MockConsensusState) -> Self { + AnyConsensusState::Mock(value) + } +} + impl ConsensusState for AnyConsensusState { fn root(&self) -> &CommitmentRoot { match self { AnyConsensusState::Tendermint(value) => value.root(), + #[cfg(any(test, feature = "mocks"))] + AnyConsensusState::Mock(value) => value.root(), } } fn timestamp(&self) -> Timestamp { match self { AnyConsensusState::Tendermint(value) => value.timestamp(), + #[cfg(any(test, feature = "mocks"))] + AnyConsensusState::Mock(value) => value.timestamp(), } } @@ -73,6 +104,10 @@ impl ConsensusState for AnyConsensusState { match self { AnyConsensusState::Tendermint(value) => { ibc::core::ics02_client::consensus_state::ConsensusState::encode_vec(value) + }, + #[cfg(any(test, feature = "mocks"))] + AnyConsensusState::Mock(value) => { + ibc::core::ics02_client::consensus_state::ConsensusState::encode_vec(value) } } } @@ -91,6 +126,33 @@ impl TryInto > { match self { AnyConsensusState::Tendermint(value) => Ok(value), + #[cfg(any(test, feature = "mocks"))] + AnyConsensusState::Mock(_) => Err(ClientError::Other { + description: "Cannot convert mock consensus state to \ + tendermint" + .to_string(), + }), + } + } +} + +#[cfg(any(test, feature = "mocks"))] +impl TryInto + for AnyConsensusState +{ + type Error = ClientError; + + fn try_into( + self, + ) -> Result + { + match self { + AnyConsensusState::Mock(value) => Ok(value), + AnyConsensusState::Tendermint(_) => Err(ClientError::Other { + description: "Cannot convert tendermint consensus state to \ + mock" + .to_string(), + }), } } } diff --git a/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs b/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs index cfb56b9c..bdfb21a4 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs @@ -44,7 +44,7 @@ impl ClientExecutionContext for SolanaIbcStorage { msg!( "store_client_state - path: {}, client_state: {:?}", client_state_path, - client_state + client_state, ); let client_state_key = client_state_path.0.to_string(); let serialized_client_state = @@ -58,7 +58,11 @@ impl ClientExecutionContext for SolanaIbcStorage { consensus_state_path: ClientConsensusStatePath, consensus_state: Self::AnyConsensusState, ) -> Result { - msg!("{}-{}", consensus_state_path.epoch, consensus_state_path.height); + msg!( + "store_consensus_state - path: {}, consensus_state: {:?}", + consensus_state_path, + consensus_state + ); let consensus_state_key = ( consensus_state_path.client_id.to_string(), (consensus_state_path.epoch, consensus_state_path.height), @@ -67,6 +71,8 @@ impl ClientExecutionContext for SolanaIbcStorage { serde_json::to_string(&consensus_state).unwrap(); self.consensus_states .insert(consensus_state_key, serialized_consensus_state); + self.height.0 = consensus_state_path.epoch; + self.height.1 = consensus_state_path.height; Ok(()) } } @@ -84,6 +90,7 @@ impl ExecutionContext for SolanaIbcStorage { height: Height, timestamp: Timestamp, ) -> Result { + msg!("I am here inside update time"); msg!( "store_update_time - client_id: {}, height: {}, timestamp: {}", client_id, diff --git a/solana/solana-ibc/programs/solana-ibc/src/lib.rs b/solana/solana-ibc/programs/solana-ibc/src/lib.rs index b69daee9..b2d86651 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/lib.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/lib.rs @@ -12,7 +12,7 @@ use module_holder::ModuleHolder; const SOLANA_IBC_STORAGE_SEED: &[u8] = b"solana_ibc_storage"; -declare_id!("7MEuaEwNMsjVCJy9N31ZgvQf1dFkRNXYFREaAjMsoE5g"); +declare_id!("EnfDJsAK7BGgetnmKzBx86CsgC5kfSPcsktFCQ4YLC81"); mod client_state; mod consensus_state; @@ -45,15 +45,15 @@ pub mod solana_ibc { }) .collect::>(); - let _errors = + msg!("These are messages {:?}", all_messages); + let router = &mut solana_ibc_store.clone(); + + let errors = all_messages.into_iter().fold(vec![], |mut errors, msg| { match ibc::core::MsgEnvelope::try_from(msg) { Ok(msg) => { - match ibc::core::dispatch( - &mut solana_ibc_store.clone(), - solana_ibc_store, - msg, - ) { + match ibc::core::dispatch(solana_ibc_store, router, msg) + { Ok(()) => (), Err(e) => errors.push(e), } @@ -63,6 +63,9 @@ pub mod solana_ibc { errors }); + msg!("These are errors {:?}", errors); + msg!("This is final structure {:?}", solana_ibc_store); + Ok(()) } } @@ -71,7 +74,7 @@ pub mod solana_ibc { pub struct Deliver<'info> { #[account(mut)] pub sender: Signer<'info>, - #[account(init, payer = sender, seeds = [SOLANA_IBC_STORAGE_SEED],bump, space = 10000)] + #[account(init_if_needed, payer = sender, seeds = [SOLANA_IBC_STORAGE_SEED],bump, space = 10000)] pub storage: Account<'info, SolanaIbcStorage>, pub system_program: Program<'info, System>, } diff --git a/solana/solana-ibc/programs/solana-ibc/src/tests.rs b/solana/solana-ibc/programs/solana-ibc/src/tests.rs index 780047a7..16ce4cff 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/tests.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/tests.rs @@ -4,6 +4,7 @@ use std::time::Duration; use anchor_client::anchor_lang::system_program; use anchor_client::solana_client::rpc_client::RpcClient; +use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig; use anchor_client::solana_sdk::commitment_config::CommitmentConfig; use anchor_client::solana_sdk::pubkey::Pubkey; use anchor_client::solana_sdk::signature::{Keypair, Signature, Signer}; @@ -88,14 +89,19 @@ fn anchor_test_deliver() -> Result<()> { .args(instruction::Deliver { messages: all_messages }) .payer(authority.clone()) .signer(&*authority) - .send()?; // ? gives us the log messages on the why the tx did fail ( better than unwrap ) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + })?; // ? gives us the log messages on the why the tx did fail ( better than unwrap ) println!("demo sig: {sig}"); // Retrieve and validate state - let _solana_ibc_storage_account: SolanaIbcStorage = + let solana_ibc_storage_account: SolanaIbcStorage = program.account(solana_ibc_storage).unwrap(); + println!("This is solana storage account {:?}", solana_ibc_storage_account); + Ok(()) } diff --git a/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs b/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs index a19c95c6..db2e48ee 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use std::time::Duration; -use anchor_lang::prelude::Pubkey; +use anchor_lang::prelude::{Clock, Pubkey, SolanaSysvar}; use ibc::core::ics02_client::error::ClientError; use ibc::core::ics03_connection::connection::ConnectionEnd; use ibc::core::ics03_connection::error::ConnectionError; @@ -88,12 +88,9 @@ impl ValidationContext for SolanaIbcStorage { } fn host_timestamp(&self) -> std::result::Result { - let host_height = self.host_height()?; - match self.host_consensus_state(&host_height)? { - AnyConsensusState::Tendermint(consensus_state) => { - Ok(consensus_state.timestamp.into()) - } - } + let clock = Clock::get().unwrap(); + let current_timestamp = clock.unix_timestamp as u64; + Ok(Timestamp::from_nanoseconds(current_timestamp).unwrap()) } fn host_consensus_state(