diff --git a/Cargo.lock b/Cargo.lock index 05e386c99..529ce282c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8177,6 +8177,7 @@ dependencies = [ "pallet-ismp-host-executive", "pallet-ismp-relayer", "pallet-ismp-runtime-api", + "pallet-log-emitter", "pallet-mmr-runtime-api", "pallet-mmr-tree", "pallet-state-coprocessor", @@ -9447,6 +9448,37 @@ dependencies = [ "substrate-state-machine", ] +[[package]] +name = "hyperbridge-ismp-parachain" +version = "0.1.0" +dependencies = [ + "hex", + "hex-literal 0.4.1", + "ismp", + "log", + "pallet-ismp", + "parity-scale-codec", + "polkadot-sdk", + "primitive-types 0.13.1", + "scale-info", + "serde", + "substrate-state-machine", +] + +[[package]] +name = "hyperbridge-ismp-parachain-inherent" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "hyperbridge-ismp-parachain", + "ismp", + "ismp-parachain", + "log", + "parity-scale-codec", + "polkadot-sdk", +] + [[package]] name = "hyperclient" version = "0.6.0" @@ -11841,6 +11873,7 @@ dependencies = [ "pallet-ismp-host-executive", "pallet-ismp-relayer", "pallet-ismp-runtime-api", + "pallet-log-emitter", "pallet-mmr-runtime-api", "pallet-mmr-tree", "pallet-revive-ismp-dispatcher", @@ -14896,6 +14929,16 @@ dependencies = [ "zstd-safe 7.2.4", ] +[[package]] +name = "pallet-log-emitter" +version = "0.1.0" +dependencies = [ + "hex", + "parity-scale-codec", + "polkadot-sdk", + "scale-info", +] + [[package]] name = "pallet-lottery" version = "41.0.0" @@ -15432,8 +15475,6 @@ dependencies = [ [[package]] name = "pallet-revive" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "474840408264f98eea7f187839ff2157f83a92bec6f3f3503dbf855c38f4de6b" dependencies = [ "alloy-core", "derive_more 0.99.20", diff --git a/Cargo.toml b/Cargo.toml index 870ed8559..5b3b127dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ members = [ # Airdrop - "modules/pallets/bridge-drop", + "modules/pallets/bridge-drop", "modules/pallets/pallet-log-emitter", "modules/ismp/clients/hyperbridge-parachain/client", "modules/ismp/clients/hyperbridge-parachain/inherent", ] # Config for 'cargo dist' @@ -239,6 +239,7 @@ arbitrum-verifier = { path = "./modules/ismp/clients/arbitrum", default-features op-verifier = { path = "./modules/ismp/clients/optimism", default-features = false } ismp-arbitrum = { path = "modules/ismp/clients/ismp-arbitrum", default-features = false } ismp-optimism = { path = "modules/ismp/clients/ismp-optimism", default-features = false } +hyperbridge-ismp-parachain = { path = "modules/ismp/clients/hyperbridge-parachain/client", default-features = false } # state machine clients evm-state-machine = { path = "./modules/ismp/state-machines/evm", default-features = false } @@ -257,6 +258,8 @@ pallet-state-coprocessor = { path = "modules/pallets/state-coprocessor", default pallet-token-gateway-inspector = { path = "modules/pallets/token-gateway-inspector", default-features = false } pallet-bridge-airdrop = { path = "modules/pallets/bridge-drop", default-features = false } +pallet-log-emitter = { path = "modules/pallets/pallet-log-emitter", default-features = false } + # merkle trees ethereum-triedb = { version = "0.1.1", path = "./modules/trees/ethereum", default-features = false } pallet-mmr-tree = { path = "modules/pallets/mmr", default-features = false } @@ -268,6 +271,7 @@ mmr-gadget = { path = "modules/pallets/mmr/gadget" } # runtimes gargantua-runtime = { path = "./parachain/runtimes/gargantua", default-features = false } nexus-runtime = { path = "./parachain/runtimes/nexus", default-features = false } +messier-runtime = { path = "./parachain/runtimes/messier", default-features = false } # tesseract tesseract-primitives = { path = "tesseract/messaging/primitives" } @@ -343,3 +347,7 @@ features = ["derive"] [workspace.dependencies.reconnecting-jsonrpsee-ws-client] version = "0.5.0" default-features = false + + +[patch.crates-io] +pallet-revive = { path = "vendor/pallet-revive" } diff --git a/modules/ismp/clients/hyperbridge-parachain/client/Cargo.toml b/modules/ismp/clients/hyperbridge-parachain/client/Cargo.toml new file mode 100644 index 000000000..cadd982e1 --- /dev/null +++ b/modules/ismp/clients/hyperbridge-parachain/client/Cargo.toml @@ -0,0 +1,59 @@ +[package] +name = "hyperbridge-ismp-parachain" +version = "0.1.0" +edition = "2024" +authors = ["Polytope Labs "] +license = "Apache-2.0" + +[dependencies] +# crates.io +serde = { workspace = true, features = ["derive"], optional = false } +codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } +hex-literal = { workspace = true } +hex = { workspace = true } +primitive-types = { workspace = true } +log = { workspace = true } +alloy-rlp = { workspace = true } +alloy-sol-types = { workspace = true, default-features = false } +# local +substrate-state-machine = { workspace = true } +ismp = { workspace = true } +pallet-ismp = { workspace = true } + +[dependencies.polkadot-sdk] +workspace = true +features = [ + "frame-support", + "frame-system", + "sp-trie", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-consensus-aura", + "cumulus-pallet-parachain-system", + "cumulus-primitives-core", + "pallet-revive" +] + +[features] +default = ["std"] +std = [ + "codec/std", + "polkadot-sdk/std", + "scale-info/std", + "ismp/std", + "log/std", + "primitive-types/std", + "pallet-ismp/std", + "substrate-state-machine/std", + "alloy-rlp/std", + "alloy-sol-types/std", +] +try-runtime = ["polkadot-sdk/try-runtime"] +runtime-benchmarks = [ + "polkadot-sdk/frame-benchmarking", + "polkadot-sdk/runtime-benchmarks", + "pallet-ismp/runtime-benchmarks", +] + diff --git a/modules/ismp/clients/hyperbridge-parachain/client/src/IHyperbridgeVerifier.sol b/modules/ismp/clients/hyperbridge-parachain/client/src/IHyperbridgeVerifier.sol new file mode 100644 index 000000000..8a1b024d4 --- /dev/null +++ b/modules/ismp/clients/hyperbridge-parachain/client/src/IHyperbridgeVerifier.sol @@ -0,0 +1,14 @@ +interface IHyperbridgeVerifier { + struct StateCommitment { + uint64 timestamp; + bytes32 overlayRoot; + bytes32 stateRoot; + } + + struct StateCommitmentHeight { + StateCommitment commitment; + uint64 height; + } + + function latestStateCommitment() external view returns (StateCommitmentHeight memory); +} diff --git a/modules/ismp/clients/hyperbridge-parachain/client/src/lib.rs b/modules/ismp/clients/hyperbridge-parachain/client/src/lib.rs new file mode 100644 index 000000000..0a7f9b7a8 --- /dev/null +++ b/modules/ismp/clients/hyperbridge-parachain/client/src/lib.rs @@ -0,0 +1,333 @@ +// Copyright (C) Polytope Labs Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod precompile; + +pub use pallet::*; +use polkadot_sdk::*; + +extern crate alloc; +use alloc::{vec, vec::Vec}; +use codec::{Decode, DecodeWithMemTracking, Encode}; +use cumulus_pallet_parachain_system::{RelaychainDataProvider, RelaychainStateProvider}; +use frame_support::{pallet_prelude::*, traits::Get}; +use frame_system::pallet_prelude::*; +use pallet_revive::{H160, H256, inherent_handlers::InherentHandler}; +use sp_consensus_aura::{AURA_ENGINE_ID, Slot}; +use sp_core::ConstU32; +use sp_io::hashing::{keccak_256, twox_64}; +use sp_runtime::{ + DigestItem, DispatchError, DispatchResult, + app_crypto::sp_core::storage::StorageKey, + generic::Header, + traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, +}; +use sp_std::time::Duration; +use sp_trie::StorageProof; +use substrate_state_machine::read_proof_check_for_parachain; + +/// Hyperbridge's Inherent identifier +pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"hypbridg"; + +/// The `ConsensusEngineId` of ISMP `ConsensusDigest` in the parachain header. +pub const ISMP_ID: sp_runtime::ConsensusEngineId = *b"ISMP"; + +pub const ISMP_TIMESTAMP_ID: sp_runtime::ConsensusEngineId = *b"ISTM"; + +/// Timestamp log digest for pallet ismp +#[derive(Encode, Decode, Clone, scale_info::TypeInfo, Default)] +pub struct TimestampDigest { + /// Timestamp value in seconds + pub timestamp: u64, +} + +/// Consensus log digest for pallet ismp +#[derive(Encode, Decode, Clone, scale_info::TypeInfo, Default)] +pub struct ConsensusDigest { + /// Mmr root hash + pub mmr_root: H256, + /// Child trie root hash + pub child_trie_root: H256, +} + +/// Proof for proving finality +#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo, DecodeWithMemTracking)] +pub struct HyperbridgeConsensusProof { + /// Height of the relay chain for the given proof + pub relay_height: u32, + /// Storage proof for the parachain headers + pub storage_proof: Vec>, +} + +/// Commitment to Hyperbridge State Machine at a given height +#[derive( + Debug, + Encode, + Decode, + Clone, + PartialEq, + Eq, + DecodeWithMemTracking, + TypeInfo, + Default, + MaxEncodedLen, +)] +pub struct StateCommitment { + /// Timestamp in seconds + pub timestamp: u64, + /// Root hash of the request/response overlay trie if the state machine supports it. + pub overlay_root: Option, + /// Root hash of the global state trie. + pub state_root: H256, +} + +/// Identifies a state commitment at a given height +#[derive( + Debug, + Encode, + Decode, + Clone, + PartialEq, + Eq, + DecodeWithMemTracking, + TypeInfo, + Default, + MaxEncodedLen, +)] +pub struct StateCommitmentHeight { + /// The state machine identifier + pub commitment: StateCommitment, + /// The corresponding block height + pub height: u64, +} + +#[polkadot_sdk::frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: + polkadot_sdk::frame_system::Config + + pallet_revive::Config + + cumulus_pallet_parachain_system::Config + { + /// The specific parachain ID for Hyperbridge that this pallet verifies. + #[pallet::constant] + type HyperbridgeParaId: Get; + + /// The ISMP Host contract address for Hyperbridge + #[pallet::constant] + type IsmpHostContractAddress: Get; + } + + /// Stores the latest verified StateCommitmentHeight for Hyperbridge. + #[pallet::storage] + #[pallet::getter(fn hyperbridge_state_commitment_height)] + pub type HyperbridgeStateCommitmentHeight = + StorageValue<_, StateCommitmentHeight, OptionQuery>; + + /// Tracks whether the inherent has run for the current block. + #[pallet::storage] + #[pallet::getter(fn inherent_processed)] + pub type InherentProcessed = StorageValue<_, bool, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + HyperbridgeProofVerified { commitment: StateCommitmentHeight }, + } + + #[pallet::error] + pub enum Error { + InherentAlreadyProcessed, + StorageProofVerificationFailed, + HyperbridgeHeaderNotFound, + HeaderDecodingFailed, + DigestExtractionError, + TimestampNotFound, + HandlerProofDecodingFailed, + } + + #[pallet::call] + impl Pallet { + /// Inherent extrinsic to submit and verify the Hyperbridge proof. + #[pallet::call_index(0)] + #[pallet::weight(Weight::MAX)] + pub fn submit_hyperbridge_proof( + origin: OriginFor, + proof: HyperbridgeConsensusProof, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + ensure!(!InherentProcessed::::get(), Error::::InherentAlreadyProcessed); + + let commitment = Self::verify_store_and_extract(&proof)?; + + Self::deposit_event(Event::HyperbridgeProofVerified { commitment }); + + InherentProcessed::::put(true); + Ok(Pays::No.into()) + } + } + + #[pallet::inherent] + impl ProvideInherent for Pallet { + type Call = Call; + type Error = sp_inherents::MakeFatalError<()>; + const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; + + fn create_inherent(data: &InherentData) -> Option { + if InherentProcessed::::get() { + return None; + } + let proof_data: HyperbridgeConsensusProof = + data.get_data(&Self::INHERENT_IDENTIFIER).ok().flatten()?; + Some(Call::submit_hyperbridge_proof { proof: proof_data }) + } + + fn is_inherent(call: &Self::Call) -> bool { + matches!(call, Call::submit_hyperbridge_proof { .. }) && !InherentProcessed::::get() + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + InherentProcessed::::put(false); + Weight::zero() + } + } + + impl InherentHandler for Pallet { + fn handler_name() -> &'static [u8] { + b"hyperbridge_proof_verifier_v1" + } + + fn handle_message(message: Vec) -> DispatchResult { + let proof = HyperbridgeConsensusProof::decode(&mut &message[..]) + .map_err(|_| Error::::HandlerProofDecodingFailed)?; + + let final_commitment = Self::verify_store_and_extract(&proof)?; + + let ismp_host_address = T::IsmpHostContractAddress::get(); + let topic_0_hash = keccak_256(b"StateMachineUpdated(bytes32,bytes)"); + let topic_0: H256 = H256::from(topic_0_hash); + + let topics = vec![topic_0]; + + let data = final_commitment.encode(); + + >::deposit_event( + ::RuntimeEvent::from( + pallet_revive::Event::ContractEmitted { + contract: ismp_host_address, + topics, + data, + }, + ), + ); + + Ok(()) + } + } +} + +impl Pallet { + /// Verifies proof for Hyperbridge, extracts digest info, stores the commitment height. + fn verify_store_and_extract( + proof: &HyperbridgeConsensusProof, + ) -> Result { + let state = RelaychainDataProvider::::current_relay_chain_state(); + let relay_root = state.state_root; + + let storage_proof = StorageProof::new(proof.storage_proof.clone()); + let hyperbridge_para_id = T::HyperbridgeParaId::get(); + let header_key = parachain_header_storage_key(hyperbridge_para_id).0; + let keys_to_prove = alloc::vec![header_key]; + + let read_result = read_proof_check_for_parachain::( + &relay_root, + storage_proof, + keys_to_prove.iter().cloned(), + ) + .map_err(|e| { + log::error!(target: "runtime::hyperbridge-verifier", "Storage proof verification failed: {:?}", e); + Error::::StorageProofVerificationFailed + })?; + + let header_bytes = read_result + .get(&keys_to_prove[0]) + .cloned() + .flatten() + .ok_or(Error::::HyperbridgeHeaderNotFound)?; + + let decoded_vec: Vec = + Decode::decode(&mut &header_bytes[..]).map_err(|_| Error::::HeaderDecodingFailed)?; + let header = Header::::decode(&mut &decoded_vec[..]) + .map_err(|_| Error::::HeaderDecodingFailed)?; + + let mut timestamp: u64 = 0; + let mut overlay_root: H256 = H256::default(); + for digest in header.digest().logs.iter() { + match digest { + DigestItem::Consensus(consensus_engine_id, value) + if *consensus_engine_id == ISMP_TIMESTAMP_ID => + { + let timestamp_digest = TimestampDigest::decode(&mut &value[..]) + .map_err(|_| Error::::DigestExtractionError)?; + timestamp = timestamp_digest.timestamp; + }, + DigestItem::Consensus(consensus_engine_id, value) + if *consensus_engine_id == ISMP_ID => + { + if let Ok(log) = ConsensusDigest::decode(&mut &value[..]) { + overlay_root = log.child_trie_root; + } else { + log::warn!(target: "ismp::hyperbridge-verifier", "Invalid ISMP digest found"); + } + }, + _ => {}, + }; + } + + ensure!(timestamp > 0, Error::::TimestampNotFound); + + let block_height: u64 = (*header.number()).into(); + + let commitment_height = StateCommitmentHeight { + commitment: StateCommitment { + timestamp, + overlay_root: Some(overlay_root), + state_root: *header.state_root(), + }, + height: block_height, + }; + HyperbridgeStateCommitmentHeight::::put(commitment_height.clone()); + + Ok(commitment_height) + } +} + +pub fn parachain_header_storage_key(para_id: u32) -> StorageKey { + let mut storage_key = storage::storage_prefix(b"Paras", b"Heads").to_vec(); + let encoded_para_id = para_id.encode(); + storage_key.extend_from_slice(twox_64(&encoded_para_id).as_slice()); + storage_key.extend_from_slice(&encoded_para_id); + StorageKey(storage_key) +} diff --git a/modules/ismp/clients/hyperbridge-parachain/client/src/precompile.rs b/modules/ismp/clients/hyperbridge-parachain/client/src/precompile.rs new file mode 100644 index 000000000..a158e43bc --- /dev/null +++ b/modules/ismp/clients/hyperbridge-parachain/client/src/precompile.rs @@ -0,0 +1,81 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use polkadot_sdk::*; +use alloc::vec::Vec; +use core::{marker::PhantomData, num::NonZero}; +use frame_support::{traits::Get, weights::Weight}; +use pallet_revive::precompiles::{ + alloy::{ + primitives::{FixedBytes}, + self, + sol_types::{Revert, SolValue}, + }, + AddressMatcher, Error, Ext, Precompile, +}; + +use crate::{Config as VerifierConfig, Pallet as VerifierPallet}; + +alloy::sol!("src/IHyperbridgeVerifier.sol"); +use IHyperbridgeVerifier::IHyperbridgeVerifierCalls; + +/// Trait that provides weights for the verifier precompile operations. +pub trait VerifierWeightSchedule { + /// Weight for fetching the latest state commitment. + fn latest_state_commitment() -> Weight; +} + +/// [`pallet_revive::precompiles::Precompile`] implementation for the +/// Hyperbridge ismp parachain pallet. +pub struct VerifierPrecompile( + PhantomData<(Runtime, WeightSchedule)>, +); + + +impl Precompile for VerifierPrecompile +where + Runtime: VerifierConfig + pallet_revive::Config, + WeightSchedule: VerifierWeightSchedule, +{ + type T = Runtime; + + const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(3368).unwrap()); + + const HAS_CONTRACT_INFO: bool = false; + + type Interface = IHyperbridgeVerifier::IHyperbridgeVerifierCalls; + + fn call( + _address: &[u8; 20], + input: &Self::Interface, + env: &mut impl Ext, + ) -> Result, Error> { + match input { + IHyperbridgeVerifier::IHyperbridgeVerifierCalls::latestStateCommitment( + _call, + ) => { + env.charge(WeightSchedule::latest_state_commitment())?; + + let state = VerifierPallet::::hyperbridge_state_commitment_height() + .ok_or(Error::Revert(Revert { + reason: "No verified state commitment found".into(), + }))?; + + let commitment = IHyperbridgeVerifier::StateCommitment { + timestamp: state.commitment.timestamp, + overlayRoot: FixedBytes(state.commitment.overlay_root.unwrap_or_default().0), + stateRoot: FixedBytes(state.commitment.state_root.0), + }; + + let sol_state = IHyperbridgeVerifier::StateCommitmentHeight { + commitment, + height: state.height, + }; + + Ok(sol_state.abi_encode()) + } + } + } +} + diff --git a/modules/ismp/clients/hyperbridge-parachain/inherent/Cargo.toml b/modules/ismp/clients/hyperbridge-parachain/inherent/Cargo.toml new file mode 100644 index 000000000..994d5755f --- /dev/null +++ b/modules/ismp/clients/hyperbridge-parachain/inherent/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "hyperbridge-ismp-parachain-inherent" +version = "0.1.0" +edition = "2024" +authors = ["Polytope Labs "] +license = "Apache-2.0" + + +[dependencies] +# crates.io +async-trait = { version = "0.1.63" } +codec = { workspace = true, features = ["derive"], default-features = true } +anyhow = { workspace = true } +log = { workspace = true } + +# local +ismp = { workspace = true, default-features = true } +ismp-parachain = { workspace = true, default-features = true } +hyperbridge-ismp-parachain = { workspace = true, default-features = true } + +[dependencies.polkadot-sdk] +workspace = true +default-features = true +features = [ + "sp-inherents", + "sp-api", + "sp-blockchain", + "sp-runtime", + "cumulus-primitives-core", + "cumulus-relay-chain-interface", +] \ No newline at end of file diff --git a/modules/ismp/clients/hyperbridge-parachain/inherent/src/lib.rs b/modules/ismp/clients/hyperbridge-parachain/inherent/src/lib.rs new file mode 100644 index 000000000..f42b41f75 --- /dev/null +++ b/modules/ismp/clients/hyperbridge-parachain/inherent/src/lib.rs @@ -0,0 +1,123 @@ +use anyhow::anyhow; +use cumulus_relay_chain_interface::RelayChainInterface; +use hyperbridge_ismp_parachain::{parachain_header_storage_key, HyperbridgeConsensusProof, INHERENT_IDENTIFIER}; +use polkadot_sdk::*; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_blockchain::HeaderBackend; +use sp_inherents::{Error, InherentData, InherentDataProvider, InherentIdentifier}; +use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Hash as HashT, Header as HeaderT}}; +use std::sync::Arc; + +sp_api::decl_runtime_apis! { + /// Hyperbridge Parachain consensus client runtime APIs + pub trait HyperbridgeVerifierApi { + /// Return the hyperbridge para id + fn hyperbridge_para_id() -> u32; + /// Return the current relay chain state. + fn current_relay_chain_state() -> Option; + } +} + +/// Provides the `HyperbridgeConsensusProof` inherent data. +pub struct HyperbridgeInherentProvider { + relay_chain_interface: Arc, + consensus_proof: Option, +} + +impl HyperbridgeInherentProvider { + /// Creates the inherent provider and fetches the necessary proof data for Hyperbridge. + pub async fn create( + parent_hash: B::Hash, + client: Arc, + relay_chain_interface: Arc, + ) -> Result + where + B: BlockT, + B::Hash: HashT, + C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, + C::Api: HyperbridgeVerifierApi, + ::Hash: HashT + From, + { + if !client.runtime_api().has_api::>(parent_hash)? { + log::trace!("HyperbridgeVerifierApi not implemented by runtime"); + return Ok(Self { relay_chain_interface, consensus_proof: None }); + } + + let hyperbridge_para_id = client.runtime_api().hyperbridge_para_id(parent_hash)?; + log::trace!("Target Hyperbridge ParaId from runtime: {}", hyperbridge_para_id); + + let maybe_relay_state = client + .runtime_api() + .current_relay_chain_state(parent_hash) + .map_err(|e| anyhow!("Failed to get current relay chain state: {:?}", e))?; + + let relay_state = match maybe_relay_state { + Some(state) => state, + None => { + log::warn!("Runtime not providing relay chain state via API."); + return Ok(Self { relay_chain_interface, consensus_proof: None }); + } + }; + + let relay_height = relay_state.number; + log::trace!("Current relay chain height from runtime API: {}", relay_height); + + if relay_height == 0 { + return Ok(Self { relay_chain_interface, consensus_proof: None }); + } + + let relay_header = match relay_chain_interface.header(BlockId::Number(relay_height)).await { + Ok(Some(header)) => header, + _ => { + log::trace!("Relay chain header not available for height {}", relay_height); + return Ok(Self { relay_chain_interface, consensus_proof: None }); + } + }; + let relay_hash = relay_header.hash(); + + let header_key = parachain_header_storage_key(hyperbridge_para_id).0; + let keys_to_prove = vec![header_key]; + + let storage_proof = match relay_chain_interface.prove_read(relay_hash, &keys_to_prove).await { + Ok(proof) => proof.into_iter_nodes().collect(), + Err(e) => { + log::error!("Failed to get storage proof from relay chain for height {}: {:?}", relay_height, e); + return Ok(Self { relay_chain_interface, consensus_proof: None }); + } + }; + + let proof = HyperbridgeConsensusProof { + relay_height, + storage_proof, + }; + + log::trace!("Successfully created Hyperbridge consensus proof for relay height {}", relay_height); + Ok(Self { relay_chain_interface, consensus_proof: Some(proof) }) + } +} + +#[async_trait::async_trait] +impl InherentDataProvider for HyperbridgeInherentProvider { + async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { + if let Some(ref proof) = self.consensus_proof { + inherent_data.put_data(INHERENT_IDENTIFIER, proof)?; + log::trace!("Provided inherent data for {}", String::from_utf8_lossy(&INHERENT_IDENTIFIER)); + } + Ok(()) + } + + async fn try_handle_error(&self, identifier: &InherentIdentifier, error: &[u8]) -> Option> { + if *identifier == INHERENT_IDENTIFIER { + log::error!( + target: "hyperbridge-inherent", + "Inherent error for {}: {}", + String::from_utf8_lossy(identifier), + String::from_utf8_lossy(error) + ); + None + } else { + None + } + } +} + diff --git a/parachain/runtimes/gargantua/Cargo.toml b/parachain/runtimes/gargantua/Cargo.toml index 95507728e..559199894 100644 --- a/parachain/runtimes/gargantua/Cargo.toml +++ b/parachain/runtimes/gargantua/Cargo.toml @@ -48,6 +48,7 @@ pallet-token-gateway = { workspace = true } pallet-bridge-airdrop = { workspace = true } ismp-arbitrum = { workspace = true } ismp-optimism = { workspace = true } +pallet-log-emitter = { workspace = true } [dependencies.polkadot-sdk] workspace = true @@ -106,7 +107,8 @@ features = [ "pallet-collator-selection", "staging-parachain-info", "parachains-common", - "pallet-vesting" + "pallet-vesting", + "pallet-revive", ] [features] @@ -143,7 +145,8 @@ std = [ "frame-benchmarking/std", "pallet-bridge-airdrop/std", "ismp-arbitrum/std", - "ismp-optimism/std" + "ismp-optimism/std", + "pallet-log-emitter/std" ] runtime-benchmarks = [ "hex-literal", diff --git a/parachain/runtimes/gargantua/src/lib.rs b/parachain/runtimes/gargantua/src/lib.rs index 68b21ab3a..1fea14a12 100644 --- a/parachain/runtimes/gargantua/src/lib.rs +++ b/parachain/runtimes/gargantua/src/lib.rs @@ -85,6 +85,8 @@ pub use sp_runtime::{MultiAddress, Perbill, Permill}; #[cfg(feature = "runtime-benchmarks")] use staging_xcm::latest::Location; use xcm::XcmOriginToTransactDispatchOrigin; +use pallet_revive::evm::runtime::EthExtra; +use pallet_revive::inherent_handlers::{InherentHandler, InherentHandlers}; #[cfg(any(feature = "std", test))] pub use sp_runtime::BuildStorage; @@ -107,6 +109,7 @@ use staging_xcm::latest::prelude::BodyId; use pallet_collective::PrimeDefaultVote; #[cfg(feature = "runtime-benchmarks")] use pallet_treasury::ArgumentsFactory; +use polkadot_sdk::frame_system::EnsureSigned; use pallet_ismp::offchain::{Leaf, ProofKeys}; use sp_core::{crypto::AccountId32, Get}; @@ -129,6 +132,9 @@ pub type Balance = u128; /// Index of a transaction in the chain. pub type Index = u32; +/// Type alias for Nonce +pub type Nonce = Index; + /// A hash of some data used by the chain. pub type Hash = sp_core::H256; @@ -150,6 +156,19 @@ pub type SignedBlock = generic::SignedBlock; /// BlockId type as expected by this runtime. pub type BlockId = generic::BlockId; +/// The extension to the basic transaction logic. +pub type TxExtension = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, + frame_metadata_hash_extension::CheckMetadataHash, +); + /// The SignedExtension to the basic transaction logic. pub type SignedExtra = ( frame_system::CheckNonZeroSender, @@ -163,12 +182,48 @@ pub type SignedExtra = ( frame_metadata_hash_extension::CheckMetadataHash, ); +/// Default extensions applied to Ethereum transactions. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EthExtraImpl; + +impl EthExtra for EthExtraImpl { + type Config = Runtime; + type Extension = TxExtension; + + fn get_eth_extension(nonce: u32, tip: Balance) -> Self::Extension { + ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::from(generic::Era::Immortal), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(tip), + frame_metadata_hash_extension::CheckMetadataHash::::new(false), + ) + } +} + + /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = - generic::UncheckedExtrinsic; +pallet_revive::evm::runtime::UncheckedExtrinsic; /// Extrinsic type that has already been checked. -pub type CheckedExtrinsic = generic::CheckedExtrinsic; +pub type CheckedExtrinsic = generic::CheckedExtrinsic; + +// This impl is no longer necessary post stable-2506. +impl TryFrom for pallet_revive::Call { + type Error = (); + + fn try_from(value: RuntimeCall) -> Result { + match value { + RuntimeCall::Revive(call) => Ok(call), + _ => Err(()), + } + } +} /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< @@ -711,6 +766,42 @@ impl pallet_vesting::Config for Runtime { type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; type BlockNumberProvider = System; } +parameter_types! { + pub const DepositPerItem: Balance = EXISTENTIAL_DEPOSIT; + pub const DepositPerByte: Balance = EXISTENTIAL_DEPOSIT; + pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); +} + +impl pallet_revive::Config for Runtime { + type Time = Timestamp; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type DepositPerItem = DepositPerItem; + type DepositPerByte = DepositPerByte; + type WeightPrice = pallet_transaction_payment::Pallet; + type WeightInfo = (); + type Precompiles = (); + type AddressMapper = pallet_revive::AccountId32Mapper; + type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; + type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>; + type UnsafeUnstableInterface = ConstBool; + type UploadOrigin = EnsureSigned; + type InstantiateOrigin = EnsureSigned; + type RuntimeHoldReason = RuntimeHoldReason; + type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent; + type ChainId = ConstU64<420_420_420>; + type NativeToEthRatio = ConstU32<1_000_000>; // 10^(18 - 12) Eth is 10^18, Native is 10^12. + type EthGasEncoder = (); + type FindAuthor = ::FindAuthor; + + type InherentHandlers = ( + pallet_log_emitter::Pallet, + ); +} + +impl pallet_log_emitter::Config for Runtime { +} // Create the runtime by composing the FRAME pallets that were previously configured. #[frame_support::runtime] @@ -825,6 +916,11 @@ mod runtime { pub type IsmpArbitrum = ismp_arbitrum::pallet; #[runtime::pallet_index(84)] pub type IsmpOptimism = ismp_optimism::pallet; + + #[runtime::pallet_index(85)] + pub type Revive = pallet_revive; + #[runtime::pallet_index(86)] + pub type LogEmitter = pallet_log_emitter; // consensus clients #[runtime::pallet_index(255)] pub type IsmpGrandpa = ismp_grandpa; @@ -862,10 +958,14 @@ mod benches { ); } -impl_runtime_apis! { + +pallet_revive::impl_runtime_apis_plus_revive!( + Runtime, + Executive, + EthExtraImpl, impl sp_consensus_aura::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) + sp_consensus_aura::SlotDuration::from_millis(Aura::slot_duration()) } fn authorities() -> Vec { @@ -1009,6 +1109,21 @@ impl_runtime_apis! { TransactionPayment::length_to_fee(length) } } + + impl sp_genesis_builder::GenesisBuilder for Runtime { + fn build_state(config: Vec) -> sp_genesis_builder::Result { + build_state::(config) + } + + fn get_preset(id: &Option) -> Option> { + get_preset::(id, crate::genesis_config::get_preset) + } + + fn preset_names() -> Vec { + crate::genesis_config::preset_names() + } + } + impl pallet_mmr_runtime_api::MmrRuntimeApi::Hash, BlockNumber, Leaf> for Runtime { /// Return Block number where pallet-mmr was added to the runtime fn pallet_genesis() -> Result, sp_mmr_primitives::Error> { @@ -1039,19 +1154,21 @@ impl_runtime_apis! { impl pallet_ismp_runtime_api::IsmpRuntimeApi::Hash> for Runtime { fn host_state_machine() -> StateMachine { - ::HostStateMachine::get() + crate::ismp::HostStateMachine::get() } fn challenge_period(state_machine_id: StateMachineId) -> Option { Ismp::challenge_period(state_machine_id) } - /// Fetch all ISMP events in the block, should only be called from runtime-api. + + + /// Fetch all ISMP events fn block_events() -> Vec<::ismp::events::Event> { Ismp::block_events() } - /// Fetch all ISMP events and their extrinsic metadata, should only be called from runtime-api. + /// Fetch all ISMP events and their extrinsic metadata fn block_events_with_metadata() -> Vec<(::ismp::events::Event, Option)> { Ismp::block_events_with_metadata() } @@ -1071,7 +1188,6 @@ impl_runtime_apis! { Ismp::latest_state_machine_height(id) } - /// Get actual requests fn requests(commitments: Vec) -> Vec { Ismp::requests(commitments) @@ -1138,27 +1254,27 @@ impl_runtime_apis! { fn dispatch_benchmark( config: frame_benchmarking::BenchmarkConfig - ) -> Result, alloc::string::String> { + ) -> Result, sp_runtime::RuntimeString> { use frame_benchmarking::{Benchmarking, BenchmarkBatch}; + use frame_support::traits::TrackedStorageKey; use frame_system_benchmarking::Pallet as SystemBench; - - impl frame_system_benchmarking::Config for Runtime { - fn setup_set_code_requirements(code: &sp_std::vec::Vec) -> Result<(), frame_benchmarking::BenchmarkError> { - ParachainSystem::initialize_for_set_code_benchmark(code.len() as u32); - Ok(()) - } - - fn verify_set_code() { - System::assert_last_event(cumulus_pallet_parachain_system::Event::::ValidationFunctionStored.into()); - } - } - - use cumulus_pallet_session_benchmarking::Pallet as SessionBench; + + impl frame_system_benchmarking::Config for Runtime {} impl cumulus_pallet_session_benchmarking::Config for Runtime {} - use frame_support::traits::WhitelistedStorageKeys; - let whitelist = AllPalletsWithSystem::whitelisted_storage_keys(); + let whitelist: Vec = vec![ + // Block Number + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac").to_vec().into(), + // Total Issuance + hex_literal::hex!("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80").to_vec().into(), + // Execution Phase + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a").to_vec().into(), + // Event Count + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850").to_vec().into(), + // System Events + hex_literal::hex!("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7").to_vec().into(), + ]; let mut batches = Vec::::new(); let params = (&config, &*whitelist); @@ -1169,20 +1285,6 @@ impl_runtime_apis! { } } - impl sp_genesis_builder::GenesisBuilder for Runtime { - fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) - } - - fn get_preset(id: &Option) -> Option> { - get_preset::(id, crate::genesis_config::get_preset) - } - - fn preset_names() -> Vec { - crate::genesis_config::preset_names() - } - } - impl simnode_runtime_api::CreateTransactionApi for Runtime where RuntimeCall: codec::Codec, @@ -1212,7 +1314,7 @@ impl_runtime_apis! { ); let signature = MultiSignature::from(sr25519::Signature::default()); let address = sp_runtime::traits::AccountIdLookup::unlookup(account.into()); - let ext = generic::UncheckedExtrinsic::::new_signed( + let ext = generic::UncheckedExtrinsic::::new_signed( call, address, signature, @@ -1222,7 +1324,7 @@ impl_runtime_apis! { } } -} +); cumulus_pallet_parachain_system::register_validate_block! { Runtime = Runtime, diff --git a/parachain/runtimes/messier/Cargo.toml b/parachain/runtimes/messier/Cargo.toml index 9f5e1460a..64efecb6b 100644 --- a/parachain/runtimes/messier/Cargo.toml +++ b/parachain/runtimes/messier/Cargo.toml @@ -53,6 +53,7 @@ ismp-arbitrum = { workspace = true } ismp-optimism = { workspace = true } pallet-revive-ismp-dispatcher = { workspace = true } pallet-hyperbridge = { workspace = true } +pallet-log-emitter = { workspace = true } [dependencies.polkadot-sdk] workspace = true @@ -151,6 +152,7 @@ std = [ "ismp-arbitrum/std", "ismp-optimism/std", "pallet-revive-ismp-dispatcher/std", + "pallet-log-emitter/std", "pallet-hyperbridge/std", "alloy-primitives/std", ] diff --git a/parachain/runtimes/messier/src/lib.rs b/parachain/runtimes/messier/src/lib.rs index 30603542d..bad3af564 100644 --- a/parachain/runtimes/messier/src/lib.rs +++ b/parachain/runtimes/messier/src/lib.rs @@ -756,6 +756,9 @@ impl pallet_revive::Config for Runtime { type FindAuthor = ::FindAuthor; } +impl pallet_log_emitter::Config for Runtime { +} + // Create the runtime by composing the FRAME pallets that were previously configured. #[frame_support::runtime] mod runtime { @@ -871,6 +874,9 @@ mod runtime { pub type IsmpArbitrum = ismp_arbitrum::pallet; #[runtime::pallet_index(84)] pub type IsmpOptimism = ismp_optimism::pallet; + + #[runtime::pallet_index(85)] + pub type LogEmitter = pallet_log_emitter; // consensus clients #[runtime::pallet_index(255)] pub type IsmpGrandpa = ismp_grandpa;