diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index 3e8126096d106..e145627294c08 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -19,7 +19,7 @@ use crate::{ subxt_client::{ self, revive::{ - calls::types::EthTransact, + calls::types::{EthTransact, SystemLogDispatch}, events::{ContractEmitted, EthExtrinsicRevert}, }, SrcChainConfig, @@ -30,7 +30,7 @@ use crate::{ use futures::{stream, StreamExt}; use pallet_revive::{ create1, - evm::{GenericTransaction, Log, ReceiptGasInfo, ReceiptInfo, TransactionSigned, H256, U256}, + evm::{Byte, GenericTransaction, Log, ReceiptGasInfo, ReceiptInfo, TransactionSigned, H256, U256}, }; use sp_core::keccak_256; use std::{future::Future, pin::Pin, sync::Arc}; @@ -169,11 +169,11 @@ impl ReceiptExtractor { } /// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] from an extrinsic. - async fn extract_from_extrinsic( + async fn extract_from_eth_extrinsic( &self, substrate_block: &SubstrateBlock, eth_block_hash: H256, - ext: subxt::blocks::ExtrinsicDetails>, + ext: &ExtrinsicDetails>, call: EthTransact, receipt_gas_info: ReceiptGasInfo, transaction_index: usize, @@ -254,6 +254,73 @@ impl ReceiptExtractor { Ok((signed_tx, receipt)) } + /// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] from a System Log extrinsic. + async fn extract_from_system_log( + &self, + substrate_block: &SubstrateBlock, + eth_block_hash: H256, + ext: &ExtrinsicDetails>, + _call: SystemLogDispatch, + transaction_index: usize, + ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let block_number = U256::from(substrate_block.number()); + let events = ext.events().await?; + + let success = !events.has::().inspect_err(|err| { + log::debug!( + target: LOG_TARGET, + "Failed to lookup for EthExtrinsicRevert event in block {block_number}: {err:?}" + ); + })?; + + let transaction_hash = ext.hash(); + + let signed_tx = TransactionSigned::default(); + let from = H160::zero(); + let gas_price = U256::zero(); + let gas_used = U256::zero(); + + let logs = events + .iter() + .filter_map(|event_details| { + let event_details = event_details.ok()?; + let event = event_details.as_event::().ok()??; + + Some(Log { + address: event.contract, + topics: event.topics, + data: Some(event.data.into()), + block_number, + transaction_hash, + transaction_index: transaction_index.into(), + block_hash: eth_block_hash, + log_index: event_details.index().into(), + ..Default::default() + }) + }) + .collect(); + + let contract_address = None; + let to_address = None; + let tx_type: Byte = Byte(0u8); + + let receipt = ReceiptInfo::new( + eth_block_hash, + block_number, + contract_address, + from, + logs, + to_address, + gas_price, + gas_used, + success, + transaction_hash, + transaction_index.into(), + tx_type, + ); + Ok((signed_tx, receipt)) + } + /// Extract receipts from block. pub async fn extract_from_block( &self, @@ -263,7 +330,9 @@ impl ReceiptExtractor { return Ok(vec![]); } - let ext_iter = self.get_block_extrinsics(block).await?; + let extrinsics = block.extrinsics().await.inspect_err(|err| { + log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number()); + })?; let substrate_block_number = block.number() as u64; let substrate_block_hash = block.hash(); @@ -272,72 +341,53 @@ impl ReceiptExtractor { .await .unwrap_or(substrate_block_hash); - // Process extrinsics in order while maintaining parallelism within buffer window - stream::iter(ext_iter) - .map(|(ext, call, receipt, ext_idx)| async move { - self.extract_from_extrinsic(block, eth_block_hash, ext, call, receipt, ext_idx) - .await - .inspect_err(|err| { - log::warn!(target: LOG_TARGET, "Error extracting extrinsic: {err:?}"); - }) - }) - .buffered(10) - .collect::>>() - .await - .into_iter() - .collect::, _>>() - } - - /// Return the ETH extrinsics of the block grouped with reconstruction receipt info and - /// extrinsic index - pub async fn get_block_extrinsics( - &self, - block: &SubstrateBlock, - ) -> Result< - impl Iterator< - Item = ( - ExtrinsicDetails>, - EthTransact, - ReceiptGasInfo, - usize, - ), - >, - ClientError, - > { - // Filter extrinsics from pallet_revive - let extrinsics = block.extrinsics().await.inspect_err(|err| { - log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number()); - })?; - - let receipt_data = (self.fetch_receipt_data)(block.hash()) - .await - .ok_or(ClientError::ReceiptDataNotFound)?; - let extrinsics: Vec<_> = extrinsics - .iter() - .enumerate() - .flat_map(|(ext_idx, ext)| { - let call = ext.as_extrinsic::().ok()??; - Some((ext, call, ext_idx)) - }) - .collect(); + let receipt_data_map: Option> = (self.fetch_receipt_data)(substrate_block_hash).await; + let mut receipt_data_iter = receipt_data_map.clone().unwrap_or_default().into_iter(); + + let mut results = Vec::new(); + let mut eth_tx_count = 0; + + for (ext_idx, ext) in extrinsics.iter().enumerate() { + if let Some(call) = ext.as_extrinsic::().ok().flatten() { + let receipt_gas_info = receipt_data_iter.next().ok_or_else(|| { + log::error!( + target: LOG_TARGET, + "Receipt data missing for EthTransact at index {} in block {}", + ext_idx, substrate_block_number + ); + ClientError::ReceiptDataLengthMismatch + })?; + + match self.extract_from_eth_extrinsic(block, eth_block_hash, &ext, call, receipt_gas_info, ext_idx).await { + Ok(receipt_info) => results.push(Ok(receipt_info)), + Err(e) => { + log::warn!(target: LOG_TARGET, "Error extracting EthTransact extrinsic: {e:?}"); + } + } + eth_tx_count += 1; + } + else if let Some(call) = ext.as_extrinsic::().ok().flatten() { + match self.extract_from_system_log(block, eth_block_hash, &ext, call, ext_idx).await { + Ok(receipt_info) => results.push(Ok(receipt_info)), + Err(e) => { + log::warn!(target: LOG_TARGET, "Error extracting SystemLogDispatch extrinsic: {e:?}"); + } + } + } + } - // Sanity check we received enough data from the pallet revive. - if receipt_data.len() != extrinsics.len() { + if eth_tx_count != receipt_data_map.clone().unwrap_or_default().len() { log::error!( - target: LOG_TARGET, - "Receipt data length ({}) does not match extrinsics length ({})", - receipt_data.len(), - extrinsics.len() - ); - Err(ClientError::ReceiptDataLengthMismatch) - } else { - Ok(extrinsics - .into_iter() - .zip(receipt_data) - .map(|((extr, call, ext_idx), rec)| (extr, call, rec, ext_idx))) + target: LOG_TARGET, + "Processed {} EthTransact but found {} ReceiptGasInfo entries in block {}", + eth_tx_count, receipt_data_map.unwrap_or_default().len(), substrate_block_number + ); } + + results.into_iter().collect::, _>>() } + /// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] for a specific transaction in a /// [`SubstrateBlock`] pub async fn extract_from_transaction( @@ -345,11 +395,10 @@ impl ReceiptExtractor { block: &SubstrateBlock, transaction_index: usize, ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { - let ext_iter = self.get_block_extrinsics(block).await?; - - let (ext, eth_call, receipt_gas_info, _) = ext_iter - .into_iter() - .find(|(_, _, _, ext_idx)| *ext_idx == transaction_index) + let extrinsics = block.extrinsics().await?; + let ext = extrinsics + .iter() + .nth(transaction_index) .ok_or(ClientError::EthExtrinsicNotFound)?; let substrate_block_number = block.number() as u64; @@ -359,15 +408,31 @@ impl ReceiptExtractor { .await .unwrap_or(substrate_block_hash); - self.extract_from_extrinsic( - block, - eth_block_hash, - ext, - eth_call, - receipt_gas_info, - transaction_index, - ) - .await + if let Some(call) = ext.as_extrinsic::().ok().flatten() { + let receipt_data = (self.fetch_receipt_data)(block.hash()) + .await + .ok_or(ClientError::ReceiptDataNotFound)?; + + let eth_tx_index = extrinsics.iter().take(transaction_index).filter(|e| { + e.as_extrinsic::().ok().flatten().is_some() + }).count(); + + let receipt_gas_info = receipt_data.get(eth_tx_index).cloned().ok_or_else(|| { + log::error!( + target: LOG_TARGET, + "Receipt data missing for EthTransact at index {} (eth_tx_index {}) in block {}", + transaction_index, eth_tx_index, substrate_block_number + ); + ClientError::ReceiptDataLengthMismatch + })?; + + self.extract_from_eth_extrinsic(block, eth_block_hash, &ext, call, receipt_gas_info, transaction_index).await + + } else if let Some(call) = ext.as_extrinsic::().ok().flatten() { + self.extract_from_system_log(block, eth_block_hash, &ext, call, transaction_index).await + } else { + Err(ClientError::EthExtrinsicNotFound) + } } /// Get the Ethereum block hash for the Substrate block with specific hash. diff --git a/substrate/frame/revive/src/inherent_handlers.rs b/substrate/frame/revive/src/inherent_handlers.rs new file mode 100644 index 0000000000000..a3008e6074ebc --- /dev/null +++ b/substrate/frame/revive/src/inherent_handlers.rs @@ -0,0 +1,60 @@ +use alloc::vec::Vec; +use impl_trait_for_tuples::impl_for_tuples; +use sp_runtime::{DispatchError, DispatchResult}; +use crate::InherentHandlerMessage; + +/// Defines the interface for a pallet capable of handling a specific type of inherent system message. +/// +/// Pallets implementing this trait register themselves as potential handlers for messages +/// identified by a unique `name`. +pub trait InherentHandler { + /// A unique identifier for this handler + fn handler_name() -> &'static [u8]; + + /// The function that processes the raw message byte intended for this handler. + fn handle_message(message: Vec) -> DispatchResult; +} + +/// Defines the interface for a collection(tuple) of `InherentHandler` +/// +/// This trait provides functions to validate handler names and dispatch mesaages to the +/// appropriate handler within the collection. +pub trait InherentHandlers { + /// Checks if a handler with the given `name` exists within this collection. + /// + /// Used during unsigned transaction validation(`ValidateUnsigned`) to ensure the targe handler is known before accepting the extrinsic. + fn is_valid_handler(name: &[u8]) -> bool; + + /// Finds the handler matching the `handler_name` within the message and call its `handle_message` + fn dispatch_message(message: InherentHandlerMessage) -> DispatchResult; +} + + +/// Base case implementation for an empty tuple `()` +impl InherentHandlers for () { + fn is_valid_handler(_name: &[u8]) -> bool { + false + } + + fn dispatch_message(_message: InherentHandlerMessage) -> DispatchResult { + Err(DispatchError::Other("No matching inherent handler found")) + } +} + +/// Recursive implementation for a non-empty tuple up to 8 elements . +#[impl_for_tuples(1, 8)] +#[tuple_types_custom_trait_bound(InherentHandler)] +impl InherentHandlers for Tuple { + fn is_valid_handler(name: &[u8]) -> bool { + for_tuples!( #( if Tuple::handler_name() == name { return true; } )* ); + false + } + + fn dispatch_message(message: InherentHandlerMessage) -> DispatchResult { + let handler_name = message.handler_name.as_slice(); + for_tuples!( #( if Tuple::handler_name() == handler_name { + return Tuple::handle_message(message.raw_message); + } )* ); + Err(DispatchError::Other("No matching inherent handler found")) + } +} \ No newline at end of file diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 997dde2f85bd6..c6610310051f5 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -45,6 +45,7 @@ pub mod precompiles; pub mod test_utils; pub mod tracing; pub mod weights; +pub mod inherent_handlers; use crate::{ evm::{ @@ -56,13 +57,14 @@ use crate::{ }, exec::{AccountIdOf, ExecError, Stack as ExecStack}, gas::GasMeter, + inherent_handlers::InherentHandlers, storage::{meter::Meter as StorageMeter, AccountType, DeletionQueueManager}, tracing::if_tracing, vm::{pvm::extract_code_and_data, CodeInfo, RuntimeCosts}, weightinfo_extension::OnFinalizeBlockParts, }; use alloc::{boxed::Box, format, vec}; -use codec::{Codec, Decode, Encode}; +use codec::{Codec, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; use environmental::*; use frame_support::{ dispatch::{ @@ -117,6 +119,25 @@ type TrieId = BoundedVec>; type ImmutableData = BoundedVec>; type CallOf = ::RuntimeCall; +/// Represents a message intended for routing to a specific `InherentHandler`. +/// +/// This struct wraps the raw message payload alongside the name of the handler +/// designated to process it. It's used as the argument for the +/// `system_log_dispatch` extrinsic in `pallet-revive`. +#[derive(Debug, Clone, PartialEq, Eq, DecodeWithMemTracking, Encode, Decode, TypeInfo)] +pub struct InherentHandlerMessage { + /// The unique byte slice identifier of the target `InherentHandler`. + /// + /// This name is used by the `InherentHandlers` dispatch logic to find the + /// correct handler pallet. e.g: `b"hyperbridge_proof_verifier_v1"`. + pub handler_name: Vec, + /// The raw byte payload of the message. + /// + /// This data will be passed to the `handle_message` function of the + /// designated handler. Its internal format is specific to the handler. + pub raw_message: Vec +} + /// Used as a sentinel value when reading and writing contract memory. /// /// It is usually used to signal `None` to a contract when only a primitive is allowed @@ -334,6 +355,23 @@ pub mod pallet { /// Allows debug-mode configuration, such as enabling unlimited contract size. #[pallet::constant] type DebugEnabled: Get; + + /// Specifies the type responsible for dispatching inherent system log messages. + /// + /// This type must implement the [`inherent_handlers::InherentHandlers`] trait, which provides + /// the logic for validating handler names (`is_valid_handler`) and routing messages + /// (`dispatch_message`). + /// + /// In the runtime, this is typically configured as a tuple containing the specific + /// pallets that implement the [`inherent_handlers::InherentHandler`] trait. + /// + /// e,g Runtime Configuration: + /// ```ignore + /// type InherentHandlers = ( + /// hyperbridge_ismp-parachain::Pallet, + /// ); + /// ``` + type InherentHandlers: InherentHandlers; } /// Container for different types that implement [`DefaultConfig`]` of this pallet. @@ -416,6 +454,8 @@ pub mod pallet { type FeeInfo = (); type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight; type DebugEnabled = ConstBool; + + type InherentHandlers = (); } } @@ -1456,6 +1496,51 @@ pub mod pallet { T::AddressMapper::to_fallback_account_id(&T::AddressMapper::to_address(&origin)); call.dispatch(RawOrigin::Signed(unmapped_account).into()) } + + /// Dispatches a system log message to the appropriate `InherentHandler`. + /// + /// This extrinsic is designed to be **unsigned** and is typically submitted + /// as an **inherent** by a block author or triggered internally by the runtime. + /// It acts as a router, taking a wrapped message and calling the dispatch logic + /// provided by the `Config::InherentHandlers` type. + #[pallet::call_index(12)] + #[pallet::weight(Weight::zero())] + pub fn system_log_dispatch( + origin: OriginFor, + message: InherentHandlerMessage, + ) -> DispatchResult { + ensure_none(origin)?; + + T::InherentHandlers::dispatch_message(message)?; + + Ok(()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + /// Validates unsigned `system_log_dispatch` calls before they enter the transaction pool. + /// + /// This function implements the `ValidateUnsigned` trait for `pallet-revive`. Its primary role + /// is to ensure that incoming unsigned transactions destined for `system_log_dispatch` + /// are valid before being accepted. + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + if let Call::system_log_dispatch { message } = call { + ensure!(T::InherentHandlers::is_valid_handler(&message.handler_name), InvalidTransaction::Call); + + T::InherentHandlers::dispatch_message(message.clone()).map_err(|_| InvalidTransaction::BadProof)?; + + ValidTransaction::with_tag_prefix("SystemLogDispatch") + .and_provides(message.encode()) + .longevity(TransactionLongevity::MAX) + .propagate(true) + .build() + } else { + InvalidTransaction::Call.into() + } + } } } @@ -1475,6 +1560,8 @@ fn dispatch_result( .map_err(|e| DispatchErrorWithPostInfo { post_info, error: e }) } + + impl Pallet { /// A generalized version of [`Self::call`]. ///