From 49f8a18d5ea1ade3e6c456a6bbd402c2713c5ed6 Mon Sep 17 00:00:00 2001 From: David Salami <31099392+Wizdave97@users.noreply.github.com> Date: Wed, 14 Aug 2024 21:14:44 +0100 Subject: [PATCH] Configure state machines with independent challenge periods (#293) --- modules/hyperclient/src/any_client.rs | 2 +- modules/hyperclient/src/internals.rs | 10 +- .../hyperclient/src/internals/post_request.rs | 8 +- modules/hyperclient/src/providers/evm.rs | 2 +- .../hyperclient/src/providers/interface.rs | 5 +- .../hyperclient/src/providers/substrate.rs | 4 +- .../ismp/clients/parachain/client/src/lib.rs | 25 ++- modules/ismp/core/src/error.rs | 8 +- modules/ismp/core/src/handlers.rs | 10 +- modules/ismp/core/src/handlers/consensus.rs | 7 +- modules/ismp/core/src/host.rs | 4 +- modules/ismp/core/src/messaging.rs | 7 +- modules/ismp/pallets/pallet/src/errors.rs | 6 +- modules/ismp/pallets/pallet/src/host.rs | 8 +- modules/ismp/pallets/pallet/src/lib.rs | 10 +- modules/ismp/pallets/pallet/src/utils.rs | 11 +- modules/ismp/pallets/rpc/src/lib.rs | 18 +- modules/ismp/pallets/runtime-api/src/lib.rs | 6 +- .../pallets/state-coprocessor/src/impls.rs | 16 +- modules/ismp/pallets/testsuite/src/runtime.rs | 2 +- .../testsuite/src/tests/pallet_ismp.rs | 43 ++++- .../src/tests/pallet_ismp_relayer.rs | 30 +++- modules/ismp/testsuite/src/lib.rs | 26 ++- modules/ismp/testsuite/src/mocks.rs | 4 +- parachain/runtimes/gargantua/src/lib.rs | 12 +- parachain/runtimes/messier/src/lib.rs | 10 +- parachain/runtimes/nexus/src/lib.rs | 10 +- parachain/simtests/src/hyperbridge_client.rs | 18 +- parachain/simtests/src/pallet_ismp.rs | 6 +- tesseract/evm/src/provider.rs | 136 ++++++++++++++- tesseract/messaging/src/get_requests.rs | 2 +- tesseract/messaging/src/lib.rs | 15 +- tesseract/primitives/src/lib.rs | 17 +- tesseract/primitives/src/mocks.rs | 12 +- tesseract/relayer/src/fees.rs | 14 +- tesseract/substrate/src/provider.rs | 155 ++++++++++++++++-- 36 files changed, 498 insertions(+), 181 deletions(-) diff --git a/modules/hyperclient/src/any_client.rs b/modules/hyperclient/src/any_client.rs index fc0aed30f..f458c4af8 100644 --- a/modules/hyperclient/src/any_client.rs +++ b/modules/hyperclient/src/any_client.rs @@ -281,7 +281,7 @@ impl Client for AnyClient { async fn query_challenge_period( &self, - id: ismp::consensus::ConsensusStateId, + id: ismp::consensus::StateMachineId, ) -> Result { match self { AnyClient::Evm(inner) => inner.query_challenge_period(id).await, diff --git a/modules/hyperclient/src/internals.rs b/modules/hyperclient/src/internals.rs index 883f2d94c..80c13649d 100644 --- a/modules/hyperclient/src/internals.rs +++ b/modules/hyperclient/src/internals.rs @@ -150,9 +150,8 @@ pub async fn encode_request_message_and_wait_for_challenge_period( let calldata = encode_request_call_data(hyperbridge, dest_client, post, commitment, height).await?; let proof_height = StateMachineHeight { id: hyperbridge.state_machine, height }; - let challenge_period = dest_client - .query_challenge_period(hyperbridge.state_machine_id().consensus_state_id) - .await?; + let challenge_period = + dest_client.query_challenge_period(hyperbridge.state_machine_id()).await?; let update_time = dest_client.query_state_machine_update_time(proof_height).await?; wait_for_challenge_period(dest_client, update_time, challenge_period).await?; @@ -167,9 +166,8 @@ pub async fn encode_response_message_and_wait_for_challenge_period( ) -> Result, anyhow::Error> { let calldata = encode_response_call_data(hyperbridge, dest_client, response, height).await?; let proof_height = StateMachineHeight { id: hyperbridge.state_machine, height }; - let challenge_period = dest_client - .query_challenge_period(hyperbridge.state_machine_id().consensus_state_id) - .await?; + let challenge_period = + dest_client.query_challenge_period(hyperbridge.state_machine_id()).await?; let update_time = dest_client.query_state_machine_update_time(proof_height).await?; wait_for_challenge_period(dest_client, update_time, challenge_period).await?; diff --git a/modules/hyperclient/src/internals/post_request.rs b/modules/hyperclient/src/internals/post_request.rs index e0d821f48..02289cb77 100644 --- a/modules/hyperclient/src/internals/post_request.rs +++ b/modules/hyperclient/src/internals/post_request.rs @@ -745,9 +745,7 @@ pub async fn timeout_post_request_stream( timeout_proof: Proof { height, proof }, }); let challenge_period = hyperbridge_client - .query_challenge_period( - dest_client.state_machine_id().consensus_state_id, - ) + .query_challenge_period(dest_client.state_machine_id()) .await?; let update_time = hyperbridge_client.query_state_machine_update_time(height).await?; @@ -851,9 +849,7 @@ pub async fn timeout_post_request_stream( timeout_proof: Proof { height, proof }, }); let challenge_period = source_client - .query_challenge_period( - hyperbridge_client.state_machine_id().consensus_state_id, - ) + .query_challenge_period(hyperbridge_client.state_machine_id()) .await?; let update_time = source_client.query_state_machine_update_time(height).await?; diff --git a/modules/hyperclient/src/providers/evm.rs b/modules/hyperclient/src/providers/evm.rs index 1b37858b4..86cef05ce 100644 --- a/modules/hyperclient/src/providers/evm.rs +++ b/modules/hyperclient/src/providers/evm.rs @@ -720,7 +720,7 @@ impl Client for EvmClient { Ok(Duration::from_secs(value.low_u64())) } - async fn query_challenge_period(&self, _id: ConsensusStateId) -> Result { + async fn query_challenge_period(&self, _id: StateMachineId) -> Result { let contract = EvmHost::new(self.host_address, self.client.clone()); let value = contract.challenge_period().call().await?; Ok(Duration::from_secs(value.low_u64())) diff --git a/modules/hyperclient/src/providers/interface.rs b/modules/hyperclient/src/providers/interface.rs index f1af510cc..ac953e443 100644 --- a/modules/hyperclient/src/providers/interface.rs +++ b/modules/hyperclient/src/providers/interface.rs @@ -19,7 +19,7 @@ use crate::types::{BoxStream, EventMetadata}; use core::time::Duration; use ethers::{prelude::H256, types::H160}; use ismp::{ - consensus::{ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId}, + consensus::{StateCommitment, StateMachineHeight, StateMachineId}, events::{Event, StateMachineUpdated}, host::StateMachine, messaging::Message, @@ -163,8 +163,7 @@ pub trait Client: Clone + Send + Sync + 'static { ) -> Result; /// Query the challenge period for client - async fn query_challenge_period(&self, id: ConsensusStateId) - -> Result; + async fn query_challenge_period(&self, id: StateMachineId) -> Result; } pub async fn wait_for_challenge_period( diff --git a/modules/hyperclient/src/providers/substrate.rs b/modules/hyperclient/src/providers/substrate.rs index 730bf7417..54fa2331e 100644 --- a/modules/hyperclient/src/providers/substrate.rs +++ b/modules/hyperclient/src/providers/substrate.rs @@ -27,7 +27,7 @@ use futures::{stream, StreamExt}; use hashbrown::HashMap; use hex_literal::hex; use ismp::{ - consensus::{ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId}, + consensus::{StateCommitment, StateMachineHeight, StateMachineId}, events::{Event, StateMachineUpdated}, host::StateMachine, messaging::{hash_request, hash_response, Message}, @@ -543,7 +543,7 @@ impl Client for SubstrateClient { Ok(Duration::from_secs(value)) } - async fn query_challenge_period(&self, id: ConsensusStateId) -> Result { + async fn query_challenge_period(&self, id: StateMachineId) -> Result { let params = rpc_params![id]; let response: u64 = self.client.rpc().request("ismp_queryChallengePeriod", params).await?; diff --git a/modules/ismp/clients/parachain/client/src/lib.rs b/modules/ismp/clients/parachain/client/src/lib.rs index 18a7f66ab..d9baebe08 100644 --- a/modules/ismp/clients/parachain/client/src/lib.rs +++ b/modules/ismp/clients/parachain/client/src/lib.rs @@ -41,7 +41,8 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use ismp::{ - host::IsmpHost, + consensus::StateMachineId, + host::{IsmpHost, StateMachine}, messaging::{ConsensusMessage, Message}, }; use migration::StorageV0; @@ -126,8 +127,18 @@ pub mod pallet { #[pallet::weight(::DbWeight::get().writes(para_ids.len() as u64))] pub fn add_parachain(origin: OriginFor, para_ids: Vec) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; + let host = ::default(); for para in ¶_ids { + let state_id = match host.host_state_machine() { + StateMachine::Kusama(_) => StateMachine::Kusama(para.id), + StateMachine::Polkadot(_) => StateMachine::Polkadot(para.id), + _ => continue, + }; Parachains::::insert(para.id, para.slot_duration); + let _ = host.store_challenge_period( + StateMachineId { state_id, consensus_state_id: PARACHAIN_CONSENSUS_ID }, + 0, + ); } Self::deposit_event(Event::ParachainsAdded { para_ids }); @@ -212,10 +223,20 @@ pub mod pallet { impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Pallet::::initialize(); + let host = ::default(); // insert the parachain ids for para in &self.parachains { Parachains::::insert(para.id, para.slot_duration); + let state_id = match host.host_state_machine() { + StateMachine::Kusama(_) => StateMachine::Kusama(para.id), + StateMachine::Polkadot(_) => StateMachine::Polkadot(para.id), + _ => continue, + }; + let _ = host.store_challenge_period( + StateMachineId { state_id, consensus_state_id: PARACHAIN_CONSENSUS_ID }, + 0, + ); } } } @@ -242,7 +263,7 @@ impl Pallet { // insert empty bytes consensus_state: vec![], unbonding_period: u64::MAX, - challenge_period: 0, + challenge_periods: Default::default(), consensus_state_id: PARACHAIN_CONSENSUS_ID, consensus_client_id: PARACHAIN_CONSENSUS_ID, state_machine_commitments: vec![], diff --git a/modules/ismp/core/src/error.rs b/modules/ismp/core/src/error.rs index 4e64998e0..cf45fc4c0 100644 --- a/modules/ismp/core/src/error.rs +++ b/modules/ismp/core/src/error.rs @@ -16,7 +16,7 @@ //! ISMP error definitions use crate::{ - consensus::{ConsensusClientId, ConsensusStateId, StateMachineHeight}, + consensus::{ConsensusClientId, ConsensusStateId, StateMachineHeight, StateMachineId}, events::Meta, }; use alloc::{string::String, vec::Vec}; @@ -37,7 +37,7 @@ pub enum Error { /// new consensus updates in the mean time. ChallengePeriodNotElapsed { /// The consensus client identifier - consensus_state_id: ConsensusStateId, + state_machine_id: StateMachineId, /// The last time the consensus client was updated update_time: Duration, /// The current time @@ -122,8 +122,8 @@ pub enum Error { /// Challenge period has not been configured for this consensus state ChallengePeriodNotConfigured { - /// Consensus state Id - consensus_state_id: ConsensusStateId, + /// State Machine Id + state_machine: StateMachineId, }, /// Consensus state id already exists DuplicateConsensusStateId { diff --git a/modules/ismp/core/src/handlers.rs b/modules/ismp/core/src/handlers.rs index 2ae6ff58e..6e1823e6c 100644 --- a/modules/ismp/core/src/handlers.rs +++ b/modules/ismp/core/src/handlers.rs @@ -75,11 +75,9 @@ where H: IsmpHost, { let update_time = host.state_machine_update_time(*proof_height)?; - let delay_period = host.challenge_period(proof_height.id.consensus_state_id).ok_or( - Error::ChallengePeriodNotConfigured { - consensus_state_id: proof_height.id.consensus_state_id, - }, - )?; + let delay_period = host + .challenge_period(proof_height.id) + .ok_or(Error::ChallengePeriodNotConfigured { state_machine: proof_height.id })?; let current_timestamp = host.timestamp(); Ok(delay_period.as_secs() == 0 || current_timestamp.saturating_sub(update_time) > delay_period) } @@ -109,7 +107,7 @@ where // Ensure delay period has elapsed if !verify_delay_passed(host, &proof_height)? { return Err(Error::ChallengePeriodNotElapsed { - consensus_state_id: proof_height.id.consensus_state_id, + state_machine_id: proof_height.id, current_time: host.timestamp(), update_time: host.state_machine_update_time(proof_height)?, }); diff --git a/modules/ismp/core/src/handlers/consensus.rs b/modules/ismp/core/src/handlers/consensus.rs index 316707407..40394f39f 100644 --- a/modules/ismp/core/src/handlers/consensus.rs +++ b/modules/ismp/core/src/handlers/consensus.rs @@ -98,9 +98,14 @@ where // Store the initial state for the consensus client host.store_consensus_state(message.consensus_state_id, message.consensus_state)?; host.store_unbonding_period(message.consensus_state_id, message.unbonding_period)?; - host.store_challenge_period(message.consensus_state_id, message.challenge_period)?; host.store_consensus_state_id(message.consensus_state_id, message.consensus_client_id)?; + // Store all challenge periods + for (state_id, challenge_period) in message.challenge_periods { + let id = StateMachineId { state_id, consensus_state_id: message.consensus_state_id }; + host.store_challenge_period(id, challenge_period)?; + } + // Store all intermediate state machine commitments for (id, state_commitment) in message.state_machine_commitments { let height = StateMachineHeight { id, height: state_commitment.height }; diff --git a/modules/ismp/core/src/host.rs b/modules/ismp/core/src/host.rs index cd882ae62..854a1e0a7 100644 --- a/modules/ismp/core/src/host.rs +++ b/modules/ismp/core/src/host.rs @@ -190,12 +190,12 @@ pub trait IsmpHost: Keccak256 { fn consensus_clients(&self) -> Vec>; /// Should return the configured delay period for a consensus state - fn challenge_period(&self, consensus_state_id: ConsensusStateId) -> Option; + fn challenge_period(&self, state_machine: StateMachineId) -> Option; /// Set the challenge period in seconds for a consensus state. fn store_challenge_period( &self, - consensus_state_id: ConsensusStateId, + state_machine: StateMachineId, period: u64, ) -> Result<(), Error>; diff --git a/modules/ismp/core/src/messaging.rs b/modules/ismp/core/src/messaging.rs index c30c72a78..433579d87 100644 --- a/modules/ismp/core/src/messaging.rs +++ b/modules/ismp/core/src/messaging.rs @@ -18,11 +18,14 @@ // Messages are processed in batches, all messages in a batch should // originate from the same chain +use alloc::collections::BTreeMap; + use crate::{ consensus::{ ConsensusClientId, ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId, }, error::Error, + host::StateMachine, router::{GetResponse, PostRequest, PostResponse, Request, RequestResponse, Response}, }; use alloc::{string::ToString, vec::Vec}; @@ -95,8 +98,8 @@ pub struct CreateConsensusState { pub consensus_state_id: ConsensusStateId, /// Unbonding period for this consensus state. pub unbonding_period: u64, - /// Challenge period for this consensus state - pub challenge_period: u64, + /// Challenge period for the supported state machines + pub challenge_periods: BTreeMap, /// State machine commitments pub state_machine_commitments: Vec<(StateMachineId, StateCommitmentHeight)>, } diff --git a/modules/ismp/pallets/pallet/src/errors.rs b/modules/ismp/pallets/pallet/src/errors.rs index a68a80596..b97f38037 100644 --- a/modules/ismp/pallets/pallet/src/errors.rs +++ b/modules/ismp/pallets/pallet/src/errors.rs @@ -30,7 +30,7 @@ pub enum HandlingError { update_time: u64, current_time: u64, delay_period: Option, - consensus_client_id: Option, + consensus_client_id: Option, }, ConsensusStateNotFound { id: ConsensusClientId, @@ -172,14 +172,14 @@ impl From for HandlingError { fn from(value: ismp::error::Error) -> Self { match value { IsmpError::ChallengePeriodNotElapsed { - consensus_state_id, + state_machine_id, current_time, update_time, } => HandlingError::ChallengePeriodNotElapsed { update_time: update_time.as_secs(), current_time: current_time.as_secs(), delay_period: None, - consensus_client_id: Some(consensus_state_id), + consensus_client_id: Some(state_machine_id), }, IsmpError::ConsensusStateNotFound { consensus_state_id } => HandlingError::ConsensusStateNotFound { id: consensus_state_id }, diff --git a/modules/ismp/pallets/pallet/src/host.rs b/modules/ismp/pallets/pallet/src/host.rs index 27d683347..40d73a6bc 100644 --- a/modules/ismp/pallets/pallet/src/host.rs +++ b/modules/ismp/pallets/pallet/src/host.rs @@ -271,16 +271,16 @@ impl IsmpHost for Pallet { ::ConsensusClients::consensus_clients() } - fn challenge_period(&self, id: ConsensusStateId) -> Option { - ChallengePeriod::::get(&id).map(Duration::from_secs) + fn challenge_period(&self, state_machine: StateMachineId) -> Option { + ChallengePeriod::::get(&state_machine).map(Duration::from_secs) } fn store_challenge_period( &self, - consensus_state_id: ConsensusStateId, + state_machine: StateMachineId, period: u64, ) -> Result<(), Error> { - ChallengePeriod::::insert(consensus_state_id, period); + ChallengePeriod::::insert(state_machine, period); Ok(()) } diff --git a/modules/ismp/pallets/pallet/src/lib.rs b/modules/ismp/pallets/pallet/src/lib.rs index e8f169b56..7413245c8 100644 --- a/modules/ismp/pallets/pallet/src/lib.rs +++ b/modules/ismp/pallets/pallet/src/lib.rs @@ -344,11 +344,11 @@ pub mod pallet { pub type UnbondingPeriod = StorageMap<_, Blake2_128Concat, ConsensusStateId, u64, OptionQuery>; - /// A mapping of consensus state identifiers to their challenge periods + /// A mapping of state machine Ids to their challenge periods #[pallet::storage] #[pallet::getter(fn challenge_period)] pub type ChallengePeriod = - StorageMap<_, Blake2_128Concat, ConsensusStateId, u64, OptionQuery>; + StorageMap<_, Blake2_128Concat, StateMachineId, u64, OptionQuery>; /// Holds a map of consensus clients frozen due to byzantine /// behaviour @@ -512,8 +512,10 @@ pub mod pallet { .map_err(|_| Error::::UnbondingPeriodUpdateFailed)?; } - if let Some(challenge_period) = message.challenge_period { - host.store_challenge_period(message.consensus_state_id, challenge_period) + for (state_id, period) in message.challenge_periods { + let id = + StateMachineId { state_id, consensus_state_id: message.consensus_state_id }; + host.store_challenge_period(id, period) .map_err(|_| Error::::UnbondingPeriodUpdateFailed)?; } diff --git a/modules/ismp/pallets/pallet/src/utils.rs b/modules/ismp/pallets/pallet/src/utils.rs index a678d6e9d..8f5c7d3ee 100644 --- a/modules/ismp/pallets/pallet/src/utils.rs +++ b/modules/ismp/pallets/pallet/src/utils.rs @@ -15,9 +15,14 @@ //! Pallet utilities +use alloc::collections::BTreeMap; + use codec::{Decode, Encode}; use frame_support::PalletId; -use ismp::consensus::{ConsensusClient, ConsensusStateId}; +use ismp::{ + consensus::{ConsensusClient, ConsensusStateId}, + host::StateMachine, +}; use sp_core::{ crypto::{AccountId32, ByteArray}, H160, H256, @@ -31,8 +36,8 @@ pub struct UpdateConsensusState { pub consensus_state_id: ConsensusStateId, /// Unbonding duration pub unbonding_period: Option, - /// Challenge period duration - pub challenge_period: Option, + /// Challenge period duration for different state machines + pub challenge_periods: BTreeMap, } /// Holds a commitment to either a request or response diff --git a/modules/ismp/pallets/rpc/src/lib.rs b/modules/ismp/pallets/rpc/src/lib.rs index dd4cc73b6..56b144a11 100644 --- a/modules/ismp/pallets/rpc/src/lib.rs +++ b/modules/ismp/pallets/rpc/src/lib.rs @@ -72,7 +72,7 @@ use jsonrpsee::{ use anyhow::anyhow; use codec::Encode; use ismp::{ - consensus::{ConsensusClientId, StateMachineId}, + consensus::{ConsensusClientId, StateMachineHeight, StateMachineId}, events::Event, router::{Request, Response}, }; @@ -186,12 +186,12 @@ where ) -> RpcResult>; /// Query timestamp of when this client was last updated in seconds - #[method(name = "ismp_queryConsensusUpdateTime")] - fn query_consensus_update_time(&self, client_id: ConsensusClientId) -> RpcResult; + #[method(name = "ismp_queryStateMachineUpdateTime")] + fn query_state_machine_update_time(&self, height: StateMachineHeight) -> RpcResult; - /// Query the challenge period for client + /// Query the challenge period for a state machine #[method(name = "ismp_queryChallengePeriod")] - fn query_challenge_period(&self, client_id: ConsensusClientId) -> RpcResult; + fn query_challenge_period(&self, client_id: StateMachineId) -> RpcResult; /// Query the latest height for a state machine #[method(name = "ismp_queryStateMachineLatestHeight")] @@ -360,19 +360,19 @@ where .ok_or_else(|| runtime_error_into_rpc_error("Error fetching Consensus state")) } - fn query_consensus_update_time(&self, client_id: ConsensusClientId) -> RpcResult { + fn query_state_machine_update_time(&self, height: StateMachineHeight) -> RpcResult { let api = self.client.runtime_api(); let at = self.client.info().best_hash; - api.consensus_update_time(at, client_id) + api.state_machine_update_time(at, height) .ok() .flatten() .ok_or_else(|| runtime_error_into_rpc_error("Error fetching Consensus update time")) } - fn query_challenge_period(&self, client_id: ConsensusClientId) -> RpcResult { + fn query_challenge_period(&self, state_machine_id: StateMachineId) -> RpcResult { let api = self.client.runtime_api(); let at = self.client.info().best_hash; - api.challenge_period(at, client_id) + api.challenge_period(at, state_machine_id) .ok() .flatten() .ok_or_else(|| runtime_error_into_rpc_error("Error fetching Challenge period")) diff --git a/modules/ismp/pallets/runtime-api/src/lib.rs b/modules/ismp/pallets/runtime-api/src/lib.rs index 11346c6aa..fdbd1ab5e 100644 --- a/modules/ismp/pallets/runtime-api/src/lib.rs +++ b/modules/ismp/pallets/runtime-api/src/lib.rs @@ -22,7 +22,7 @@ extern crate alloc; use alloc::vec::Vec; use ismp::{ - consensus::{ConsensusClientId, StateMachineId}, + consensus::{ConsensusClientId, StateMachineHeight, StateMachineId}, host::StateMachine, router::{Request, Response}, }; @@ -51,10 +51,10 @@ sp_api::decl_runtime_apis! { fn consensus_state(id: ConsensusClientId) -> Option>; /// Return the timestamp this client was last updated in seconds - fn consensus_update_time(id: ConsensusClientId) -> Option; + fn state_machine_update_time(id: StateMachineHeight) -> Option; /// Return the challenge period timestamp - fn challenge_period(id: ConsensusClientId) -> Option; + fn challenge_period(id: StateMachineId) -> Option; /// Return the latest height of the state machine fn latest_state_machine_height(id: StateMachineId) -> Option; diff --git a/modules/ismp/pallets/state-coprocessor/src/impls.rs b/modules/ismp/pallets/state-coprocessor/src/impls.rs index 1d1f3671c..590ee3636 100644 --- a/modules/ismp/pallets/state-coprocessor/src/impls.rs +++ b/modules/ismp/pallets/state-coprocessor/src/impls.rs @@ -20,6 +20,7 @@ use alloc::{string::ToString, vec, vec::Vec}; use codec::{Decode, Encode}; use evm_common::{derive_unhashed_map_key, presets::REQUEST_COMMITMENTS_SLOT}; use ismp::{ + events::RequestResponseHandled, handlers::validate_state_machine, host::{IsmpHost, StateMachine}, messaging::{hash_get_response, hash_request, Proof}, @@ -170,15 +171,18 @@ where for get_response in get_responses { let full = Request::Get(get_response.get.clone()); host.store_request_receipt(&full, &address)?; - Self::dispatch_get_response(get_response) - .map_err(|_| Error::Custom("Failed to dispatch get response".to_string()))? + Self::dispatch_get_response(get_response, address.clone()) + .map_err(|_| Error::Custom("Failed to dispatch get response".to_string()))?; } Ok(()) } /// Insert a get response into the MMR and emits an event - pub fn dispatch_get_response(get_response: GetResponse) -> Result<(), ismp::Error> { + pub fn dispatch_get_response( + get_response: GetResponse, + address: Vec, + ) -> Result<(), ismp::Error> { let commitment = hash_get_response::<::IsmpHost>(&get_response); let req_commitment = hash_request::<::IsmpHost>(&Request::Get(get_response.get.clone())); @@ -206,6 +210,12 @@ where }, ); pallet_ismp::Responded::::insert(req_commitment, true); + pallet_ismp::Pallet::::deposit_pallet_event(event); + let event = pallet_ismp::Event::GetRequestHandled(RequestResponseHandled { + commitment: req_commitment, + relayer: address.clone(), + }); + pallet_ismp::Pallet::::deposit_pallet_event(event); Ok(()) diff --git a/modules/ismp/pallets/testsuite/src/runtime.rs b/modules/ismp/pallets/testsuite/src/runtime.rs index 49fc21528..526a45d3f 100644 --- a/modules/ismp/pallets/testsuite/src/runtime.rs +++ b/modules/ismp/pallets/testsuite/src/runtime.rs @@ -390,7 +390,7 @@ where consensus_client_id: MOCK_CONSENSUS_CLIENT_ID, consensus_state_id: MOCK_CONSENSUS_STATE_ID, unbonding_period: 1_000_000, - challenge_period: 0, + challenge_periods: vec![(StateMachine::Evm(1), 0)].into_iter().collect(), state_machine_commitments: vec![( StateMachineId { state_id: StateMachine::Evm(1), diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_ismp.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_ismp.rs index de30867fd..5afef3be2 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_ismp.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_ismp.rs @@ -77,7 +77,12 @@ fn should_reject_updates_within_challenge_period() { ext.execute_with(|| { set_timestamp(None); let host = Ismp::default(); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 1_000_000).unwrap(); + + let id = StateMachineId { + state_id: StateMachine::Evm(11155111), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }; + host.store_challenge_period(id, 1_000_000).unwrap(); check_challenge_period(&host).unwrap() }) } @@ -89,7 +94,11 @@ fn should_reject_messages_for_frozen_state_machines() { ext.execute_with(|| { set_timestamp(None); let host = Ismp::default(); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 1_000_000).unwrap(); + let id = StateMachineId { + state_id: StateMachine::Evm(11155111), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }; + host.store_challenge_period(id, 1_000_000).unwrap(); missing_state_commitment_check(&host).unwrap() }) } @@ -102,7 +111,11 @@ fn should_reject_expired_check_clients() { set_timestamp(None); let host = Ismp::default(); host.store_unbonding_period(MOCK_CONSENSUS_STATE_ID, 1_000_000).unwrap(); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 1_000_000).unwrap(); + let id = StateMachineId { + state_id: StateMachine::Evm(11155111), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }; + host.store_challenge_period(id, 1_000_000).unwrap(); check_client_expiry(&host).unwrap() }) } @@ -114,7 +127,11 @@ fn should_handle_post_request_timeouts_correctly() { ext.execute_with(|| { set_timestamp(Some(0)); let host = Ismp::default(); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 0).unwrap(); + let id = StateMachineId { + state_id: StateMachine::Evm(11155111), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }; + host.store_challenge_period(id, 0).unwrap(); post_request_timeout_check(&host).unwrap() }) } @@ -126,7 +143,11 @@ fn should_handle_post_response_timeouts_correctly() { ext.execute_with(|| { set_timestamp(None); let host = Ismp::default(); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 1_000_000).unwrap(); + let id = StateMachineId { + state_id: StateMachine::Evm(11155111), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }; + host.store_challenge_period(id, 1_000_000).unwrap(); post_response_timeout_check(&host).unwrap() }) } @@ -137,7 +158,11 @@ fn should_handle_get_request_timeouts_correctly() { ext.execute_with(|| { let host = Ismp::default(); setup_mock_client::<_, Test>(&host); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 0).unwrap(); + let id = StateMachineId { + state_id: StateMachine::Evm(11155111), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }; + host.store_challenge_period(id, 0).unwrap(); let requests = (0..2) .into_iter() .map(|i| { @@ -185,7 +210,11 @@ fn should_handle_get_request_responses_correctly() { ext.execute_with(|| { let host = Ismp::default(); setup_mock_client::<_, Test>(&host); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 0).unwrap(); + let id = StateMachineId { + state_id: StateMachine::Evm(11155111), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }; + host.store_challenge_period(id, 0).unwrap(); let requests = (0..2) .into_iter() .map(|i| { diff --git a/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs b/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs index 79b62420e..a2849a252 100644 --- a/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs +++ b/modules/ismp/pallets/testsuite/src/tests/pallet_ismp_relayer.rs @@ -248,7 +248,23 @@ fn test_withdrawal_proof() { host.store_unbonding_period(MOCK_CONSENSUS_STATE_ID, 10_000_000_000).unwrap(); - host.store_challenge_period(MOCK_CONSENSUS_STATE_ID, 0).unwrap(); + host.store_challenge_period( + StateMachineId { + state_id: StateMachine::Kusama(2001), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }, + 0, + ) + .unwrap(); + + host.store_challenge_period( + StateMachineId { + state_id: StateMachine::Kusama(2000), + consensus_state_id: MOCK_CONSENSUS_STATE_ID, + }, + 0, + ) + .unwrap(); let withdrawal_proof = WithdrawalProof { commitments: keys, @@ -526,8 +542,7 @@ fn test_evm_accumulate_fees() { ) .unwrap(); - host.store_challenge_period(claim_proof.source_proof.height.id.consensus_state_id, 0) - .unwrap(); + host.store_challenge_period(claim_proof.source_proof.height.id, 0).unwrap(); host.store_unbonding_period( claim_proof.dest_proof.height.id.consensus_state_id, @@ -535,8 +550,7 @@ fn test_evm_accumulate_fees() { ) .unwrap(); - host.store_challenge_period(claim_proof.dest_proof.height.id.consensus_state_id, 0) - .unwrap(); + host.store_challenge_period(claim_proof.dest_proof.height.id, 0).unwrap(); pallet_ismp_relayer::Pallet::::accumulate_fees( RuntimeOrigin::none(), @@ -694,8 +708,7 @@ fn setup_host_for_accumulate_fees() -> WithdrawalProof { ) .unwrap(); - host.store_challenge_period(claim_proof.source_proof.height.id.consensus_state_id, 0) - .unwrap(); + host.store_challenge_period(claim_proof.source_proof.height.id, 0).unwrap(); host.store_unbonding_period( claim_proof.dest_proof.height.id.consensus_state_id, @@ -703,8 +716,7 @@ fn setup_host_for_accumulate_fees() -> WithdrawalProof { ) .unwrap(); - host.store_challenge_period(claim_proof.dest_proof.height.id.consensus_state_id, 0) - .unwrap(); + host.store_challenge_period(claim_proof.dest_proof.height.id, 0).unwrap(); claim_proof } diff --git a/modules/ismp/testsuite/src/lib.rs b/modules/ismp/testsuite/src/lib.rs index c03903ef9..85aebe24d 100644 --- a/modules/ismp/testsuite/src/lib.rs +++ b/modules/ismp/testsuite/src/lib.rs @@ -105,7 +105,7 @@ fn setup_mock_proxy_client( pub fn check_challenge_period(host: &H) -> Result<(), &'static str> { let intermediate_state = setup_mock_client(host); // Set the previous update time - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp() - (challenge_period / 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); @@ -181,7 +181,7 @@ pub fn check_client_expiry(host: &H) -> Result<(), &'static str> { pub fn frozen_consensus_client_check(host: &H) -> Result<(), &'static str> { let intermediate_state = setup_mock_client(host); // Set the previous update time - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); @@ -215,7 +215,7 @@ pub fn frozen_consensus_client_check(host: &H) -> Result<(), &'stat pub fn missing_state_commitment_check(host: &H) -> Result<(), &'static str> { let intermediate_state = setup_mock_client(host); // Set the previous update time - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); @@ -278,7 +278,7 @@ where H::Balance: From + Default, { let intermediate_state = setup_mock_client(host); - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp().saturating_sub(challenge_period * 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); @@ -351,7 +351,7 @@ where H::Balance: From + Default, { let intermediate_state = setup_mock_client(host); - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); @@ -478,10 +478,9 @@ pub fn prevent_request_timeout_on_proxy_with_known_state_machine( let mut host = Host::default(); host.proxy = Some(proxy_state_machine); - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); - let previous_update_time = host.timestamp() - (challenge_period * 2); - let proxy = setup_mock_proxy_client(&host, proxy_state_machine); + let challenge_period = host.challenge_period(proxy.height.id).unwrap(); + let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_proxy_consensus_state_id(), previous_update_time) .unwrap(); @@ -562,7 +561,7 @@ pub fn prevent_response_timeout_on_proxy_with_known_state_machine( host.proxy = Some(proxy_state_machine); let intermediate_state = setup_mock_client(&host); - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); @@ -648,10 +647,9 @@ pub fn prevent_request_processing_on_proxy_with_known_state_machine( let proxy_state_machine = StateMachine::Kusama(2000); let mut host = Host::default(); host.proxy = Some(proxy_state_machine); - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); - let previous_update_time = host.timestamp() - (challenge_period * 2); - let proxy = setup_mock_proxy_client(&host, proxy_state_machine); + let challenge_period = host.challenge_period(proxy.height.id).unwrap(); + let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_proxy_consensus_state_id(), previous_update_time) .unwrap(); @@ -712,7 +710,7 @@ pub fn prevent_request_processing_on_proxy_with_known_state_machine( pub fn check_request_source_and_destination() -> Result<(), &'static str> { let host = Host::default(); let intermediate_state = setup_mock_client(&host); - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); @@ -749,7 +747,7 @@ pub fn check_request_source_and_destination() -> Result<(), &'static str> { pub fn check_response_source() -> Result<(), &'static str> { let host = Host::default(); let intermediate_state = setup_mock_client(&host); - let challenge_period = host.challenge_period(mock_consensus_state_id()).unwrap(); + let challenge_period = host.challenge_period(intermediate_state.height.id).unwrap(); let previous_update_time = host.timestamp() - (challenge_period * 2); host.store_consensus_update_time(mock_consensus_state_id(), previous_update_time) .unwrap(); diff --git a/modules/ismp/testsuite/src/mocks.rs b/modules/ismp/testsuite/src/mocks.rs index 62f67454f..0f34930ea 100644 --- a/modules/ismp/testsuite/src/mocks.rs +++ b/modules/ismp/testsuite/src/mocks.rs @@ -366,13 +366,13 @@ impl IsmpHost for Host { vec![Box::new(MockClient), Box::new(MockProxyClient)] } - fn challenge_period(&self, _consensus_state_id: ConsensusStateId) -> Option { + fn challenge_period(&self, _state_machine: StateMachineId) -> Option { Some(Duration::from_secs(60 * 60)) } fn store_challenge_period( &self, - _consensus_state_id: ConsensusStateId, + _state_machine: StateMachineId, _period: u64, ) -> Result<(), Error> { Ok(()) diff --git a/parachain/runtimes/gargantua/src/lib.rs b/parachain/runtimes/gargantua/src/lib.rs index 87d16894a..b7634c83a 100644 --- a/parachain/runtimes/gargantua/src/lib.rs +++ b/parachain/runtimes/gargantua/src/lib.rs @@ -49,7 +49,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use ::ismp::{ - consensus::{ConsensusClientId, StateMachineId}, + consensus::{ConsensusClientId, StateMachineHeight, StateMachineId}, router::{Request, Response}, }; @@ -214,7 +214,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("gargantua"), impl_name: create_runtime_str!("gargantua"), authoring_version: 1, - spec_version: 400, + spec_version: 500, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -797,8 +797,8 @@ impl_runtime_apis! { ::HostStateMachine::get() } - fn challenge_period(consensus_state_id: [u8; 4]) -> Option { - Ismp::challenge_period(consensus_state_id) + fn challenge_period(state_machine_id: StateMachineId) -> Option { + Ismp::challenge_period(state_machine_id) } /// Generate a proof for the provided leaf indices @@ -824,8 +824,8 @@ impl_runtime_apis! { } /// Return the timestamp this client was last updated in seconds - fn consensus_update_time(id: ConsensusClientId) -> Option { - Ismp::consensus_update_time(id) + fn state_machine_update_time(height: StateMachineHeight) -> Option { + Ismp::state_machine_update_time(height) } /// Return the latest height of the state machine diff --git a/parachain/runtimes/messier/src/lib.rs b/parachain/runtimes/messier/src/lib.rs index 507e56c21..c84d5f440 100644 --- a/parachain/runtimes/messier/src/lib.rs +++ b/parachain/runtimes/messier/src/lib.rs @@ -50,7 +50,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use ::ismp::{ - consensus::{ConsensusClientId, StateMachineId}, + consensus::{ConsensusClientId, StateMachineHeight, StateMachineId}, router::{Request, Response}, }; use frame_support::{ @@ -801,8 +801,8 @@ impl_runtime_apis! { ::HostStateMachine::get() } - fn challenge_period(consensus_state_id: [u8; 4]) -> Option { - Ismp::challenge_period(consensus_state_id) + fn challenge_period(id: StateMachineId) -> Option { + Ismp::challenge_period(id) } /// Generate a proof for the provided leaf indices @@ -828,8 +828,8 @@ impl_runtime_apis! { } /// Return the timestamp this client was last updated in seconds - fn consensus_update_time(id: ConsensusClientId) -> Option { - Ismp::consensus_update_time(id) + fn state_machine_update_time(height: StateMachineHeight) -> Option { + Ismp::state_machine_update_time(height) } /// Return the latest height of the state machine diff --git a/parachain/runtimes/nexus/src/lib.rs b/parachain/runtimes/nexus/src/lib.rs index 4c5208831..3e978cc6c 100644 --- a/parachain/runtimes/nexus/src/lib.rs +++ b/parachain/runtimes/nexus/src/lib.rs @@ -51,7 +51,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; use ::ismp::{ - consensus::{ConsensusClientId, StateMachineId}, + consensus::{ConsensusClientId, StateMachineHeight, StateMachineId}, router::{Request, Response}, }; use frame_support::{ @@ -817,8 +817,8 @@ impl_runtime_apis! { ::HostStateMachine::get() } - fn challenge_period(consensus_state_id: [u8; 4]) -> Option { - Ismp::challenge_period(consensus_state_id) + fn challenge_period(id: StateMachineId) -> Option { + Ismp::challenge_period(id) } /// Generate a proof for the provided leaf indices @@ -844,8 +844,8 @@ impl_runtime_apis! { } /// Return the timestamp this client was last updated in seconds - fn consensus_update_time(id: ConsensusClientId) -> Option { - Ismp::consensus_update_time(id) + fn state_machine_update_time(height: StateMachineHeight) -> Option { + Ismp::state_machine_update_time(height) } /// Return the latest height of the state machine diff --git a/parachain/simtests/src/hyperbridge_client.rs b/parachain/simtests/src/hyperbridge_client.rs index 0b54d9b7f..6cbe8f203 100644 --- a/parachain/simtests/src/hyperbridge_client.rs +++ b/parachain/simtests/src/hyperbridge_client.rs @@ -66,7 +66,7 @@ async fn test_will_accept_paid_requests() -> Result<(), anyhow::Error> { runtime_types::pallet_ismp_host_executive::pallet::Call::set_host_params { params: vec![ ( - StateMachine::Polkadot(para_id).into(), + StateMachine::Kusama(para_id).into(), runtime_types::pallet_ismp_host_executive::params::HostParam::SubstrateHostParam( runtime_types::pallet_hyperbridge::VersionedHostParams::V1(per_byte_fee) ) @@ -104,7 +104,7 @@ async fn test_will_accept_paid_requests() -> Result<(), anyhow::Error> { } let post = PostRequest { - source: StateMachine::Polkadot(para_id), + source: StateMachine::Kusama(para_id), dest: StateMachine::Evm(8002), nonce: 0, from: H256::random().as_bytes().to_vec(), @@ -148,7 +148,7 @@ async fn test_will_accept_paid_requests() -> Result<(), anyhow::Error> { StateCommitment { timestamp: 0, overlay_root: Some(root), state_root: root }; let height = StateMachineHeight { id: StateMachineId { - state_id: StateMachine::Polkadot(para_id).into(), + state_id: StateMachine::Kusama(para_id).into(), consensus_state_id: *b"PARA", }, height: 200, @@ -259,7 +259,7 @@ async fn test_will_reject_unpaid_requests() -> Result<(), anyhow::Error> { runtime_types::pallet_ismp_host_executive::pallet::Call::set_host_params { params: vec![ ( - StateMachine::Polkadot(para_id).into(), + StateMachine::Kusama(para_id).into(), runtime_types::pallet_ismp_host_executive::params::HostParam::SubstrateHostParam( runtime_types::pallet_hyperbridge::VersionedHostParams::V1(per_byte_fee) ) @@ -295,7 +295,7 @@ async fn test_will_reject_unpaid_requests() -> Result<(), anyhow::Error> { } let post = PostRequest { - source: StateMachine::Polkadot(para_id), + source: StateMachine::Kusama(para_id), dest: StateMachine::Evm(8002), nonce: 0, from: H256::random().as_bytes().to_vec(), @@ -334,7 +334,7 @@ async fn test_will_reject_unpaid_requests() -> Result<(), anyhow::Error> { StateCommitment { timestamp: 0, overlay_root: Some(root), state_root: root }; let height = StateMachineHeight { id: StateMachineId { - state_id: StateMachine::Polkadot(para_id).into(), + state_id: StateMachine::Kusama(para_id).into(), consensus_state_id: *b"PARA", }, height: 200, @@ -444,7 +444,7 @@ async fn test_will_reject_partially_paid_requests() -> Result<(), anyhow::Error> runtime_types::pallet_ismp_host_executive::pallet::Call::set_host_params { params: vec![ ( - StateMachine::Polkadot(para_id).into(), + StateMachine::Kusama(para_id).into(), runtime_types::pallet_ismp_host_executive::params::HostParam::SubstrateHostParam( runtime_types::pallet_hyperbridge::VersionedHostParams::V1(per_byte_fee) ) @@ -480,7 +480,7 @@ async fn test_will_reject_partially_paid_requests() -> Result<(), anyhow::Error> } let post = PostRequest { - source: StateMachine::Polkadot(para_id), + source: StateMachine::Kusama(para_id), dest: StateMachine::Evm(8002), nonce: 0, from: H256::random().as_bytes().to_vec(), @@ -524,7 +524,7 @@ async fn test_will_reject_partially_paid_requests() -> Result<(), anyhow::Error> StateCommitment { timestamp: 0, overlay_root: Some(root), state_root: root }; let height = StateMachineHeight { id: StateMachineId { - state_id: StateMachine::Polkadot(para_id).into(), + state_id: StateMachine::Kusama(para_id).into(), consensus_state_id: *b"PARA", }, height: 200, diff --git a/parachain/simtests/src/pallet_ismp.rs b/parachain/simtests/src/pallet_ismp.rs index 97af5cdbd..2686a825d 100644 --- a/parachain/simtests/src/pallet_ismp.rs +++ b/parachain/simtests/src/pallet_ismp.rs @@ -82,7 +82,7 @@ async fn test_txpool_should_reject_duplicate_requests() -> Result<(), anyhow::Er runtime_types::pallet_ismp_host_executive::pallet::Call::set_host_params { params: vec![ ( - StateMachine::Polkadot(para_id).into(), + StateMachine::Kusama(para_id).into(), runtime_types::pallet_ismp_host_executive::params::HostParam::SubstrateHostParam( runtime_types::pallet_hyperbridge::VersionedHostParams::V1(0) ) @@ -118,7 +118,7 @@ async fn test_txpool_should_reject_duplicate_requests() -> Result<(), anyhow::Er } let post = PostRequest { - source: StateMachine::Polkadot(para_id), + source: StateMachine::Kusama(para_id), dest: StateMachine::Evm(8002), nonce: 0, from: H256::random().as_bytes().to_vec(), @@ -157,7 +157,7 @@ async fn test_txpool_should_reject_duplicate_requests() -> Result<(), anyhow::Er StateCommitment { timestamp: 0, overlay_root: Some(root), state_root: root }; let height = StateMachineHeight { id: StateMachineId { - state_id: StateMachine::Polkadot(para_id).into(), + state_id: StateMachine::Kusama(para_id).into(), consensus_state_id: *b"PARA", }, height: 200, diff --git a/tesseract/evm/src/provider.rs b/tesseract/evm/src/provider.rs index 4521e6be3..0064238da 100644 --- a/tesseract/evm/src/provider.rs +++ b/tesseract/evm/src/provider.rs @@ -16,7 +16,7 @@ use ethers::{ use evm_common::types::EvmStateProof; use ismp::{ consensus::{ConsensusStateId, StateMachineId}, - events::Event, + events::{Event, StateCommitmentVetoed}, messaging::{hash_request, hash_response, Message, StateCommitmentHeight}, }; use ismp_solidity_abi::evm_host::{PostRequestHandledFilter, PostResponseHandledFilter}; @@ -45,8 +45,8 @@ use primitive_types::U256; use sp_core::{H160, H256}; use std::{collections::BTreeMap, sync::Arc, time::Duration}; use tesseract_primitives::{ - BoxStream, EstimateGasReturnParams, Hasher, IsmpProvider, Query, Signature, - StateMachineUpdated, StateProofQueryType, TxReceipt, + wait_for_challenge_period, BoxStream, EstimateGasReturnParams, Hasher, IsmpProvider, Query, + Signature, StateMachineUpdated, StateProofQueryType, TxReceipt, }; #[async_trait::async_trait] @@ -123,7 +123,7 @@ impl IsmpProvider for EvmClient { Ok(Duration::from_secs(value.low_u64())) } - async fn query_challenge_period(&self, _id: ConsensusStateId) -> Result { + async fn query_challenge_period(&self, _id: StateMachineId) -> Result { let contract = EvmHost::new(self.config.ismp_host, self.client.clone()); let value = contract.challenge_period().call().await?; Ok(Duration::from_secs(value.low_u64())) @@ -500,14 +500,90 @@ impl IsmpProvider for EvmClient { return Ok(fee_metadata.fee); } + async fn state_commitment_vetoed_notification( + &self, + from: u64, + update_height: StateMachineHeight, + ) -> BoxStream { + let (tx, recv) = tokio::sync::mpsc::channel(256); + let client = self.clone(); + let poll_interval = 10; + tokio::spawn(async move { + let mut latest_height = from; + let state_machine = client.state_machine; + loop { + tokio::time::sleep(Duration::from_secs(poll_interval)).await; + // wait for an update with a greater height + let block_number = match client.client.get_block_number().await { + Ok(number) => number.low_u64(), + Err(err) => { + if let Err(err) = tx + .send(Err(anyhow!( + "Error fetching latest block height on {state_machine:?} {err:?}" + ))) + .await + { + log::error!(target: "tesseract", "Failed to send message over channel on {state_machine:?} \n {err:?}"); + } + continue; + }, + }; + + if block_number <= latest_height { + continue; + } + + let event = StateMachineUpdated { + state_machine_id: client.state_machine_id(), + latest_height: block_number, + }; + + let events = match client.query_ismp_events(latest_height, event).await { + Ok(events) => events, + Err(err) => { + if let Err(err) = tx + .send(Err(anyhow!( + "Error encountered while querying ismp events {err:?}" + ))) + .await + { + log::error!(target: "tesseract", "Failed to send message over channel on {state_machine:?} \n {err:?}"); + } + latest_height = block_number; + continue; + }, + }; + + let event = events + .into_iter() + .find_map(|ev| match ev { + Event::StateCommitmentVetoed(update) if update.height == update_height => Some(update), + _ => None, + }); + + if let Some(event) = event { + if let Err(err) = tx.send(Ok(event.clone())).await { + log::trace!(target: "tesseract", "Failed to send state commitment vetoed event over channel on {state_machine:?}->{:?} \n {err:?}", update_height.id.state_id); + return + }; + } + latest_height = block_number; + } + }.boxed()); + + Box::pin(tokio_stream::wrappers::ReceiverStream::new(recv)) + } + async fn state_machine_update_notification( &self, - _counterparty_state_id: StateMachineId, + counterparty_state_id: StateMachineId, ) -> Result, Error> { + use futures::StreamExt; let initial_height = self.client.get_block_number().await?.low_u64(); let (tx, recv) = tokio::sync::mpsc::channel(256); let client = self.clone(); let poll_interval = self.config.poll_interval.unwrap_or(10); + let challenge_period = self.query_challenge_period(counterparty_state_id).await?; tokio::spawn(async move { let mut latest_height = initial_height; let state_machine = client.state_machine; @@ -563,9 +639,53 @@ impl IsmpProvider for EvmClient { .max_by(|a, b| a.latest_height.cmp(&b.latest_height)); if let Some(event) = event { - if let Err(err) = tx.send(Ok(event.clone())).await { - log::trace!(target: "tesseract", "Failed to send state machine update over channel on {state_machine:?}->{:?} \n {err:?}", _counterparty_state_id.state_id); - return + // We wait for the challenge period and see if the update will be vetoed before yielding + let commitment_height = StateMachineHeight { id: counterparty_state_id, height: event.latest_height }; + let state_machine_update_time = match client.query_state_machine_update_time(commitment_height).await { + Ok(val) => val, + Err(err) => { + if let Err(err) = tx + .send(Err(anyhow!( + "Error encountered while querying state_machine_update_time {err:?}" + ))) + .await + { + log::error!(target: "tesseract", "Failed to send message over channel on {state_machine:?} \n {err:?}"); + } + latest_height = block_number; + continue; + } + }; + + let mut state_commitment_vetoed_stream = client.state_commitment_vetoed_notification(latest_height, commitment_height).await; + let provider = Arc::new(client.clone()); + // Yield if the challenge period elapses and the state commitment is not vetoed + tokio::select! { + _res = wait_for_challenge_period(provider, state_machine_update_time, challenge_period) => { + match _res { + Ok(_) => { + if let Err(err) = tx.send(Ok(event.clone())).await { + log::trace!(target: "tesseract", "Failed to send state machine update over channel on {state_machine:?} - {:?} \n {err:?}", counterparty_state_id.state_id); + return + }; + } + Err(err) => { + log::error!(target: "tesseract", "Error waiting for challenge period in {state_machine:?} - {:?} update stream \n {err:?}", counterparty_state_id.state_id); + } + } + } + + _res = state_commitment_vetoed_stream.next() => { + match _res { + Some(Ok(_)) => { + log::error!(target: "tesseract", "State Commitment for {event:?} was vetoed on {state_machine}"); + } + + _ => { + log::error!(target: "tesseract", "Error in state machine vetoed stream {state_machine:?} - {:?}", counterparty_state_id.state_id); + } + } + } }; } latest_height = block_number; diff --git a/tesseract/messaging/src/get_requests.rs b/tesseract/messaging/src/get_requests.rs index 4716c6b86..d45e62e0a 100644 --- a/tesseract/messaging/src/get_requests.rs +++ b/tesseract/messaging/src/get_requests.rs @@ -10,7 +10,7 @@ use ismp::{ }; use pallet_state_coprocessor::impls::GetRequestsWithProof; use tesseract_primitives::{ - observe_challenge_period, Cost, HandleGetResponse, Hasher, IsmpProvider, StateMachineUpdated, + observe_challenge_period, HandleGetResponse, Hasher, IsmpProvider, StateMachineUpdated, StateProofQueryType, }; use tokio::sync::mpsc::Receiver; diff --git a/tesseract/messaging/src/lib.rs b/tesseract/messaging/src/lib.rs index 6bfce5e30..ab014bfa4 100644 --- a/tesseract/messaging/src/lib.rs +++ b/tesseract/messaging/src/lib.rs @@ -34,9 +34,8 @@ use futures::{FutureExt, StreamExt}; use ismp::{consensus::StateMachineHeight, events::Event, host::StateMachine, router::GetRequest}; use tesseract_primitives::{ - config::RelayerConfig, observe_challenge_period, wait_for_challenge_period, - wait_for_state_machine_update, HandleGetResponse, HyperbridgeClaim, IsmpProvider, - StateMachineUpdated, TxReceipt, + config::RelayerConfig, observe_challenge_period, wait_for_state_machine_update, + HandleGetResponse, HyperbridgeClaim, IsmpProvider, StateMachineUpdated, TxReceipt, }; use transaction_fees::TransactionPayment; @@ -328,16 +327,6 @@ async fn handle_update( height: state_machine_update.latest_height, }; - let last_consensus_update = - chain_a.query_state_machine_update_time(state_machine_height).await?; - let challenge_period = chain_a - .query_challenge_period(chain_b.state_machine_id().consensus_state_id) - .await?; - - // Wait for the challenge period for the consensus update to elapse before submitting - // messages.So that calls to debug_traceCall can succeed - wait_for_challenge_period(chain_a.clone(), last_consensus_update, challenge_period).await?; - let (messages, unprofitable) = translate_events_to_messages( chain_b.clone(), chain_a.clone(), diff --git a/tesseract/primitives/src/lib.rs b/tesseract/primitives/src/lib.rs index fc7e9d87d..f702dbeba 100644 --- a/tesseract/primitives/src/lib.rs +++ b/tesseract/primitives/src/lib.rs @@ -23,7 +23,7 @@ use futures::{Stream, StreamExt}; pub use ismp::events::StateMachineUpdated; use ismp::{ consensus::{ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId}, - events::Event, + events::{Event, StateCommitmentVetoed}, host::StateMachine, messaging::{CreateConsensusState, Keccak256, Message}, router::PostRequest, @@ -191,8 +191,7 @@ pub trait IsmpProvider: Send + Sync { ) -> Result; /// Query the challenge period for client - async fn query_challenge_period(&self, id: ConsensusStateId) - -> Result; + async fn query_challenge_period(&self, id: StateMachineId) -> Result; /// Query the latest timestamp for chain async fn query_timestamp(&self) -> Result; @@ -275,6 +274,14 @@ pub trait IsmpProvider: Send + Sync { counterparty_state_id: StateMachineId, ) -> Result, anyhow::Error>; + /// Return a stream that watches for state machine commitment vetoes, starting at [`from`] + /// yields when a [`StateCommitmentVetoed`] event is observed for [`height`] + async fn state_commitment_vetoed_notification( + &self, + from: u64, + height: StateMachineHeight, + ) -> BoxStream; + /// This should be used to submit new messages [`Vec`] from a counterparty chain to /// this chain. /// @@ -481,9 +488,7 @@ pub async fn observe_challenge_period( hyperbridge: Arc, height: u64, ) -> anyhow::Result<()> { - let challenge_period = hyperbridge - .query_challenge_period(chain.state_machine_id().consensus_state_id) - .await?; + let challenge_period = hyperbridge.query_challenge_period(chain.state_machine_id()).await?; let height = StateMachineHeight { id: chain.state_machine_id(), height }; let last_consensus_update = hyperbridge.query_state_machine_update_time(height).await?; wait_for_challenge_period(hyperbridge, last_consensus_update, challenge_period).await?; diff --git a/tesseract/primitives/src/mocks.rs b/tesseract/primitives/src/mocks.rs index aeded1f08..eb09a59f7 100644 --- a/tesseract/primitives/src/mocks.rs +++ b/tesseract/primitives/src/mocks.rs @@ -5,7 +5,7 @@ use crate::{ use anyhow::{anyhow, Error}; use ismp::{ consensus::{ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId}, - events::Event, + events::{Event, StateCommitmentVetoed}, host::StateMachine, messaging::{CreateConsensusState, Message}, }; @@ -111,7 +111,7 @@ impl IsmpProvider for MockHost { Ok(Duration::from_secs(0)) } - async fn query_challenge_period(&self, _id: ConsensusStateId) -> Result { + async fn query_challenge_period(&self, _id: StateMachineId) -> Result { Ok(Duration::from_secs(0)) } @@ -187,6 +187,14 @@ impl IsmpProvider for MockHost { todo!() } + async fn state_commitment_vetoed_notification( + &self, + _from: u64, + _height: StateMachineHeight, + ) -> BoxStream { + todo!() + } + async fn submit(&self, _messages: Vec) -> Result, Error> { todo!() } diff --git a/tesseract/relayer/src/fees.rs b/tesseract/relayer/src/fees.rs index 157502952..b9b5ed6ea 100644 --- a/tesseract/relayer/src/fees.rs +++ b/tesseract/relayer/src/fees.rs @@ -11,9 +11,8 @@ use ismp::{ use sp_core::U256; use std::{collections::HashMap, str::FromStr, sync::Arc, time::Duration}; use tesseract_primitives::{ - config::RelayerConfig, observe_challenge_period, wait_for_challenge_period, - wait_for_state_machine_update, Cost, Hasher, HyperbridgeClaim, IsmpProvider, Query, - WithdrawFundsResult, + config::RelayerConfig, observe_challenge_period, wait_for_state_machine_update, Cost, Hasher, + HyperbridgeClaim, IsmpProvider, Query, WithdrawFundsResult, }; use tesseract_substrate::config::KeccakSubstrateChain; use tracing::instrument; @@ -420,15 +419,6 @@ async fn deliver_post_request( }; } - let state_machine_id = hyperbridge.state_machine_id(); - let challenge_period = - dest_chain.query_challenge_period(state_machine_id.consensus_state_id).await?; - let height = StateMachineHeight { id: state_machine_id, height: latest_height }; - let last_consensus_update = dest_chain.query_state_machine_update_time(height).await?; - - log::info!("Waiting for challenge period to elapse"); - - wait_for_challenge_period(dest_chain.clone(), last_consensus_update, challenge_period).await?; let query = Query { source_chain: result.post.source, dest_chain: result.post.dest, diff --git a/tesseract/substrate/src/provider.rs b/tesseract/substrate/src/provider.rs index 92e9ec8c1..aa4b3095b 100644 --- a/tesseract/substrate/src/provider.rs +++ b/tesseract/substrate/src/provider.rs @@ -22,10 +22,8 @@ use codec::{Decode, Encode}; use futures::{stream::FuturesOrdered, FutureExt}; use hex_literal::hex; use ismp::{ - consensus::{ - ConsensusClientId, ConsensusStateId, StateCommitment, StateMachineHeight, StateMachineId, - }, - events::Event, + consensus::{ConsensusClientId, StateCommitment, StateMachineHeight, StateMachineId}, + events::{Event, StateCommitmentVetoed}, host::StateMachine, messaging::{CreateConsensusState, Message}, }; @@ -59,8 +57,8 @@ use subxt::{ use subxt_utils::{host_params_storage_key, send_extrinsic, state_machine_update_time_storage_key}; use tesseract_primitives::{ - BoxStream, EstimateGasReturnParams, IsmpProvider, Query, StateMachineUpdated, - StateProofQueryType, TxReceipt, + wait_for_challenge_period, BoxStream, EstimateGasReturnParams, IsmpProvider, Query, + StateMachineUpdated, StateProofQueryType, TxReceipt, }; use crate::{ @@ -411,13 +409,106 @@ where Ok(leaf_meta.meta.fee.into()) } + async fn state_commitment_vetoed_notification( + &self, + from: u64, + update_height: StateMachineHeight, + ) -> BoxStream { + let client = self.clone(); + let (tx, recv) = tokio::sync::mpsc::channel(256); + tokio::task::spawn(async move { + let mut latest_height = from; + let state_machine = client.state_machine; + loop { + tokio::time::sleep(Duration::from_secs(10)).await; + let header = match client.client.rpc().finalized_head().await { + Ok(hash) => match client.client.rpc().header(Some(hash)).await { + Ok(Some(header)) => header, + _ => { + if let Err(err) = tx + .send(Err(anyhow!( + "Error encountered while fething finalized head" + ))) + .await + { + log::error!(target: "tesseract", "Failed to send message over channel on {state_machine:?} \n {err:?}"); + } + continue; + }, + }, + Err(err) => { + if let Err(err) = tx + .send(Err(anyhow!( + "Error encountered while fetching finalized head: {err:?}" + ))) + .await + { + log::error!(target: "tesseract", "Failed to send message over channel on {state_machine:?} \n {err:?}"); + } + continue; + }, + }; + + if header.number().into() <= latest_height { + continue; + } + + let event = StateMachineUpdated { + state_machine_id: client.state_machine_id(), + latest_height: header.number().into(), + }; + + let events = match client.query_ismp_events(latest_height, event).await { + Ok(e) => e, + Err(err) => { + if let Err(err) = tx + .send(Err(anyhow!( + "Error encountered while querying ismp events {err:?}" + ))) + .await + { + log::error!(target: "tesseract", "Failed to send message over channel on {state_machine:?} \n {err:?}"); + } + latest_height = header.number().into(); + continue; + }, + }; + + let event = events + .into_iter() + .find_map(|event| match event { + Event::StateCommitmentVetoed(e) + if e.height == update_height => + Some(e), + _ => None, + }); + + match event { + Some(event) => { + if let Err(err) = tx.send(Ok(event.clone())).await { + log::trace!(target: "tesseract", "Failed to send state commitment veto event over channel on {state_machine:?} - {:?} \n {err:?}", update_height.id.state_id); + return + }; + }, + None => {}, + }; + + latest_height = header.number().into(); + } + }.boxed()); + + Box::pin(tokio_stream::wrappers::ReceiverStream::new(recv)) + } + async fn state_machine_update_notification( &self, counterparty_state_id: StateMachineId, ) -> Result, anyhow::Error> { + use futures::StreamExt; let client = self.clone(); let (tx, recv) = tokio::sync::mpsc::channel(256); let latest_height = client.query_finalized_height().await?; + let challenge_period = self.query_challenge_period(counterparty_state_id).await?; tokio::task::spawn(async move { let mut latest_height = latest_height; let state_machine = client.state_machine; @@ -488,9 +579,50 @@ where match event { Some(event) => { - if let Err(err) = tx.send(Ok(event.clone())).await { - log::trace!(target: "tesseract", "Failed to send state machine update over channel on {state_machine:?} - {:?} \n {err:?}", counterparty_state_id.state_id); - return + // We wait for the challenge period and see if the update will be vetoed before yielding + let commitment_height = StateMachineHeight { id: counterparty_state_id, height: event.latest_height }; + let state_machine_update_time = match client.query_state_machine_update_time(commitment_height).await { + Ok(val) => val, + Err(err) => { + if let Err(err) = tx + .send(Err(anyhow!( + "Error encountered while querying state_machine_update_time {err:?}" + ))) + .await + { + log::error!(target: "tesseract", "Failed to send message over channel on {state_machine:?} \n {err:?}"); + } + latest_height = header.number().into(); + continue; + } + }; + + let mut state_commitment_vetoed_stream = client.state_commitment_vetoed_notification(latest_height, commitment_height).await; + let provider = Arc::new(client.clone()); + tokio::select! { + _res = wait_for_challenge_period(provider, state_machine_update_time, challenge_period) => { + match _res { + Ok(_) => { + if let Err(err) = tx.send(Ok(event.clone())).await { + log::trace!(target: "tesseract", "Failed to send state machine update over channel on {state_machine:?} - {:?} \n {err:?}", counterparty_state_id.state_id); + return + }; + } + Err(err) => { + log::error!(target: "tesseract", "Error waiting for challenge period in {state_machine:?} - {:?} update stream \n {err:?}", counterparty_state_id.state_id); + } + } + } + _res = state_commitment_vetoed_stream.next() => { + match _res { + Some(Ok(_)) => { + log::error!(target: "tesseract", "State Commitment for {event:?} was vetoed on {state_machine}"); + } + _ => { + log::error!(target: "tesseract", "Error in state machine vetoed stream {state_machine:?} - {:?}", counterparty_state_id.state_id); + } + } + } }; }, None => {}, @@ -541,10 +673,7 @@ where Ok(Default::default()) } - async fn query_challenge_period( - &self, - id: ConsensusStateId, - ) -> Result { + async fn query_challenge_period(&self, id: StateMachineId) -> Result { let params = rpc_params![id]; let response: u64 = self.client.rpc().request("ismp_queryChallengePeriod", params).await?;