From 0d9c3f79fd8f79a48113c72f5a8164a3042f001c Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 25 Mar 2026 16:45:38 +0100 Subject: [PATCH 01/54] revert and rebase --- Cargo.lock | 68 +- cumulus/polkadot-omni-node/lib/Cargo.toml | 3 + .../polkadot-omni-node/lib/src/common/rpc.rs | 160 +++ .../frame/revive/dev-node/node/Cargo.toml | 34 +- substrate/frame/revive/dev-node/node/build.rs | 2 +- .../revive/dev-node/node/src/chain_spec.rs | 5 +- .../frame/revive/dev-node/node/src/cli.rs | 2 +- .../frame/revive/dev-node/node/src/command.rs | 3 +- .../frame/revive/dev-node/node/src/main.rs | 2 +- .../frame/revive/dev-node/node/src/rpc.rs | 9 +- .../frame/revive/dev-node/node/src/service.rs | 17 +- .../frame/revive/dev-node/runtime/Cargo.toml | 107 +- .../frame/revive/dev-node/runtime/build.rs | 2 +- .../frame/revive/dev-node/runtime/src/lib.rs | 128 +- substrate/frame/revive/rpc/Cargo.toml | 44 +- .../frame/revive/rpc/src/apis/debug_apis.rs | 16 +- .../frame/revive/rpc/src/apis/health_api.rs | 16 +- .../frame/revive/rpc/src/apis/polkadot_api.rs | 27 +- .../revive/rpc/src/block_info_provider.rs | 171 +-- substrate/frame/revive/rpc/src/block_sync.rs | 145 ++- substrate/frame/revive/rpc/src/cli.rs | 451 ++++--- substrate/frame/revive/rpc/src/client.rs | 1052 ++++++++--------- .../revive/rpc/src/client/runtime_api.rs | 25 +- .../revive/rpc/src/client/storage_api.rs | 8 + substrate/frame/revive/rpc/src/example.rs | 1 + substrate/frame/revive/rpc/src/lib.rs | 174 +-- .../rpc/src/native_block_info_provider.rs | 213 ++++ .../frame/revive/rpc/src/native_client.rs | 510 ++++++++ .../frame/revive/rpc/src/receipt_extractor.rs | 566 +++++---- .../frame/revive/rpc/src/receipt_provider.rs | 760 ++++++------ .../frame/revive/rpc/src/substrate_client.rs | 246 ++++ .../frame/revive/rpc/src/subxt_client.rs | 626 +++++++++- 32 files changed, 3874 insertions(+), 1719 deletions(-) create mode 100644 substrate/frame/revive/rpc/src/native_block_info_provider.rs create mode 100644 substrate/frame/revive/rpc/src/native_client.rs create mode 100644 substrate/frame/revive/rpc/src/substrate_client.rs diff --git a/Cargo.lock b/Cargo.lock index 1a2eeb4ffb682..2bb7768e6fbfa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13348,6 +13348,8 @@ name = "pallet-revive-eth-rpc" version = "0.1.0" dependencies = [ "anyhow", + "asset-hub-westend-runtime", + "async-trait", "clap", "derive_more 0.99.17", "env_logger 0.11.3", @@ -13364,12 +13366,17 @@ dependencies = [ "revive-dev-runtime", "rlp 0.6.1", "sc-cli", + "sc-client-api 28.0.0", + "sc-network 0.34.0", "sc-rpc", "sc-rpc-api", "sc-service", + "sc-transaction-pool-api 28.0.0", "serde", "serde_json", + "sp-api 26.0.0", "sp-arithmetic 23.0.0", + "sp-blockchain 28.0.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", "sp-io 30.0.0", @@ -16095,6 +16102,7 @@ dependencies = [ "jsonrpsee", "log", "nix 0.29.0", + "pallet-revive-eth-rpc", "pallet-transaction-payment", "pallet-transaction-payment-rpc", "pallet-transaction-payment-rpc-runtime-api", @@ -18815,8 +18823,34 @@ dependencies = [ "futures", "futures-timer", "jsonrpsee", - "polkadot-sdk", + "pallet-timestamp", + "pallet-transaction-payment-rpc", "revive-dev-runtime", + "sc-basic-authorship", + "sc-cli", + "sc-client-api 28.0.0", + "sc-consensus", + "sc-consensus-manual-seal", + "sc-executor 0.32.0", + "sc-network 0.34.0", + "sc-rpc", + "sc-rpc-api", + "sc-service", + "sc-telemetry 15.0.0", + "sc-transaction-pool", + "sc-transaction-pool-api 28.0.0", + "sp-api 26.0.0", + "sp-block-builder", + "sp-blockchain 28.0.0", + "sp-core 28.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-keyring", + "sp-runtime 31.0.1", + "sp-timestamp", + "substrate-build-script-utils", + "substrate-frame-rpc-system", ] [[package]] @@ -18824,11 +18858,41 @@ name = "revive-dev-runtime" version = "0.1.0" dependencies = [ "array-bytes 6.2.2", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "pallet-balances", + "pallet-revive", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parachains-common", "parity-scale-codec", - "polkadot-sdk", + "polkadot-primitives", + "polkadot-runtime-common", "scale-info", "serde_json", + "sp-api 26.0.0", + "sp-block-builder", + "sp-consensus-aura", + "sp-core 28.0.0", "sp-debug-derive 14.0.0", + "sp-genesis-builder 0.8.0", + "sp-inherents 26.0.0", + "sp-io 30.0.0", + "sp-keyring", + "sp-offchain", + "sp-runtime 31.0.1", + "sp-session", + "sp-std 14.0.0", + "sp-tracing 16.0.0", + "sp-transaction-pool", + "sp-version 29.0.0", + "sp-weights 27.0.0", + "substrate-wasm-builder", ] [[package]] diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index 4ee8b868988be..a60bd269b3073 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -39,6 +39,7 @@ frame-support = { optional = true, workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } frame-try-runtime = { optional = true, workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } +pallet-revive-eth-rpc = { workspace = true, default-features = false, optional = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } @@ -106,6 +107,7 @@ cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } futures-timer = { workspace = true } sc-consensus-aura = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread"] } [dev-dependencies] assert_cmd = { workspace = true } @@ -116,6 +118,7 @@ wait-timeout = { workspace = true } [features] default = [] +eth-rpc = ["dep:pallet-revive-eth-rpc"] rococo-native = ["polkadot-cli/rococo-native"] westend-native = ["polkadot-cli/westend-native"] runtime-benchmarks = [ diff --git a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs index 51841a3516dee..f20b3dff5cf55 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs @@ -35,6 +35,166 @@ use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer /// A type representing all RPC extensions. pub type RpcExtension = jsonrpsee::RpcModule<()>; +#[cfg(feature = "eth-rpc")] +pub use eth_rpc::BuildParachainReviveRpcExtensions; + +#[cfg(feature = "eth-rpc")] +mod eth_rpc { + use super::*; + use pallet_revive_eth_rpc::{ + cli::{build_eth_rpc_module, build_native_inmemory_client}, + client::SubscriptionType, + native_client::ReviveRuntimeApiT, + NativeClientBlockInfoProvider, NativeSubstrateClient, + }; + use sp_api::{Metadata as _, ProvideRuntimeApi}; + use sp_core::H256; + + /// The default number of recent blocks kept in the in-memory receipt cache. + const DEFAULT_KEEP_LATEST_BLOCKS: usize = 256; + + /// Reads the `ChainId` constant from the `Revive` pallet via the runtime metadata. + fn read_revive_chain_id( + client: &Arc, + best_hash: B::Hash, + ) -> Result> + where + B: BlockT, + C: ProvideRuntimeApi, + C::Api: sp_api::Metadata, + { + use codec::Decode as _; + + let opaque = client + .runtime_api() + .metadata(best_hash) + .map_err(|e| format!("metadata API error: {e}"))?; + + let meta = subxt_metadata::Metadata::decode(&mut &opaque[..]) + .map_err(|e| format!("metadata decode error: {e}"))?; + + let value = meta + .pallet_by_name("Revive") + .and_then(|p| p.constant_by_name("ChainId")) + .map(|c| c.value().to_vec()) + .ok_or("Revive pallet `ChainId` constant not found in runtime metadata")?; + + u64::decode(&mut &value[..]).map_err(|e| format!("ChainId decode error: {e}").into()) + } + + pub struct BuildParachainReviveRpcExtensions( + PhantomData<(Block, RuntimeApi)>, + ); + + impl + BuildRpcExtensions< + ParachainClient, + ParachainBackend, + sc_transaction_pool::TransactionPoolHandle>, + sc_statement_store::Store, + > for BuildParachainReviveRpcExtensions + where + Block: BlockT + Send + Sync + 'static, + Block::Header: + sp_runtime::traits::Header + Unpin + Send + Sync + 'static, + RuntimeApi: ConstructNodeRuntimeApi> + + Send + + Sync + + 'static, + RuntimeApi::RuntimeApi: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + + substrate_frame_rpc_system::AccountNonceApi + + ReviveRuntimeApiT, + { + fn build_rpc_extensions( + client: Arc>, + backend: Arc>, + pool: Arc< + sc_transaction_pool::TransactionPoolHandle< + Block, + ParachainClient, + >, + >, + statement_store: Option>, + spawn_handle: Arc, + ) -> sc_service::error::Result { + let build = || -> Result> { + let mut module = RpcExtension::new(()); + + // Standard parachain RPCs. + module.merge(System::new(client.clone(), pool.clone()).into_rpc())?; + module.merge(TransactionPayment::new(client.clone()).into_rpc())?; + module.merge(StateMigration::new(client.clone(), backend).into_rpc())?; + if let Some(statement_store) = statement_store { + module.merge( + StatementStore::new(statement_store, spawn_handle.clone()).into_rpc(), + )?; + } + module.merge(Dev::new(client.clone()).into_rpc())?; + + // ETH RPC server. + let best_hash = client.chain_info().best_hash; + let chain_id = read_revive_chain_id::(&client, best_hash)?; + + let block_provider = NativeClientBlockInfoProvider::new(client.clone()) + .map_err(|e| format!("block info provider: {e}"))?; + + let native_client = + NativeSubstrateClient::new(client.clone(), pool, chain_id, false) + .map_err(|e| format!("native substrate client: {e}"))?; + + let eth_client = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on(build_native_inmemory_client( + native_client, + block_provider, + DEFAULT_KEEP_LATEST_BLOCKS, + false, + )) + }) + .map_err(|e| format!("ETH RPC client init: {e}"))?; + + let eth_best = eth_client.clone(); + spawn_handle.spawn( + "eth-rpc-best-blocks", + Some("eth-rpc"), + Box::pin(async move { + if let Err(e) = eth_best + .subscribe_and_cache_new_blocks(SubscriptionType::BestBlocks) + .await + { + log::error!( + target: "eth-rpc", + "Best-block subscription error: {e:?}" + ); + } + }), + ); + let eth_finalized = eth_client.clone(); + spawn_handle.spawn( + "eth-rpc-finalized-blocks", + Some("eth-rpc"), + Box::pin(async move { + if let Err(e) = eth_finalized + .subscribe_and_cache_new_blocks(SubscriptionType::FinalizedBlocks) + .await + { + log::error!( + target: "eth-rpc", + "Finalized-block subscription error: {e:?}" + ); + } + }), + ); + + let eth_module = build_eth_rpc_module(false, eth_client, false)?; + module.merge(eth_module)?; + + Ok(module) + }; + build().map_err(Into::into) + } + } +} + pub(crate) trait BuildRpcExtensions { fn build_rpc_extensions( client: Arc, diff --git a/substrate/frame/revive/dev-node/node/Cargo.toml b/substrate/frame/revive/dev-node/node/Cargo.toml index d618d6065ed6b..f683df03c990d 100644 --- a/substrate/frame/revive/dev-node/node/Cargo.toml +++ b/substrate/frame/revive/dev-node/node/Cargo.toml @@ -22,15 +22,41 @@ futures = { features = ["thread-pool"], workspace = true } futures-timer = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } -polkadot-sdk = { workspace = true, features = ["experimental", "node"] } revive-dev-runtime = { workspace = true } +sc-basic-authorship = { workspace = true, default-features = true } +sc-cli = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-consensus = { workspace = true, default-features = true } +sc-consensus-manual-seal = { workspace = true, default-features = true } +sc-executor = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-rpc = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } +sc-telemetry = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-block-builder = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-keyring = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-timestamp = { workspace = true, default-features = true } +substrate-frame-rpc-system = { workspace = true, default-features = true } +pallet-transaction-payment-rpc = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } + [build-dependencies] -polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } +substrate-build-script-utils = { workspace = true, default-features = true } [features] default = ["std"] std = [ - "polkadot-sdk/std", - "revive-dev-runtime/std", + "revive-dev-runtime/std", + "sp-genesis-builder/std", ] diff --git a/substrate/frame/revive/dev-node/node/build.rs b/substrate/frame/revive/dev-node/node/build.rs index 47ab77ae29690..fa7686e01099f 100644 --- a/substrate/frame/revive/dev-node/node/build.rs +++ b/substrate/frame/revive/dev-node/node/build.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use polkadot_sdk::substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; fn main() { generate_cargo_keys(); diff --git a/substrate/frame/revive/dev-node/node/src/chain_spec.rs b/substrate/frame/revive/dev-node/node/src/chain_spec.rs index 5efa2989512f8..ef24499fb07fa 100644 --- a/substrate/frame/revive/dev-node/node/src/chain_spec.rs +++ b/substrate/frame/revive/dev-node/node/src/chain_spec.rs @@ -15,11 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use polkadot_sdk::{ - sc_service::{ChainType, Properties}, - *, -}; use revive_dev_runtime::WASM_BINARY; +use sc_service::{ChainType, Properties}; /// This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec; diff --git a/substrate/frame/revive/dev-node/node/src/cli.rs b/substrate/frame/revive/dev-node/node/src/cli.rs index fe7717dd76511..05cf8b93a28dd 100644 --- a/substrate/frame/revive/dev-node/node/src/cli.rs +++ b/substrate/frame/revive/dev-node/node/src/cli.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use polkadot_sdk::{sc_cli::RunCmd, *}; +use sc_cli::RunCmd; #[derive(Debug, Clone, Copy)] pub enum Consensus { diff --git a/substrate/frame/revive/dev-node/node/src/command.rs b/substrate/frame/revive/dev-node/node/src/command.rs index 7d92eeae1512a..3a731cea6fb25 100644 --- a/substrate/frame/revive/dev-node/node/src/command.rs +++ b/substrate/frame/revive/dev-node/node/src/command.rs @@ -20,7 +20,8 @@ use crate::{ cli::{Cli, Subcommand}, service, }; -use polkadot_sdk::{sc_cli::SubstrateCli, sc_service::PartialComponents, *}; +use sc_cli::SubstrateCli; +use sc_service::PartialComponents; impl SubstrateCli for Cli { fn impl_name() -> String { diff --git a/substrate/frame/revive/dev-node/node/src/main.rs b/substrate/frame/revive/dev-node/node/src/main.rs index 8f36da5bf83a8..3cf7d98311eaa 100644 --- a/substrate/frame/revive/dev-node/node/src/main.rs +++ b/substrate/frame/revive/dev-node/node/src/main.rs @@ -24,6 +24,6 @@ mod command; mod rpc; mod service; -fn main() -> polkadot_sdk::sc_cli::Result<()> { +fn main() -> sc_cli::Result<()> { command::run() } diff --git a/substrate/frame/revive/dev-node/node/src/rpc.rs b/substrate/frame/revive/dev-node/node/src/rpc.rs index 48e435825f49f..e8fdd3825ac97 100644 --- a/substrate/frame/revive/dev-node/node/src/rpc.rs +++ b/substrate/frame/revive/dev-node/node/src/rpc.rs @@ -24,12 +24,9 @@ use crate::cli::Consensus; use jsonrpsee::{core::RpcResult, proc_macros::rpc, RpcModule}; -use polkadot_sdk::{ - sc_transaction_pool_api::TransactionPool, - sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}, - *, -}; use revive_dev_runtime::{AccountId, Nonce, OpaqueBlock}; +use sc_transaction_pool_api::TransactionPool; +use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use std::sync::Arc; /// Full client dependencies. @@ -89,7 +86,7 @@ where C::Api: substrate_frame_rpc_system::AccountNonceApi, P: TransactionPool + 'static, { - use polkadot_sdk::substrate_frame_rpc_system::{System, SystemApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); let FullDeps { client, pool, consensus } = deps; diff --git a/substrate/frame/revive/dev-node/node/src/service.rs b/substrate/frame/revive/dev-node/node/src/service.rs index 1eeb511515128..a2b48dad46bf3 100644 --- a/substrate/frame/revive/dev-node/node/src/service.rs +++ b/substrate/frame/revive/dev-node/node/src/service.rs @@ -16,15 +16,12 @@ // limitations under the License. use crate::cli::Consensus; -use polkadot_sdk::{ - sc_client_api::StorageProvider, - sc_executor::WasmExecutor, - sc_service::{error::Error as ServiceError, Configuration, TaskManager}, - sc_telemetry::{Telemetry, TelemetryWorker}, - sp_runtime::traits::Block as BlockT, - *, -}; use revive_dev_runtime::{OpaqueBlock as Block, Runtime, RuntimeApi}; +use sc_client_api::StorageProvider; +use sc_executor::WasmExecutor; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; +use sc_telemetry::{Telemetry, TelemetryWorker}; +use sp_runtime::traits::Block as BlockT; use std::sync::Arc; type HostFunctions = sp_io::SubstrateHostFunctions; @@ -178,7 +175,7 @@ pub fn new_full::Ha ); // Due to instant seal or low block time multiple blocks can have the same timestamp. - // This is because Etereum only uses second granularity (as opposed to ms). + // This is because Ethereum only uses second granularity (as opposed to ms). // Here we make sure that we increment by at least a second from the last block. // // # Warning @@ -193,7 +190,7 @@ pub fn new_full::Ha let client = client.clone(); async move { let key = sp_core::storage::StorageKey( - polkadot_sdk::pallet_timestamp::Now::::hashed_key().to_vec(), + pallet_timestamp::Now::::hashed_key().to_vec(), ); let current = sp_timestamp::Timestamp::current(); let next = client diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 39d9146f364a9..0702b35174b64 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -11,32 +11,109 @@ edition.workspace = true [dependencies] array-bytes = { workspace = true } codec = { workspace = true } -polkadot-sdk = { workspace = true, features = [ - "pallet-balances", - "pallet-revive", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "parachains-common", - "polkadot-primitives", - "polkadot-runtime-common", - "runtime", - "with-tracing", -] } scale-info = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } sp-debug-derive = { workspace = true } +frame-executive = { workspace = true, default-features = false } +frame-support = { workspace = true, default-features = false } +frame-system = { workspace = true, default-features = false } +frame-system-rpc-runtime-api = { workspace = true, default-features = false } +frame-try-runtime = { workspace = true, default-features = false, optional = true } +pallet-balances = { workspace = true, default-features = false } +pallet-revive = { workspace = true, default-features = false } +pallet-sudo = { workspace = true, default-features = false } +pallet-timestamp = { workspace = true, default-features = false } +pallet-transaction-payment = { workspace = true, default-features = false } +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = false } +parachains-common = { workspace = true, default-features = false } +polkadot-runtime-common = { workspace = true, default-features = false } +polkadot-primitives = { workspace = true, default-features = false } +sp-api = { workspace = true, default-features = false } +sp-block-builder = { workspace = true, default-features = false } +sp-consensus-aura = { workspace = true, default-features = false } +sp-core = { workspace = true, default-features = false } +sp-genesis-builder = { workspace = true, default-features = false } +sp-inherents = { workspace = true, default-features = false } +sp-io = { workspace = true, default-features = false } +sp-keyring = { workspace = true, default-features = false } +sp-offchain = { workspace = true, default-features = false } +sp-runtime = { workspace = true, default-features = false } +sp-session = { workspace = true, default-features = false } +sp-std = { workspace = true, default-features = false } +sp-transaction-pool = { workspace = true, default-features = false } +sp-tracing = { workspace = true, default-features = false, optional = true } +sp-version = { workspace = true, default-features = false } +sp-weights = { workspace = true, default-features = false } + [build-dependencies] -polkadot-sdk = { optional = true, workspace = true, features = ["substrate-wasm-builder"] } +substrate-wasm-builder = { workspace = true, optional = true } [features] default = ["std"] std = [ "codec/std", - "polkadot-sdk/std", + "frame-executive/std", + "frame-support/std", + "frame-system/std", + "frame-system-rpc-runtime-api/std", + "pallet-balances/std", + "pallet-revive/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "parachains-common/std", + "polkadot-runtime-common/std", + "polkadot-primitives/std", "scale-info/std", "serde_json/std", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", "sp-debug-derive/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-io/std", + "sp-keyring/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", + "sp-transaction-pool/std", + "sp-version/std", + "sp-weights/std", + "substrate-wasm-builder", + "frame-try-runtime?/std", + "sp-tracing?/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-revive/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-transaction-payment/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks" +] +try-runtime = [ + "dep:frame-try-runtime", + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-revive/try-runtime", + "pallet-sudo/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "polkadot-runtime-common/try-runtime", + "sp-runtime/try-runtime", + "frame-try-runtime/try-runtime", + "parachains-common/try-runtime", ] +with-tracing = ["dep:sp-tracing"] diff --git a/substrate/frame/revive/dev-node/runtime/build.rs b/substrate/frame/revive/dev-node/runtime/build.rs index 2cb2966b5d822..e6f92757e2254 100644 --- a/substrate/frame/revive/dev-node/runtime/build.rs +++ b/substrate/frame/revive/dev-node/runtime/build.rs @@ -18,6 +18,6 @@ fn main() { #[cfg(feature = "std")] { - polkadot_sdk::substrate_wasm_builder::WasmBuilder::build_using_defaults(); + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } } diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index b4ac9bc93d197..3426787311a62 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -25,11 +25,15 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use currency::*; -use frame_support::weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, - Weight, +use frame_support::{ + derive_impl, parameter_types, + traits::{ConstBool, ConstU32, ConstU64}, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, }; -use frame_system::limits::BlockWeights; +use frame_system::{limits::BlockWeights, EnsureSigned}; use pallet_revive::{ evm::{ fees::{BlockRatioFee, Info as FeeInfo}, @@ -38,20 +42,25 @@ use pallet_revive::{ AccountId32Mapper, }; use pallet_transaction_payment::{ConstFeeMultiplier, FeeDetails, Multiplier, RuntimeDispatchInfo}; -use polkadot_sdk::{ - polkadot_sdk_frame::{ - deps::sp_genesis_builder, - runtime::{apis, prelude::*}, - traits::Block as BlockT, - }, - *, +use polkadot_primitives::MAX_POV_SIZE; +use sp_api::impl_runtime_apis; +use sp_genesis_builder::PresetId; +use sp_runtime::{ + generic, + traits::{Block as BlockT, One}, + Perbill, }; +use sp_version::{runtime_version, RuntimeVersion}; use sp_weights::ConstantMultiplier; -pub use polkadot_sdk::{ - parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, - polkadot_sdk_frame::runtime::types_common::OpaqueBlock, -}; +#[cfg(feature = "std")] +use sp_version::NativeVersion; + +// Re-export types used by the node +pub use parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}; + +// OpaqueBlock used by the node +pub type OpaqueBlock = generic::Block; pub mod currency { use super::Balance; @@ -64,13 +73,12 @@ pub mod currency { pub mod genesis_config_presets { use super::*; use crate::{ - currency::DOLLARS, sp_keyring::Sr25519Keyring, Balance, BalancesConfig, ReviveConfig, - RuntimeGenesisConfig, SudoConfig, + currency::DOLLARS, Balance, BalancesConfig, ReviveConfig, RuntimeGenesisConfig, SudoConfig, }; - use alloc::{vec, vec::Vec}; use pallet_revive::is_eth_derived; use serde_json::Value; + use sp_keyring::Sr25519Keyring; pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS; @@ -165,7 +173,7 @@ pub fn native_version() -> NativeVersion { /// The address format for describing accounts. pub type Address = sp_runtime::MultiAddress; /// Block type as expected by this runtime. -pub type Block = sp_runtime::generic::Block; +pub type Block = generic::Block; /// The transaction extensions that are added to the runtime. type TxExtension = ( // Checks that the sender is not the zero address. @@ -229,7 +237,7 @@ type Executive = frame_executive::Executive< >; // Composes the runtime by adding all the used pallets and deriving necessary types. -#[frame_construct_runtime] +#[frame_support::runtime] mod runtime { /// The main runtime type. #[runtime::runtime] @@ -279,22 +287,20 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); /// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. -const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), - polkadot_primitives::MAX_POV_SIZE as u64, -); +const MAXIMUM_BLOCK_WEIGHT: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), MAX_POV_SIZE as u64); parameter_types! { pub const Version: RuntimeVersion = VERSION; pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() .base_block(BlockExecutionWeight::get()) - .for_class(DispatchClass::all(), |weights| { + .for_class(frame_support::dispatch::DispatchClass::all(), |weights| { weights.base_extrinsic = ExtrinsicBaseWeight::get(); }) - .for_class(DispatchClass::Normal, |weights| { + .for_class(frame_support::dispatch::DispatchClass::Normal, |weights| { weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); }) - .for_class(DispatchClass::Operational, |weights| { + .for_class(frame_support::dispatch::DispatchClass::Operational, |weights| { weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); // Operational transactions have some extra reserved space, so that they // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. @@ -378,7 +384,7 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( Executive, EthExtraImpl, - impl apis::Core for Runtime { + impl sp_api::Core for Runtime { fn version() -> RuntimeVersion { VERSION } @@ -387,17 +393,17 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( Executive::execute_block(block) } - fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { + fn initialize_block(header: &Header) -> sp_runtime::ExtrinsicInclusionMode { Executive::initialize_block(header) } } - impl apis::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) + impl sp_api::Metadata for Runtime { + fn metadata() -> sp_core::OpaqueMetadata { + sp_core::OpaqueMetadata::new(Runtime::metadata().into()) } - fn metadata_at_version(version: u32) -> Option { + fn metadata_at_version(version: u32) -> Option { Runtime::metadata_at_version(version) } @@ -406,57 +412,59 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( } } - impl apis::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> sp_runtime::ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) } - fn finalize_block() -> HeaderFor { + fn finalize_block() -> ::Header { Executive::finalize_block() } - fn inherent_extrinsics(data: InherentData) -> Vec> { + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { data.create_extrinsics() } fn check_inherents( block: ::LazyBlock, - data: InherentData, - ) -> CheckInherentsResult { + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { data.check_extrinsics(&block) } } - impl apis::TaggedTransactionQueue for Runtime { + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { fn validate_transaction( - source: TransactionSource, - tx: ExtrinsicFor, + source: sp_runtime::transaction_validity::TransactionSource, + tx: ::Extrinsic, block_hash: ::Hash, - ) -> TransactionValidity { + ) -> sp_runtime::transaction_validity::TransactionValidity { Executive::validate_transaction(source, tx, block_hash) } } - impl apis::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &HeaderFor) { + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { Executive::offchain_worker(header) } } - impl apis::SessionKeys for Runtime { - fn generate_session_keys(_owner: Vec, _seed: Option>) -> apis::OpaqueGeneratedSessionKeys { + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys( + _owner: Vec, + _seed: Option>, + ) -> sp_session::OpaqueGeneratedSessionKeys { Default::default() - } - + } - fn decode_session_keys( - _encoded: Vec, - ) -> Option, apis::KeyTypeId)>> { - Default::default() - } + fn decode_session_keys( + _encoded: Vec, + ) -> Option, sp_core::crypto::KeyTypeId)>> { + Default::default() } +} - impl apis::AccountNonceApi for Runtime { + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Nonce { System::account_nonce(account) } @@ -466,10 +474,10 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( Block, Balance, > for Runtime { - fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { + fn query_info(uxt: ::Extrinsic, len: u32) -> RuntimeDispatchInfo { TransactionPayment::query_info(uxt, len) } - fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { + fn query_fee_details(uxt: ::Extrinsic, len: u32) -> FeeDetails { TransactionPayment::query_fee_details(uxt, len) } fn query_weight_to_fee(weight: Weight) -> Balance { @@ -480,13 +488,13 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( } } - impl apis::GenesisBuilder for Runtime { + impl sp_genesis_builder::GenesisBuilder for Runtime { fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) + frame_support::genesis_builder_helper::build_state::(config) } fn get_preset(id: &Option) -> Option> { - get_preset::(id, self::genesis_config_presets::get_preset) + frame_support::genesis_builder_helper::get_preset::(id, self::genesis_config_presets::get_preset) } fn preset_names() -> Vec { diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index c492e062bb05e..75e419624613d 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -19,8 +19,32 @@ exclude-from-umbrella = true name = "eth-rpc" path = "src/main.rs" +[[example]] +name = "tx-types" +required-features = ["subxt"] + +[[example]] +name = "transfer" +required-features = ["subxt"] + +[[example]] +name = "extrinsic" +required-features = ["subxt"] + +[[example]] +name = "remark-extrinsic" +required-features = ["subxt"] + +[features] +default = ["subxt"] +subxt = [ + "dep:subxt", + "dep:subxt-signer", +] + [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } codec = { workspace = true, features = ["derive"] } derive_more = { workspace = true } @@ -32,15 +56,17 @@ pallet-revive = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } rlp = { workspace = true } sc-cli = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -serde = { workspace = true, default-features = true, features = [ - "alloc", - "derive", -] } +sc-network = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +serde = { workspace = true, default-features = true, features = ["alloc", "derive"] } serde_json = { workspace = true } +sp-api = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-rpc = { workspace = true, default-features = true } @@ -48,24 +74,26 @@ sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true } sp-weights = { workspace = true, default-features = true } sqlx = { workspace = true, features = ["macros", "runtime-tokio", "sqlite"] } -subxt = { workspace = true, default-features = true, features = [ - "reconnecting-rpc-client", -] } -subxt-signer = { workspace = true, features = ["unstable-eth"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tokio-stream = { workspace = true } +# Optional +subxt = { workspace = true, default-features = true, features = ["reconnecting-rpc-client"], optional = true } +subxt-signer = { workspace = true, features = ["unstable-eth"], optional = true } + [dev-dependencies] env_logger = { workspace = true } pallet-revive-fixtures = { workspace = true, default-features = true } pretty_assertions = { workspace = true } revive-dev-node = { workspace = true } sp-io = { workspace = true, default-features = true } +subxt-signer = { workspace = true, features = ["unstable-eth"] } tempfile = { workspace = true } [build-dependencies] git2 = { workspace = true } +asset-hub-westend-runtime = { workspace = true, default-features = true } revive-dev-runtime = { workspace = true, default-features = true } sp-io = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/rpc/src/apis/debug_apis.rs b/substrate/frame/revive/rpc/src/apis/debug_apis.rs index 3eb1822fb8b64..ad1300f8e6c1e 100644 --- a/substrate/frame/revive/rpc/src/apis/debug_apis.rs +++ b/substrate/frame/revive/rpc/src/apis/debug_apis.rs @@ -14,10 +14,10 @@ // 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. -use crate::*; +use crate::{BlockInfoProvider, client::Client, substrate_client::SubstrateClientT, *}; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; -/// Debug Ethererum JSON-RPC apis. +/// Debug Ethereum JSON-RPC APIs. #[rpc(server, client)] pub trait DebugRpc { /// Returns the tracing of the execution of a specific block using its number. @@ -61,12 +61,12 @@ pub trait DebugRpc { async fn get_automine(&self) -> RpcResult; } -pub struct DebugRpcServerImpl { - client: client::Client, +pub struct DebugRpcServerImpl { + client: Client, } -impl DebugRpcServerImpl { - pub fn new(client: client::Client) -> Self { +impl DebugRpcServerImpl { + pub fn new(client: Client) -> Self { Self { client } } } @@ -90,7 +90,7 @@ async fn with_timeout( } #[async_trait] -impl DebugRpcServer for DebugRpcServerImpl { +impl DebugRpcServer for DebugRpcServerImpl { async fn trace_block_by_number( &self, block: BlockNumberOrTag, @@ -120,6 +120,6 @@ impl DebugRpcServer for DebugRpcServerImpl { } async fn get_automine(&self) -> RpcResult { - sc_service::Result::Ok(self.client.get_automine().await) + Ok(self.client.get_automine().await) } } diff --git a/substrate/frame/revive/rpc/src/apis/health_api.rs b/substrate/frame/revive/rpc/src/apis/health_api.rs index 312aed816e4f2..f6edd2d479057 100644 --- a/substrate/frame/revive/rpc/src/apis/health_api.rs +++ b/substrate/frame/revive/rpc/src/apis/health_api.rs @@ -16,7 +16,7 @@ // limitations under the License. //! Heatlh JSON-RPC methods. -use crate::*; +use crate::{BlockInfoProvider, SubstrateClientT, client::Client, *}; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sc_rpc_api::system::helpers::Health; @@ -31,18 +31,20 @@ pub trait SystemHealthRpc { async fn net_peer_count(&self) -> RpcResult; } -pub struct SystemHealthRpcServerImpl { - client: client::Client, +pub struct SystemHealthRpcServerImpl { + client: Client, } -impl SystemHealthRpcServerImpl { - pub fn new(client: client::Client) -> Self { +impl SystemHealthRpcServerImpl { + pub fn new(client: Client) -> Self { Self { client } } } #[async_trait] -impl SystemHealthRpcServer for SystemHealthRpcServerImpl { +impl SystemHealthRpcServer + for SystemHealthRpcServerImpl +{ async fn system_health(&self) -> RpcResult { let (sync_state, health) = tokio::try_join!(self.client.sync_state(), self.client.system_health())?; @@ -57,7 +59,7 @@ impl SystemHealthRpcServer for SystemHealthRpcServerImpl { "Client is out of sync. Current block: {}, latest cache block: {latest}", sync_state.current_block, ); - return Err(ErrorCode::InternalError.into()); + return Err(jsonrpsee::types::ErrorCode::InternalError.into()); } Ok(Health { diff --git a/substrate/frame/revive/rpc/src/apis/polkadot_api.rs b/substrate/frame/revive/rpc/src/apis/polkadot_api.rs index bf683b5e4d989..375088d46f2c0 100644 --- a/substrate/frame/revive/rpc/src/apis/polkadot_api.rs +++ b/substrate/frame/revive/rpc/src/apis/polkadot_api.rs @@ -16,7 +16,7 @@ // limitations under the License. //! Polkadot-specific JSON-RPC methods. -use crate::*; +use crate::{BlockInfoProvider, SubstrateClientT, client::Client, *}; use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_runtime::Weight; @@ -27,20 +27,31 @@ pub trait PolkadotRpc { async fn post_dispatch_weight(&self, transaction_hash: H256) -> RpcResult>; } -pub struct PolkadotRpcServerImpl { - client: client::Client, +pub struct PolkadotRpcServerImpl { + client: Client, } -impl PolkadotRpcServerImpl { - pub fn new(client: client::Client) -> Self { +impl PolkadotRpcServerImpl { + pub fn new(client: Client) -> Self { Self { client } } } #[async_trait] -impl PolkadotRpcServer for PolkadotRpcServerImpl { +impl PolkadotRpcServer + for PolkadotRpcServerImpl +{ async fn post_dispatch_weight(&self, transaction_hash: H256) -> RpcResult> { - let weight = self.client.post_dispatch_weight(&transaction_hash).await; - Ok(weight) + let Some(receipt) = self.client.receipt(&transaction_hash).await else { + return Ok(None); + }; + let Some(block_hash) = self.client.resolve_substrate_hash(&receipt.block_hash).await else { + return Ok(None); + }; + Ok(self + .client + .backend + .extrinsic_post_dispatch_weight(block_hash, receipt.transaction_index.as_usize()) + .await) } } diff --git a/substrate/frame/revive/rpc/src/block_info_provider.rs b/substrate/frame/revive/rpc/src/block_info_provider.rs index 7f56acfa575fc..ec2e93ea5aba8 100644 --- a/substrate/frame/revive/rpc/src/block_info_provider.rs +++ b/substrate/frame/revive/rpc/src/block_info_provider.rs @@ -17,141 +17,61 @@ use crate::{ ClientError, - client::{SubscriptionType, SubstrateBlock, SubstrateBlockNumber}, - subxt_client::SrcChainConfig, + client::{SubscriptionType, SubstrateBlockNumber}, }; use jsonrpsee::core::async_trait; use sp_core::H256; use std::sync::Arc; -use subxt::{OnlineClient, backend::legacy::LegacyRpcMethods}; -use tokio::sync::RwLock; -/// BlockInfoProvider cache and retrieves information about blocks. +/// Provides information about a block. +pub trait BlockInfo: Send + Sync + 'static { + /// Returns the block hash. + fn hash(&self) -> H256; + /// Returns the block number. + fn number(&self) -> SubstrateBlockNumber; + /// Returns the parent hash. + fn parent_hash(&self) -> H256; +} + +/// A generic block-info provider that caches and retrieves block metadata. #[async_trait] -pub trait BlockInfoProvider: Send + Sync { - /// Update the latest block - async fn update_latest(&self, block: Arc, subscription_type: SubscriptionType); +pub trait BlockInfoProvider: Send + Sync + Clone + 'static { + /// The concrete block type this provider returns. + type Block: BlockInfo + Clone; + + /// Update the cached latest block. + async fn update_latest(&self, block: Arc, subscription_type: SubscriptionType); /// Return the latest finalized block. - async fn latest_finalized_block(&self) -> Arc; + async fn latest_finalized_block(&self) -> Arc; - /// Return the latest block. - async fn latest_block(&self) -> Arc; + /// Return the latest best block. + async fn latest_block(&self) -> Arc; - /// Return the latest block number + /// Return the latest block number. async fn latest_block_number(&self) -> SubstrateBlockNumber { - return self.latest_block().await.number(); + self.latest_block().await.number() } - /// Get block by block_number. + /// Get a block by block number. async fn block_by_number( &self, block_number: SubstrateBlockNumber, - ) -> Result>, ClientError>; - - /// Get block by block hash. - async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError>; -} - -/// Provides information about blocks. -#[derive(Clone)] -pub struct SubxtBlockInfoProvider { - /// The latest block. - latest_block: Arc>>, + ) -> Result>, ClientError>; - /// The latest finalized block. - latest_finalized_block: Arc>>, - - /// The rpc client, used to fetch blocks not in the cache. - rpc: LegacyRpcMethods, - - /// The api client, used to fetch blocks not in the cache. - api: OnlineClient, -} - -impl SubxtBlockInfoProvider { - pub async fn new( - api: OnlineClient, - rpc: LegacyRpcMethods, - ) -> Result { - let latest = Arc::new(api.blocks().at_latest().await?); - Ok(Self { - api, - rpc, - latest_block: Arc::new(RwLock::new(latest.clone())), - latest_finalized_block: Arc::new(RwLock::new(latest)), - }) - } + /// Get a block by hash. + async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError>; } -#[async_trait] -impl BlockInfoProvider for SubxtBlockInfoProvider { - async fn update_latest(&self, block: Arc, subscription_type: SubscriptionType) { - let mut latest = match subscription_type { - SubscriptionType::FinalizedBlocks => self.latest_finalized_block.write().await, - SubscriptionType::BestBlocks => self.latest_block.write().await, - }; - *latest = block; - } - - async fn latest_block(&self) -> Arc { - self.latest_block.read().await.clone() - } - - async fn latest_finalized_block(&self) -> Arc { - self.latest_finalized_block.read().await.clone() - } - - async fn block_by_number( - &self, - block_number: SubstrateBlockNumber, - ) -> Result>, ClientError> { - let latest = self.latest_block().await; - if block_number == latest.number() { - return Ok(Some(latest)); - } - - let latest_finalized = self.latest_finalized_block().await; - if block_number == latest_finalized.number() { - return Ok(Some(latest_finalized)); - } - - let Some(hash) = self.rpc.chain_get_block_hash(Some(block_number.into())).await? else { - return Ok(None); - }; - - match self.api.blocks().at(hash).await { - Ok(block) => Ok(Some(Arc::new(block))), - Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), - Err(err) => Err(err.into()), - } - } - - async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError> { - let latest = self.latest_block().await; - if hash == &latest.hash() { - return Ok(Some(latest)); - } - - let latest_finalized = self.latest_finalized_block().await; - if hash == &latest_finalized.hash() { - return Ok(Some(latest_finalized)); - } - - match self.api.blocks().at(*hash).await { - Ok(block) => Ok(Some(Arc::new(block))), - Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), - Err(err) => Err(err.into()), - } - } -} +/// The subxt-backed block info provider. +#[cfg(feature = "subxt")] +pub use crate::subxt_client::SubxtBlockInfoProvider; -#[cfg(test)] pub mod test { use super::*; - use crate::BlockInfo; + use crate::client::SubstrateBlockNumber; - /// A Noop BlockInfoProvider used to test [`db::ReceiptProvider`]. + /// A noop [`BlockInfoProvider`] used in unit tests. pub struct MockBlockInfoProvider; pub struct MockBlockInfo { @@ -166,22 +86,33 @@ pub mod test { fn number(&self) -> SubstrateBlockNumber { self.number } + fn parent_hash(&self) -> H256 { + H256::default() + } + } + + impl Clone for MockBlockInfo { + fn clone(&self) -> Self { + Self { number: self.number, hash: self.hash } + } } #[async_trait] impl BlockInfoProvider for MockBlockInfoProvider { + type Block = MockBlockInfo; + async fn update_latest( &self, - _block: Arc, + _block: Arc, _subscription_type: SubscriptionType, ) { } - async fn latest_finalized_block(&self) -> Arc { + async fn latest_finalized_block(&self) -> Arc { unimplemented!() } - async fn latest_block(&self) -> Arc { + async fn latest_block(&self) -> Arc { unimplemented!() } @@ -192,15 +123,21 @@ pub mod test { async fn block_by_number( &self, _block_number: SubstrateBlockNumber, - ) -> Result>, ClientError> { + ) -> Result>, ClientError> { Ok(None) } async fn block_by_hash( &self, _hash: &H256, - ) -> Result>, ClientError> { + ) -> Result>, ClientError> { Ok(None) } } + + impl Clone for MockBlockInfoProvider { + fn clone(&self) -> Self { + MockBlockInfoProvider + } + } } diff --git a/substrate/frame/revive/rpc/src/block_sync.rs b/substrate/frame/revive/rpc/src/block_sync.rs index 3c83551de1781..1a919e96e3d02 100644 --- a/substrate/frame/revive/rpc/src/block_sync.rs +++ b/substrate/frame/revive/rpc/src/block_sync.rs @@ -19,9 +19,12 @@ use crate::{ BlockInfoProvider, + block_info_provider::BlockInfo, client::{Client, ClientError, SubstrateBlockNumber}, + substrate_client::SubstrateClientT, }; use pallet_revive::evm::H256; +use std::sync::Arc; const LOG_TARGET: &str = "eth-rpc::block-sync"; @@ -87,10 +90,16 @@ struct BackwardSyncRange { checkpoint_tail: bool, } -impl Client { +impl Client { /// Verify that the stored genesis hash matches the connected chain. async fn validate_chain_identity(&self) -> Result { - let genesis_hash: H256 = self.api().genesis_hash(); + // Fetch block 0 (genesis) to use as the chain identity fingerprint. + let genesis_hash: H256 = self + .block_provider() + .block_by_number(0) + .await? + .ok_or(ClientError::BlockNotFound)? + .hash(); if let Some(checkpoint) = self.receipt_provider().get_sync_label(ChainMetadata::Genesis).await? @@ -108,24 +117,27 @@ impl Client { /// Verify that a stored boundary block still exists on the finalized chain. async fn verify_boundary(&self, checkpoint: &SyncCheckpoint) -> Result<(), ClientError> { let num = checkpoint.block_number; - let hash = checkpoint.block_hash; - match (num, hash) { - (_, None) => { - log::error!(target: LOG_TARGET, - "Boundary #{num}: missing stored hash"); + match checkpoint.block_hash { + None => { + log::error!(target: LOG_TARGET, "Boundary #{num}: missing stored hash"); Err(ClientError::SyncBoundaryMismatch) }, - (_, Some(stored_hash)) => { - let block = self.block_provider().block_by_number(num).await?.ok_or_else(|| { - log::error!(target: LOG_TARGET, - "Boundary #{num}: block not found on chain \ - (node may have pruned it — use an archive node with --eth-pruning archive)"); - ClientError::SyncBoundaryMismatch - })?; + Some(stored_hash) => { + let block: Arc = + self.block_provider().block_by_number(num).await?.ok_or_else(|| { + log::error!( + target: LOG_TARGET, + "Boundary #{num}: block not found on chain \ + (node may have pruned it — use an archive node with --eth-pruning archive)" + ); + ClientError::SyncBoundaryMismatch + })?; if block.hash() != stored_hash { - log::error!(target: LOG_TARGET, - "Boundary #{num}: hash mismatch — stored {stored_hash:?}, \ - chain {:?}", block.hash()); + log::error!( + target: LOG_TARGET, + "Boundary #{num}: hash mismatch — stored {stored_hash:?}, chain {:?}", + block.hash() + ); return Err(ClientError::SyncBoundaryMismatch); } Ok(()) @@ -150,9 +162,11 @@ impl Client { /// Fatal errors (chain/DB mismatch) are propagated; transient errors are swallowed /// to avoid taking down the RPC server. pub async fn sync_backward(&self) -> Result<(), ClientError> { - log::info!(target: LOG_TARGET, + log::info!( + target: LOG_TARGET, "🔄 Historical block sync enabled. \ - For a complete sync, the connected node should be an archive node."); + For a complete sync, the connected node should be an archive node." + ); match self.sync_backward_inner().await { Ok(()) => Ok(()), Err(err) if err.is_chain_validation_error() => Err(err), @@ -165,7 +179,8 @@ impl Client { async fn sync_backward_inner(&self) -> Result<(), ClientError> { let genesis_hash = self.validate_chain_identity().await?; - let latest_finalized_block = self.latest_finalized_block().await; + + let latest_finalized_block: Arc = self.latest_finalized_block().await; let latest_finalized = SyncCheckpoint::new(latest_finalized_block.number(), latest_finalized_block.hash()); @@ -182,18 +197,24 @@ impl Client { match (tail, head) { (Some(tail), Some(head)) => { // Verify boundary hashes still match the finalized chain. - tokio::try_join!(self.verify_boundary(&tail), self.verify_boundary(&head),)?; + tokio::try_join!(self.verify_boundary(&tail), self.verify_boundary(&head))?; self.sync_backward_resume(tail, head, latest_finalized).await?; }, (Some(_), None) => { - log::warn!(target: LOG_TARGET, + log::warn!( + target: LOG_TARGET, "🗄️ Tail exists without Head — possible partial corruption, \ - starting fresh sync from #{}", latest_finalized.block_number); + starting fresh sync from #{}", + latest_finalized.block_number + ); self.sync_backward_fresh(latest_finalized.block_number).await?; }, _ => { - log::info!(target: LOG_TARGET, - "🗄️ Fresh sync: syncing backward from #{}", latest_finalized.block_number); + log::info!( + target: LOG_TARGET, + "🗄️ Fresh sync: syncing backward from #{}", + latest_finalized.block_number + ); self.sync_backward_fresh(latest_finalized.block_number).await?; }, } @@ -226,9 +247,13 @@ impl Client { head: SyncCheckpoint, latest_finalized: SyncCheckpoint, ) -> Result<(), ClientError> { - log::info!(target: LOG_TARGET, + log::info!( + target: LOG_TARGET, "🗄️ Resuming sync: DB has blocks #{}..#{}, chain head is #{}", - tail.block_number, head.block_number, latest_finalized.block_number); + tail.block_number, + head.block_number, + latest_finalized.block_number + ); let top_gap = async { // Top gap: sync from latest_finalized down to head + 1. @@ -278,13 +303,16 @@ impl Client { BackwardSyncRange { from, to, set_head, checkpoint_tail }: BackwardSyncRange, ) -> Result<(), ClientError> { if from < to { - log::debug!(target: LOG_TARGET, "⬇️ Backward sync: nothing to sync (#{from}..#{to})"); + log::debug!( + target: LOG_TARGET, + "⬇️ Backward sync: nothing to sync (#{from}..#{to})" + ); return Ok(()); } log::info!(target: LOG_TARGET, "⬇️ Backward sync: #{from} down to #{to}"); - let mut block = self + let mut block: Arc = self .block_provider() .block_by_number(from) .await? @@ -299,25 +327,22 @@ impl Client { let block_number = block.number(); let block_hash = block.hash(); - let ethereum_hash = match self - .runtime_api(block_hash) - .eth_block_hash(pallet_revive::evm::U256::from(block_number)) - .await - { - Ok(h) => h, - Err(err) => { - log::error!(target: LOG_TARGET, "⚠️ eth_block_hash failed for #{block_number}: {err:?}, stopping"); - break Err(err.into()); - }, - }; + let ethereum_hash: Option = self + .backend + .eth_block_hash(block_hash, pallet_revive::evm::U256::from(block_number)) + .await?; match ethereum_hash { Some(hash) => { - if let Err(err) = - self.receipt_provider().insert_block_receipts_past(&block, &hash).await + if let Err(err) = self + .receipt_provider() + .insert_block_receipts_past(block.as_ref(), &hash) + .await { - log::error!(target: LOG_TARGET, - "⚠️ Insert failed for #{block_number}: {err:?}, stopping"); + log::error!( + target: LOG_TARGET, + "⚠️ Insert failed for #{block_number}: {err:?}, stopping" + ); break Err(err); } @@ -329,8 +354,10 @@ impl Client { } if at_checkpoint(blocks_synced) { - log::debug!(target: LOG_TARGET, - "⬇️ Backward sync progress: #{block_number} ({blocks_synced} blocks synced)"); + log::debug!( + target: LOG_TARGET, + "⬇️ Backward sync progress: #{block_number} ({blocks_synced} blocks synced)" + ); if checkpoint_tail { self.checkpoint_sync_label(SyncLabel::Tail, block_number, block_hash) .await; @@ -339,12 +366,17 @@ impl Client { }, None => { let first_evm_block = block_number.saturating_add(1); - log::debug!(target: LOG_TARGET, - "🔍 No EVM hash at #{block_number}, setting first_evm_block to #{first_evm_block}"); + log::debug!( + target: LOG_TARGET, + "🔍 No EVM hash at #{block_number}, setting first_evm_block to #{first_evm_block}" + ); if let Err(err) = self.receipt_provider().set_first_evm_block(first_evm_block).await { - log::warn!(target: LOG_TARGET, "Failed to persist first-evm-block: {err:?}"); + log::warn!( + target: LOG_TARGET, + "Failed to persist first-evm-block: {err:?}" + ); } break Ok(()); @@ -352,7 +384,7 @@ impl Client { } if block_number > to { - let parent_hash = block.header().parent_hash; + let parent_hash = block.parent_hash(); match self .block_provider() .block_by_hash(&parent_hash) @@ -362,8 +394,10 @@ impl Client { { Ok(b) => block = b, Err(err) => { - log::error!(target: LOG_TARGET, - "⚠️ Could not fetch parent of #{block_number}: {err:?}, stopping"); + log::error!( + target: LOG_TARGET, + "⚠️ Could not fetch parent of #{block_number}: {err:?}, stopping" + ); break Err(err); }, } @@ -379,9 +413,10 @@ impl Client { } } - log::info!(target: LOG_TARGET, - "⬇️ Backward sync: {blocks_synced} blocks synced \ - (requested #{from}..#{to})"); + log::info!( + target: LOG_TARGET, + "⬇️ Backward sync: {blocks_synced} blocks synced (requested #{from}..#{to})" + ); loop_result } diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 78b4d2bdfc97b..5424d7760061e 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -16,10 +16,10 @@ // limitations under the License. //! The Ethereum JSON-RPC server. use crate::{ - DebugRpcServer, DebugRpcServerImpl, EthRpcServer, EthRpcServerImpl, LOG_TARGET, - PolkadotRpcServer, PolkadotRpcServerImpl, ReceiptExtractor, ReceiptProvider, - SubxtBlockInfoProvider, SystemHealthRpcServer, SystemHealthRpcServerImpl, - client::{Client, SubscriptionType, connect}, + BlockInfoProvider, DebugRpcServer, DebugRpcServerImpl, EthRpcServer, EthRpcServerImpl, + LOG_TARGET, PolkadotRpcServer, PolkadotRpcServerImpl, ReceiptExtractor, ReceiptProvider, + SubstrateClientT, SystemHealthRpcServer, SystemHealthRpcServerImpl, + client::{Client, SubscriptionType, SubstrateBlockNumber}, }; use clap::{CommandFactory, FromArgMatches, Parser}; use futures::{FutureExt, future::BoxFuture, pin_mut}; @@ -89,7 +89,7 @@ const DEFAULT_RPC_PORT: u16 = 8545; const DEFAULT_DATABASE_NAME: &str = "eth-rpc.db"; // Parsed command instructions from the command line -#[derive(Parser, Debug)] +#[derive(Parser, Debug, Clone)] #[clap(author, about, version)] pub struct CliCommand { /// The node url to connect to @@ -116,7 +116,7 @@ pub struct CliCommand { pub prometheus_params: PrometheusParams, /// By default, the node rejects any transaction that's unprotected (i.e., that doesn't have a - /// chain-id). If the user wishes the submit such a transaction then they can use this flag to + /// chain-id). If the user wishes to submit such a transaction they can use this flag to /// instruct the RPC to ignore this check. #[arg(long)] pub allow_unprotected_txs: bool, @@ -178,12 +178,6 @@ fn init_logger(params: &SharedParams) -> anyhow::Result<()> { } /// Resolve the base directory for persistent database storage. -/// -/// - If `base_path` is `Some` (explicit `--base-path` or `--dev` temp dir), use it directly. -/// - If `base_path` is `None`, use the platform default: -/// - macOS: `~/Library/Application Support/eth-rpc/` -/// - Linux: `~/.local/share/eth-rpc/` -/// - Windows: `%APPDATA%\eth-rpc\` fn resolve_db_dir(base_path: Option) -> PathBuf { match base_path { Some(path) => path.path().to_path_buf(), @@ -214,81 +208,139 @@ fn resolve_db_options( } } -fn build_client( - tokio_handle: &tokio::runtime::Handle, +fn dev_accounts() -> Vec { + #[cfg(feature = "subxt")] + { + vec![ + crate::Account::from(subxt_signer::eth::dev::alith()), + crate::Account::from(subxt_signer::eth::dev::baltathar()), + crate::Account::from(subxt_signer::eth::dev::charleth()), + crate::Account::from(subxt_signer::eth::dev::dorothy()), + crate::Account::from(subxt_signer::eth::dev::ethan()), + ] + } + #[cfg(not(feature = "subxt"))] + { + log::warn!( + target: LOG_TARGET, + "⚠️ --dev mode: the `subxt` feature is disabled, no dev accounts are pre-loaded." + ); + vec![] + } +} + +/// Create the JSON-RPC module from a `Client`. +pub fn build_eth_rpc_module( + is_dev: bool, + client: Client, + allow_unprotected_txs: bool, +) -> Result, sc_service::Error> +where + C: SubstrateClientT, + BP: BlockInfoProvider, +{ + let eth_api = EthRpcServerImpl::new(client.clone()) + .with_accounts(if is_dev { dev_accounts() } else { vec![] }) + .with_allow_unprotected_txs(allow_unprotected_txs) + .with_use_pending_for_estimate_gas(is_dev) + .into_rpc(); + + let health_api = SystemHealthRpcServerImpl::new(client.clone()).into_rpc(); + let debug_api = DebugRpcServerImpl::new(client.clone()).into_rpc(); + let polkadot_api = PolkadotRpcServerImpl::new(client).into_rpc(); + + let mut module = RpcModule::new(()); + module.merge(eth_api).map_err(|e| sc_service::Error::Application(e.into()))?; + module.merge(health_api).map_err(|e| sc_service::Error::Application(e.into()))?; + module.merge(debug_api).map_err(|e| sc_service::Error::Application(e.into()))?; + module + .merge(polkadot_api) + .map_err(|e| sc_service::Error::Application(e.into()))?; + Ok(module) +} + +/// Build a [`Client`] backed by a native in-process Substrate client using an +/// **in-memory** SQLite database that retains only the latest `keep_latest_n_blocks` blocks. +pub async fn build_native_inmemory_client( + backend: C, + block_provider: BP, + keep_latest_n_blocks: usize, + automine: bool, +) -> anyhow::Result> +where + C: SubstrateClientT, + BP: BlockInfoProvider, +{ + let db_options = SqliteConnectOptions::new().in_memory(true); + build_client_from_backend( + backend, + block_provider, + EthPruningMode::KeepLatest(keep_latest_n_blocks), + db_options, + automine, + ) + .await +} + +/// Build a `Client` from an already-constructed backend and block-provider. +pub async fn build_client_from_backend( + backend: C, + block_provider: BP, eth_pruning: EthPruningMode, - node_rpc_url: &str, db_options: SqliteConnectOptions, - max_request_size: u32, - max_response_size: u32, - abort_signal: Signals, -) -> anyhow::Result { - let fut = async { - let (api, rpc_client, rpc) = - connect(node_rpc_url, max_request_size, max_response_size).await?; - let block_provider = SubxtBlockInfoProvider::new(api.clone(), rpc.clone()).await?; - - let (pool, keep_latest_n_blocks) = match eth_pruning { - EthPruningMode::Archive => { - (SqlitePoolOptions::new().connect_with(db_options).await?, None) - }, - EthPruningMode::KeepLatest(max_blocks) => { - log::info!(target: LOG_TARGET, - "💾 Using in-memory database, keeping only {max_blocks} blocks"); - // see sqlite in-memory issue: https://github.com/launchbadge/sqlx/issues/2510 - let pool = SqlitePoolOptions::new() - .max_connections(1) - .idle_timeout(None) - .max_lifetime(None) - .connect_with(db_options) - .await?; - (pool, Some(max_blocks)) - }, - }; - - let receipt_extractor = ReceiptExtractor::new(api.clone()).await?; - - let receipt_provider = ReceiptProvider::new( - pool, - block_provider.clone(), - receipt_extractor.clone(), - keep_latest_n_blocks, - ) - .await?; - - let client = Client::new( - api, - rpc_client, - rpc, - block_provider, - receipt_provider, - eth_pruning.is_archive(), - ) - .await?; - - Ok(client) - } - .fuse(); - pin_mut!(fut); + automine: bool, +) -> anyhow::Result> +where + C: SubstrateClientT, + BP: BlockInfoProvider, +{ + let earliest_receipt_block: Option = None; + let receipt_extractor = + ReceiptExtractor::new_from_substrate_client(backend.clone(), earliest_receipt_block); + + let (pool, keep_latest_n_blocks) = match eth_pruning { + EthPruningMode::KeepLatest(n) => { + log::warn!( + target: LOG_TARGET, + "💾 Using in-memory database, keeping only {n} blocks in memory" + ); + let pool = SqlitePoolOptions::new() + .max_connections(1) + .idle_timeout(None) + .max_lifetime(None) + .connect_with(db_options) + .await?; + (pool, Some(n)) + }, + EthPruningMode::Archive => (SqlitePoolOptions::new().connect_with(db_options).await?, None), + }; - match tokio_handle.block_on(abort_signal.try_until_signal(fut)) { - Ok(Ok(client)) => Ok(client), - Ok(Err(err)) => Err(err), - Err(_) => anyhow::bail!("Process interrupted"), - } + let receipt_provider = + ReceiptProvider::new(pool, block_provider.clone(), receipt_extractor, keep_latest_n_blocks) + .await?; + + let client = Client::from_backend(backend, block_provider, receipt_provider, automine)?; + Ok(client) } -/// Start the JSON-RPC server using the given command line arguments. -pub fn run(cmd: CliCommand) -> anyhow::Result<()> { +/// Start the JSON-RPC server using a pre-built native client. +pub fn run_with_native_client( + cmd: CliCommand, + backend: C, + block_provider: BP, +) -> anyhow::Result<()> +where + C: SubstrateClientT, + BP: BlockInfoProvider, +{ let CliCommand { - rpc_params, + rpc_params: _, prometheus_params, - node_rpc_url, - eth_pruning, shared_params, allow_unprotected_txs, + eth_pruning, .. - } = cmd; + } = cmd.clone(); #[cfg(not(test))] init_logger(&shared_params)?; @@ -307,25 +359,26 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let db_options = resolve_db_options(eth_pruning, base_path)?; - let rpc_addrs: Option> = rpc_params + let rpc_addrs: Option> = cmd + .rpc_params .rpc_addr(is_dev, false, DEFAULT_RPC_PORT)? .map(|addrs| addrs.into_iter().map(Into::into).collect()); let rpc_config = RpcConfiguration { addr: rpc_addrs, - methods: rpc_params.rpc_methods.into(), - max_connections: rpc_params.rpc_max_connections, - cors: rpc_params.rpc_cors(is_dev)?, - max_request_size: rpc_params.rpc_max_request_size, - max_response_size: rpc_params.rpc_max_response_size, + methods: cmd.rpc_params.rpc_methods.into(), + max_connections: cmd.rpc_params.rpc_max_connections, + cors: cmd.rpc_params.rpc_cors(is_dev)?, + max_request_size: cmd.rpc_params.rpc_max_request_size, + max_response_size: cmd.rpc_params.rpc_max_response_size, id_provider: None, - max_subs_per_conn: rpc_params.rpc_max_subscriptions_per_connection, - port: rpc_params.rpc_port.unwrap_or(DEFAULT_RPC_PORT), - message_buffer_capacity: rpc_params.rpc_message_buffer_capacity_per_connection, - batch_config: rpc_params.rpc_batch_config()?, - rate_limit: rpc_params.rpc_rate_limit, - rate_limit_whitelisted_ips: rpc_params.rpc_rate_limit_whitelisted_ips, - rate_limit_trust_proxy_headers: rpc_params.rpc_rate_limit_trust_proxy_headers, + max_subs_per_conn: cmd.rpc_params.rpc_max_subscriptions_per_connection, + port: cmd.rpc_params.rpc_port.unwrap_or(DEFAULT_RPC_PORT), + message_buffer_capacity: cmd.rpc_params.rpc_message_buffer_capacity_per_connection, + batch_config: cmd.rpc_params.rpc_batch_config()?, + rate_limit: cmd.rpc_params.rpc_rate_limit, + rate_limit_whitelisted_ips: cmd.rpc_params.rpc_rate_limit_whitelisted_ips, + rate_limit_trust_proxy_headers: cmd.rpc_params.rpc_rate_limit_trust_proxy_headers, request_logger_limit: if is_dev { 1024 * 1024 } else { 1024 }, }; @@ -337,17 +390,20 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let tokio_handle = tokio_runtime.handle(); let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; - let client = build_client( - tokio_handle, - eth_pruning, - &node_rpc_url, - db_options, - rpc_config.max_request_size * 1024 * 1024, - rpc_config.max_response_size * 1024 * 1024, - tokio_runtime.block_on(async { Signals::capture() })?, - )?; + let client = { + let fut = + build_client_from_backend(backend, block_provider, eth_pruning, db_options, false) + .fuse(); + pin_mut!(fut); + + let abort_signal = tokio_runtime.block_on(async { Signals::capture() })?; + match tokio_handle.block_on(abort_signal.try_until_signal(fut)) { + Ok(Ok(c)) => c, + Ok(Err(e)) => return Err(e), + Err(_) => anyhow::bail!("Process interrupted"), + } + }; - // Prometheus metrics. if let Some(PrometheusConfig { port, registry }) = prometheus_config.clone() { task_manager.spawn_handle().spawn( "prometheus-endpoint", @@ -359,7 +415,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let rpc_runtime = create_rpc_runtime(rpc_config.max_connections) .map_err(|e| anyhow::anyhow!("Failed to create RPC runtime: {}", e))?; - let rpc_api = rpc_module(is_dev, client.clone(), allow_unprotected_txs)?; + let rpc_api = build_eth_rpc_module(is_dev, client.clone(), allow_unprotected_txs)?; let rpc_server_handle = start_rpc_servers( &rpc_config, prometheus_registry, @@ -372,7 +428,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { task_manager .spawn_essential_handle() .spawn("block-subscription", None, async move { - let mut futures: Vec>> = vec![ + let mut futures: Vec>> = vec![ Box::pin(client.subscribe_and_cache_new_blocks(SubscriptionType::BestBlocks)), Box::pin(client.subscribe_and_cache_new_blocks(SubscriptionType::FinalizedBlocks)), ]; @@ -382,7 +438,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { } if let Err(err) = futures::future::try_join_all(futures).await { - panic!("Block subscription task failed: {err:?}",) + panic!("Block subscription task failed: {err:?}") } }); @@ -392,40 +448,165 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { Ok(()) } -/// Create the JSON-RPC module. -fn rpc_module( - is_dev: bool, - client: Client, - allow_unprotected_txs: bool, -) -> Result, sc_service::Error> { - let eth_api = EthRpcServerImpl::new(client.clone()) - .with_accounts(if is_dev { - vec![ - crate::Account::from(subxt_signer::eth::dev::alith()), - crate::Account::from(subxt_signer::eth::dev::baltathar()), - crate::Account::from(subxt_signer::eth::dev::charleth()), - crate::Account::from(subxt_signer::eth::dev::dorothy()), - crate::Account::from(subxt_signer::eth::dev::ethan()), - ] - } else { - vec![] - }) - .with_allow_unprotected_txs(allow_unprotected_txs) - .with_use_pending_for_estimate_gas(is_dev) - .into_rpc(); +/// Run the ETH RPC server using a `subxt` WebSocket connection to a Substrate node. +#[cfg(feature = "subxt")] +pub fn run(cmd: CliCommand) -> anyhow::Result<()> { + use crate::{ + SubxtBlockInfoProvider, + subxt_client::{SubxtClient, connect}, + }; - let health_api = SystemHealthRpcServerImpl::new(client.clone()).into_rpc(); - let debug_api = DebugRpcServerImpl::new(client.clone()).into_rpc(); - let polkadot_api = PolkadotRpcServerImpl::new(client).into_rpc(); + let CliCommand { + prometheus_params, + shared_params, + allow_unprotected_txs, + node_rpc_url, + eth_pruning, + .. + } = cmd.clone(); - let mut module = RpcModule::new(()); - module.merge(eth_api).map_err(|e| sc_service::Error::Application(e.into()))?; - module.merge(health_api).map_err(|e| sc_service::Error::Application(e.into()))?; - module.merge(debug_api).map_err(|e| sc_service::Error::Application(e.into()))?; - module - .merge(polkadot_api) - .map_err(|e| sc_service::Error::Application(e.into()))?; - Ok(module) + #[cfg(not(test))] + init_logger(&shared_params)?; + + let is_dev = shared_params.dev; + let explicit_base_path = shared_params.base_path.is_some(); + let base_path = shared_params.base_path()?; + + if is_dev && eth_pruning.is_archive() && !explicit_base_path { + log::warn!( + target: LOG_TARGET, + "⚠️ Running in --dev mode with --eth-pruning=archive but no --base-path. \ + The database will be stored in a temporary directory and lost on exit. \ + Use --base-path to persist the database." + ); + } + + let db_options = resolve_db_options(eth_pruning, base_path)?; + + let rpc_addrs: Option> = cmd + .rpc_params + .rpc_addr(is_dev, false, DEFAULT_RPC_PORT)? + .map(|addrs| addrs.into_iter().map(Into::into).collect()); + + let rpc_config = RpcConfiguration { + addr: rpc_addrs, + methods: cmd.rpc_params.rpc_methods.into(), + max_connections: cmd.rpc_params.rpc_max_connections, + cors: cmd.rpc_params.rpc_cors(is_dev)?, + max_request_size: cmd.rpc_params.rpc_max_request_size, + max_response_size: cmd.rpc_params.rpc_max_response_size, + id_provider: None, + max_subs_per_conn: cmd.rpc_params.rpc_max_subscriptions_per_connection, + port: cmd.rpc_params.rpc_port.unwrap_or(DEFAULT_RPC_PORT), + message_buffer_capacity: cmd.rpc_params.rpc_message_buffer_capacity_per_connection, + batch_config: cmd.rpc_params.rpc_batch_config()?, + rate_limit: cmd.rpc_params.rpc_rate_limit, + rate_limit_whitelisted_ips: cmd.rpc_params.rpc_rate_limit_whitelisted_ips, + rate_limit_trust_proxy_headers: cmd.rpc_params.rpc_rate_limit_trust_proxy_headers, + request_logger_limit: if is_dev { 1024 * 1024 } else { 1024 }, + }; + + let prometheus_config = + prometheus_params.prometheus_config(DEFAULT_PROMETHEUS_PORT, "eth-rpc".into()); + let prometheus_registry = prometheus_config.as_ref().map(|config| &config.registry); + + let tokio_runtime = sc_cli::build_runtime()?; + let tokio_handle = tokio_runtime.handle(); + let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; + + let (client, _block_provider) = { + let fut = async { + let (api, rpc_client, rpc) = + connect(&node_rpc_url, rpc_config.max_request_size, rpc_config.max_response_size) + .await?; + + let subxt_client = SubxtClient::new(api.clone(), rpc_client, rpc.clone()).await?; + // Explicit type annotation to resolve the inference ambiguity + let block_provider: SubxtBlockInfoProvider = + SubxtBlockInfoProvider::new(api, rpc).await?; + + let client = build_client_from_backend( + subxt_client, + block_provider.clone(), + eth_pruning, + db_options, + false, + ) + .await?; + + Ok::<_, anyhow::Error>((client, block_provider)) + } + .fuse(); + + futures::pin_mut!(fut); + let abort_signal = tokio_runtime.block_on(async { sc_cli::Signals::capture() })?; + match tokio_handle.block_on(abort_signal.try_until_signal(fut)) { + Ok(Ok(result)) => result, + Ok(Err(e)) => return Err(e), + Err(_) => anyhow::bail!("Process interrupted during startup"), + } + }; + + // Prometheus metrics. + if let Some(sc_service::config::PrometheusConfig { port, registry }) = prometheus_config.clone() + { + task_manager.spawn_handle().spawn( + "prometheus-endpoint", + None, + prometheus_endpoint::init_prometheus(port, registry).map(drop), + ); + } + + let rpc_runtime = create_rpc_runtime(rpc_config.max_connections) + .map_err(|e| anyhow::anyhow!("Failed to create RPC runtime: {}", e))?; + let rpc_api = build_eth_rpc_module(is_dev, client.clone(), allow_unprotected_txs)?; + let rpc_server_handle = start_rpc_servers( + &rpc_config, + prometheus_registry, + tokio_handle, + rpc_api, + rpc_runtime, + None, + )?; + + // Archive mode: spawn the historical backward sync task. + if eth_pruning.is_archive() { + let sync_client = client.clone(); + task_manager + .spawn_essential_handle() + .spawn("block-backward-sync", None, async move { + if let Err(err) = sync_client.sync_backward().await { + panic!("Chain validation failed during backward sync: {err:?}"); + } + }); + } + + task_manager + .spawn_essential_handle() + .spawn("block-subscription", None, async move { + let futures: Vec>> = vec![ + Box::pin(client.subscribe_and_cache_new_blocks(SubscriptionType::BestBlocks)), + Box::pin(client.subscribe_and_cache_new_blocks(SubscriptionType::FinalizedBlocks)), + ]; + + if let Err(err) = futures::future::try_join_all(futures).await { + panic!("Block subscription task failed: {err:?}") + } + }); + + task_manager.keep_alive(rpc_server_handle); + let signals = tokio_runtime.block_on(async { sc_cli::Signals::capture() })?; + tokio_runtime.block_on(signals.run_until_signal(task_manager.future().fuse()))?; + Ok(()) +} + +/// Fallback when the `subxt` feature is disabled. +#[cfg(not(feature = "subxt"))] +pub fn run(_cmd: CliCommand) -> anyhow::Result<()> { + anyhow::bail!( + "The standalone `eth-rpc` binary requires the `subxt` feature. \ + Rebuild with `--features subxt` or embed the server using `run_with_native_client`." + ) } #[cfg(test)] diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 17b4849349367..d21314a0214cc 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -17,66 +17,43 @@ //! The client connects to the source substrate chain //! and is used by the rpc server to query and send transactions to the substrate chain. +#[cfg(feature = "subxt")] pub(crate) mod runtime_api; +#[cfg(feature = "subxt")] pub(crate) mod storage_api; use crate::{ - BlockInfoProvider, BlockTag, FeeHistoryProvider, ReceiptProvider, SubxtBlockInfoProvider, - SyncLabel, TracerType, TransactionInfo, - block_sync::SyncCheckpoint, - subxt_client::{self, SrcChainConfig, revive::calls::types::EthTransact}, + BlockInfoProvider, BlockNumberOrTag, BlockTag, FeeHistoryProvider, ReceiptProvider, TracerType, + TransactionInfo, + block_info_provider::BlockInfo, + substrate_client::{NodeHealth, SubmitResult, SubstrateClientT}, }; -use futures::TryStreamExt; use jsonrpsee::types::{ErrorObjectOwned, error::CALL_EXECUTION_FAILED_CODE}; use pallet_revive::{ EthTransactError, evm::{ - Block, BlockNumberOrTag, BlockNumberOrTagOrHash, FeeHistoryResult, Filter, - GenericTransaction, H256, HashesOrTransactionInfos, Log, ReceiptInfo, SyncingProgress, - SyncingStatus, Trace, TransactionSigned, TransactionTrace, U256, decode_revert_reason, + Block, BlockNumberOrTagOrHash, FeeHistoryResult, Filter, GenericTransaction, H256, + HashesOrTransactionInfos, Log, ReceiptInfo, SyncingProgress, SyncingStatus, Trace, + TransactionSigned, TransactionTrace, decode_revert_reason, }, }; -use runtime_api::RuntimeApi; -use sp_runtime::traits::Block as BlockT; +use sp_core::U256; use sp_weights::Weight; use std::{ + ops::Range, sync::{ Arc, atomic::{AtomicBool, Ordering}, }, - time::Duration, -}; -use storage_api::StorageApi; -use subxt::{ - Config, OnlineClient, - backend::{ - StreamOf, StreamOfResults, - legacy::{ - LegacyRpcMethods, - rpc_methods::{SystemHealth, TransactionStatus}, - }, - rpc::{ - RpcClient, - reconnecting_rpc_client::{ExponentialBackoff, RpcClient as ReconnectingRpcClient}, - }, - }, - config::{HashFor, Header}, - ext::subxt_rpcs::rpc_params, }; use thiserror::Error; use tokio::sync::Mutex; -/// The substrate block type. -pub type SubstrateBlock = subxt::blocks::Block>; - -/// The substrate block header. -pub type SubstrateBlockHeader = ::Header; - /// The substrate block number type. -pub type SubstrateBlockNumber = ::Number; +pub type SubstrateBlockNumber = u32; /// The substrate block hash type. -pub type SubstrateBlockHash = HashFor; +pub type SubstrateBlockHash = sp_core::H256; /// The runtime balance type. pub type Balance = u128; @@ -110,8 +87,13 @@ pub enum SubmitError { Unknown, } -impl From> for SubmitError { - fn from(status: TransactionStatus) -> Self { +impl From> + for SubmitError +{ + fn from( + status: sc_transaction_pool_api::TransactionStatus, + ) -> Self { + use sc_transaction_pool_api::TransactionStatus; match status { TransactionStatus::Usurped(_) => SubmitError::Usurped, TransactionStatus::Dropped => SubmitError::Dropped, @@ -127,17 +109,23 @@ pub enum ClientError { /// A [`jsonrpsee::core::ClientError`] wrapper error. #[error(transparent)] Jsonrpsee(#[from] jsonrpsee::core::ClientError), - /// A [`subxt::Error`] wrapper error. - #[error(transparent)] - SubxtError(#[from] subxt::Error), - #[error(transparent)] - RpcError(#[from] subxt::ext::subxt_rpcs::Error), + /// An error originating from the native in-process Substrate client + #[error("Native client error: {0}")] + NativeClientError(String), /// A [`sqlx::Error`] wrapper error. #[error(transparent)] SqlxError(#[from] sqlx::Error), /// A [`codec::Error`] wrapper error. #[error(transparent)] CodecError(#[from] codec::Error), + /// A [`subxt::Error`] wrapper error. + #[cfg(feature = "subxt")] + #[error(transparent)] + SubxtError(#[from] subxt::Error), + /// A [`subxt::ext::subxt_rpcs::Error`] wrapper error. + #[cfg(feature = "subxt")] + #[error(transparent)] + RpcError(#[from] subxt::ext::subxt_rpcs::Error), /// author_submitExtrinsic failed. #[error("Invalid transaction: {0}")] SubmitError(SubmitError), @@ -155,7 +143,7 @@ pub enum ClientError { ContractNotFound, #[error("No Ethereum extrinsic found")] EthExtrinsicNotFound, - /// The transaction fee could not be found + /// The transaction fee could not be found. #[error("transactionFeePaid event not found")] TxFeeNotFound, /// Failed to decode a raw payload into a signed transaction. @@ -207,18 +195,11 @@ const NOTIFIER_CAPACITY: usize = 16; impl From for ErrorObjectOwned { fn from(err: ClientError) -> Self { match err { - ClientError::SubxtError(subxt::Error::Rpc(subxt::error::RpcError::ClientError( - subxt::ext::subxt_rpcs::Error::User(err), - ))) | - ClientError::RpcError(subxt::ext::subxt_rpcs::Error::User(err)) => { - ErrorObjectOwned::owned::>(err.code, err.message, None) - }, ClientError::TransactError(EthTransactError::Data(data)) => { let msg = match decode_revert_reason(&data) { Some(reason) => format!("execution reverted: {reason}"), None => "execution reverted".to_string(), }; - let data = format!("0x{}", hex::encode(data)); ErrorObjectOwned::owned::(REVERT_CODE, msg, Some(data)) }, @@ -232,30 +213,28 @@ impl From for ErrorObjectOwned { } } -/// A client that connects to a substrate node and provides Ethereum-compatible RPC functionality. +/// A client that connects to a Substrate node and maintains a receipt/block cache. #[derive(Clone)] -pub struct Client { - api: OnlineClient, - rpc_client: RpcClient, - rpc: LegacyRpcMethods, - receipt_provider: ReceiptProvider, - block_provider: SubxtBlockInfoProvider, +pub struct Client { + /// The underlying substrate client backend. + pub(crate) backend: C, + /// Block info provider (caches latest best/finalized blocks). + block_provider: BP, + /// Receipt storage / cache. + receipt_provider: ReceiptProvider, + /// Fee history cache. fee_history_provider: FeeHistoryProvider, - chain_id: u64, - max_block_weight: Weight, /// Whether the node has automine enabled. automine: bool, - /// A notifier, that informs subscribers of new best blocks. + /// Block notifier for automine. block_notifier: Option>, - /// A lock to ensure only one subscription can perform write operations at a time. + /// Serialises write operations across concurrent subscriptions. subscription_lock: Arc>, /// Block subscription sender side. block_subscription_tx: tokio::sync::broadcast::Sender, /// Log subscription sender side. log_subscription_tx: tokio::sync::broadcast::Sender, - /// Whether archive mode is enabled - is_archive: bool, /// Whether historic backfill has completed. `false` if not started or in progress. backfill_complete: Arc, } @@ -271,105 +250,42 @@ fn known_first_evm_block_for_chain(chain_id: u64) -> Option { } } -/// Fetch the chain ID from the substrate chain. -async fn chain_id(api: &OnlineClient) -> Result { - let query = subxt_client::constants().revive().chain_id(); - api.constants().at(&query).map_err(|err| err.into()) -} - -/// Fetch the max block weight from the substrate chain. -async fn max_block_weight(api: &OnlineClient) -> Result { - let query = subxt_client::constants().system().block_weights(); - let weights = api.constants().at(&query)?; - let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block); - Ok(max_block.0) -} - -/// Get the automine status from the node. -async fn get_automine(rpc_client: &RpcClient) -> bool { - match rpc_client.request::("getAutomine", rpc_params![]).await { - Ok(val) => val, - Err(err) => { - log::info!(target: LOG_TARGET, "Node does not have getAutomine RPC. Defaulting to automine=false. error: {err:?}"); - false - }, - } -} - -/// Connect to a node at the given URL, and return the underlying API, RPC client, and legacy RPC -/// clients. -pub async fn connect( - node_rpc_url: &str, - max_request_size: u32, - max_response_size: u32, -) -> Result<(OnlineClient, RpcClient, LegacyRpcMethods), ClientError> -{ - log::info!(target: LOG_TARGET, "🌐 Connecting to node at: {node_rpc_url} ..."); - let rpc_client = ReconnectingRpcClient::builder() - .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) - .max_request_size(max_request_size) - .max_response_size(max_response_size) - .build(node_rpc_url.to_string()) - .await?; - let rpc_client = RpcClient::new(rpc_client); - log::info!(target: LOG_TARGET, "🌟 Connected to node at: {node_rpc_url}"); - - let api = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; - let rpc = LegacyRpcMethods::::new(rpc_client.clone()); - Ok((api, rpc_client, rpc)) -} - -impl Client { - /// Create a new client instance. - pub async fn new( - api: OnlineClient, - rpc_client: RpcClient, - rpc: LegacyRpcMethods, - block_provider: SubxtBlockInfoProvider, - receipt_provider: ReceiptProvider, - is_archive: bool, +impl Client { + /// Create a `Client` from an arbitrary backend and block-info-provider. + pub fn from_backend( + backend: C, + block_provider: BP, + receipt_provider: ReceiptProvider, + automine: bool, ) -> Result { - let (chain_id, max_block_weight, automine) = - tokio::try_join!(chain_id(&api), max_block_weight(&api), async { - Ok(get_automine(&rpc_client).await) - },)?; - - // Fall back to 0 when the hardcoded value exceeds the current best block (e.g. zombienet - // reusing a known chain ID) and backward sync is disabled. - if !is_archive { - if let Some(known) = known_first_evm_block_for_chain(chain_id) { - let best = block_provider.latest_block_number().await; - if known > best { - log::debug!( - target: LOG_TARGET, - "Hardcoded first EVM block {known} exceeds best block {best} \ - for chain {chain_id}, defaulting to 0" - ); - receipt_provider.set_first_evm_block(0).await?; - } - } - } + let block_notifier = + automine.then(|| tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY).0); - let client = Self { - api, - rpc_client, - rpc, - receipt_provider, + let (block_subscription_tx, _) = + tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY); + let (log_subscription_tx, _) = tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY); + + Ok(Self { + backend, block_provider, + receipt_provider, fee_history_provider: FeeHistoryProvider::default(), - chain_id, - max_block_weight, automine, - block_notifier: automine - .then(|| tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY).0), + block_notifier, subscription_lock: Arc::new(Mutex::new(())), - block_subscription_tx: tokio::sync::broadcast::channel(256).0, - log_subscription_tx: tokio::sync::broadcast::channel(1000).0, - is_archive, backfill_complete: Arc::new(AtomicBool::new(false)), - }; + block_subscription_tx, + log_subscription_tx, + }) + } - Ok(client) + pub fn from_native_backend( + backend: C, + block_provider: BP, + receipt_provider: ReceiptProvider, + automine: bool, + ) -> Result { + Self::from_backend(backend, block_provider, receipt_provider, automine) } /// Mark historic backfill as complete. @@ -377,9 +293,14 @@ impl Client { self.backfill_complete.store(true, Ordering::Release); } - /// Whether archive sync is active and backfill has completed. - fn should_advance_head(&self) -> bool { - self.is_archive && self.backfill_complete.load(Ordering::Acquire) + /// Expose the receipt provider (used by block_sync). + pub(crate) fn receipt_provider(&self) -> &ReceiptProvider { + &self.receipt_provider + } + + /// Expose the block provider (used by block_sync). + pub(crate) fn block_provider(&self) -> &BP { + &self.block_provider } /// Creates a block notifier instance. @@ -387,21 +308,62 @@ impl Client { self.block_notifier = Some(tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY).0); } - /// Sets a block notifier + /// Sets a block notifier. pub fn set_block_notifier(&mut self, notifier: Option>) { self.block_notifier = notifier; } - pub(crate) fn api(&self) -> &OnlineClient { - &self.api + /// Create a receiver for the block broadcast channel. + pub fn get_block_subscription_rx(&self) -> tokio::sync::broadcast::Receiver { + self.block_subscription_tx.subscribe() } - pub(crate) fn receipt_provider(&self) -> &ReceiptProvider { - &self.receipt_provider + /// Create a receiver for the log broadcast channel. + pub fn get_log_subscription_rx(&self) -> tokio::sync::broadcast::Receiver { + self.log_subscription_tx.subscribe() } - pub(crate) fn block_provider(&self) -> &SubxtBlockInfoProvider { - &self.block_provider + /// Subscribe to past blocks, executing the callback for each block in `range`. + async fn subscribe_past_blocks( + &self, + range: Range, + callback: F, + ) -> Result<(), ClientError> + where + F: Fn(Arc) -> Fut + Send + Sync, + Fut: std::future::Future> + Send, + { + let mut block = self + .block_provider + .block_by_number(range.end) + .await? + .ok_or(ClientError::BlockNotFound)?; + + loop { + let block_number = block.number(); + log::trace!( + target: "eth-rpc::subscription", + "Processing past block #{block_number}" + ); + + let parent_hash = block.parent_hash(); + callback(block.clone()).await.inspect_err(|err| { + log::error!( + target: "eth-rpc::subscription", + "Failed to process past block #{block_number}: {err:?}" + ); + })?; + + if range.start < block_number { + block = self + .block_provider + .block_by_hash(&parent_hash) + .await? + .ok_or(ClientError::BlockNotFound)?; + } else { + return Ok(()); + } + } } /// The earliest block number where the ReviveApi is available. @@ -409,7 +371,7 @@ impl Client { fn earliest_block_number(&self) -> u32 { self.receipt_provider .first_evm_block() - .or_else(|| known_first_evm_block_for_chain(self.chain_id)) + .or_else(|| known_first_evm_block_for_chain(self.chain_id())) .unwrap_or(0) } @@ -420,48 +382,30 @@ impl Client { callback: F, ) -> Result<(), ClientError> where - F: Fn(SubstrateBlock) -> Fut + Send + Sync, + F: Fn(Arc) -> Fut + Send + Sync + 'static, Fut: std::future::Future> + Send, { - let mut block_stream = match subscription_type { - SubscriptionType::BestBlocks => self.api.blocks().subscribe_best().await, - SubscriptionType::FinalizedBlocks => self.api.blocks().subscribe_finalized().await, - } - .inspect_err(|err| { - log::error!(target: LOG_TARGET, "Failed to subscribe to blocks: {err:?}"); - })?; - - while let Some(block) = block_stream.next().await { - let block = match block { - Ok(block) => block, - Err(err) => { - if err.is_disconnected_will_reconnect() { - log::warn!( - target: LOG_TARGET, - "The RPC connection was lost and we may have missed a few blocks ({subscription_type:?}): {err:?}" - ); - continue; - } - - log::error!(target: LOG_TARGET, "Failed to fetch block ({subscription_type:?}): {err:?}"); - return Err(err.into()); - }, - }; - - // Acquire lock to ensure only one subscription can perform write operations at a time - let _guard = self.subscription_lock.lock().await; - - let block_number = block.number(); - log::trace!(target: LOG_TARGET_SUBSCRIPTION, "⏳ Processing {subscription_type:?} block: {block_number}"); - if let Err(err) = callback(block).await { - log::error!(target: LOG_TARGET, "Failed to process block {block_number}: {err:?}"); - } else { - log::trace!(target: LOG_TARGET_SUBSCRIPTION, "✅ Processed {subscription_type:?} block: {block_number}"); - } - } - - log::info!(target: LOG_TARGET, "Block subscription ended"); - Ok(()) + let callback = Arc::new(callback); + let lock = self.subscription_lock.clone(); + let block_provider = self.block_provider.clone(); + + self.backend + .subscribe_blocks(subscription_type, move |info: C::BlockInfo| { + let callback = Arc::clone(&callback); + let lock_ref = Arc::clone(&lock); + let block_provider = block_provider.clone(); + + async move { + let block = block_provider + .block_by_hash(&info.hash()) + .await? + .ok_or(ClientError::BlockNotFound)?; + + let _guard = lock_ref.lock().await; + callback(block).await + } + }) + .await } /// Start the block subscription, and populate the block cache. @@ -469,65 +413,95 @@ impl Client { &self, subscription_type: SubscriptionType, ) -> Result<(), ClientError> { - log::info!(target: LOG_TARGET, "🔌 Subscribing to new blocks ({subscription_type:?})"); - self.subscribe_new_blocks(subscription_type, |block| async { - let hash = block.hash(); - let block_number = block.number(); - let evm_block = self.runtime_api(hash).eth_block().await?; + log::info!( + target: LOG_TARGET, + "🔌 Subscribing to new blocks ({subscription_type:?})" + ); + + let backend = self.backend.clone(); + let receipt_provider = self.receipt_provider.clone(); + let block_provider_update = self.block_provider.clone(); + let fee_history_provider = self.fee_history_provider.clone(); + let block_notifier = self.block_notifier.clone(); + // ↓ NEW: capture the broadcast senders + let block_subscription_tx = self.block_subscription_tx.clone(); + let log_subscription_tx = self.log_subscription_tx.clone(); + + self.subscribe_new_blocks(subscription_type, move |block: Arc| { + let backend = backend.clone(); + let receipt_provider = receipt_provider.clone(); + let block_provider_update = block_provider_update.clone(); + let fee_history_provider = fee_history_provider.clone(); + let block_notifier = block_notifier.clone(); + let block_subscription_tx = block_subscription_tx.clone(); + let log_subscription_tx = log_subscription_tx.clone(); + + async move { + let hash = block.hash(); + let number = block.number(); + let evm_block = backend.eth_block(hash).await?; + + let (_, receipts): (Vec<_>, Vec<_>) = receipt_provider + .insert_block_receipts_by_hash(hash, number, &evm_block.hash) + .await? + .into_iter() + .unzip(); - let (_, receipts): (Vec<_>, Vec<_>) = self - .receipt_provider - .insert_block_receipts(&block, &evm_block.hash) - .await? - .into_iter() - .unzip(); + block_provider_update.update_latest(Arc::clone(&block), subscription_type).await; + fee_history_provider.update_fee_history(&evm_block, &receipts).await; - self.block_provider.update_latest(Arc::new(block), subscription_type).await; - self.fee_history_provider.update_fee_history(&evm_block, &receipts).await; + // Broadcast new block to eth_subscribe("newHeads") subscribers. + if subscription_type == SubscriptionType::BestBlocks && + block_subscription_tx.receiver_count() > 0 + { + let _ = block_subscription_tx.send(evm_block.clone()); + } - match (subscription_type, &self.block_notifier) { - (SubscriptionType::FinalizedBlocks, _) if self.should_advance_head() => { - // Track finalized block in sync_state - if let Err(err) = self - .receipt_provider - .advance_sync_label( - SyncLabel::Head, - SyncCheckpoint::new(block_number, hash), - ) - .await - { - log::warn!(target: LOG_TARGET, - "Failed to update sync_label[{}]: {err:?}", - SyncLabel::Head); + // Broadcast logs to eth_subscribe("logs") subscribers. + if log_subscription_tx.receiver_count() > 0 { + for receipt in &receipts { + for log in &receipt.logs { + let _ = log_subscription_tx.send(log.clone()); + } } - }, - // Only broadcast for best blocks to avoid duplicate notifications. - (SubscriptionType::BestBlocks, Some(sender)) if sender.receiver_count() > 0 => { - let _ = sender.send(hash); - }, - _ => {}, - } - - // Broadcast the best blocks - if let SubscriptionType::BestBlocks = subscription_type && - self.block_subscription_tx.receiver_count() > 0 - { - let _ = self.block_subscription_tx.send(evm_block); - } + } - // Broadcast the logs, we require a finalized subscription for this so that all of the - // events we broadcast are finalized and not prone to reorgs. - if let SubscriptionType::FinalizedBlocks = subscription_type && - self.log_subscription_tx.receiver_count() > 0 - { - receipts.iter().flat_map(|receipt| receipt.logs.iter()).for_each(|log| { - let _ = self.log_subscription_tx.send(log.clone()); - }); + match (subscription_type, &block_notifier) { + (SubscriptionType::BestBlocks, Some(sender)) if sender.receiver_count() > 0 => { + let _ = sender.send(hash); + }, + _ => {}, + } + Ok(()) } + }) + .await + } + /// Cache old blocks up to the given block number. + pub async fn subscribe_and_cache_blocks( + &self, + index_last_n_blocks: SubstrateBlockNumber, + ) -> Result<(), ClientError> { + let last = self.latest_block().await.number().saturating_sub(1); + let range = last.saturating_sub(index_last_n_blocks)..last; + log::info!(target: LOG_TARGET, "🗄️ Indexing past blocks in range {range:?}"); + + self.subscribe_past_blocks(range, |block: Arc| async move { + let ethereum_hash = self + .backend + .eth_block_hash(block.hash(), pallet_revive::evm::U256::from(block.number())) + .await? + .ok_or(ClientError::EthereumBlockNotFound)?; + self.receipt_provider + .insert_block_receipts_by_hash(block.hash(), block.number(), ðereum_hash) + .await?; Ok(()) }) - .await + .await?; + + log::info!(target: LOG_TARGET, "🗄️ Finished indexing past blocks"); + Ok(()) } /// Get the block hash for the given block number or tag. @@ -542,110 +516,97 @@ impl Client { .ok_or(ClientError::EthereumBlockNotFound), BlockNumberOrTagOrHash::BlockNumber(block_number) => { let n: SubstrateBlockNumber = - (block_number).try_into().map_err(|_| ClientError::ConversionFailed)?; + block_number.try_into().map_err(|_| ClientError::ConversionFailed)?; let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; Ok(hash) }, BlockNumberOrTagOrHash::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { - let block = self.latest_finalized_block().await; - Ok(block.hash()) - }, - BlockNumberOrTagOrHash::BlockTag(BlockTag::Earliest) => { - let hash = self - .get_block_hash(self.earliest_block_number()) - .await? - .ok_or(ClientError::BlockNotFound)?; - Ok(hash) - }, - BlockNumberOrTagOrHash::BlockTag(_) => { - let block = self.latest_block().await; - Ok(block.hash()) + Ok(self.latest_finalized_block().await.hash()) }, + BlockNumberOrTagOrHash::BlockTag(_) => Ok(self.latest_block().await.hash()), } } - /// Get the storage API for the given block. - pub fn storage_api(&self, block_hash: H256) -> StorageApi { - StorageApi::new(self.api.storage().at(block_hash)) - } - - /// Get the runtime API for the given block. - pub fn runtime_api(&self, block_hash: H256) -> RuntimeApi { - RuntimeApi::new(self.api.runtime_api().at(block_hash)) - } - /// Get the latest finalized block. - pub async fn latest_finalized_block(&self) -> Arc { + pub async fn latest_finalized_block(&self) -> Arc { self.block_provider.latest_finalized_block().await } /// Get the latest best block. - pub async fn latest_block(&self) -> Arc { + pub async fn latest_block(&self) -> Arc { self.block_provider.latest_block().await } - /// Submit an ethereum transaction and return a stream of transaction status updates. - async fn submit_transaction( + /// Get the block number of the latest block. + pub async fn block_number(&self) -> Result { + Ok(self.block_provider.latest_block().await.number()) + } + + /// Get a block hash for the given block number. + pub async fn get_block_hash( + &self, + block_number: SubstrateBlockNumber, + ) -> Result, ClientError> { + Ok(self.block_provider.block_by_number(block_number).await?.map(|b| b.hash())) + } + + /// Get a block for the specified hash or number. + pub async fn block_by_number_or_tag( + &self, + block: &BlockNumberOrTag, + ) -> Result>, ClientError> { + match block { + BlockNumberOrTag::U256(n) => { + let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?; + self.block_by_number(n).await + }, + BlockNumberOrTag::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { + Ok(Some(self.block_provider.latest_finalized_block().await)) + }, + BlockNumberOrTag::BlockTag(_) => Ok(Some(self.block_provider.latest_block().await)), + } + } + + /// Get a block by hash. + pub async fn block_by_hash( &self, - call: subxt::tx::DefaultPayload, - ) -> Result>, ClientError> { - let ext = self.api.tx().create_unsigned(&call).map_err(ClientError::from)?; + hash: &SubstrateBlockHash, + ) -> Result>, ClientError> { + self.block_provider.block_by_hash(hash).await + } - let sub = self - .rpc_client - .subscribe( - "author_submitAndWatchExtrinsic", - rpc_params![to_hex(ext.encoded())], - "author_unwatchExtrinsic", - ) - .await?; + /// Resolve Ethereum block hash to Substrate block hash. + pub async fn resolve_substrate_hash(&self, ethereum_hash: &H256) -> Option { + self.receipt_provider.get_substrate_hash(ethereum_hash).await + } - let sub = sub.map_err(|e| e.into()); - Ok(StreamOf::new(Box::pin(sub))) + /// Resolve Substrate block hash to Ethereum block hash. + pub async fn resolve_ethereum_hash(&self, substrate_hash: &H256) -> Option { + self.receipt_provider.get_ethereum_hash(substrate_hash).await } - /// Expose the transaction API. - pub async fn submit( + /// Get a block by Ethereum hash, falling back to treating it as a Substrate hash. + pub async fn block_by_ethereum_hash( &self, - call: subxt::tx::DefaultPayload, - ) -> Result, ClientError> { - let mut progress = self.submit_transaction(call).await.inspect_err(|err| { - log::debug!(target: LOG_TARGET, "Failed to submit transaction: {err:?}"); - })?; - - tokio::time::timeout(Duration::from_secs(5), async { - if let Some(status) = progress.next().await { - match status { - Ok( - tx @ (TransactionStatus::Future | - TransactionStatus::Ready | - // Add other events that follow Ready here for completeness, - // but they can be ignored. - TransactionStatus::Broadcast(_) | - TransactionStatus::InBlock(_) | - TransactionStatus::FinalityTimeout(_) | - TransactionStatus::Retracted(_) | - TransactionStatus::Finalized(_)), - ) => { - return Ok(tx); - }, - Ok( - tx @ (TransactionStatus::Usurped(_) | - TransactionStatus::Dropped | - TransactionStatus::Invalid), - ) => { - return Err(ClientError::SubmitError(tx.into())); - }, - Err(err) => { - log::debug!(target: LOG_TARGET, "Transaction submission failed: {err:?}"); - return Err(ClientError::from(err)); - }, - } - } - return Err(ClientError::SubmitError(SubmitError::StreamEnded)); - }) - .await - .map_err(|_| ClientError::Timeout)? + ethereum_hash: &H256, + ) -> Result>, ClientError> { + if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await { + return self.block_by_hash(&substrate_hash).await; + } + self.block_by_hash(ethereum_hash).await + } + + /// Get a block by number. + pub async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result>, ClientError> { + self.block_provider.block_by_number(block_number).await + } + + /// Submit an ethereum transaction payload. + pub async fn submit(&self, payload: Vec) -> Result { + self.backend.submit_extrinsic(payload).await } /// Get an EVM transaction receipt by hash. @@ -653,30 +614,35 @@ impl Client { self.receipt_provider.receipt_by_hash(tx_hash).await } - /// Get The post dispatch weight associated with this Ethereum transaction hash. - pub async fn post_dispatch_weight(&self, tx_hash: &H256) -> Option { - use crate::subxt_client::system::events::ExtrinsicSuccess; - let ReceiptInfo { block_hash, transaction_index, .. } = self.receipt(tx_hash).await?; - let block_hash = self.resolve_substrate_hash(&block_hash).await?; - let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; - let ext = block.extrinsics().await.ok()?.iter().nth(transaction_index.as_u32() as _)?; - let event = ext.events().await.ok()?.find_first::().ok()??; - Some(event.dispatch_info.weight.0) + /// Get an EVM transaction receipt by block hash and index. + pub async fn receipt_by_hash_and_index( + &self, + block_hash: &H256, + transaction_index: usize, + ) -> Option { + self.receipt_provider + .receipt_by_block_hash_and_index(block_hash, transaction_index) + .await + } + + pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option { + self.receipt_provider.signed_tx_by_hash(tx_hash).await + } + + /// Get receipts count per block. + pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option { + self.receipt_provider.receipts_count_per_block(block_hash).await } pub async fn sync_state( &self, ) -> Result, ClientError> { - let client = self.rpc_client.clone(); - let sync_state: sc_rpc::system::SyncState = - client.request("system_syncState", Default::default()).await?; - Ok(sync_state) + self.backend.sync_state().await } /// Get the syncing status of the chain. pub async fn syncing(&self) -> Result { - let health = self.rpc.system_health().await?; - + let health = self.backend.system_health().await?; let status = if health.is_syncing { let sync_state = self.sync_state().await?; SyncingProgress { @@ -692,126 +658,173 @@ impl Client { Ok(status) } - /// Get an EVM transaction receipt by hash. - pub async fn receipt_by_hash_and_index( - &self, - block_hash: &H256, - transaction_index: usize, - ) -> Option { - self.receipt_provider - .receipt_by_block_hash_and_index(block_hash, transaction_index) - .await + /// Get the system health. + pub async fn system_health(&self) -> Result { + self.backend.system_health().await } - pub async fn signed_tx_by_hash(&self, tx_hash: &H256) -> Option { - self.receipt_provider.signed_tx_by_hash(tx_hash).await + /// Get the chain ID. + pub fn chain_id(&self) -> u64 { + self.backend.chain_id() } - /// Get receipts count per block. - pub async fn receipts_count_per_block(&self, block_hash: &SubstrateBlockHash) -> Option { - self.receipt_provider.receipts_count_per_block(block_hash).await + /// Get the max block weight. + pub fn max_block_weight(&self) -> Weight { + self.backend.max_block_weight() } - /// Get an EVM transaction receipt by specified Ethereum block hash. - pub async fn receipt_by_ethereum_hash_and_index( - &self, - ethereum_hash: &H256, - transaction_index: usize, - ) -> Option { - // Fallback: use hash as Substrate hash if Ethereum hash cannot be resolved - let substrate_hash = - self.resolve_substrate_hash(ethereum_hash).await.unwrap_or(*ethereum_hash); - self.receipt_by_hash_and_index(&substrate_hash, transaction_index).await + /// Get the block notifier. + pub fn block_notifier(&self) -> Option> { + self.block_notifier.clone() } - /// Get the system health. - pub async fn system_health(&self) -> Result { - let health = self.rpc.system_health().await?; - Ok(health) + /// Check if automine is enabled. + pub fn is_automine(&self) -> bool { + self.automine } - /// Get the block number of the latest block. - pub async fn block_number(&self) -> Result { - let latest_block = self.block_provider.latest_block().await; - Ok(latest_block.number()) + /// Get the automine status from the node. + pub async fn get_automine(&self) -> bool { + self.backend.get_automine().await } - /// Get a block hash for the given block number. - pub async fn get_block_hash( + /// Get the EVM block for the given block. + pub async fn evm_block( &self, - block_number: SubstrateBlockNumber, - ) -> Result, ClientError> { - let maybe_block = self.block_provider.block_by_number(block_number).await?; - Ok(maybe_block.map(|block| block.hash())) - } + block: Arc, + hydrated_transactions: bool, + ) -> Option { + log::trace!( + target: LOG_TARGET, + "Get Ethereum block for hash {:?}", + block.hash() + ); + match self.backend.eth_block(block.hash()).await { + Ok(mut eth_block) => { + log::trace!( + target: LOG_TARGET, + "Ethereum block from runtime API hash {:?}", + eth_block.hash + ); - /// Get a block for the specified hash or number. - pub async fn block_by_number_or_tag( - &self, - block: &BlockNumberOrTag, - ) -> Result>, ClientError> { - match block { - BlockNumberOrTag::U256(n) => { - let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?; - self.block_by_number(n).await - }, - BlockNumberOrTag::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { - let block = self.block_provider.latest_finalized_block().await; - Ok(Some(block)) - }, - BlockNumberOrTag::BlockTag(BlockTag::Earliest) => { - self.block_by_number(self.earliest_block_number()).await + if hydrated_transactions { + // Hydrate the block. + let tx_infos = self + .receipt_provider + .receipts_from_block_by_hash(block.hash(), block.number()) + .await + .unwrap_or_default() + .into_iter() + .map(|(signed_tx, receipt)| TransactionInfo::new(&receipt, signed_tx)) + .collect::>(); + + eth_block.transactions = HashesOrTransactionInfos::TransactionInfos(tx_infos); + } + + Some(eth_block) }, - BlockNumberOrTag::BlockTag(_) => { - let block = self.block_provider.latest_block().await; - Ok(Some(block)) + Err(err) => { + log::error!( + target: LOG_TARGET, + "Failed to get Ethereum block for hash {:?}: {err:?}", + block.hash() + ); + None }, } } - /// Get a block by hash - pub async fn block_by_hash( + /// Get the logs matching the given filter. + pub async fn logs(&self, filter: Option) -> Result, ClientError> { + let latest_block_number = self.block_number().await?; + + let resolve_block_number = move |block: BlockNumberOrTag| -> anyhow::Result { + match block { + BlockNumberOrTag::U256(v) => Ok(v), + BlockNumberOrTag::BlockTag(BlockTag::Earliest) => Ok(U256::zero()), + BlockNumberOrTag::BlockTag( + BlockTag::Latest | BlockTag::Pending | BlockTag::Safe | BlockTag::Finalized, + ) => Ok(U256::from(latest_block_number)), + } + }; + + self.receipt_provider + .logs(filter, resolve_block_number) + .await + .map_err(ClientError::LogFilterFailed) + } + + pub async fn fee_history( &self, - hash: &SubstrateBlockHash, - ) -> Result>, ClientError> { - self.block_provider.block_by_hash(hash).await + block_count: u32, + latest_block: BlockNumberOrTag, + reward_percentiles: Option>, + ) -> Result { + let Some(latest_block) = self.block_by_number_or_tag(&latest_block).await? else { + return Err(ClientError::BlockNotFound); + }; + + self.fee_history_provider + .fee_history(block_count, latest_block.number(), reward_percentiles) + .await } - /// Resolve Ethereum block hash to Substrate block hash, then get the block. - /// This method provides the abstraction layer needed by the RPC APIs. - pub async fn resolve_substrate_hash(&self, ethereum_hash: &H256) -> Option { - self.receipt_provider.get_substrate_hash(ethereum_hash).await + /// Get the gas price at the given block. + pub async fn gas_price_at( + &self, + block_hash: SubstrateBlockHash, + ) -> Result { + self.backend.gas_price(block_hash).await } - /// Resolve Substrate block hash to Ethereum block hash, then get the block. - /// This method provides the abstraction layer needed by the RPC APIs. - pub async fn resolve_ethereum_hash(&self, substrate_hash: &H256) -> Option { - self.receipt_provider.get_ethereum_hash(substrate_hash).await + /// Get the balance at the given block. + pub async fn balance_at( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result { + self.backend.balance(block_hash, address).await } - /// Get a block by Ethereum hash with automatic resolution to Substrate hash. - /// Falls back to treating the hash as a Substrate hash if no mapping exists. - pub async fn block_by_ethereum_hash( + /// Get the nonce at the given block. + pub async fn nonce_at( &self, - ethereum_hash: &H256, - ) -> Result>, ClientError> { - // First try to resolve the Ethereum hash to a Substrate hash - if let Some(substrate_hash) = self.resolve_substrate_hash(ethereum_hash).await { - return self.block_by_hash(&substrate_hash).await; - } + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result { + self.backend.nonce(block_hash, address).await + } - // Fallback: treat the provided hash as a Substrate hash (backward compatibility) - self.block_by_hash(ethereum_hash).await + /// Get the code at the given block. + pub async fn code_at( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result, ClientError> { + self.backend.code(block_hash, address).await } - /// Get a block by number - pub async fn block_by_number( + /// Get the storage at the given block. + pub async fn storage_at( &self, - block_number: SubstrateBlockNumber, - ) -> Result>, ClientError> { - self.block_provider.block_by_number(block_number).await + block_hash: SubstrateBlockHash, + address: sp_core::H160, + key: [u8; 32], + ) -> Result>, ClientError> { + self.backend.get_storage(block_hash, address, key).await + } + + /// Dry-run a transaction and return the estimated gas as `U256`. + pub async fn dry_run( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result { + self.backend.dry_run(block_hash, tx, block).await.map(|info| info.eth_gas) } + /// Fetch the raw signed block (used for tracing). async fn tracing_block( &self, block_hash: H256, @@ -822,16 +835,7 @@ impl Client { >, ClientError, > { - let signed_block: Option< - sp_runtime::generic::SignedBlock< - sp_runtime::generic::Block< - sp_runtime::generic::Header, - sp_runtime::OpaqueExtrinsic, - >, - >, - > = self.rpc_client.request("chain_getBlock", rpc_params![block_hash]).await?; - - Ok(signed_block.ok_or(ClientError::BlockNotFound)?.block) + self.backend.signed_block(block_hash).await } /// Get the transaction traces for the given block. @@ -846,13 +850,7 @@ impl Client { let block_hash = self.block_hash_for_tag(at.into()).await?; let block = self.tracing_block(block_hash).await?; - let parent_hash = block.header().parent_hash; - // Block 0 has no parent — there is nothing to trace. - if parent_hash == Default::default() { - return Ok(vec![]); - } - let runtime_api = RuntimeApi::new(self.api.runtime_api().at(parent_hash)); - let traces = runtime_api.trace_block(block, config.clone()).await?; + let traces = self.backend.trace_block(block_hash, block, config).await?; let mut hashes = self .receipt_provider @@ -880,13 +878,10 @@ impl Client { .ok_or(ClientError::EthExtrinsicNotFound)?; let block = self.tracing_block(block_hash).await?; - let parent_hash = block.header.parent_hash; - let runtime_api = self.runtime_api(parent_hash); - - runtime_api.trace_tx(block, transaction_index as u32, config).await + self.backend.trace_tx(block_hash, block, transaction_index as u32, config).await } - /// Get the transaction traces for the given block. + /// Dry-run a call and return its trace. pub async fn trace_call( &self, transaction: GenericTransaction, @@ -894,130 +889,23 @@ impl Client { config: TracerType, ) -> Result { let block_hash = self.block_hash_for_tag(block).await?; - let runtime_api = self.runtime_api(block_hash); - runtime_api.trace_call(transaction, config).await - } - - /// Get the EVM block for the given Substrate block. - pub async fn evm_block( - &self, - block: Arc, - hydrated_transactions: bool, - ) -> Option { - log::trace!(target: LOG_TARGET, "Get Ethereum block for hash {:?}", block.hash()); - - if self - .receipt_provider - .is_before_earliest_block(&BlockNumberOrTag::U256(U256::from(block.number()))) - { - log::trace!(target: LOG_TARGET, - "Block #{} is before receipt floor, skipping", block.number()); - return None; - } - - // This could potentially fail under below circumstances: - // - state has been pruned - // - the block author cannot be obtained from the digest logs (highly unlikely) - // - the node we are targeting has an outdated revive pallet (or ETH block functionality is - // disabled) - match self.runtime_api(block.hash()).eth_block().await { - Ok(mut eth_block) => { - log::trace!(target: LOG_TARGET, "Ethereum block from runtime API hash {:?}", eth_block.hash); - - if hydrated_transactions { - // Hydrate the block. - let tx_infos = self - .receipt_provider - .receipts_from_block(&block) - .await - .unwrap_or_default() - .into_iter() - .map(|(signed_tx, receipt)| TransactionInfo::new(&receipt, signed_tx)) - .collect::>(); - - eth_block.transactions = HashesOrTransactionInfos::TransactionInfos(tx_infos); - } - - Some(eth_block) - }, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to get Ethereum block for hash {:?}: {err:?}", block.hash()); - None - }, - } - } - - /// Get the chain ID. - pub fn chain_id(&self) -> u64 { - self.chain_id + self.backend.trace_call(block_hash, transaction, config).await } - /// Get the Max Block Weight. - pub fn max_block_weight(&self) -> Weight { - self.max_block_weight - } - - /// Get the block notifier, if automine is enabled or Self::create_block_notifier was called. - pub fn block_notifier(&self) -> Option> { - self.block_notifier.clone() - } - - /// Get the logs matching the given filter. - pub async fn logs(&self, filter: Option) -> Result, ClientError> { - let earliest = U256::from(self.earliest_block_number()); - let latest = U256::from(self.latest_block().await.number()); - let resolve_block_number = |block: BlockNumberOrTag| match block { - BlockNumberOrTag::U256(v) => Ok(v), - BlockNumberOrTag::BlockTag(BlockTag::Earliest) => Ok(earliest), - BlockNumberOrTag::BlockTag(BlockTag::Latest) => Ok(latest), - BlockNumberOrTag::BlockTag(tag) => anyhow::bail!("Unsupported tag: {tag:?}"), - }; - - let logs = self - .receipt_provider - .logs(filter, &resolve_block_number) - .await - .map_err(ClientError::LogFilterFailed)?; - - Ok(logs) - } - - pub async fn fee_history( - &self, - block_count: u32, - latest_block: BlockNumberOrTag, - reward_percentiles: Option>, - ) -> Result { - let Some(latest_block) = self.block_by_number_or_tag(&latest_block).await? else { - return Err(ClientError::BlockNotFound); - }; - - self.fee_history_provider - .fee_history(block_count, latest_block.number(), reward_percentiles) + /// Get the post-dispatch weight associated with this Ethereum transaction hash. + pub async fn post_dispatch_weight(&self, tx_hash: &H256) -> Option { + let receipt = self.receipt(tx_hash).await?; + let block_hash = self.resolve_substrate_hash(&receipt.block_hash).await?; + self.backend + .extrinsic_post_dispatch_weight(block_hash, receipt.transaction_index.as_usize()) .await } - - /// Check if automine is enabled. - pub fn is_automine(&self) -> bool { - self.automine - } - - /// Get the automine status from the node. - pub async fn get_automine(&self) -> bool { - get_automine(&self.rpc_client).await - } - - /// Gets the block subscription rx side of the channel. - pub fn get_block_subscription_rx(&self) -> tokio::sync::broadcast::Receiver { - self.block_subscription_tx.subscribe() - } - - /// Gets the log subscription rx side of the channel. - pub fn get_log_subscription_rx(&self) -> tokio::sync::broadcast::Receiver { - self.log_subscription_tx.subscribe() - } } -fn to_hex(bytes: impl AsRef<[u8]>) -> String { - format!("0x{}", hex::encode(bytes.as_ref())) +#[cfg(feature = "subxt")] +impl Client { + /// Expose the underlying subxt OnlineClient. + pub fn api(&self) -> &subxt::OnlineClient { + &self.backend.api + } } diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 9a727661d91ee..825332459f865 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "subxt")] use crate::{ ClientError, client::Balance, @@ -30,18 +31,22 @@ use pallet_revive::{ }; use sp_core::H256; use sp_timestamp::Timestamp; -use subxt::{Error::Metadata, OnlineClient, error::MetadataError, ext::subxt_rpcs::UserError}; +#[cfg(feature = "subxt")] +use subxt::{Error::Metadata, error::MetadataError, ext::subxt_rpcs::UserError}; const LOG_TARGET: &str = "eth-rpc::runtime_api"; -/// A Wrapper around subxt Runtime API -#[derive(Clone)] -pub struct RuntimeApi(subxt::runtime_api::RuntimeApi>); +/// A wrapper around the subxt Runtime API. +#[cfg(feature = "subxt")] +pub struct RuntimeApi( + subxt::runtime_api::RuntimeApi>, +); +#[cfg(feature = "subxt")] impl RuntimeApi { /// Create a new instance. pub fn new( - api: subxt::runtime_api::RuntimeApi>, + api: subxt::runtime_api::RuntimeApi>, ) -> Self { Self(api) } @@ -66,8 +71,8 @@ impl RuntimeApi { Ok(result) } - /// Estimates the minimum gas limit required for the transaction execution. Returns a [`U256`] - /// of the gas limit. + /// Estimates the minimum gas limit required for the transaction execution. + #[allow(dead_code)] pub async fn estimate_gas( &self, tx: GenericTransaction, @@ -199,14 +204,16 @@ impl RuntimeApi { Ok(*gas_price) } - /// Convert a weight to a fee. + /// Get the block gas limit. + #[allow(dead_code)] pub async fn block_gas_limit(&self) -> Result { let payload = subxt_client::apis().revive_api().block_gas_limit(); let gas_limit = self.0.call(payload).await?; Ok(*gas_limit) } - /// Get the miner address + /// Get the block author address. + #[allow(dead_code)] pub async fn block_author(&self) -> Result { let payload = subxt_client::apis().revive_api().block_author(); let author = self.0.call(payload).await?; diff --git a/substrate/frame/revive/rpc/src/client/storage_api.rs b/substrate/frame/revive/rpc/src/client/storage_api.rs index e9e3a8d2b7073..02cc7c21ac76d 100644 --- a/substrate/frame/revive/rpc/src/client/storage_api.rs +++ b/substrate/frame/revive/rpc/src/client/storage_api.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "subxt")] use crate::{ ClientError, H160, subxt_client::{ @@ -22,19 +23,25 @@ use crate::{ runtime_types::pallet_revive::storage::{AccountType, ContractInfo}, }, }; +#[cfg(feature = "subxt")] use subxt::{OnlineClient, storage::Storage}; /// A wrapper around the Substrate Storage API. +#[cfg(feature = "subxt")] +#[allow(dead_code)] #[derive(Clone)] pub struct StorageApi(Storage>); +#[cfg(feature = "subxt")] impl StorageApi { /// Create a new instance of the StorageApi. + #[allow(dead_code)] pub fn new(api: Storage>) -> Self { Self(api) } /// Get the contract info for the given contract address. + #[allow(dead_code)] pub async fn get_contract_info( &self, contract_address: &H160, @@ -55,6 +62,7 @@ impl StorageApi { } /// Get the contract trie id for the given contract address. + #[allow(dead_code)] pub async fn get_contract_trie_id(&self, address: &H160) -> Result, ClientError> { let ContractInfo { trie_id, .. } = self.get_contract_info(address).await?; Ok(trie_id.0) diff --git a/substrate/frame/revive/rpc/src/example.rs b/substrate/frame/revive/rpc/src/example.rs index ba15c0959c67b..ae8bb67abe6ce 100644 --- a/substrate/frame/revive/rpc/src/example.rs +++ b/substrate/frame/revive/rpc/src/example.rs @@ -277,6 +277,7 @@ impl TransactionBuilder { } } +#[cfg(feature = "subxt")] #[test] fn test_dummy_payload_has_correct_len() { let signer = Account::from(subxt_signer::eth::dev::ethan()); diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 7af2e504c5d61..040fefe2fde5a 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -26,19 +26,22 @@ use jsonrpsee::{ }; use pallet_revive::evm::*; use sp_core::{H160, H256, U256, keccak_256}; -use subxt::backend::legacy::rpc_methods::TransactionStatus; -use subxt_signer::bip39::core::pin::Pin; +use std::pin::Pin; use thiserror::Error; use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError}; -mod block_sync; -pub(crate) use block_sync::{ChainMetadata, SyncLabel, SyncStateKey}; +pub mod block_sync; pub mod cli; pub mod client; pub mod example; +pub mod native_block_info_provider; +pub mod native_client; +pub mod substrate_client; + +#[cfg(feature = "subxt")] pub mod subxt_client; -#[cfg(test)] +#[cfg(all(test, feature = "subxt"))] mod tests; mod block_info_provider; @@ -51,17 +54,24 @@ mod fee_history_provider; pub use fee_history_provider::*; mod receipt_extractor; -pub use receipt_extractor::*; +pub use receipt_extractor::ReceiptExtractor; mod apis; pub use apis::*; +pub use native_block_info_provider::NativeClientBlockInfoProvider; +pub use native_client::NativeSubstrateClient; +pub use substrate_client::SubstrateClientT; + +#[cfg(feature = "subxt")] +pub use subxt_client::SubxtClient; + pub const LOG_TARGET: &str = "eth-rpc"; /// An EVM RPC server implementation. -pub struct EthRpcServerImpl { +pub struct EthRpcServerImpl { /// The client used to interact with the substrate node. - client: client::Client, + client: client::Client, /// The accounts managed by the server. accounts: Vec, @@ -73,9 +83,9 @@ pub struct EthRpcServerImpl { use_pending_for_estimate_gas: bool, } -impl EthRpcServerImpl { +impl EthRpcServerImpl { /// Creates a new [`EthRpcServerImpl`]. - pub fn new(client: client::Client) -> Self { + pub fn new(client: client::Client) -> Self { Self { client, accounts: vec![], @@ -140,15 +150,14 @@ impl From for ErrorObjectOwned { } #[async_trait] -impl EthRpcServer for EthRpcServerImpl { +impl EthRpcServer for EthRpcServerImpl { async fn net_version(&self) -> RpcResult { Ok(self.client.chain_id().to_string()) } async fn net_listening(&self) -> RpcResult { let syncing = self.client.syncing().await?; - let listening = matches!(syncing, SyncingStatus::Bool(false)); - Ok(listening) + Ok(matches!(syncing, SyncingStatus::Bool(false))) } async fn syncing(&self) -> RpcResult { @@ -156,16 +165,14 @@ impl EthRpcServer for EthRpcServerImpl { } async fn block_number(&self) -> RpcResult { - let number = self.client.block_number().await?; - Ok(number.into()) + Ok(self.client.block_number().await?.into()) } async fn get_transaction_receipt( &self, transaction_hash: H256, ) -> RpcResult> { - let receipt = self.client.receipt(&transaction_hash).await; - Ok(receipt) + Ok(self.client.receipt(&transaction_hash).await) } /// Performs gas estimations to find the lowest gas limit required to run the transaction. @@ -177,7 +184,10 @@ impl EthRpcServer for EthRpcServerImpl { transaction: GenericTransaction, block: Option, ) -> RpcResult { - log::trace!(target: LOG_TARGET, "estimate_gas transaction={transaction:?} block={block:?}"); + log::trace!( + target: LOG_TARGET, + "estimate_gas transaction={transaction:?} block={block:?}" + ); let block = block.unwrap_or_else(|| { if self.use_pending_for_estimate_gas { @@ -186,15 +196,17 @@ impl EthRpcServer for EthRpcServerImpl { Default::default() } }); - let hash = self.client.block_hash_for_tag(block.into()).await?; - let gas_estimate = - self.client.runtime_api(hash).estimate_gas(transaction, block.into()).await?; + let block_tag_or_hash: BlockNumberOrTagOrHash = block.into(); + let hash = self.client.block_hash_for_tag(block_tag_or_hash.clone()).await?; - log::trace!( - target: LOG_TARGET, - "estimate_gas result={gas_estimate:?}", - ); - Ok(gas_estimate) + let eth_gas = self + .client + .dry_run(hash, transaction, block_tag_or_hash) + .await + .map_err(ClientError::from)?; + + log::trace!(target: LOG_TARGET, "estimate_gas result={eth_gas:?}"); + Ok(eth_gas) } async fn call( @@ -204,19 +216,30 @@ impl EthRpcServer for EthRpcServerImpl { ) -> RpcResult { let block = block.unwrap_or_default(); let hash = self.client.block_hash_for_tag(block.clone()).await?; - let runtime_api = self.client.runtime_api(hash); - let dry_run = runtime_api.dry_run(transaction, block).await?; - Ok(dry_run.data.into()) + + let info = self + .client + .backend + .dry_run(hash, transaction, block) + .await + .map_err(ClientError::from)?; + Ok(info.data.into()) } async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult { let hash = H256(keccak_256(&transaction.0)); - log::trace!(target: LOG_TARGET, "send_raw_transaction transaction: {transaction:?} ethereum_hash: {hash:?}"); + log::trace!( + target: LOG_TARGET, + "send_raw_transaction transaction: {transaction:?} ethereum_hash: {hash:?}" + ); if !self.allow_unprotected_txs { - let signed_transaction = TransactionSigned::decode(transaction.0.as_slice()) - .map_err(|err| { - log::trace!(target: LOG_TARGET, "Transaction decoding failed. ethereum_hash: {hash:?}, error: {err:?}"); + let signed_transaction = + TransactionSigned::decode(transaction.0.as_slice()).map_err(|err| { + log::trace!( + target: LOG_TARGET, + "Transaction decoding failed. ethereum_hash: {hash:?}, error: {err:?}" + ); EthRpcError::InvalidTransaction })?; @@ -239,23 +262,27 @@ impl EthRpcServer for EthRpcServerImpl { }; if !is_chain_id_provided { - log::trace!(target: LOG_TARGET, "Invalid Transaction: transaction doesn't include a chain-id. ethereum_hash: {hash:?}"); + log::trace!( + target: LOG_TARGET, + "Invalid Transaction: no chain-id. ethereum_hash: {hash:?}" + ); Err(EthRpcError::InvalidTransaction)?; } } - let call = subxt_client::tx().revive().eth_transact(transaction.0); - // Subscribe to new block only when automine is enabled. let receiver = self.client.block_notifier().map(|sender| sender.subscribe()); - // Submit the transaction - let tx_status = self.client.submit(call).await.map_err(|err| { - log::trace!(target: LOG_TARGET, "send_raw_transaction ethereum_hash: {hash:?} failed: {err:?}"); + // Submit the transaction. + let tx_status = self.client.submit(transaction.0.clone()).await.map_err(|err| { + log::trace!( + target: LOG_TARGET, + "send_raw_transaction ethereum_hash: {hash:?} failed: {err:?}" + ); err })?; - if matches!(tx_status, TransactionStatus::Future) { + if matches!(tx_status, sc_transaction_pool_api::TransactionStatus::Future) { return Ok(hash); } @@ -264,11 +291,17 @@ impl EthRpcServer for EthRpcServerImpl { loop { if let Ok(block_hash) = receiver.recv().await { let Ok(Some(block)) = self.client.block_by_hash(&block_hash).await else { - log::debug!(target: LOG_TARGET, "Could not find the block with the received hash: {hash:?}."); + log::debug!( + target: LOG_TARGET, + "Could not find block with hash: {hash:?}." + ); continue; }; let Some(evm_block) = self.client.evm_block(block, false).await else { - log::debug!(target: LOG_TARGET, "Failed to get the EVM block for substrate block with hash: {hash:?}"); + log::debug!( + target: LOG_TARGET, + "Failed to get EVM block for substrate block with hash: {hash:?}" + ); continue; }; if evm_block.transactions.contains_tx(hash) { @@ -327,15 +360,12 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_ethereum_hash(&block_hash).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await; - Ok(block) + Ok(self.client.evm_block(block, hydrated_transactions).await) } async fn get_balance(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult { let hash = self.client.block_hash_for_tag(block).await?; - let runtime_api = self.client.runtime_api(hash); - let balance = runtime_api.balance(address).await?; - Ok(balance) + Ok(self.client.balance_at(hash, address).await?) } async fn chain_id(&self) -> RpcResult { @@ -344,8 +374,7 @@ impl EthRpcServer for EthRpcServerImpl { async fn gas_price(&self) -> RpcResult { let hash = self.client.block_hash_for_tag(BlockTag::Latest.into()).await?; - let runtime_api = self.client.runtime_api(hash); - Ok(runtime_api.gas_price().await?) + Ok(self.client.gas_price_at(hash).await?) } async fn max_priority_fee_per_gas(&self) -> RpcResult { @@ -356,12 +385,11 @@ impl EthRpcServer for EthRpcServerImpl { async fn get_code(&self, address: H160, block: BlockNumberOrTagOrHash) -> RpcResult { let hash = self.client.block_hash_for_tag(block).await?; - let code = self.client.runtime_api(hash).code(address).await?; - Ok(code.into()) + Ok(self.client.code_at(hash, address).await?.into()) } async fn accounts(&self) -> RpcResult> { - Ok(self.accounts.iter().map(|account| account.address()).collect()) + Ok(self.accounts.iter().map(|a| a.address()).collect()) } async fn get_block_by_number( @@ -372,20 +400,15 @@ impl EthRpcServer for EthRpcServerImpl { let Some(block) = self.client.block_by_number_or_tag(&block_number).await? else { return Ok(None); }; - let block = self.client.evm_block(block, hydrated_transactions).await; - Ok(block) + Ok(self.client.evm_block(block, hydrated_transactions).await) } async fn get_block_transaction_count_by_hash( &self, block_hash: Option, ) -> RpcResult> { - let block_hash = if let Some(block_hash) = block_hash { - block_hash - } else { - self.client.latest_block().await.hash() - }; - + let block_hash = + if let Some(h) = block_hash { h } else { self.client.latest_block().await.hash() }; let Some(substrate_hash) = self.client.resolve_substrate_hash(&block_hash).await else { return Ok(None); }; @@ -411,8 +434,7 @@ impl EthRpcServer for EthRpcServerImpl { } async fn get_logs(&self, filter: Option) -> RpcResult { - let logs = self.client.logs(filter).await?; - Ok(FilterResults::Logs(logs)) + Ok(FilterResults::Logs(self.client.logs(filter).await?)) } async fn get_storage_at( @@ -422,17 +444,8 @@ impl EthRpcServer for EthRpcServerImpl { block: BlockNumberOrTagOrHash, ) -> RpcResult { let hash = self.client.block_hash_for_tag(block).await?; - let runtime_api = self.client.runtime_api(hash); - let bytes = match runtime_api.get_storage(address, storage_slot.to_big_endian()).await { - Ok(value) => value.unwrap_or([0u8; 32].into()), - // Per Ethereum spec, return zero for non-contract addresses. - Err(ClientError::ContractNotFound) => { - log::trace!(target: LOG_TARGET, "get_storage_at: ContractNotFound for {address:?}, returning zero"); - [0u8; 32].into() - }, - Err(err) => return Err(err.into()), - }; - Ok(bytes.into()) + let bytes = self.client.storage_at(hash, address, storage_slot.to_big_endian()).await?; + Ok(bytes.unwrap_or_else(|| [0u8; 32].to_vec()).into()) } async fn get_transaction_by_block_hash_and_index( @@ -469,11 +482,7 @@ impl EthRpcServer for EthRpcServerImpl { ) -> RpcResult> { let receipt = self.client.receipt(&transaction_hash).await; let signed_tx = self.client.signed_tx_by_hash(&transaction_hash).await; - if let (Some(receipt), Some(signed_tx)) = (receipt, signed_tx) { - return Ok(Some(TransactionInfo::new(&receipt, signed_tx))); - } - - Ok(None) + Ok(receipt.zip(signed_tx).map(|(r, tx)| TransactionInfo::new(&r, tx))) } async fn get_transaction_count( @@ -482,9 +491,7 @@ impl EthRpcServer for EthRpcServerImpl { block: BlockNumberOrTagOrHash, ) -> RpcResult { let hash = self.client.block_hash_for_tag(block).await?; - let runtime_api = self.client.runtime_api(hash); - let nonce = runtime_api.nonce(address).await?; - Ok(nonce) + Ok(self.client.nonce_at(hash, address).await?) } async fn web3_client_version(&self) -> RpcResult { @@ -501,8 +508,7 @@ impl EthRpcServer for EthRpcServerImpl { reward_percentiles: Option>, ) -> RpcResult { let block_count: u32 = block_count.try_into().map_err(|_| EthRpcError::ConversionError)?; - let result = self.client.fee_history(block_count, newest_block, reward_percentiles).await?; - Ok(result) + Ok(self.client.fee_history(block_count, newest_block, reward_percentiles).await?) } async fn eth_subscribe( @@ -541,7 +547,7 @@ impl EthRpcServer for EthRpcServerImpl { } } -impl EthRpcServerImpl { +impl EthRpcServerImpl { async fn get_transaction_by_substrate_block_hash_and_index( &self, substrate_block_hash: H256, diff --git a/substrate/frame/revive/rpc/src/native_block_info_provider.rs b/substrate/frame/revive/rpc/src/native_block_info_provider.rs new file mode 100644 index 0000000000000..f64a42b6ee65c --- /dev/null +++ b/substrate/frame/revive/rpc/src/native_block_info_provider.rs @@ -0,0 +1,213 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) 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. +//! A [`BlockInfoProvider`] implementation backed by the native in-process Substrate client. +//! +//! This avoids the need to open a `subxt` WebSocket connection when the ETH-RPC +//! server is embedded directly inside the node binary (e.g. Asset Hub). +use crate::{ + ClientError, + block_info_provider::{BlockInfo, BlockInfoProvider}, + client::{SubscriptionType, SubstrateBlockNumber}, + native_client::OpaqueBlock, +}; +use codec::Decode; +use jsonrpsee::core::async_trait; +use sc_client_api::{BlockBackend, BlockchainEvents, HeaderBackend}; +use sp_core::H256; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use std::sync::Arc; +use tokio::sync::RwLock; + +/// A lightweight cached block record produced by the native client. +#[derive(Clone, Debug)] +pub struct NativeCachedBlock { + pub hash: H256, + pub number: SubstrateBlockNumber, + pub parent_hash: H256, + pub opaque: OpaqueBlock, +} + +impl NativeCachedBlock { + pub fn hash(&self) -> H256 { + self.hash + } + pub fn number(&self) -> SubstrateBlockNumber { + self.number + } + pub fn header(&self) -> &sp_runtime::generic::Header { + &self.opaque.header + } +} + +impl BlockInfo for NativeCachedBlock { + fn hash(&self) -> H256 { + self.hash + } + + fn number(&self) -> SubstrateBlockNumber { + self.number + } + + fn parent_hash(&self) -> H256 { + self.parent_hash + } +} + +/// A [`BlockInfoProvider`] that fetches blocks directly from the in-process Substrate client. +pub struct NativeClientBlockInfoProvider { + client: Arc, + latest_block: Arc>>, + latest_finalized_block: Arc>>, + _block: std::marker::PhantomData, +} + +impl Clone for NativeClientBlockInfoProvider { + fn clone(&self) -> Self { + Self { + client: Arc::clone(&self.client), + latest_block: Arc::clone(&self.latest_block), + latest_finalized_block: Arc::clone(&self.latest_finalized_block), + _block: std::marker::PhantomData, + } + } +} + +impl NativeClientBlockInfoProvider +where + Block: BlockT + Send + Sync + 'static, + Block::Header: HeaderT, + Client: HeaderBackend + + BlockBackend + + BlockchainEvents + + Send + + Sync + + 'static, +{ + /// Create a new provider, seeding the cache with the current best block. + pub fn new(client: Arc) -> Result { + let info = client.info(); + let best = + Self::fetch_block_inner(&client, info.best_hash)?.ok_or(ClientError::BlockNotFound)?; + let finalized = + Self::fetch_block_inner(&client, info.finalized_hash)?.unwrap_or_else(|| best.clone()); + + Ok(Self { + client, + latest_block: Arc::new(RwLock::new(best)), + latest_finalized_block: Arc::new(RwLock::new(finalized)), + _block: std::marker::PhantomData, + }) + } + + fn fetch_block_inner( + client: &Arc, + hash: H256, + ) -> Result>, ClientError> { + let Some(signed) = + client.block(hash).map_err(|e| ClientError::NativeClientError(e.to_string()))? + else { + return Ok(None); + }; + + let encoded = signed.block.encode(); + let opaque = OpaqueBlock::decode(&mut &encoded[..]) + .map_err(|e| ClientError::NativeClientError(e.to_string()))?; + + let number: u32 = (*signed.block.header().number()).into(); + Ok(Some(Arc::new(NativeCachedBlock { + hash, + number, + parent_hash: *signed.block.header().parent_hash(), + opaque, + }))) + } +} + +#[async_trait] +impl BlockInfoProvider for NativeClientBlockInfoProvider +where + Block: BlockT + Send + Sync + 'static, + Block::Header: HeaderT + Send + Sync, + Client: HeaderBackend + + BlockBackend + + BlockchainEvents + + Send + + Sync + + 'static, +{ + type Block = NativeCachedBlock; + + async fn update_latest( + &self, + block: Arc, + subscription_type: SubscriptionType, + ) { + let mut slot = match subscription_type { + SubscriptionType::FinalizedBlocks => self.latest_finalized_block.write().await, + SubscriptionType::BestBlocks => self.latest_block.write().await, + }; + *slot = block; + } + + async fn latest_block(&self) -> Arc { + self.latest_block.read().await.clone() + } + + async fn latest_finalized_block(&self) -> Arc { + self.latest_finalized_block.read().await.clone() + } + + async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result>, ClientError> { + let latest = self.latest_block.read().await.clone(); + if latest.number() == block_number { + return Ok(Some(latest)); + } + let finalized = self.latest_finalized_block.read().await.clone(); + if finalized.number() == block_number { + return Ok(Some(finalized)); + } + + let hash = self + .client + .block_hash(block_number.into()) + .map_err(|e| ClientError::NativeClientError(e.to_string()))?; + + match hash { + Some(h) => Self::fetch_block_inner(&self.client, h), + None => Ok(None), + } + } + + async fn block_by_hash( + &self, + hash: &H256, + ) -> Result>, ClientError> { + let latest = self.latest_block.read().await.clone(); + if &latest.hash() == hash { + return Ok(Some(latest)); + } + let finalized = self.latest_finalized_block.read().await.clone(); + if &finalized.hash() == hash { + return Ok(Some(finalized)); + } + + Self::fetch_block_inner(&self.client, *hash) + } +} diff --git a/substrate/frame/revive/rpc/src/native_client.rs b/substrate/frame/revive/rpc/src/native_client.rs new file mode 100644 index 0000000000000..2a2b05e101f1f --- /dev/null +++ b/substrate/frame/revive/rpc/src/native_client.rs @@ -0,0 +1,510 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) 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. + +//! Native in-process [`SubstrateClientT`] implementation. +//! +//! [`NativeSubstrateClient`] drives the ETH-RPC server directly through the +//! Substrate node's in-memory client APIs (`sc_client_api`, `sp_api`, etc.), +//! removing the need for a separate `subxt`/WebSocket connection when the RPC +//! server is embedded inside the node binary. +use crate::{ + ClientError, SubstrateClientT, + client::{Balance, SubscriptionType, SubstrateBlockHash, SubstrateBlockNumber}, + native_block_info_provider::NativeCachedBlock, + substrate_client::{NodeHealth, RawExtrinsic, SubmitResult}, +}; +use codec::{Decode, Encode}; +use futures::StreamExt; +use jsonrpsee::core::async_trait; +use pallet_revive::{ + EthTransactInfo, ReviveApi, + evm::{ + Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, + TracerType, U256, + }, +}; +use sc_client_api::{BlockBackend, BlockchainEvents, HeaderBackend}; +use sc_transaction_pool_api::TransactionPool; +use sp_api::ProvideRuntimeApi; +use sp_core::{H160, H256}; +use sp_runtime::{ + OpaqueExtrinsic, + traits::{Block as BlockT, Header as HeaderT}, +}; +use sp_weights::Weight; +use std::{future::Future, marker::PhantomData, sync::Arc}; + +/// Convenience bound that bundles all runtime-API traits required by the native client. +pub trait ReviveRuntimeApiT: + pallet_revive::ReviveApi + + sp_api::Core + + sp_api::Metadata +{ +} + +impl ReviveRuntimeApiT for T where + T: pallet_revive::ReviveApi + + sp_api::Core + + sp_api::Metadata +{ +} + +/// The opaque block type used by Asset Hub (and most Substrate parachains). +pub type OpaqueBlock = sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, +>; + +#[inline] +fn native_err(e: impl std::fmt::Display) -> ClientError { + ClientError::NativeClientError(e.to_string()) +} + +/// A [`SubstrateClientT`] backed by the node's native in-process Substrate client. +pub struct NativeSubstrateClient +where + Block: BlockT, +{ + client: Arc, + pool: Arc, + chain_id: u64, + max_block_weight: Weight, + automine: bool, + _block: PhantomData, + _moment: PhantomData, +} + +impl Clone for NativeSubstrateClient +where + Block: BlockT, +{ + fn clone(&self) -> Self { + Self { + client: Arc::clone(&self.client), + pool: Arc::clone(&self.pool), + chain_id: self.chain_id, + max_block_weight: self.max_block_weight, + automine: self.automine, + _block: PhantomData, + _moment: PhantomData, + } + } +} + +impl NativeSubstrateClient +where + Block: BlockT, + Block::Header: HeaderT, + Moment: codec::Codec + Send + Sync + 'static, + Client: HeaderBackend + + ProvideRuntimeApi + + BlockBackend + + BlockchainEvents + + Send + + Sync + + 'static, + Client::Api: ReviveRuntimeApiT, + Pool: TransactionPool + Send + Sync + 'static, +{ + /// Create a new native client. + pub fn new( + client: Arc, + pool: Arc, + chain_id: u64, + automine: bool, + ) -> Result { + let best_hash = client.info().best_hash; + let runtime_api = client.runtime_api(); + + let block_gas_limit: u64 = runtime_api + .block_gas_limit(best_hash) + .map(|v: U256| v.min(U256::from(u64::MAX)).as_u64()) + .unwrap_or(u64::MAX); + + Ok(Self { + client, + pool, + chain_id, + max_block_weight: Weight::from_parts(block_gas_limit, u64::MAX), + automine, + _block: PhantomData, + _moment: PhantomData, + }) + } + + /// Build a [`NativeCachedBlock`] from a block hash by querying the in-process client. + fn block_info_from_hash( + &self, + block_hash: H256, + ) -> Result, ClientError> { + let Some(signed) = self.client.block(block_hash).map_err(native_err)? else { + return Ok(None); + }; + + let encoded = signed.block.encode(); + let opaque = OpaqueBlock::decode(&mut &encoded[..]).map_err(native_err)?; + + let number: u32 = (*signed.block.header().number()).into(); + Ok(Some(NativeCachedBlock { + hash: block_hash, + number, + parent_hash: *signed.block.header().parent_hash(), + opaque, + })) + } +} + +#[async_trait] +impl SubstrateClientT + for NativeSubstrateClient +where + Block: BlockT + Send + Sync + 'static, + Block::Header: HeaderT + Send + Sync, + Moment: codec::Codec + Clone + Send + Sync + 'static, + Client: HeaderBackend + + ProvideRuntimeApi + + BlockBackend + + BlockchainEvents + + Send + + Sync + + 'static, + Client::Api: ReviveRuntimeApiT, + Pool: TransactionPool + Send + Sync + 'static, +{ + type BlockInfo = NativeCachedBlock; + + fn chain_id(&self) -> u64 { + self.chain_id + } + + fn max_block_weight(&self) -> Weight { + self.max_block_weight + } + + async fn block_by_hash( + &self, + hash: &SubstrateBlockHash, + ) -> Result, ClientError> { + self.block_info_from_hash(*hash) + } + + async fn block_by_number( + &self, + number: SubstrateBlockNumber, + ) -> Result, ClientError> { + let hash = self.client.block_hash(number.into()).map_err(native_err)?; + match hash { + Some(h) => self.block_info_from_hash(h), + None => Ok(None), + } + } + + async fn latest_block(&self) -> Result { + let info = self.client.info(); + self.block_info_from_hash(info.best_hash)?.ok_or(ClientError::BlockNotFound) + } + + async fn latest_finalized_block(&self) -> Result { + let info = self.client.info(); + self.block_info_from_hash(info.finalized_hash)? + .ok_or(ClientError::BlockNotFound) + } + + async fn dry_run( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + _block: BlockNumberOrTagOrHash, + ) -> Result, ClientError> { + self.client + .runtime_api() + .eth_transact(block_hash, tx) + .map_err(native_err)? + .map_err(ClientError::TransactError) + } + + async fn gas_price(&self, block_hash: SubstrateBlockHash) -> Result { + self.client.runtime_api().gas_price(block_hash).map_err(native_err) + } + + async fn balance( + &self, + block_hash: SubstrateBlockHash, + address: H160, + ) -> Result { + self.client.runtime_api().balance(block_hash, address).map_err(native_err) + } + + async fn nonce( + &self, + block_hash: SubstrateBlockHash, + address: H160, + ) -> Result { + self.client + .runtime_api() + .nonce(block_hash, address) + .map(|n: u32| U256::from(n)) + .map_err(native_err) + } + + async fn code( + &self, + block_hash: SubstrateBlockHash, + address: H160, + ) -> Result, ClientError> { + self.client.runtime_api().code(block_hash, address).map_err(native_err) + } + + async fn get_storage( + &self, + block_hash: SubstrateBlockHash, + address: H160, + key: [u8; 32], + ) -> Result>, ClientError> { + self.client + .runtime_api() + .get_storage(block_hash, address, key) + .map_err(native_err)? + .map_err(|_| ClientError::ContractNotFound) + } + + async fn eth_block(&self, block_hash: SubstrateBlockHash) -> Result { + self.client.runtime_api().eth_block(block_hash).map_err(native_err) + } + + async fn eth_block_hash( + &self, + block_hash: SubstrateBlockHash, + number: U256, + ) -> Result, ClientError> { + self.client.runtime_api().eth_block_hash(block_hash, number).map_err(native_err) + } + + async fn eth_receipt_data( + &self, + block_hash: SubstrateBlockHash, + ) -> Result, ClientError> { + self.client.runtime_api().eth_receipt_data(block_hash).map_err(native_err) + } + + async fn trace_block( + &self, + _block_hash: SubstrateBlockHash, + block: OpaqueBlock, + config: TracerType, + ) -> Result, ClientError> { + let parent = *block.header().parent_hash(); + let block_generic: Block = Block::decode(&mut &block.encode()[..]).map_err(native_err)?; + self.client + .runtime_api() + .trace_block(parent, block_generic, config) + .map_err(native_err) + } + + async fn trace_tx( + &self, + _block_hash: SubstrateBlockHash, + block: OpaqueBlock, + transaction_index: u32, + config: TracerType, + ) -> Result { + let parent = *block.header().parent_hash(); + let block_generic: Block = Block::decode(&mut &block.encode()[..]).map_err(native_err)?; + self.client + .runtime_api() + .trace_tx(parent, block_generic, transaction_index, config) + .map_err(native_err)? + .ok_or(ClientError::EthExtrinsicNotFound) + } + + async fn trace_call( + &self, + block_hash: SubstrateBlockHash, + transaction: GenericTransaction, + config: TracerType, + ) -> Result { + self.client + .runtime_api() + .trace_call(block_hash, transaction, config) + .map_err(native_err)? + .map_err(ClientError::TransactError) + } + + async fn submit_extrinsic(&self, payload: Vec) -> Result { + let opaque_xt = OpaqueExtrinsic::try_from_encoded_extrinsic(&payload) + .map_err(|_| ClientError::TxDecodingFailed)?; + + let at = self.client.info().best_hash; + + self.pool + .submit_one(at, sc_transaction_pool_api::TransactionSource::External, opaque_xt) + .await + .map(|_| sc_transaction_pool_api::TransactionStatus::Ready) + .map_err(native_err) + } + + async fn sync_state( + &self, + ) -> Result, ClientError> { + let info = self.client.info(); + Ok(sc_rpc::system::SyncState { + starting_block: 0, + current_block: info.best_number, + highest_block: info.best_number, + }) + } + + async fn system_health(&self) -> Result { + Ok(NodeHealth { peers: 0, is_syncing: false, should_have_peers: false }) + } + + async fn get_automine(&self) -> bool { + self.automine + } + + async fn subscribe_blocks( + &self, + subscription_type: SubscriptionType, + callback: F, + ) -> Result<(), ClientError> + where + F: Fn(NativeCachedBlock) -> Fut + Send + Sync, + Fut: Future> + Send, + { + log::debug!( + target: crate::LOG_TARGET, + "NativeSubstrateClient::subscribe_blocks ({subscription_type:?}): \ + native notification stream active" + ); + + match subscription_type { + SubscriptionType::BestBlocks => { + let mut stream = self.client.import_notification_stream(); + while let Some(notification) = stream.next().await { + if !notification.is_new_best { + continue; + } + + let hash = notification.hash; + let block_info = match self.block_info_from_hash(hash) { + Ok(Some(b)) => b, + Ok(None) => { + log::warn!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: best-block notification for unknown hash {:?}", + hash + ); + continue; + }, + Err(err) => { + log::error!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: failed to fetch best-block info: {err:?}" + ); + continue; + }, + }; + + log::trace!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: new best block #{} ({:?})", + block_info.number, + block_info.hash + ); + + if let Err(err) = callback(block_info).await { + log::error!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: best-block callback error: {err:?}" + ); + } + } + }, + + SubscriptionType::FinalizedBlocks => { + let mut stream = self.client.finality_notification_stream(); + while let Some(notification) = stream.next().await { + let hash = notification.hash; + let block_info = match self.block_info_from_hash(hash) { + Ok(Some(b)) => b, + Ok(None) => { + log::warn!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: finalized-block notification for unknown hash {:?}", + hash + ); + continue; + }, + Err(err) => { + log::error!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: failed to fetch finalized-block info: {err:?}" + ); + continue; + }, + }; + + log::trace!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: finalized block #{} ({:?})", + block_info.number, + block_info.hash + ); + + if let Err(err) = callback(block_info).await { + log::error!( + target: crate::LOG_TARGET, + "NativeSubstrateClient: finalized-block callback error: {err:?}" + ); + } + } + }, + } + Ok(()) + } + + async fn signed_block( + &self, + block_hash: SubstrateBlockHash, + ) -> Result { + let signed = self + .client + .block(block_hash) + .map_err(native_err)? + .ok_or(ClientError::BlockNotFound)?; + + let encoded = signed.block.encode(); + OpaqueBlock::decode(&mut &encoded[..]).map_err(native_err) + } + + async fn block_extrinsics( + &self, + block_hash: SubstrateBlockHash, + ) -> Result, ClientError> { + let body = self + .client + .block_body(block_hash) + .map_err(native_err)? + .ok_or(ClientError::BlockNotFound)?; + + Ok(body + .into_iter() + .enumerate() + .map(|(index, ext)| RawExtrinsic { payload: ext.encode(), index }) + .collect()) + } +} diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index f5720887f55a7..03731fc02458c 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -15,20 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - ClientError, H160, LOG_TARGET, - client::{SubstrateBlock, SubstrateBlockNumber, runtime_api::RuntimeApi}, - subxt_client::{ - SrcChainConfig, - revive::{ - calls::types::EthTransact, - events::{ContractEmitted, EthExtrinsicRevert}, - }, - }, + ClientError, H160, LOG_TARGET, client::SubstrateBlockNumber, substrate_client::RawExtrinsic, }; -use futures::{StreamExt, stream}; use pallet_revive::{ - create1, + ReviveApi, create1, evm::{GenericTransaction, H256, Log, ReceiptGasInfo, ReceiptInfo, TransactionSigned, U256}, }; use sp_core::keccak_256; @@ -40,7 +31,12 @@ use std::{ atomic::{AtomicU32, Ordering}, }, }; -use subxt::{OnlineClient, blocks::ExtrinsicDetails}; + +const PALLET_REVIVE_INDEX: u8 = 253; +const ETH_TRANSACT_CALL_INDEX: u8 = 0; + +/// Sentinel value meaning "not yet discovered". +const NOT_DISCOVERED: u32 = u32::MAX; type FetchReceiptDataFn = Arc< dyn Fn(H256) -> Pin>> + Send>> + Send + Sync, @@ -51,6 +47,23 @@ type FetchEthBlockHashFn = type RecoverEthAddressFn = Arc Result + Send + Sync>; +type FetchBlockEventsFn = + Arc Pin> + Send>> + Send + Sync>; + +/// Fetches the raw extrinsics for a given block hash. +type FetchBlockExtrinsicsFn = Arc< + dyn Fn(H256) -> Pin>> + Send>> + Send + Sync, +>; + +#[derive(Clone, Debug, Default)] +pub struct ExtrinsicEvents { + pub success: bool, + pub logs: Vec<(H160, Vec, Option>)>, +} + +/// All extrinsic events for a block. +pub type BlockEvents = Vec; + /// Utility to extract receipts from extrinsics. #[derive(Clone)] pub struct ReceiptExtractor { @@ -59,10 +72,18 @@ pub struct ReceiptExtractor { /// Fetch ethereum block hash. fetch_eth_block_hash: FetchEthBlockHashFn, + /// Fetch decoded events for every extrinsic in a block (optional). + fetch_block_events: Option, + /// Fetch raw extrinsics for a block. + fetch_block_extrinsics: Option, + + /// The earliest block from which receipts should be extracted. + /// Configured at startup; blocks before this are skipped entirely. + earliest_receipt_block: Option, /// Auto-discovered first EVM block on the chain. - /// Set once during backward sync when the first non-EVM block is encountered. - /// Uses `u32::MAX` as sentinel for "not yet discovered". + /// Stored as `NOT_DISCOVERED` (u32::MAX) when not yet known. + /// Only ever decreases once set. first_evm_block: Arc, /// Recover the ethereum address from a transaction signature. @@ -70,53 +91,196 @@ pub struct ReceiptExtractor { } impl ReceiptExtractor { - /// Create a new `ReceiptExtractor`. - pub async fn new(api: OnlineClient) -> Result { - Self::new_with_custom_address_recovery( - api, - Arc::new(|signed_tx: &TransactionSigned| signed_tx.recover_eth_address()), - ) - .await + /// Check if the block is before the earliest block. + pub fn is_before_earliest_block(&self, block_number: SubstrateBlockNumber) -> bool { + block_number < self.earliest_receipt_block.unwrap_or_default() + } + + /// Check if `block_number` is before the auto-discovered `first_evm_block`. + /// Returns `false` when first_evm_block has not yet been discovered. + pub fn is_before_first_evm_block(&self, block_number: SubstrateBlockNumber) -> bool { + let first = self.first_evm_block.load(Ordering::Acquire); + if first == NOT_DISCOVERED { + return false; + } + block_number < first } - /// Create a new `ReceiptExtractor` with custom Ethereum address recovery logic. - /// - /// Use `ReceiptExtractor::new` if the default Ethereum address recovery - /// logic ([`TransactionSigned::recover_eth_address`] based) is enough. - pub async fn new_with_custom_address_recovery( - api: OnlineClient, - recover_eth_address_fn: RecoverEthAddressFn, - ) -> Result { - let api_inner = api.clone(); - let fetch_eth_block_hash = Arc::new(move |block_hash, block_number| { - let api_inner = api_inner.clone(); + /// Return the auto-discovered first EVM block, or `None` if not yet discovered. + pub fn first_evm_block(&self) -> Option { + let v = self.first_evm_block.load(Ordering::Acquire); + if v == NOT_DISCOVERED { None } else { Some(v) } + } + + /// Set the auto-discovered first EVM block. + /// The value only decreases: if a smaller block number is discovered later, it wins. + pub fn set_first_evm_block(&self, block_number: SubstrateBlockNumber) { + // Use a compare-and-swap loop so the value only ever decreases. + let mut current = self.first_evm_block.load(Ordering::Acquire); + loop { + if current != NOT_DISCOVERED && block_number >= current { + // Already at or below this value; nothing to do. + return; + } + match self.first_evm_block.compare_exchange( + current, + block_number, + Ordering::AcqRel, + Ordering::Acquire, + ) { + Ok(_) => return, + Err(actual) => current = actual, + } + } + } + + /// Create a `ReceiptExtractor` for the **native in-process** path with custom closures. + pub fn new_native( + fetch_receipt_data_fn: impl Fn( + H256, + ) -> Pin< + Box>> + Send>, + > + Send + + Sync + + 'static, + fetch_eth_block_hash_fn: impl Fn( + H256, + u64, + ) -> Pin> + Send>> + + Send + + Sync + + 'static, + earliest_receipt_block: Option, + fetch_block_events_fn: Option< + impl Fn(H256) -> Pin> + Send>> + + Send + + Sync + + 'static, + >, + fetch_block_extrinsics_fn: Option< + impl Fn(H256) -> Pin>> + Send>> + + Send + + Sync + + 'static, + >, + ) -> Self { + Self { + fetch_receipt_data: Arc::new(fetch_receipt_data_fn), + fetch_eth_block_hash: Arc::new(fetch_eth_block_hash_fn), + fetch_block_events: fetch_block_events_fn.map(|f| Arc::new(f) as FetchBlockEventsFn), + fetch_block_extrinsics: fetch_block_extrinsics_fn + .map(|f| Arc::new(f) as FetchBlockExtrinsicsFn), + earliest_receipt_block, + first_evm_block: Arc::new(AtomicU32::new(NOT_DISCOVERED)), + recover_eth_address: Arc::new(|signed_tx: &TransactionSigned| { + signed_tx.recover_eth_address() + }), + } + } + /// Build a `ReceiptExtractor` for the native path directly from a Substrate client. + pub fn new_native_from_client( + client: Arc, + earliest_receipt_block: Option, + ) -> Self + where + Block: sp_runtime::traits::Block + + Send + + Sync + + 'static, + Moment: codec::Codec + Clone + Send + Sync + 'static, + Client: sp_api::ProvideRuntimeApi + + sc_client_api::HeaderBackend + + sc_client_api::BlockBackend + + Send + + Sync + + 'static, + Client::Api: crate::native_client::ReviveRuntimeApiT, + { + use codec::Encode; + + let client_for_data = client.clone(); + let fetch_receipt_data_fn = move |block_hash: H256| { + let client = client_for_data.clone(); + let fut = async move { client.runtime_api().eth_receipt_data(block_hash).ok() }; + Box::pin(fut) as Pin>> + Send>> + }; + + let client_for_hash = client.clone(); + let fetch_eth_block_hash_fn = move |block_hash: H256, block_number: u64| { + let client = client_for_hash.clone(); let fut = async move { - let runtime_api = RuntimeApi::new(api_inner.runtime_api().at(block_hash)); - runtime_api.eth_block_hash(U256::from(block_number)).await.ok().flatten() + client + .runtime_api() + .eth_block_hash(block_hash, U256::from(block_number)) + .ok() + .flatten() }; + Box::pin(fut) as Pin> + Send>> + }; - Box::pin(fut) as Pin> - }); + let client_for_ext = client.clone(); + let fetch_block_extrinsics_fn = move |block_hash: H256| { + let client = client_for_ext.clone(); + let fut = async move { + let body = client.block_body(block_hash).ok()??; + Some( + body.into_iter() + .enumerate() + .map(|(index, ext)| RawExtrinsic { payload: ext.encode(), index }) + .collect(), + ) + }; + Box::pin(fut) as Pin>> + Send>> + }; - let api_inner = api.clone(); - let fetch_receipt_data = Arc::new(move |block_hash| { - let api_inner = api_inner.clone(); + Self::new_native( + fetch_receipt_data_fn, + fetch_eth_block_hash_fn, + earliest_receipt_block, + None:: Pin> + Send>>>, + Some(fetch_block_extrinsics_fn), + ) + } + + /// Create a `ReceiptExtractor` from a [`SubstrateClientT`] implementation. + pub fn new_from_substrate_client( + client: C, + earliest_receipt_block: Option, + ) -> Self + where + C: crate::SubstrateClientT + Clone + 'static, + { + let client_for_data = client.clone(); + let fetch_receipt_data_fn = move |block_hash: H256| { + let c = client_for_data.clone(); + let fut = async move { c.eth_receipt_data(block_hash).await.ok() }; + Box::pin(fut) as Pin>> + Send>> + }; + let client_for_hash = client.clone(); + let fetch_eth_block_hash_fn = move |block_hash: H256, block_number: u64| { + let c = client_for_hash.clone(); let fut = async move { - let runtime_api = RuntimeApi::new(api_inner.runtime_api().at(block_hash)); - runtime_api.eth_receipt_data().await.ok() + c.eth_block_hash(block_hash, U256::from(block_number)).await.ok().flatten() }; + Box::pin(fut) as Pin> + Send>> + }; - Box::pin(fut) as Pin> - }); + let client_for_ext = client.clone(); + let fetch_block_extrinsics_fn = move |block_hash: H256| { + let c = client_for_ext.clone(); + let fut = async move { c.block_extrinsics(block_hash).await.ok() }; + Box::pin(fut) as Pin>> + Send>> + }; - Ok(Self { - fetch_receipt_data, - fetch_eth_block_hash, - first_evm_block: Arc::new(AtomicU32::new(u32::MAX)), - recover_eth_address: recover_eth_address_fn, - }) + Self::new_native( + fetch_receipt_data_fn, + fetch_eth_block_hash_fn, + earliest_receipt_block, + None:: Pin> + Send>>>, + Some(fetch_block_extrinsics_fn), + ) } #[cfg(test)] @@ -133,59 +297,62 @@ impl ReceiptExtractor { Self { fetch_receipt_data, fetch_eth_block_hash, - first_evm_block: Arc::new(AtomicU32::new(u32::MAX)), + fetch_block_events: None, + fetch_block_extrinsics: None, + earliest_receipt_block: None, + first_evm_block: Arc::new(AtomicU32::new(NOT_DISCOVERED)), recover_eth_address: Arc::new(|signed_tx: &TransactionSigned| { signed_tx.recover_eth_address() }), } } - /// Check if the block is before the `first_evm_block` floor. - /// When sentinel (`u32::MAX`), no blocks are rejected (permissive default). - pub fn is_before_first_evm_block(&self, block_number: SubstrateBlockNumber) -> bool { - let val = self.first_evm_block.load(Ordering::Acquire); - val != u32::MAX && block_number < val - } + /// Try to decode a raw extrinsic as an `eth_transact` call. + fn decode_eth_transact(raw: &RawExtrinsic) -> Option> { + let bytes = &raw.payload; + + let mut offset = 0usize; + let first = *bytes.get(offset)?; + let len_len = match first & 0b11 { + 0 => 1, + 1 => 2, + 2 => 4, + _ => return None, + }; + offset += len_len; - /// Set the first EVM block. Only stores if lower than the current value. - pub fn set_first_evm_block(&self, block_number: SubstrateBlockNumber) { - let prev = self.first_evm_block.fetch_min(block_number, Ordering::AcqRel); - if block_number > prev { - log::debug!(target: LOG_TARGET, - "Ignored attempt to raise first_evm_block to #{block_number}, current is #{prev}"); + let _version = *bytes.get(offset)?; + offset += 1; + + let pallet_idx = *bytes.get(offset)?; + offset += 1; + let call_idx = *bytes.get(offset)?; + offset += 1; + + if pallet_idx != PALLET_REVIVE_INDEX || call_idx != ETH_TRANSACT_CALL_INDEX { + return None; } - } - /// The auto-discovered first EVM block, or `None` if not yet discovered. - pub fn first_evm_block(&self) -> Option { - let val = self.first_evm_block.load(Ordering::Acquire); - (val != u32::MAX).then_some(val) + use codec::Decode; + let mut rest = &bytes[offset..]; + let payload = Vec::::decode(&mut rest).ok()?; + Some(payload) } - /// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] from an extrinsic. - async fn extract_from_extrinsic( + /// Extract a receipt from a decoded call and its associated events. + fn build_receipt( &self, - substrate_block: &SubstrateBlock, + substrate_block_number: u32, eth_block_hash: H256, - ext: subxt::blocks::ExtrinsicDetails>, - call: EthTransact, - receipt_gas_info: ReceiptGasInfo, + ext_events: &ExtrinsicEvents, + eth_payload: &[u8], + receipt_gas_info: &ReceiptGasInfo, transaction_index: usize, ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { - let events = ext.events().await?; - let block_number: U256 = substrate_block.number().into(); - - 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 = H256(keccak_256(&call.payload)); - + let transaction_hash = H256(keccak_256(eth_payload)); let signed_tx = - TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?; + TransactionSigned::decode(eth_payload).map_err(|_| ClientError::TxDecodingFailed)?; + let from = (self.recover_eth_address)(&signed_tx).map_err(|_| { log::error!(target: LOG_TARGET, "Failed to recover eth address from signed tx"); ClientError::RecoverEthAddressFailed @@ -197,24 +364,22 @@ impl ReceiptExtractor { Some(from), ); - // get logs from ContractEmitted event - let logs = events + let block_number: U256 = substrate_block_number.into(); + + let logs = ext_events + .logs .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() - }) + .enumerate() + .map(|(log_idx, (address, topics, data))| Log { + address: *address, + topics: topics.clone(), + data: data.as_ref().map(|d| d.clone().into()), + block_number, + transaction_hash, + transaction_index: transaction_index.into(), + block_hash: eth_block_hash, + log_index: log_idx.into(), + ..Default::default() }) .collect(); @@ -240,7 +405,7 @@ impl ReceiptExtractor { tx_info.to, receipt_gas_info.effective_gas_price, U256::from(receipt_gas_info.gas_used), - success, + ext_events.success, transaction_hash, transaction_index.into(), tx_info.r#type.unwrap_or_default(), @@ -248,120 +413,54 @@ impl ReceiptExtractor { Ok((signed_tx, receipt)) } - /// Extract receipts from block. - pub async fn extract_from_block( + /// Extract receipts from a block using `RawExtrinsic` data. + pub async fn extract_from_block_raw( &self, - block: &SubstrateBlock, + _block_hash: H256, + block_number: u32, + eth_block_hash: H256, + raw_extrinsics: &[RawExtrinsic], + receipt_data: &[ReceiptGasInfo], + block_events: Option<&[ExtrinsicEvents]>, ) -> Result, ClientError> { - if self.is_before_first_evm_block(block.number()) { - return Ok(vec![]); - } - - let ext_iter = self.get_block_extrinsics(block).await?; - - let substrate_block_number = block.number() as u64; - let substrate_block_hash = block.hash(); - let eth_block_hash = - (self.fetch_eth_block_hash)(substrate_block_hash, substrate_block_number) - .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 + let eth_extrinsics: Vec<(usize, Vec)> = raw_extrinsics .iter() - .enumerate() - .flat_map(|(ext_idx, ext)| { - let call = ext.as_extrinsic::().ok()??; - Some((ext, call, ext_idx)) + .filter_map(|raw| { + let payload = Self::decode_eth_transact(raw)?; + Some((raw.index, payload)) }) .collect(); - // Sanity check we received enough data from the pallet revive. - if receipt_data.len() != extrinsics.len() { + if eth_extrinsics.len() != receipt_data.len() { log::error!( target: LOG_TARGET, - "Receipt data length ({}) does not match extrinsics length ({})", + "Receipt data length ({}) != eth extrinsics ({}) for block #{block_number}", receipt_data.len(), - extrinsics.len() + eth_extrinsics.len() ); - Err(ClientError::ReceiptDataLengthMismatch) - } else { - Ok(extrinsics - .into_iter() - .zip(receipt_data) - .map(|((extr, call, ext_idx), rec)| (extr, call, rec, ext_idx))) + return Err(ClientError::ReceiptDataLengthMismatch); } - } - /// Extract a [`TransactionSigned`] and a [`ReceiptInfo`] for a specific transaction in a - /// [`SubstrateBlock`] - pub async fn extract_from_transaction( - &self, - block: &SubstrateBlock, - transaction_index: usize, - ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { - let ext_iter = self.get_block_extrinsics(block).await?; + let mut results = Vec::with_capacity(eth_extrinsics.len()); + for (i, (ext_index, eth_payload)) in eth_extrinsics.iter().enumerate() { + let events = if let Some(all_events) = block_events { + all_events.get(*ext_index).cloned().unwrap_or_default() + } else { + // Native path: no event scanning available; mark success = true. + ExtrinsicEvents { success: true, logs: vec![] } + }; - let (ext, eth_call, receipt_gas_info, _) = ext_iter - .into_iter() - .find(|(_, _, _, ext_idx)| *ext_idx == transaction_index) - .ok_or(ClientError::EthExtrinsicNotFound)?; - - let substrate_block_number = block.number() as u64; - let substrate_block_hash = block.hash(); - let eth_block_hash = - (self.fetch_eth_block_hash)(substrate_block_hash, substrate_block_number) - .await - .unwrap_or(substrate_block_hash); - - self.extract_from_extrinsic( - block, - eth_block_hash, - ext, - eth_call, - receipt_gas_info, - transaction_index, - ) - .await + let receipt = self.build_receipt( + block_number, + eth_block_hash, + &events, + eth_payload, + &receipt_data[i], + *ext_index, + )?; + results.push(receipt); + } + Ok(results) } /// Get the Ethereum block hash for the Substrate block with specific hash. @@ -372,6 +471,63 @@ impl ReceiptExtractor { ) -> Option { (self.fetch_eth_block_hash)(*block_hash, block_number).await } + + /// Extract receipts from a block, given only its hash and number. + pub async fn extract_from_block_by_hash( + &self, + block_hash: H256, + block_number: SubstrateBlockNumber, + ) -> Result, ClientError> { + if self.is_before_earliest_block(block_number) { + return Ok(vec![]); + } + + let eth_block_hash = (self.fetch_eth_block_hash)(block_hash, block_number as u64) + .await + .unwrap_or(block_hash); + + let receipt_data = (self.fetch_receipt_data)(block_hash) + .await + .ok_or(ClientError::ReceiptDataNotFound)?; + + let block_events = if let Some(ref fetch_events) = self.fetch_block_events { + fetch_events(block_hash).await + } else { + None + }; + + let raw = if let Some(ref fetch_extrinsics) = self.fetch_block_extrinsics { + fetch_extrinsics(block_hash).await.ok_or(ClientError::BlockNotFound)? + } else { + return Err(ClientError::NativeClientError( + "fetch_block_extrinsics not configured".to_string(), + )); + }; + + self.extract_from_block_raw( + block_hash, + block_number, + eth_block_hash, + &raw, + &receipt_data, + block_events.as_deref(), + ) + .await + } + + /// Extract a single receipt from a block by transaction index. + pub async fn extract_from_transaction_by_hash( + &self, + block_hash: H256, + block_number: SubstrateBlockNumber, + transaction_index: usize, + ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { + let receipts = self.extract_from_block_by_hash(block_hash, block_number).await?; + receipts + .into_iter() + .find(|(_, r)| r.transaction_index.as_usize() == transaction_index) + .ok_or(ClientError::EthExtrinsicNotFound) + } } #[cfg(test)] diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs index 021e0bd0be15e..acf98ffdc0ecc 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -15,12 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::{ - Address, AddressOrAddresses, BlockInfoProvider, BlockNumberOrTag, Bytes, ChainMetadata, - ClientError, FilterTopic, ReceiptExtractor, SubxtBlockInfoProvider, SyncLabel, SyncStateKey, - block_sync::SyncCheckpoint, - client::{SubstrateBlock, SubstrateBlockNumber}, + Address, AddressOrAddresses, BlockInfoProvider, BlockNumberOrTag, Bytes, ClientError, + FilterTopic, ReceiptExtractor, + block_info_provider::{BlockInfo, test::MockBlockInfoProvider}, + block_sync::{ChainMetadata, SyncCheckpoint, SyncLabel, SyncStateKey}, + client::SubstrateBlockNumber, }; -use pallet_revive::evm::{Filter, Log, ReceiptInfo, TransactionSigned}; +use pallet_revive::evm::{BlockTag, Filter, Log, ReceiptInfo, TransactionSigned}; use sp_core::{H256, U256}; use sqlx::{QueryBuilder, Row, Sqlite, SqlitePool, query}; use std::{ @@ -33,11 +34,11 @@ const LOG_TARGET: &str = "eth-rpc::receipt_provider"; /// ReceiptProvider stores transaction receipts and logs in a SQLite database. #[derive(Clone)] -pub struct ReceiptProvider { +pub struct ReceiptProvider { /// The database pool. pool: SqlitePool, - /// The block provider used to fetch blocks, and reconstruct receipts. - block_provider: B, + /// The block provider used to fetch blocks and reconstruct receipts. + block_provider: BP, /// A means to extract receipts from extrinsics. receipt_extractor: ReceiptExtractor, /// When `Some`, old blocks will be pruned. @@ -59,60 +60,18 @@ impl BlockHashMap { } } -/// Provides information about a block, -/// This is an abstraction on top of [`SubstrateBlock`] that can't be mocked in tests. -/// Can be removed once is fixed. -pub trait BlockInfo { - /// Returns the block hash. - fn hash(&self) -> H256; - /// Returns the block number. - fn number(&self) -> SubstrateBlockNumber; -} - -impl BlockInfo for SubstrateBlock { - fn hash(&self) -> H256 { - SubstrateBlock::hash(self) - } - fn number(&self) -> SubstrateBlockNumber { - SubstrateBlock::number(self) - } -} - -/// Maximum number of entries kept in the block to hash map. -pub const MAX_CACHED_BLOCKS: usize = 256; - -/// Upsert a sync label row, updating only when the existing `block_number` -/// compares with `$op` against the new value. `$op` must be `"<"` or `">"`. -macro_rules! upsert_sync_label { - ($pool:expr, $op:literal, $label:expr, $checkpoint:expr) => {{ - let label_str = $label.to_string(); - let block_number = $checkpoint.block_number as i64; - let block_hash = $checkpoint.block_hash.map(|h| h.as_bytes().to_vec()); - query!( - "INSERT INTO sync_state (label, block_number, block_hash) - VALUES ($1, $2, $3) - ON CONFLICT(label) DO UPDATE - SET block_number = excluded.block_number, block_hash = excluded.block_hash - WHERE sync_state.block_number " + - $op + " excluded.block_number - ", - label_str, - block_number, - block_hash - ) - .execute($pool) - .await?; - }}; -} +/// Maximum number of entries kept in the block-to-hash map (prevents unbounded growth in +/// persistent-DB mode). +const MAX_CACHED_BLOCKS: usize = 256; -impl ReceiptProvider { - /// Create a new `ReceiptProvider` with the given database URL and block provider. +impl ReceiptProvider { + /// Create a new `ReceiptProvider`. pub async fn new( pool: SqlitePool, - block_provider: B, + block_provider: BP, receipt_extractor: ReceiptExtractor, keep_latest_n_blocks: Option, - ) -> Result { + ) -> Result { sqlx::migrate!().run(&pool).await.map_err(|e| sqlx::Error::Migrate(e.into()))?; let provider = Self { @@ -122,11 +81,54 @@ impl ReceiptProvider { keep_latest_n_blocks, block_number_to_hashes: Default::default(), }; - provider.restore_first_evm_block().await?; + + provider.restore_first_evm_block().await.map_err(|e| match e { + ClientError::SqlxError(sqlx_err) => sqlx_err, + other => sqlx::Error::Io(std::io::Error::other(other.to_string())), + })?; Ok(provider) } + /// Expose the `keep_latest_n_blocks` setting (used by `Client` to determine archive mode). + pub fn keep_latest_n_blocks(&self) -> Option { + self.keep_latest_n_blocks + } + + /// Restore the `first_evm_block` from the persisted `sync_state` table on startup. + async fn restore_first_evm_block(&self) -> Result<(), ClientError> { + let Some(checkpoint) = self.get_sync_label(ChainMetadata::FirstEvmBlock).await? else { + return Ok(()); + }; + + let block_number = checkpoint.block_number; + + // Verify this block still has an EVM hash. + let is_valid = match self.block_provider.block_by_number(block_number).await { + Ok(Some(block)) => self + .receipt_extractor + .get_ethereum_block_hash(&block.hash(), block_number as u64) + .await + .is_some(), + _ => false, + }; + + if is_valid { + self.receipt_extractor.set_first_evm_block(block_number); + } else { + log::debug!( + target: LOG_TARGET, + "Clearing stale first_evm_block #{block_number} from DB" + ); + sqlx::query("DELETE FROM sync_state WHERE label = ?1") + .bind(ChainMetadata::FirstEvmBlock.to_string()) + .execute(&self.pool) + .await?; + } + + Ok(()) + } + /// Returns `true` if the block is before the auto-discovered `first_evm_block`. pub fn is_before_earliest_block(&self, at: &BlockNumberOrTag) -> bool { match at { @@ -155,43 +157,98 @@ impl ReceiptProvider { .await } - /// Restore `first_evm_block` from DB, clearing it if the boundary has shifted. - async fn restore_first_evm_block(&self) -> Result<(), ClientError> { - let Some(evm_first) = - self.get_sync_label(ChainMetadata::FirstEvmBlock).await?.map(|c| c.block_number) - else { - return Ok(()); - }; + /// Persist a sync label (any `SyncStateKey`) to the `sync_state` table. + /// Always overwrites the existing value. + /// + /// Uses a plain `sqlx::query` (not the macro form) so that it does not require + /// a pre-populated SQLX offline query cache. + pub async fn set_sync_label( + &self, + key: K, + checkpoint: SyncCheckpoint, + ) -> Result<(), ClientError> { + let key_str = key.to_string(); + let block_number = checkpoint.block_number as i64; + let block_hash: Option> = + checkpoint.block_hash.as_ref().map(|h| h.as_bytes().to_vec()); - let has_evm_hash = |block_number: SubstrateBlockNumber| async move { - match self.block_provider.block_by_number(block_number).await.ok().flatten() { - Some(block) => self - .receipt_extractor - .get_ethereum_block_hash(&block.hash(), block_number as u64) - .await - .is_some(), - None => false, + sqlx::query( + "INSERT OR REPLACE INTO sync_state (label, block_number, block_hash) \ + VALUES (?1, ?2, ?3)", + ) + .bind(&key_str) + .bind(block_number) + .bind(block_hash) + .execute(&self.pool) + .await?; + + Ok(()) + } + + /// Retrieve a sync label from the `sync_state` table. + /// + /// Uses a plain `sqlx::query` (not the macro form) so that it does not require + /// a pre-populated SQLX offline query cache. + pub async fn get_sync_label( + &self, + key: K, + ) -> Result, ClientError> { + let key_str = key.to_string(); + + let row = sqlx::query("SELECT block_number, block_hash FROM sync_state WHERE label = ?1") + .bind(&key_str) + .fetch_optional(&self.pool) + .await?; + + Ok(row.map(|r| { + let block_number: i64 = r.get("block_number"); + let block_hash_bytes: Option> = r.get("block_hash"); + let block_hash = + block_hash_bytes.filter(|b| b.len() == 32).map(|b| H256::from_slice(&b)); + SyncCheckpoint { block_number: block_number as SubstrateBlockNumber, block_hash } + })) + } + + /// Advance a `SyncLabel` forward (only moves to higher block numbers). + pub async fn advance_sync_label( + &self, + label: SyncLabel, + checkpoint: SyncCheckpoint, + ) -> Result<(), ClientError> { + if let Some(existing) = self.get_sync_label(label).await? { + if checkpoint.block_number <= existing.block_number { + return Ok(()); } - }; + } + self.set_sync_label(label, checkpoint).await + } - // Stale if evm_first no longer has an EVM hash, or its predecessor now does. - let current_has_evm = has_evm_hash(evm_first).await; - let predecessor_has_evm = - if evm_first > 0 { has_evm_hash(evm_first - 1).await } else { false }; - - if !current_has_evm || predecessor_has_evm { - log::warn!(target: LOG_TARGET, - "🗄️ Stored first-evm-block=#{evm_first} is stale \ - (has_evm={current_has_evm}, predecessor_has_evm={predecessor_has_evm}), \ - clearing."); - if let Err(e) = self.delete_sync_label(ChainMetadata::FirstEvmBlock).await { - log::error!(target: LOG_TARGET, - "🗄️ Failed to clear stale first-evm-block from DB: {e:?}"); + /// Recede a `SyncLabel` backward (only moves to lower block numbers). + pub async fn recede_sync_label( + &self, + label: SyncLabel, + checkpoint: SyncCheckpoint, + ) -> Result<(), ClientError> { + if let Some(existing) = self.get_sync_label(label).await? { + if checkpoint.block_number >= existing.block_number { + return Ok(()); } - } else { - self.receipt_extractor.set_first_evm_block(evm_first); } - Ok(()) + self.set_sync_label(label, checkpoint).await + } + + /// Insert receipts for a block, used during backward (historical) sync. + pub async fn insert_block_receipts_past( + &self, + block: &BP::Block, + ethereum_hash: &H256, + ) -> Result<(), ClientError> { + let receipts = self + .receipt_extractor + .extract_from_block_by_hash(block.hash(), block.number()) + .await?; + self.insert_with_hashes(block.hash(), block.number(), &receipts, ethereum_hash) + .await } // Get block hash and transaction index by transaction hash @@ -214,26 +271,6 @@ impl ReceiptProvider { Some((block_hash, transaction_index)) } - /// Insert a block mapping from Ethereum block hash to Substrate block hash. - async fn insert_block_mapping(&self, block_map: &BlockHashMap) -> Result<(), ClientError> { - let ethereum_hash_ref = block_map.ethereum_hash.as_ref(); - let substrate_hash_ref = block_map.substrate_hash.as_ref(); - - query!( - r#" - INSERT OR REPLACE INTO eth_to_substrate_blocks (ethereum_block_hash, substrate_block_hash) - VALUES ($1, $2) - "#, - ethereum_hash_ref, - substrate_hash_ref, - ) - .execute(&self.pool) - .await?; - - log::trace!(target: LOG_TARGET, "Insert block mapping ethereum block: {:?} -> substrate block: {:?}", block_map.ethereum_hash, block_map.substrate_hash); - Ok(()) - } - /// Get the Substrate block hash for the given Ethereum block hash. pub async fn get_substrate_hash(&self, ethereum_block_hash: &H256) -> Option { let ethereum_hash = ethereum_block_hash.as_ref(); @@ -251,12 +288,16 @@ impl ReceiptProvider { log::error!(target: LOG_TARGET, "failed to get block mapping for ethereum block {ethereum_block_hash:?}, err: {e:?}"); }) .ok()? - .or_else(||{ + .or_else(|| { log::trace!(target: LOG_TARGET, "No block mapping found for ethereum block: {ethereum_block_hash:?}"); None })?; - log::trace!(target: LOG_TARGET, "Get block mapping ethereum block: {:?} -> substrate block: {ethereum_block_hash:?}", H256::from_slice(&result.substrate_block_hash[..])); + log::trace!( + target: LOG_TARGET, + "Get block mapping ethereum block: {:?} -> substrate block: {ethereum_block_hash:?}", + H256::from_slice(&result.substrate_block_hash[..]) + ); Some(H256::from_slice(&result.substrate_block_hash[..])) } @@ -278,16 +319,154 @@ impl ReceiptProvider { log::error!(target: LOG_TARGET, "failed to get block mapping for substrate block {substrate_block_hash:?}, err: {e:?}"); }) .ok()? - .or_else(||{ + .or_else(|| { log::trace!(target: LOG_TARGET, "No block mapping found for substrate block: {substrate_block_hash:?}"); None })?; - log::trace!(target: LOG_TARGET, "Get block mapping substrate block: {substrate_block_hash:?} -> ethereum block: {:?}", H256::from_slice(&result.ethereum_block_hash[..])); + log::trace!( + target: LOG_TARGET, + "Get block mapping substrate block: {substrate_block_hash:?} -> ethereum block: {:?}", + H256::from_slice(&result.ethereum_block_hash[..]) + ); Some(H256::from_slice(&result.ethereum_block_hash[..])) } + /// Get the number of receipts for a given substrate block hash. + pub async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { + let block_hash_ref = block_hash.as_ref(); + // Use query_unchecked! to avoid sqlx offline-cache issues with COUNT(*) aliases. + let count: i64 = + sqlx::query_scalar("SELECT COUNT(*) FROM transaction_hashes WHERE block_hash = ?") + .bind(block_hash_ref) + .fetch_one(&self.pool) + .await + .ok()?; + + Some(count as usize) + } + + /// Return all transaction hashes for the given (substrate) block hash. + pub async fn block_transaction_hashes( + &self, + block_hash: &H256, + ) -> Option> { + let block_hash_ref = block_hash.as_ref(); + let rows: Vec<(i64, Vec)> = sqlx::query_as( + "SELECT transaction_index, transaction_hash FROM transaction_hashes WHERE block_hash = ?", + ) + .bind(block_hash_ref) + .fetch_all(&self.pool) + .await + .ok()?; + + Some( + rows.into_iter() + .map(|(idx, hash)| (idx as usize, H256::from_slice(&hash))) + .collect(), + ) + } + + /// Extract receipts from a block identified by its hash and number. + pub async fn receipts_from_block_by_hash( + &self, + block_hash: H256, + block_number: SubstrateBlockNumber, + ) -> Result, ClientError> { + self.receipt_extractor + .extract_from_block_by_hash(block_hash, block_number) + .await + } + + /// Extract and insert receipts from the given block (identified by hash + number). + pub async fn insert_block_receipts_by_hash( + &self, + block_hash: H256, + block_number: SubstrateBlockNumber, + ethereum_hash: &H256, + ) -> Result, ClientError> { + let receipts = self.receipts_from_block_by_hash(block_hash, block_number).await?; + self.insert_with_hashes(block_hash, block_number, &receipts, ethereum_hash) + .await?; + Ok(receipts) + } + + /// Get the receipt for a given block hash and transaction index. + pub async fn receipt_by_block_hash_and_index( + &self, + block_hash: &H256, + transaction_index: usize, + ) -> Option { + let block = self.block_provider.block_by_hash(block_hash).await.ok()??; + let (_, receipt) = self + .receipt_extractor + .extract_from_transaction_by_hash(block.hash(), block.number(), transaction_index) + .await + .ok()?; + Some(receipt) + } + + /// Get the receipt for the given transaction hash. + pub async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { + let (block_hash, transaction_index) = self.find_transaction(transaction_hash).await?; + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (_, receipt) = self + .receipt_extractor + .extract_from_transaction_by_hash(block.hash(), block.number(), transaction_index) + .await + .ok()?; + Some(receipt) + } + + /// Get the signed transaction for the given transaction hash. + pub async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option { + let (block_hash, transaction_index) = self.find_transaction(transaction_hash).await?; + let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; + let (signed_tx, _) = self + .receipt_extractor + .extract_from_transaction_by_hash(block.hash(), block.number(), transaction_index) + .await + .ok()?; + Some(signed_tx) + } + + /// Insert receipts for a block that implements [`BlockInfo`]. + pub async fn insert( + &self, + block: &BP::Block, + receipts: &[(TransactionSigned, ReceiptInfo)], + ethereum_hash: &H256, + ) -> Result<(), ClientError> { + self.insert_with_hashes(block.hash(), block.number(), receipts, ethereum_hash) + .await + } + + /// Insert a block mapping from Ethereum block hash to Substrate block hash. + async fn insert_block_mapping(&self, block_map: &BlockHashMap) -> Result<(), ClientError> { + let ethereum_hash_ref = block_map.ethereum_hash.as_ref(); + let substrate_hash_ref = block_map.substrate_hash.as_ref(); + + query!( + r#" + INSERT OR REPLACE INTO eth_to_substrate_blocks (ethereum_block_hash, substrate_block_hash) + VALUES ($1, $2) + "#, + ethereum_hash_ref, + substrate_hash_ref, + ) + .execute(&self.pool) + .await?; + + log::trace!( + target: LOG_TARGET, + "Insert block mapping ethereum block: {:?} -> substrate block: {:?}", + block_map.ethereum_hash, + block_map.substrate_hash + ); + Ok(()) + } + /// Deletes older records from the database. async fn remove(&self, block_mappings: &[BlockHashMap]) -> Result<(), ClientError> { if block_mappings.is_empty() { @@ -297,8 +476,8 @@ impl ReceiptProvider { let placeholders = vec!["?"; block_mappings.len()].join(", "); let sql = format!("DELETE FROM transaction_hashes WHERE block_hash in ({placeholders})"); - let mut delete_tx_query = sqlx::query(&sql); + let sql = format!( "DELETE FROM eth_to_substrate_blocks WHERE substrate_block_hash in ({placeholders})" ); @@ -321,152 +500,7 @@ impl ReceiptProvider { Ok(()) } - /// Read a sync label entry. - pub async fn get_sync_label( - &self, - label: impl SyncStateKey, - ) -> Result, ClientError> { - let label_str = label.to_string(); - let row = query!( - r#" - SELECT block_number, block_hash - FROM sync_state - WHERE label = $1 - "#, - label_str - ) - .fetch_optional(&self.pool) - .await?; - - match row { - Some(row) => { - let block_number: SubstrateBlockNumber = - row.block_number.try_into().map_err(|_| { - sqlx::Error::Decode( - format!("block_number {} overflows u32", row.block_number).into(), - ) - })?; - Ok(Some(SyncCheckpoint { - block_number, - block_hash: row - .block_hash - .filter(|b| b.len() == 32) - .map(|b| H256::from_slice(&b)), - })) - }, - None => Ok(None), - } - } - - /// Upsert a sync label entry. - pub async fn set_sync_label( - &self, - label: impl SyncStateKey, - checkpoint: SyncCheckpoint, - ) -> Result<(), ClientError> { - let label_str = label.to_string(); - let block_number = checkpoint.block_number as i64; - let block_hash = checkpoint.block_hash.map(|h| h.as_bytes().to_vec()); - query!( - r#" - INSERT OR REPLACE INTO sync_state (label, block_number, block_hash) - VALUES ($1, $2, $3) - "#, - label_str, - block_number, - block_hash, - ) - .execute(&self.pool) - .await?; - Ok(()) - } - - /// Delete a sync label entry. - pub async fn delete_sync_label(&self, label: impl SyncStateKey) -> Result<(), ClientError> { - let label_str = label.to_string(); - query!( - r#" - DELETE FROM sync_state WHERE label = $1 - "#, - label_str, - ) - .execute(&self.pool) - .await?; - Ok(()) - } - - /// Atomically update a sync label entry only if the new block number is strictly higher. - /// - /// Inserts the row if it doesn't exist yet. - pub async fn advance_sync_label( - &self, - label: SyncLabel, - checkpoint: SyncCheckpoint, - ) -> Result<(), ClientError> { - upsert_sync_label!(&self.pool, "<", label, checkpoint); - Ok(()) - } - - /// Atomically update a sync label entry only if the new block number is lower. - /// - /// Inserts the row if it doesn't exist yet. - pub async fn recede_sync_label( - &self, - label: SyncLabel, - checkpoint: SyncCheckpoint, - ) -> Result<(), ClientError> { - upsert_sync_label!(&self.pool, ">", label, checkpoint); - Ok(()) - } - - /// Fetch receipts from the given block. - pub async fn receipts_from_block( - &self, - block: &SubstrateBlock, - ) -> Result, ClientError> { - self.receipt_extractor.extract_from_block(block).await - } - - /// Like [`Self::insert_block_receipts`] but writes only to the DB (no cache update). - /// Used for historic sync where fork detection is unnecessary. - pub async fn insert_block_receipts_past( - &self, - block: &SubstrateBlock, - ethereum_hash: &H256, - ) -> Result<(), ClientError> { - let receipts = self.receipts_from_block(block).await?; - self.insert_into_db(block, &receipts, ethereum_hash).await?; - Ok(()) - } - - /// Extract receipts from the given block, insert them, and update the block cache. - pub async fn insert_block_receipts( - &self, - block: &SubstrateBlock, - ethereum_hash: &H256, - ) -> Result, ClientError> { - let receipts = self.receipts_from_block(block).await?; - self.insert(block, &receipts, ethereum_hash).await?; - Ok(receipts) - } - - /// Insert receipts into the provider, updating the in-memory block cache for fork detection. - /// - /// Note: Can be merged into `insert_block_receipts` once is fixed and subxt let - /// us create Mock `SubstrateBlock` - async fn insert( - &self, - block: &impl BlockInfo, - receipts: &[(TransactionSigned, ReceiptInfo)], - ethereum_hash: &H256, - ) -> Result<(), ClientError> { - let block_map = BlockHashMap::new(block.hash(), *ethereum_hash); - self.prune_blocks(block.number(), &block_map).await?; - self.insert_into_db(block, receipts, ethereum_hash).await?; - Ok(()) - } - - /// Handle fork detection (always) and DB pruning (temporary mode only). + /// Prune old blocks when the cache is full or a fork is detected. async fn prune_blocks( &self, block_number: SubstrateBlockNumber, @@ -480,12 +514,10 @@ impl ReceiptProvider { Some(old_block_map) if &old_block_map != block_map => { to_remove.push(old_block_map); - // Now loop through the blocks that were building on top of the old fork and remove - // them. - let mut next_block_number = block_number.saturating_add(1); - while let Some(old_block_map) = block_number_to_hash.remove(&next_block_number) { - to_remove.push(old_block_map); - next_block_number = next_block_number.saturating_add(1); + let mut next = block_number.saturating_add(1); + while let Some(old) = block_number_to_hash.remove(&next) { + to_remove.push(old); + next = next.saturating_add(1); } }, _ => {}, @@ -519,31 +551,37 @@ impl ReceiptProvider { Ok(()) } - /// Insert receipts into the database without updating the in-memory block cache. - async fn insert_into_db( + /// Insert receipts for a block identified by its hashes. + async fn insert_with_hashes( &self, - block: &impl BlockInfo, + substrate_block_hash: H256, + block_number: SubstrateBlockNumber, receipts: &[(TransactionSigned, ReceiptInfo)], ethereum_hash: &H256, ) -> Result<(), ClientError> { - let substrate_block_hash = block.hash(); let substrate_hash_ref = substrate_block_hash.as_ref(); - let block_number = block.number() as i64; + let block_number_i64 = block_number as i64; + let ethereum_hash_ref = ethereum_hash.as_ref(); + let block_map = BlockHashMap::new(substrate_block_hash, *ethereum_hash); + + log::trace!( + target: LOG_TARGET, + "Insert receipts for substrate block #{block_number_i64} {:?}", + substrate_block_hash + ); - log::trace!(target: LOG_TARGET, "Insert receipts for substrate block #{block_number} {:?}", substrate_block_hash); + self.prune_blocks(block_number, &block_map).await?; - // Check if mapping already exists (eg. added when processing best block and we are now - // processing finalized block) - let result = sqlx::query!( - r#"SELECT EXISTS(SELECT 1 FROM eth_to_substrate_blocks WHERE substrate_block_hash = $1) AS "exists!:bool""#, substrate_hash_ref + let exists: bool = sqlx::query_scalar( + "SELECT EXISTS(SELECT 1 FROM eth_to_substrate_blocks WHERE substrate_block_hash = ?)", ) + .bind(substrate_hash_ref) .fetch_one(&self.pool) .await?; // Assuming that if no mapping exists then no relevant entries in transaction_hashes and // logs exist - if !result.exists { - let ethereum_hash_ref = ethereum_hash.as_ref(); + if !exists { for (_, receipt) in receipts { let transaction_hash: &[u8] = receipt.transaction_hash.as_ref(); let transaction_index = receipt.transaction_index.as_u32() as i32; @@ -587,7 +625,7 @@ impl ReceiptProvider { transaction_index, log_index, address, - block_number, + block_number_i64, transaction_hash, topic_0, topic_1, @@ -599,8 +637,7 @@ impl ReceiptProvider { .await?; } } - // Insert block mapping from Ethereum to Substrate hash - let block_map = BlockHashMap::new(substrate_block_hash, *ethereum_hash); + self.insert_block_mapping(&block_map).await?; } @@ -733,99 +770,13 @@ impl ReceiptProvider { Ok(logs) } - - /// Get the number of receipts per block. - pub async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { - let block_hash = block_hash.as_ref(); - let row = query!( - r#" - SELECT COUNT(*) as count - FROM transaction_hashes - WHERE block_hash = $1 - "#, - block_hash - ) - .fetch_one(&self.pool) - .await - .ok()?; - - let count = row.count as usize; - Some(count) - } - - /// Return all transaction hashes for the given block hash. - pub async fn block_transaction_hashes( - &self, - block_hash: &H256, - ) -> Option> { - let block_hash = block_hash.as_ref(); - let rows = query!( - r#" - SELECT transaction_index, transaction_hash - FROM transaction_hashes - WHERE block_hash = $1 - "#, - block_hash - ) - .map(|row| { - let transaction_index = row.transaction_index as usize; - let transaction_hash = H256::from_slice(&row.transaction_hash); - (transaction_index, transaction_hash) - }) - .fetch_all(&self.pool) - .await - .ok()?; - - Some(rows.into_iter().collect()) - } - - /// Get the receipt for the given block hash and transaction index. - pub async fn receipt_by_block_hash_and_index( - &self, - block_hash: &H256, - transaction_index: usize, - ) -> Option { - let block = self.block_provider.block_by_hash(block_hash).await.ok()??; - let (_, receipt) = self - .receipt_extractor - .extract_from_transaction(&block, transaction_index) - .await - .ok()?; - Some(receipt) - } - - /// Get the receipt for the given transaction hash. - pub async fn receipt_by_hash(&self, transaction_hash: &H256) -> Option { - let (block_hash, transaction_index) = self.find_transaction(transaction_hash).await?; - - let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; - let (_, receipt) = self - .receipt_extractor - .extract_from_transaction(&block, transaction_index) - .await - .ok()?; - Some(receipt) - } - - /// Get the signed transaction for the given transaction hash. - pub async fn signed_tx_by_hash(&self, transaction_hash: &H256) -> Option { - let (block_hash, transaction_index) = self.find_transaction(transaction_hash).await?; - - let block = self.block_provider.block_by_hash(&block_hash).await.ok()??; - let (signed_tx, _) = self - .receipt_extractor - .extract_from_transaction(&block, transaction_index) - .await - .ok()?; - Some(signed_tx) - } } #[cfg(test)] mod tests { use super::*; - use crate::test::{MockBlockInfo, MockBlockInfoProvider}; - use pallet_revive::evm::{BlockTag, ReceiptInfo, TransactionSigned}; + use crate::block_info_provider::test::{MockBlockInfo, MockBlockInfoProvider}; + use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; use pretty_assertions::assert_eq; use sp_core::{H160, H256}; use sqlx::SqlitePool; @@ -852,7 +803,7 @@ mod tests { fn mock_provider() -> ReceiptProvider { ReceiptProvider { pool: SqlitePool::connect_lazy("sqlite::memory:").unwrap(), - block_provider: MockBlockInfoProvider {}, + block_provider: MockBlockInfoProvider, receipt_extractor: ReceiptExtractor::new_mock(), keep_latest_n_blocks: None, block_number_to_hashes: Default::default(), @@ -906,7 +857,9 @@ mod tests { let ethereum_hash = H256::from([1_u8; 32]); let block_map = BlockHashMap::new(block.hash(), ethereum_hash); - provider.insert(&block, &receipts, ðereum_hash).await?; + provider + .insert_with_hashes(block.hash(), block.number(), &receipts, ðereum_hash) + .await?; let row = provider.find_transaction(&receipts[0].1.transaction_hash).await; assert_eq!(row, Some((block.hash, 0))); @@ -937,7 +890,9 @@ mod tests { }, )]; let ethereum_hash = H256::from([(i + 1) as u8; 32]); - provider.insert(&block, &receipts, ðereum_hash).await?; + provider + .insert_with_hashes(block.hash(), block.number(), &receipts, ðereum_hash) + .await?; } assert_eq!(count(&provider.pool, "transaction_hashes", None).await, n); assert_eq!(count(&provider.pool, "logs", None).await, n); @@ -951,7 +906,7 @@ mod tests { async fn test_fork(pool: SqlitePool) -> anyhow::Result<()> { let provider = setup_sqlite_provider(pool).await; - let build_block = |seed, number| { + let build_block = |seed: u8, number: u32| { let block = MockBlockInfo { hash: H256::from([seed; 32]), number }; let transaction_hash = H256::from([seed; 32]); let receipts = vec![( @@ -971,15 +926,22 @@ mod tests { (block, receipts, ethereum_hash) }; - // Build 4 blocks on consecutive heights: 0,1,2,3. - let (block0, receipts, ethereum_hash_0) = build_block(0, 0); - provider.insert(&block0, &receipts, ðereum_hash_0).await?; - let (block1, receipts, ethereum_hash_1) = build_block(1, 1); - provider.insert(&block1, &receipts, ðereum_hash_1).await?; - let (block2, receipts, ethereum_hash_2) = build_block(2, 2); - provider.insert(&block2, &receipts, ðereum_hash_2).await?; - let (block3, receipts, ethereum_hash_3) = build_block(3, 3); - provider.insert(&block3, &receipts, ðereum_hash_3).await?; + let (block0, receipts, eh0) = build_block(0, 0); + provider + .insert_with_hashes(block0.hash(), block0.number(), &receipts, &eh0) + .await?; + let (block1, receipts, eh1) = build_block(1, 1); + provider + .insert_with_hashes(block1.hash(), block1.number(), &receipts, &eh1) + .await?; + let (block2, receipts, eh2) = build_block(2, 2); + provider + .insert_with_hashes(block2.hash(), block2.number(), &receipts, &eh2) + .await?; + let (block3, receipts, eh3) = build_block(3, 3); + provider + .insert_with_hashes(block3.hash(), block3.number(), &receipts, &eh3) + .await?; assert_eq!(count(&provider.pool, "transaction_hashes", None).await, 4); assert_eq!(count(&provider.pool, "logs", None).await, 4); @@ -987,17 +949,19 @@ mod tests { assert_eq!( provider.block_number_to_hashes.lock().await.clone(), [ - (0, BlockHashMap::new(block0.hash, ethereum_hash_0)), - (1, BlockHashMap::new(block1.hash, ethereum_hash_1)), - (2, BlockHashMap::new(block2.hash, ethereum_hash_2)), - (3, BlockHashMap::new(block3.hash, ethereum_hash_3)) + (0, BlockHashMap::new(block0.hash, eh0)), + (1, BlockHashMap::new(block1.hash, eh1)), + (2, BlockHashMap::new(block2.hash, eh2)), + (3, BlockHashMap::new(block3.hash, eh3)) ] .into(), ); - // Now build another block on height 1. - let (fork_block, receipts, ethereum_hash_fork) = build_block(4, 1); - provider.insert(&fork_block, &receipts, ðereum_hash_fork).await?; + // Fork at height 1. + let (fork_block, receipts, eh_fork) = build_block(4, 1); + provider + .insert_with_hashes(fork_block.hash(), fork_block.number(), &receipts, &eh_fork) + .await?; assert_eq!(count(&provider.pool, "transaction_hashes", None).await, 2); assert_eq!(count(&provider.pool, "logs", None).await, 2); @@ -1006,8 +970,8 @@ mod tests { assert_eq!( provider.block_number_to_hashes.lock().await.clone(), [ - (0, BlockHashMap::new(block0.hash, ethereum_hash_0)), - (1, BlockHashMap::new(fork_block.hash, ethereum_hash_fork)) + (0, BlockHashMap::new(block0.hash, eh0)), + (1, BlockHashMap::new(fork_block.hash, eh_fork)) ] .into(), ); @@ -1537,7 +1501,13 @@ mod tests { #[sqlx::test] async fn persistent_mode_caps_in_memory_map(pool: SqlitePool) -> anyhow::Result<()> { // Persistent DB mode: keep_latest_n_blocks = None - let provider = mock_provider().with_pool(pool); + let provider = ReceiptProvider { + pool, + block_provider: MockBlockInfoProvider, + receipt_extractor: ReceiptExtractor::new_mock(), + keep_latest_n_blocks: None, + block_number_to_hashes: Default::default(), + }; // Insert more than MAX_CACHED_BLOCKS blocks. let start_block: u64 = 1; @@ -1558,7 +1528,9 @@ mod tests { }, )]; let ethereum_hash = H256::from_low_u64_be(i + 1); - provider.insert(&block, &receipts, ðereum_hash).await?; + provider + .insert_with_hashes(block.hash(), block.number(), &receipts, ðereum_hash) + .await?; } // The map is capped at MAX_CACHED_BLOCKS. diff --git a/substrate/frame/revive/rpc/src/substrate_client.rs b/substrate/frame/revive/rpc/src/substrate_client.rs new file mode 100644 index 0000000000000..9208e1fedf503 --- /dev/null +++ b/substrate/frame/revive/rpc/src/substrate_client.rs @@ -0,0 +1,246 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) 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. + +//! Substrate client trait and shared types for the ETH RPC server. +//! +//! This module provides a [`SubstrateClientT`] trait that abstracts over the +//! Substrate node client, allowing the ETH RPC server to be integrated directly +//! into a parachain node (e.g. Asset Hub) without requiring a separate `subxt` +//! connection. +use crate::{ + block_info_provider::BlockInfo, + client::{Balance, SubscriptionType, SubstrateBlockHash, SubstrateBlockNumber}, +}; +use jsonrpsee::core::async_trait; +use pallet_revive::{ + EthTransactInfo, + evm::{ + Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, + TracerType, U256, + }, +}; +use sc_transaction_pool_api::TransactionStatus; +use sp_core::H256; +use sp_weights::Weight; +use std::future::Future; + +/// A raw extrinsic payload with its index in the block. +#[derive(Clone, Debug)] +pub struct RawExtrinsic { + /// The SCALE-encoded extrinsic bytes. + pub payload: Vec, + /// The extrinsic index within the block. + pub index: usize, +} + +#[derive(Clone, Debug)] +pub struct NodeHealth { + pub peers: usize, + pub is_syncing: bool, + pub should_have_peers: bool, +} + +/// The result of submitting a transaction to the pool. +pub type SubmitResult = TransactionStatus; + +/// The core trait that the ETH RPC server requires from the underlying Substrate node. +#[async_trait] +pub trait SubstrateClientT: Send + Sync + Clone + 'static { + /// The concrete block-info type returned by this client. + type BlockInfo: BlockInfo + Clone + Send + Sync + 'static; + + /// Return the EVM chain-id constant. + fn chain_id(&self) -> u64; + + /// Return the maximum block weight (used to compute the block gas limit). + fn max_block_weight(&self) -> Weight; + + /// Fetch a block by its Substrate hash. + async fn block_by_hash( + &self, + hash: &SubstrateBlockHash, + ) -> Result, crate::client::ClientError>; + + /// Fetch a block by its block number. + async fn block_by_number( + &self, + number: SubstrateBlockNumber, + ) -> Result, crate::client::ClientError>; + + /// Return metadata for the current best (latest) block. + async fn latest_block(&self) -> Result; + + /// Return metadata for the current finalized block. + async fn latest_finalized_block(&self) -> Result; + + /// Dry-run a transaction at the given block. + async fn dry_run( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result, crate::client::ClientError>; + + /// Return the current gas price. + async fn gas_price( + &self, + block_hash: SubstrateBlockHash, + ) -> Result; + + /// Return the account balance. + async fn balance( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result; + + /// Return the account nonce. + async fn nonce( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result; + + /// Return the contract code. + async fn code( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result, crate::client::ClientError>; + + /// Return contract storage at the given slot. + async fn get_storage( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + key: [u8; 32], + ) -> Result>, crate::client::ClientError>; + + /// Return the Ethereum block representation for the given Substrate block. + async fn eth_block( + &self, + block_hash: SubstrateBlockHash, + ) -> Result; + + /// Return the Ethereum block hash for the given block number. + async fn eth_block_hash( + &self, + block_hash: SubstrateBlockHash, + number: U256, + ) -> Result, crate::client::ClientError>; + + /// Return the per-transaction receipt gas info for the given block. + async fn eth_receipt_data( + &self, + block_hash: SubstrateBlockHash, + ) -> Result, crate::client::ClientError>; + + /// Trace the given block. + async fn trace_block( + &self, + block_hash: SubstrateBlockHash, + block: sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >, + config: TracerType, + ) -> Result, crate::client::ClientError>; + + /// Trace a single transaction within a block. + async fn trace_tx( + &self, + block_hash: SubstrateBlockHash, + block: sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >, + transaction_index: u32, + config: TracerType, + ) -> Result; + + /// Trace a dry-run call. + async fn trace_call( + &self, + block_hash: SubstrateBlockHash, + transaction: GenericTransaction, + config: TracerType, + ) -> Result; + + /// Submit an unsigned `eth_transact` extrinsic and return the first + /// transaction status received from the pool. + async fn submit_extrinsic( + &self, + payload: Vec, + ) -> Result; + + /// Return the node sync state. + async fn sync_state( + &self, + ) -> Result, crate::client::ClientError>; + + /// Return the node health. + async fn system_health(&self) -> Result; + + /// Return whether the node has automine enabled. + async fn get_automine(&self) -> bool; + + /// Subscribe to new best or finalized blocks. + async fn subscribe_blocks( + &self, + subscription_type: SubscriptionType, + callback: F, + ) -> Result<(), crate::client::ClientError> + where + F: Fn(Self::BlockInfo) -> Fut + Send + Sync, + Fut: Future> + Send; + + /// Fetch an opaque signed block (used for tracing). + async fn signed_block( + &self, + block_hash: SubstrateBlockHash, + ) -> Result< + sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >, + crate::client::ClientError, + >; + + /// Return all raw extrinsics in the given block. + async fn block_extrinsics( + &self, + block_hash: SubstrateBlockHash, + ) -> Result, crate::client::ClientError>; + + /// Scan block events for the given extrinsic index and return its + /// post-dispatch weight (if available). + async fn extrinsic_post_dispatch_weight( + &self, + _block_hash: SubstrateBlockHash, + _extrinsic_index: usize, + ) -> Option { + None + } + + /// Return the post-dispatch weight for a given transaction hash. + async fn post_dispatch_weight( + &self, + _tx_hash: &SubstrateBlockHash, + ) -> Option { + None + } +} diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index f36a565928e49..f8fd04a38156c 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -21,7 +21,7 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; #[subxt::subxt( runtime_metadata_path = "$OUT_DIR/revive_chain.scale", - // TODO remove once subxt use the same U256 type + // TODO remove once subxt uses the same U256 type substitute_type( path = "primitive_types::U256", with = "::subxt::utils::Static<::sp_core::U256>" @@ -79,3 +79,627 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; )] mod src_chain {} pub use src_chain::*; + +use crate::{ + block_info_provider::{BlockInfo, BlockInfoProvider}, + client::{Balance, ClientError, SubscriptionType, SubstrateBlockHash, SubstrateBlockNumber}, + substrate_client::{NodeHealth, RawExtrinsic, SubmitResult, SubstrateClientT}, +}; +use futures::TryStreamExt; +use jsonrpsee::core::async_trait; +use pallet_revive::{ + EthTransactInfo, + evm::{ + Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, + TracerType, U256, + }, +}; +use sp_core::H256; +use sp_weights::Weight; +use std::{future::Future, sync::Arc, time::Duration}; +use subxt::{ + OnlineClient, + backend::{ + StreamOf, StreamOfResults, + legacy::{LegacyRpcMethods, rpc_methods::TransactionStatus as SubxtTxStatus}, + rpc::RpcClient, + }, + ext::subxt_rpcs::rpc_params, +}; +use tokio::sync::RwLock; + +/// The substrate block type (subxt-backed). +pub type SubstrateBlock = subxt::blocks::Block>; + +/// The substrate block header. +pub type SubstrateBlockHeader = ::Header; + +fn system_health_from_subxt(h: subxt::backend::legacy::rpc_methods::SystemHealth) -> NodeHealth { + NodeHealth { peers: h.peers, is_syncing: h.is_syncing, should_have_peers: h.should_have_peers } +} + +/// A lightweight block-info value returned by [`SubxtClient`]. +#[derive(Clone, Debug)] +pub struct SubxtBlockInfo { + pub hash: SubstrateBlockHash, + pub number: SubstrateBlockNumber, + pub parent_hash: SubstrateBlockHash, +} + +impl BlockInfo for SubxtBlockInfo { + fn hash(&self) -> H256 { + self.hash + } + + fn number(&self) -> SubstrateBlockNumber { + self.number + } + + fn parent_hash(&self) -> H256 { + self.parent_hash + } +} + +/// The subxt-backed block info provider. +#[derive(Clone)] +pub struct SubxtBlockInfoProvider { + /// The latest block. + latest_block: Arc>>, + + /// The latest finalized block. + latest_finalized_block: Arc>>, + + /// The rpc client, used to fetch blocks not in the cache. + rpc: LegacyRpcMethods, + + /// The api client, used to fetch blocks not in the cache. + api: OnlineClient, +} + +impl SubxtBlockInfoProvider { + pub async fn new( + api: OnlineClient, + rpc: LegacyRpcMethods, + ) -> Result { + let latest = Arc::new(api.blocks().at_latest().await?); + Ok(Self { + api, + rpc, + latest_block: Arc::new(RwLock::new(latest.clone())), + latest_finalized_block: Arc::new(RwLock::new(latest)), + }) + } +} + +#[async_trait] +impl BlockInfoProvider for SubxtBlockInfoProvider { + type Block = SubstrateBlock; + + async fn update_latest(&self, block: Arc, subscription_type: SubscriptionType) { + let mut latest = match subscription_type { + SubscriptionType::FinalizedBlocks => self.latest_finalized_block.write().await, + SubscriptionType::BestBlocks => self.latest_block.write().await, + }; + *latest = block; + } + + async fn latest_block(&self) -> Arc { + self.latest_block.read().await.clone() + } + + async fn latest_finalized_block(&self) -> Arc { + self.latest_finalized_block.read().await.clone() + } + + async fn block_by_number( + &self, + block_number: SubstrateBlockNumber, + ) -> Result>, ClientError> { + let latest = self.latest_block().await; + if block_number == latest.number() { + return Ok(Some(latest)); + } + + let latest_finalized = self.latest_finalized_block().await; + if block_number == latest_finalized.number() { + return Ok(Some(latest_finalized)); + } + + let Some(hash) = self.rpc.chain_get_block_hash(Some(block_number.into())).await? else { + return Ok(None); + }; + + match self.api.blocks().at(hash).await { + Ok(block) => Ok(Some(Arc::new(block))), + Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), + Err(err) => Err(err.into()), + } + } + + async fn block_by_hash(&self, hash: &H256) -> Result>, ClientError> { + let latest = self.latest_block().await; + if hash == &latest.hash() { + return Ok(Some(latest)); + } + + let latest_finalized = self.latest_finalized_block().await; + if hash == &latest_finalized.hash() { + return Ok(Some(latest_finalized)); + } + + match self.api.blocks().at(*hash).await { + Ok(block) => Ok(Some(Arc::new(block))), + Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), + Err(err) => Err(err.into()), + } + } +} + +// Teach the generic code about SubstrateBlock's block-info fields. +impl BlockInfo for SubstrateBlock { + fn hash(&self) -> H256 { + SubstrateBlock::hash(self) + } + + fn number(&self) -> SubstrateBlockNumber { + SubstrateBlock::number(self) + } + + fn parent_hash(&self) -> H256 { + self.header().parent_hash + } +} + +/// [`SubstrateClientT`] implementation backed by a `subxt` WebSocket connection. +#[derive(Clone)] +pub struct SubxtClient { + pub(crate) api: OnlineClient, + pub(crate) rpc_client: RpcClient, + pub(crate) rpc: LegacyRpcMethods, + pub(crate) chain_id: u64, + pub(crate) max_block_weight: Weight, +} + +/// Maximum number of retries when fetching the initial block during startup. +const MAX_STARTUP_RETRIES: u32 = 10; + +/// Delay between startup retries. +const STARTUP_RETRY_DELAY_MS: u64 = 500; + +impl SubxtClient { + pub async fn new( + api: OnlineClient, + rpc_client: RpcClient, + rpc: LegacyRpcMethods, + ) -> Result { + let latest = Self::fetch_latest_block_with_retry(&api).await?; + log::info!( + target: crate::LOG_TARGET, + "🔗 Connected to node at block #{} ({:?})", + latest.number(), + latest.hash() + ); + + let chain_id = { + let query = constants().revive().chain_id(); + api.constants().at(&query)? + }; + + let max_block_weight = { + let query = constants().system().block_weights(); + let weights = api.constants().at(&query)?; + let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block); + max_block.0 + }; + + Ok(Self { api, rpc_client, rpc, chain_id, max_block_weight }) + } + + /// Fetch the latest block, retrying up to [`MAX_STARTUP_RETRIES`] times. + async fn fetch_latest_block_with_retry( + api: &OnlineClient, + ) -> Result { + let retry_delay = Duration::from_millis(STARTUP_RETRY_DELAY_MS); + let mut last_error: Option = None; + + for attempt in 0..MAX_STARTUP_RETRIES { + match api.blocks().at_latest().await { + Ok(block) => { + if attempt > 0 { + log::info!( + target: crate::LOG_TARGET, + "✅ Fetched latest block after {} attempt(s)", + attempt + 1 + ); + } + return Ok(block); + }, + Err(err) => { + log::warn!( + target: crate::LOG_TARGET, + "⏳ Node not ready yet (attempt {}/{}): {err}. \ + Retrying in {}ms...", + attempt + 1, + MAX_STARTUP_RETRIES, + retry_delay.as_millis(), + ); + last_error = Some(err); + tokio::time::sleep(retry_delay).await; + }, + } + } + + let err = last_error.expect("loop ran at least once; qed"); + log::error!( + target: crate::LOG_TARGET, + "❌ Failed to fetch the latest block after {MAX_STARTUP_RETRIES} attempts. \ + The node may be misconfigured or still starting. Last error: {err}" + ); + Err(ClientError::from(err)) + } + + /// Build a [`SubxtBlockInfo`] from a [`SubstrateBlock`] reference. + fn block_info_from_subxt(block: &SubstrateBlock) -> SubxtBlockInfo { + SubxtBlockInfo { + hash: block.hash(), + number: block.number(), + parent_hash: block.header().parent_hash, + } + } +} + +#[async_trait] +impl SubstrateClientT for SubxtClient { + type BlockInfo = SubxtBlockInfo; + + fn chain_id(&self) -> u64 { + self.chain_id + } + + fn max_block_weight(&self) -> Weight { + self.max_block_weight + } + + async fn block_by_hash( + &self, + hash: &SubstrateBlockHash, + ) -> Result, ClientError> { + match self.api.blocks().at(*hash).await { + Ok(b) => Ok(Some(Self::block_info_from_subxt(&b))), + Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => Ok(None), + Err(e) => Err(e.into()), + } + } + + async fn block_by_number( + &self, + number: SubstrateBlockNumber, + ) -> Result, ClientError> { + let Some(hash) = self.rpc.chain_get_block_hash(Some(number.into())).await? else { + return Ok(None); + }; + self.block_by_hash(&hash).await + } + + async fn latest_block(&self) -> Result { + let block = self.api.blocks().at_latest().await?; + Ok(Self::block_info_from_subxt(&block)) + } + + async fn latest_finalized_block(&self) -> Result { + let hash = self.rpc.chain_get_finalized_head().await?; + match self.api.blocks().at(hash).await { + Ok(b) => Ok(Self::block_info_from_subxt(&b)), + Err(e) => Err(e.into()), + } + } + + async fn dry_run( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result, ClientError> { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)).dry_run(tx, block).await + } + + async fn gas_price(&self, block_hash: SubstrateBlockHash) -> Result { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)).gas_price().await + } + + async fn balance( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)).balance(address).await + } + + async fn nonce( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)).nonce(address).await + } + + async fn code( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + ) -> Result, ClientError> { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)).code(address).await + } + + async fn get_storage( + &self, + block_hash: SubstrateBlockHash, + address: sp_core::H160, + key: [u8; 32], + ) -> Result>, ClientError> { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)) + .get_storage(address, key) + .await + } + + async fn eth_block(&self, block_hash: SubstrateBlockHash) -> Result { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)).eth_block().await + } + + async fn eth_block_hash( + &self, + block_hash: SubstrateBlockHash, + number: U256, + ) -> Result, ClientError> { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)) + .eth_block_hash(number) + .await + } + + async fn eth_receipt_data( + &self, + block_hash: SubstrateBlockHash, + ) -> Result, ClientError> { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)).eth_receipt_data().await + } + + async fn trace_block( + &self, + _block_hash: SubstrateBlockHash, + block: sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >, + config: TracerType, + ) -> Result, ClientError> { + use crate::client::runtime_api::RuntimeApi; + let parent = block.header.parent_hash; + RuntimeApi::new(self.api.runtime_api().at(parent)) + .trace_block(block, config) + .await + } + + async fn trace_tx( + &self, + _block_hash: SubstrateBlockHash, + block: sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >, + transaction_index: u32, + config: TracerType, + ) -> Result { + use crate::client::runtime_api::RuntimeApi; + let parent = block.header.parent_hash; + RuntimeApi::new(self.api.runtime_api().at(parent)) + .trace_tx(block, transaction_index, config) + .await + } + + async fn trace_call( + &self, + block_hash: SubstrateBlockHash, + transaction: GenericTransaction, + config: TracerType, + ) -> Result { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)) + .trace_call(transaction, config) + .await + } + + async fn submit_extrinsic(&self, payload: Vec) -> Result { + let call = tx().revive().eth_transact(payload); + let ext = self.api.tx().create_unsigned(&call)?; + + let sub = self + .rpc_client + .subscribe( + "author_submitAndWatchExtrinsic", + rpc_params![to_hex(ext.encoded())], + "author_unwatchExtrinsic", + ) + .await?; + + let mut stream: StreamOfResults> = + StreamOf::new(Box::pin(sub.map_err(|e| e.into()))); + + tokio::time::timeout(std::time::Duration::from_secs(5), async { + while let Some(status) = stream.next().await { + let subxt_status: SubxtTxStatus = + status.map_err(ClientError::from)?; + let pool_status = subxt_tx_status_to_submit_result(subxt_status); + + match pool_status { + SubmitResult::Usurped(_) | SubmitResult::Dropped | SubmitResult::Invalid => { + return Err(ClientError::SubmitError(crate::client::SubmitError::from( + pool_status, + ))); + }, + other => return Ok(other), + } + } + Err(ClientError::SubmitError(crate::client::SubmitError::StreamEnded)) + }) + .await + .map_err(|_| ClientError::Timeout)? + } + + async fn sync_state( + &self, + ) -> Result, ClientError> { + let sync_state: sc_rpc::system::SyncState = + self.rpc_client.request("system_syncState", Default::default()).await?; + Ok(sync_state) + } + + async fn system_health(&self) -> Result { + Ok(system_health_from_subxt(self.rpc.system_health().await?)) + } + + async fn get_automine(&self) -> bool { + self.rpc_client + .request::("getAutomine", rpc_params![]) + .await + .unwrap_or(false) + } + + async fn subscribe_blocks( + &self, + subscription_type: SubscriptionType, + callback: F, + ) -> Result<(), ClientError> + where + F: Fn(SubxtBlockInfo) -> Fut + Send + Sync, + Fut: Future> + Send, + { + let mut block_stream = match subscription_type { + SubscriptionType::BestBlocks => self.api.blocks().subscribe_best().await, + SubscriptionType::FinalizedBlocks => self.api.blocks().subscribe_finalized().await, + }?; + + while let Some(block) = block_stream.next().await { + let block = match block { + Ok(b) => b, + Err(err) => { + if err.is_disconnected_will_reconnect() { + log::warn!( + target: crate::LOG_TARGET, + "RPC connection lost ({subscription_type:?}): {err:?}" + ); + continue; + } + return Err(err.into()); + }, + }; + + let info = Self::block_info_from_subxt(&block); + + if let Err(err) = callback(info).await { + log::error!( + target: crate::LOG_TARGET, + "block callback failed ({subscription_type:?}): {err:?}" + ); + } + } + Ok(()) + } + + async fn signed_block( + &self, + block_hash: SubstrateBlockHash, + ) -> Result< + sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >, + ClientError, + > { + let signed: sp_runtime::generic::SignedBlock< + sp_runtime::generic::Block< + sp_runtime::generic::Header, + sp_runtime::OpaqueExtrinsic, + >, + > = self.rpc_client.request("chain_getBlock", rpc_params![block_hash]).await?; + Ok(signed.block) + } + + async fn block_extrinsics( + &self, + block_hash: SubstrateBlockHash, + ) -> Result, ClientError> { + let block = self.api.blocks().at(block_hash).await?; + let extrinsics = block.extrinsics().await?; + Ok(extrinsics + .iter() + .enumerate() + .map(|(index, ext)| RawExtrinsic { payload: ext.bytes().to_vec(), index }) + .collect()) + } + + async fn extrinsic_post_dispatch_weight( + &self, + block_hash: SubstrateBlockHash, + extrinsic_index: usize, + ) -> Option { + use system::events::ExtrinsicSuccess; + let block = self.api.blocks().at(block_hash).await.ok()?; + let ext = block.extrinsics().await.ok()?.iter().nth(extrinsic_index)?; + let event = ext.events().await.ok()?.find_first::().ok()??; + Some(event.dispatch_info.weight.0) + } +} + +fn to_hex(bytes: impl AsRef<[u8]>) -> String { + format!("0x{}", hex::encode(bytes.as_ref())) +} + +fn subxt_tx_status_to_submit_result(s: SubxtTxStatus) -> SubmitResult { + use sc_transaction_pool_api::TransactionStatus as Pool; + match s { + SubxtTxStatus::Future => Pool::Future, + SubxtTxStatus::Ready => Pool::Ready, + SubxtTxStatus::Broadcast(peers) => Pool::Broadcast(peers), + SubxtTxStatus::InBlock(hash) => Pool::InBlock((hash, 0)), + SubxtTxStatus::Retracted(hash) => Pool::Retracted(hash), + SubxtTxStatus::FinalityTimeout(hash) => Pool::FinalityTimeout(hash), + SubxtTxStatus::Finalized(hash) => Pool::Finalized((hash, 0)), + SubxtTxStatus::Usurped(hash) => Pool::Usurped(hash), + SubxtTxStatus::Dropped => Pool::Dropped, + SubxtTxStatus::Invalid => Pool::Invalid, + } +} + +use subxt::backend::rpc::reconnecting_rpc_client::{ + ExponentialBackoff, RpcClient as ReconnectingRpcClient, +}; + +pub async fn connect( + node_rpc_url: &str, + max_request_size: u32, + max_response_size: u32, +) -> Result<(OnlineClient, RpcClient, LegacyRpcMethods), ClientError> +{ + log::info!(target: crate::LOG_TARGET, "🌐 Connecting to node at: {node_rpc_url} ..."); + let rpc_client = ReconnectingRpcClient::builder() + .retry_policy(ExponentialBackoff::from_millis(100).max_delay(Duration::from_secs(10))) + .max_request_size(max_request_size) + .max_response_size(max_response_size) + .build(node_rpc_url.to_string()) + .await?; + let rpc_client = RpcClient::new(rpc_client); + log::info!(target: crate::LOG_TARGET, "🌟 Connected to node at: {node_rpc_url}"); + + let api = OnlineClient::::from_rpc_client(rpc_client.clone()).await?; + let rpc = LegacyRpcMethods::::new(rpc_client.clone()); + Ok((api, rpc_client, rpc)) +} From 278654869c5ea95b5a43f7f4979fbd80e2efe0f7 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 25 Mar 2026 16:46:20 +0100 Subject: [PATCH 02/54] nit --- .github/workflows/tests-evm.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 3cf8d02f4c8f4..b69fce17f90d6 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -71,7 +71,7 @@ jobs: - name: script run: | - forklift cargo build --locked --release -p pallet-revive-eth-rpc --bin eth-rpc + forklift cargo build --locked --release -p pallet-revive-eth-rpc --bin eth-rpc --features subxt forklift cargo build --locked --release -p revive-dev-node --bin revive-dev-node - name: Checkout evm-tests From 10092294ee73c2cc5a8820e40f446fca9ba296dc Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Thu, 26 Mar 2026 14:54:42 +0100 Subject: [PATCH 03/54] revert dev-node changes --- Cargo.lock | 68 ++-------- Cargo.toml | 2 + cumulus/polkadot-omni-node/lib/Cargo.toml | 2 +- .../frame/revive/dev-node/node/Cargo.toml | 36 +---- substrate/frame/revive/dev-node/node/build.rs | 2 +- .../revive/dev-node/node/src/chain_spec.rs | 5 +- .../frame/revive/dev-node/node/src/cli.rs | 2 +- .../frame/revive/dev-node/node/src/command.rs | 3 +- .../frame/revive/dev-node/node/src/main.rs | 2 +- .../frame/revive/dev-node/node/src/rpc.rs | 9 +- .../frame/revive/dev-node/node/src/service.rs | 17 ++- .../frame/revive/dev-node/runtime/Cargo.toml | 109 +++------------ .../frame/revive/dev-node/runtime/build.rs | 2 +- .../frame/revive/dev-node/runtime/src/lib.rs | 128 ++++++++---------- .../frame/revive/gen-metadata/Cargo.toml | 16 +++ .../frame/revive/gen-metadata/src/main.rs | 42 ++++++ substrate/frame/revive/rpc/Cargo.toml | 4 - substrate/frame/revive/rpc/build.rs | 14 +- .../frame/revive/rpc/src/subxt_client.rs | 2 +- 19 files changed, 178 insertions(+), 287 deletions(-) create mode 100644 substrate/frame/revive/gen-metadata/Cargo.toml create mode 100644 substrate/frame/revive/gen-metadata/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 2bb7768e6fbfa..d02a9c9fdea7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13348,7 +13348,6 @@ name = "pallet-revive-eth-rpc" version = "0.1.0" dependencies = [ "anyhow", - "asset-hub-westend-runtime", "async-trait", "clap", "derive_more 0.99.17", @@ -13363,7 +13362,6 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "revive-dev-node", - "revive-dev-runtime", "rlp 0.6.1", "sc-cli", "sc-client-api 28.0.0", @@ -18823,34 +18821,8 @@ dependencies = [ "futures", "futures-timer", "jsonrpsee", - "pallet-timestamp", - "pallet-transaction-payment-rpc", + "polkadot-sdk", "revive-dev-runtime", - "sc-basic-authorship", - "sc-cli", - "sc-client-api 28.0.0", - "sc-consensus", - "sc-consensus-manual-seal", - "sc-executor 0.32.0", - "sc-network 0.34.0", - "sc-rpc", - "sc-rpc-api", - "sc-service", - "sc-telemetry 15.0.0", - "sc-transaction-pool", - "sc-transaction-pool-api 28.0.0", - "sp-api 26.0.0", - "sp-block-builder", - "sp-blockchain 28.0.0", - "sp-core 28.0.0", - "sp-genesis-builder 0.8.0", - "sp-inherents 26.0.0", - "sp-io 30.0.0", - "sp-keyring", - "sp-runtime 31.0.1", - "sp-timestamp", - "substrate-build-script-utils", - "substrate-frame-rpc-system", ] [[package]] @@ -18858,41 +18830,19 @@ name = "revive-dev-runtime" version = "0.1.0" dependencies = [ "array-bytes 6.2.2", - "frame-executive", - "frame-support", - "frame-system", - "frame-system-rpc-runtime-api", - "frame-try-runtime", - "pallet-balances", - "pallet-revive", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "parachains-common", "parity-scale-codec", - "polkadot-primitives", - "polkadot-runtime-common", + "polkadot-sdk", "scale-info", "serde_json", - "sp-api 26.0.0", - "sp-block-builder", - "sp-consensus-aura", - "sp-core 28.0.0", "sp-debug-derive 14.0.0", - "sp-genesis-builder 0.8.0", - "sp-inherents 26.0.0", +] + +[[package]] +name = "revive-gen-metadata" +version = "0.0.0" +dependencies = [ + "revive-dev-runtime", "sp-io 30.0.0", - "sp-keyring", - "sp-offchain", - "sp-runtime 31.0.1", - "sp-session", - "sp-std 14.0.0", - "sp-tracing 16.0.0", - "sp-transaction-pool", - "sp-version 29.0.0", - "sp-weights 27.0.0", - "substrate-wasm-builder", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 543bdaad6cbe1..da84087edf449 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -422,6 +422,7 @@ members = [ "substrate/frame/revive/dev-node/node", "substrate/frame/revive/dev-node/runtime", "substrate/frame/revive/fixtures", + "substrate/frame/revive/gen-metadata", "substrate/frame/revive/proc-macro", "substrate/frame/revive/rpc", "substrate/frame/revive/uapi", @@ -1065,6 +1066,7 @@ pallet-staking-reward-fn = { path = "substrate/frame/staking/reward-fn", default pallet-staking-runtime-api = { path = "substrate/frame/staking/runtime-api", default-features = false } revive-dev-node = { path = "substrate/frame/revive/dev-node/node" } revive-dev-runtime = { path = "substrate/frame/revive/dev-node/runtime" } +revive-gen-metadata = { path = "substrate/frame/revive/gen-metadata" } # TODO: remove the reward stuff as they are not needed here pallet-derivatives = { path = "polkadot/xcm/pallet-derivatives", default-features = false } pallet-staking-async = { path = "substrate/frame/staking-async", default-features = false } diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index a60bd269b3073..eeb9892c8aee0 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -117,7 +117,7 @@ tokio = { version = "1.43.1", features = ["macros", "parking_lot", "time"] } wait-timeout = { workspace = true } [features] -default = [] +default = ["eth-rpc"] eth-rpc = ["dep:pallet-revive-eth-rpc"] rococo-native = ["polkadot-cli/rococo-native"] westend-native = ["polkadot-cli/westend-native"] diff --git a/substrate/frame/revive/dev-node/node/Cargo.toml b/substrate/frame/revive/dev-node/node/Cargo.toml index f683df03c990d..15b5a2438f8ac 100644 --- a/substrate/frame/revive/dev-node/node/Cargo.toml +++ b/substrate/frame/revive/dev-node/node/Cargo.toml @@ -22,41 +22,15 @@ futures = { features = ["thread-pool"], workspace = true } futures-timer = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } +polkadot-sdk = { workspace = true, features = ["experimental", "node"] } revive-dev-runtime = { workspace = true } -sc-basic-authorship = { workspace = true, default-features = true } -sc-cli = { workspace = true, default-features = true } -sc-client-api = { workspace = true, default-features = true } -sc-consensus = { workspace = true, default-features = true } -sc-consensus-manual-seal = { workspace = true, default-features = true } -sc-executor = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } -sc-rpc = { workspace = true, default-features = true } -sc-rpc-api = { workspace = true, default-features = true } -sc-service = { workspace = true, default-features = true } -sc-telemetry = { workspace = true, default-features = true } -sc-transaction-pool = { workspace = true, default-features = true } -sc-transaction-pool-api = { workspace = true, default-features = true } -sp-api = { workspace = true, default-features = true } -sp-block-builder = { workspace = true, default-features = true } -sp-blockchain = { workspace = true, default-features = true } -sp-core = { workspace = true, default-features = true } -sp-genesis-builder = { workspace = true, default-features = true } -sp-inherents = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-keyring = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } -sp-timestamp = { workspace = true, default-features = true } -substrate-frame-rpc-system = { workspace = true, default-features = true } -pallet-transaction-payment-rpc = { workspace = true, default-features = true } -pallet-timestamp = { workspace = true, default-features = true } - [build-dependencies] -substrate-build-script-utils = { workspace = true, default-features = true } +polkadot-sdk = { workspace = true, features = ["substrate-build-script-utils"] } [features] default = ["std"] std = [ - "revive-dev-runtime/std", - "sp-genesis-builder/std", -] + "polkadot-sdk/std", + "revive-dev-runtime/std", +] \ No newline at end of file diff --git a/substrate/frame/revive/dev-node/node/build.rs b/substrate/frame/revive/dev-node/node/build.rs index fa7686e01099f..47ab77ae29690 100644 --- a/substrate/frame/revive/dev-node/node/build.rs +++ b/substrate/frame/revive/dev-node/node/build.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; +use polkadot_sdk::substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; fn main() { generate_cargo_keys(); diff --git a/substrate/frame/revive/dev-node/node/src/chain_spec.rs b/substrate/frame/revive/dev-node/node/src/chain_spec.rs index ef24499fb07fa..5efa2989512f8 100644 --- a/substrate/frame/revive/dev-node/node/src/chain_spec.rs +++ b/substrate/frame/revive/dev-node/node/src/chain_spec.rs @@ -15,8 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use polkadot_sdk::{ + sc_service::{ChainType, Properties}, + *, +}; use revive_dev_runtime::WASM_BINARY; -use sc_service::{ChainType, Properties}; /// This is a specialization of the general Substrate ChainSpec type. pub type ChainSpec = sc_service::GenericChainSpec; diff --git a/substrate/frame/revive/dev-node/node/src/cli.rs b/substrate/frame/revive/dev-node/node/src/cli.rs index 05cf8b93a28dd..fe7717dd76511 100644 --- a/substrate/frame/revive/dev-node/node/src/cli.rs +++ b/substrate/frame/revive/dev-node/node/src/cli.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use sc_cli::RunCmd; +use polkadot_sdk::{sc_cli::RunCmd, *}; #[derive(Debug, Clone, Copy)] pub enum Consensus { diff --git a/substrate/frame/revive/dev-node/node/src/command.rs b/substrate/frame/revive/dev-node/node/src/command.rs index 3a731cea6fb25..7d92eeae1512a 100644 --- a/substrate/frame/revive/dev-node/node/src/command.rs +++ b/substrate/frame/revive/dev-node/node/src/command.rs @@ -20,8 +20,7 @@ use crate::{ cli::{Cli, Subcommand}, service, }; -use sc_cli::SubstrateCli; -use sc_service::PartialComponents; +use polkadot_sdk::{sc_cli::SubstrateCli, sc_service::PartialComponents, *}; impl SubstrateCli for Cli { fn impl_name() -> String { diff --git a/substrate/frame/revive/dev-node/node/src/main.rs b/substrate/frame/revive/dev-node/node/src/main.rs index 3cf7d98311eaa..8f36da5bf83a8 100644 --- a/substrate/frame/revive/dev-node/node/src/main.rs +++ b/substrate/frame/revive/dev-node/node/src/main.rs @@ -24,6 +24,6 @@ mod command; mod rpc; mod service; -fn main() -> sc_cli::Result<()> { +fn main() -> polkadot_sdk::sc_cli::Result<()> { command::run() } diff --git a/substrate/frame/revive/dev-node/node/src/rpc.rs b/substrate/frame/revive/dev-node/node/src/rpc.rs index e8fdd3825ac97..48e435825f49f 100644 --- a/substrate/frame/revive/dev-node/node/src/rpc.rs +++ b/substrate/frame/revive/dev-node/node/src/rpc.rs @@ -24,9 +24,12 @@ use crate::cli::Consensus; use jsonrpsee::{core::RpcResult, proc_macros::rpc, RpcModule}; +use polkadot_sdk::{ + sc_transaction_pool_api::TransactionPool, + sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}, + *, +}; use revive_dev_runtime::{AccountId, Nonce, OpaqueBlock}; -use sc_transaction_pool_api::TransactionPool; -use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use std::sync::Arc; /// Full client dependencies. @@ -86,7 +89,7 @@ where C::Api: substrate_frame_rpc_system::AccountNonceApi, P: TransactionPool + 'static, { - use substrate_frame_rpc_system::{System, SystemApiServer}; + use polkadot_sdk::substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); let FullDeps { client, pool, consensus } = deps; diff --git a/substrate/frame/revive/dev-node/node/src/service.rs b/substrate/frame/revive/dev-node/node/src/service.rs index a2b48dad46bf3..1eeb511515128 100644 --- a/substrate/frame/revive/dev-node/node/src/service.rs +++ b/substrate/frame/revive/dev-node/node/src/service.rs @@ -16,12 +16,15 @@ // limitations under the License. use crate::cli::Consensus; +use polkadot_sdk::{ + sc_client_api::StorageProvider, + sc_executor::WasmExecutor, + sc_service::{error::Error as ServiceError, Configuration, TaskManager}, + sc_telemetry::{Telemetry, TelemetryWorker}, + sp_runtime::traits::Block as BlockT, + *, +}; use revive_dev_runtime::{OpaqueBlock as Block, Runtime, RuntimeApi}; -use sc_client_api::StorageProvider; -use sc_executor::WasmExecutor; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; -use sc_telemetry::{Telemetry, TelemetryWorker}; -use sp_runtime::traits::Block as BlockT; use std::sync::Arc; type HostFunctions = sp_io::SubstrateHostFunctions; @@ -175,7 +178,7 @@ pub fn new_full::Ha ); // Due to instant seal or low block time multiple blocks can have the same timestamp. - // This is because Ethereum only uses second granularity (as opposed to ms). + // This is because Etereum only uses second granularity (as opposed to ms). // Here we make sure that we increment by at least a second from the last block. // // # Warning @@ -190,7 +193,7 @@ pub fn new_full::Ha let client = client.clone(); async move { let key = sp_core::storage::StorageKey( - pallet_timestamp::Now::::hashed_key().to_vec(), + polkadot_sdk::pallet_timestamp::Now::::hashed_key().to_vec(), ); let current = sp_timestamp::Timestamp::current(); let next = client diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 0702b35174b64..5c08f46299fde 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -11,109 +11,32 @@ edition.workspace = true [dependencies] array-bytes = { workspace = true } codec = { workspace = true } +polkadot-sdk = { workspace = true, features = [ + "pallet-balances", + "pallet-revive", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parachains-common", + "polkadot-primitives", + "polkadot-runtime-common", + "runtime", + "with-tracing", +] } scale-info = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } sp-debug-derive = { workspace = true } -frame-executive = { workspace = true, default-features = false } -frame-support = { workspace = true, default-features = false } -frame-system = { workspace = true, default-features = false } -frame-system-rpc-runtime-api = { workspace = true, default-features = false } -frame-try-runtime = { workspace = true, default-features = false, optional = true } -pallet-balances = { workspace = true, default-features = false } -pallet-revive = { workspace = true, default-features = false } -pallet-sudo = { workspace = true, default-features = false } -pallet-timestamp = { workspace = true, default-features = false } -pallet-transaction-payment = { workspace = true, default-features = false } -pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = false } -parachains-common = { workspace = true, default-features = false } -polkadot-runtime-common = { workspace = true, default-features = false } -polkadot-primitives = { workspace = true, default-features = false } -sp-api = { workspace = true, default-features = false } -sp-block-builder = { workspace = true, default-features = false } -sp-consensus-aura = { workspace = true, default-features = false } -sp-core = { workspace = true, default-features = false } -sp-genesis-builder = { workspace = true, default-features = false } -sp-inherents = { workspace = true, default-features = false } -sp-io = { workspace = true, default-features = false } -sp-keyring = { workspace = true, default-features = false } -sp-offchain = { workspace = true, default-features = false } -sp-runtime = { workspace = true, default-features = false } -sp-session = { workspace = true, default-features = false } -sp-std = { workspace = true, default-features = false } -sp-transaction-pool = { workspace = true, default-features = false } -sp-tracing = { workspace = true, default-features = false, optional = true } -sp-version = { workspace = true, default-features = false } -sp-weights = { workspace = true, default-features = false } - [build-dependencies] -substrate-wasm-builder = { workspace = true, optional = true } +polkadot-sdk = { optional = true, workspace = true, features = ["substrate-wasm-builder"] } [features] default = ["std"] std = [ "codec/std", - "frame-executive/std", - "frame-support/std", - "frame-system/std", - "frame-system-rpc-runtime-api/std", - "pallet-balances/std", - "pallet-revive/std", - "pallet-sudo/std", - "pallet-timestamp/std", - "pallet-transaction-payment-rpc-runtime-api/std", - "pallet-transaction-payment/std", - "parachains-common/std", - "polkadot-runtime-common/std", - "polkadot-primitives/std", + "polkadot-sdk/std", "scale-info/std", "serde_json/std", - "sp-api/std", - "sp-block-builder/std", - "sp-consensus-aura/std", - "sp-core/std", "sp-debug-derive/std", - "sp-genesis-builder/std", - "sp-inherents/std", - "sp-io/std", - "sp-keyring/std", - "sp-offchain/std", - "sp-runtime/std", - "sp-session/std", - "sp-std/std", - "sp-transaction-pool/std", - "sp-version/std", - "sp-weights/std", - "substrate-wasm-builder", - "frame-try-runtime?/std", - "sp-tracing?/std", -] -runtime-benchmarks = [ - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "pallet-revive/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks", - "pallet-transaction-payment/runtime-benchmarks", - "polkadot-runtime-common/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "parachains-common/runtime-benchmarks", - "polkadot-primitives/runtime-benchmarks" -] -try-runtime = [ - "dep:frame-try-runtime", - "frame-executive/try-runtime", - "frame-support/try-runtime", - "frame-system/try-runtime", - "pallet-balances/try-runtime", - "pallet-revive/try-runtime", - "pallet-sudo/try-runtime", - "pallet-timestamp/try-runtime", - "pallet-transaction-payment/try-runtime", - "polkadot-runtime-common/try-runtime", - "sp-runtime/try-runtime", - "frame-try-runtime/try-runtime", - "parachains-common/try-runtime", -] -with-tracing = ["dep:sp-tracing"] +] \ No newline at end of file diff --git a/substrate/frame/revive/dev-node/runtime/build.rs b/substrate/frame/revive/dev-node/runtime/build.rs index e6f92757e2254..2cb2966b5d822 100644 --- a/substrate/frame/revive/dev-node/runtime/build.rs +++ b/substrate/frame/revive/dev-node/runtime/build.rs @@ -18,6 +18,6 @@ fn main() { #[cfg(feature = "std")] { - substrate_wasm_builder::WasmBuilder::build_using_defaults(); + polkadot_sdk::substrate_wasm_builder::WasmBuilder::build_using_defaults(); } } diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index 3426787311a62..b4ac9bc93d197 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -25,15 +25,11 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use currency::*; -use frame_support::{ - derive_impl, parameter_types, - traits::{ConstBool, ConstU32, ConstU64}, - weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, - Weight, - }, +use frame_support::weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, + Weight, }; -use frame_system::{limits::BlockWeights, EnsureSigned}; +use frame_system::limits::BlockWeights; use pallet_revive::{ evm::{ fees::{BlockRatioFee, Info as FeeInfo}, @@ -42,25 +38,20 @@ use pallet_revive::{ AccountId32Mapper, }; use pallet_transaction_payment::{ConstFeeMultiplier, FeeDetails, Multiplier, RuntimeDispatchInfo}; -use polkadot_primitives::MAX_POV_SIZE; -use sp_api::impl_runtime_apis; -use sp_genesis_builder::PresetId; -use sp_runtime::{ - generic, - traits::{Block as BlockT, One}, - Perbill, +use polkadot_sdk::{ + polkadot_sdk_frame::{ + deps::sp_genesis_builder, + runtime::{apis, prelude::*}, + traits::Block as BlockT, + }, + *, }; -use sp_version::{runtime_version, RuntimeVersion}; use sp_weights::ConstantMultiplier; -#[cfg(feature = "std")] -use sp_version::NativeVersion; - -// Re-export types used by the node -pub use parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}; - -// OpaqueBlock used by the node -pub type OpaqueBlock = generic::Block; +pub use polkadot_sdk::{ + parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, + polkadot_sdk_frame::runtime::types_common::OpaqueBlock, +}; pub mod currency { use super::Balance; @@ -73,12 +64,13 @@ pub mod currency { pub mod genesis_config_presets { use super::*; use crate::{ - currency::DOLLARS, Balance, BalancesConfig, ReviveConfig, RuntimeGenesisConfig, SudoConfig, + currency::DOLLARS, sp_keyring::Sr25519Keyring, Balance, BalancesConfig, ReviveConfig, + RuntimeGenesisConfig, SudoConfig, }; + use alloc::{vec, vec::Vec}; use pallet_revive::is_eth_derived; use serde_json::Value; - use sp_keyring::Sr25519Keyring; pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS; @@ -173,7 +165,7 @@ pub fn native_version() -> NativeVersion { /// The address format for describing accounts. pub type Address = sp_runtime::MultiAddress; /// Block type as expected by this runtime. -pub type Block = generic::Block; +pub type Block = sp_runtime::generic::Block; /// The transaction extensions that are added to the runtime. type TxExtension = ( // Checks that the sender is not the zero address. @@ -237,7 +229,7 @@ type Executive = frame_executive::Executive< >; // Composes the runtime by adding all the used pallets and deriving necessary types. -#[frame_support::runtime] +#[frame_construct_runtime] mod runtime { /// The main runtime type. #[runtime::runtime] @@ -287,20 +279,22 @@ const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(10); /// by Operational extrinsics. const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); /// We allow for 2 seconds of compute with a 6 second average block time, with maximum proof size. -const MAXIMUM_BLOCK_WEIGHT: Weight = - Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), MAX_POV_SIZE as u64); +const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( + WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), + polkadot_primitives::MAX_POV_SIZE as u64, +); parameter_types! { pub const Version: RuntimeVersion = VERSION; pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() .base_block(BlockExecutionWeight::get()) - .for_class(frame_support::dispatch::DispatchClass::all(), |weights| { + .for_class(DispatchClass::all(), |weights| { weights.base_extrinsic = ExtrinsicBaseWeight::get(); }) - .for_class(frame_support::dispatch::DispatchClass::Normal, |weights| { + .for_class(DispatchClass::Normal, |weights| { weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); }) - .for_class(frame_support::dispatch::DispatchClass::Operational, |weights| { + .for_class(DispatchClass::Operational, |weights| { weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); // Operational transactions have some extra reserved space, so that they // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. @@ -384,7 +378,7 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( Executive, EthExtraImpl, - impl sp_api::Core for Runtime { + impl apis::Core for Runtime { fn version() -> RuntimeVersion { VERSION } @@ -393,17 +387,17 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( Executive::execute_block(block) } - fn initialize_block(header: &Header) -> sp_runtime::ExtrinsicInclusionMode { + fn initialize_block(header: &Header) -> ExtrinsicInclusionMode { Executive::initialize_block(header) } } - impl sp_api::Metadata for Runtime { - fn metadata() -> sp_core::OpaqueMetadata { - sp_core::OpaqueMetadata::new(Runtime::metadata().into()) + impl apis::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) } - fn metadata_at_version(version: u32) -> Option { + fn metadata_at_version(version: u32) -> Option { Runtime::metadata_at_version(version) } @@ -412,59 +406,57 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( } } - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ::Extrinsic) -> sp_runtime::ApplyExtrinsicResult { + impl apis::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ExtrinsicFor) -> ApplyExtrinsicResult { Executive::apply_extrinsic(extrinsic) } - fn finalize_block() -> ::Header { + fn finalize_block() -> HeaderFor { Executive::finalize_block() } - fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + fn inherent_extrinsics(data: InherentData) -> Vec> { data.create_extrinsics() } fn check_inherents( block: ::LazyBlock, - data: sp_inherents::InherentData, - ) -> sp_inherents::CheckInherentsResult { + data: InherentData, + ) -> CheckInherentsResult { data.check_extrinsics(&block) } } - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + impl apis::TaggedTransactionQueue for Runtime { fn validate_transaction( - source: sp_runtime::transaction_validity::TransactionSource, - tx: ::Extrinsic, + source: TransactionSource, + tx: ExtrinsicFor, block_hash: ::Hash, - ) -> sp_runtime::transaction_validity::TransactionValidity { + ) -> TransactionValidity { Executive::validate_transaction(source, tx, block_hash) } } - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { + impl apis::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &HeaderFor) { Executive::offchain_worker(header) } } - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys( - _owner: Vec, - _seed: Option>, - ) -> sp_session::OpaqueGeneratedSessionKeys { + impl apis::SessionKeys for Runtime { + fn generate_session_keys(_owner: Vec, _seed: Option>) -> apis::OpaqueGeneratedSessionKeys { Default::default() - } + } - fn decode_session_keys( - _encoded: Vec, - ) -> Option, sp_core::crypto::KeyTypeId)>> { - Default::default() + + fn decode_session_keys( + _encoded: Vec, + ) -> Option, apis::KeyTypeId)>> { + Default::default() + } } -} - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + impl apis::AccountNonceApi for Runtime { fn account_nonce(account: AccountId) -> Nonce { System::account_nonce(account) } @@ -474,10 +466,10 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( Block, Balance, > for Runtime { - fn query_info(uxt: ::Extrinsic, len: u32) -> RuntimeDispatchInfo { + fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { TransactionPayment::query_info(uxt, len) } - fn query_fee_details(uxt: ::Extrinsic, len: u32) -> FeeDetails { + fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { TransactionPayment::query_fee_details(uxt, len) } fn query_weight_to_fee(weight: Weight) -> Balance { @@ -488,13 +480,13 @@ pallet_revive::impl_runtime_apis_plus_revive_traits!( } } - impl sp_genesis_builder::GenesisBuilder for Runtime { + impl apis::GenesisBuilder for Runtime { fn build_state(config: Vec) -> sp_genesis_builder::Result { - frame_support::genesis_builder_helper::build_state::(config) + build_state::(config) } fn get_preset(id: &Option) -> Option> { - frame_support::genesis_builder_helper::get_preset::(id, self::genesis_config_presets::get_preset) + get_preset::(id, self::genesis_config_presets::get_preset) } fn preset_names() -> Vec { diff --git a/substrate/frame/revive/gen-metadata/Cargo.toml b/substrate/frame/revive/gen-metadata/Cargo.toml new file mode 100644 index 0000000000000..1d9ef92b1cce2 --- /dev/null +++ b/substrate/frame/revive/gen-metadata/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "revive-gen-metadata" +description = "Generates the revive_chain.scale metadata file used by pallet-revive-eth-rpc." +version = "0.0.0" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +revive-dev-runtime = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/gen-metadata/src/main.rs b/substrate/frame/revive/gen-metadata/src/main.rs new file mode 100644 index 0000000000000..8f9137a4fcd26 --- /dev/null +++ b/substrate/frame/revive/gen-metadata/src/main.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) 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. + +//! Generates `revive_chain.scale` — the runtime metadata file consumed by the +//! `#[subxt::subxt]` macro in `pallet-revive-eth-rpc`. +//! +//! Run this whenever `revive-dev-runtime`'s API changes: +//! +//! ```text +//! cargo run -p revive-gen-metadata +//! ``` + +use std::fs; + +fn main() { + let out_path = + std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../rpc/revive_chain.scale"); + + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| { + let metadata = revive_dev_runtime::Runtime::metadata_at_version(16) + .expect("metadata v16 must be supported; qed"); + let bytes: &[u8] = &metadata; + fs::write(&out_path, bytes).expect("failed to write revive_chain.scale"); + }); + + println!("Written to {}", out_path.display()); +} diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 75e419624613d..d0e5d4d84f9ad 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -93,7 +93,3 @@ tempfile = { workspace = true } [build-dependencies] git2 = { workspace = true } -asset-hub-westend-runtime = { workspace = true, default-features = true } -revive-dev-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/rpc/build.rs b/substrate/frame/revive/rpc/build.rs index b37b23d4562f5..44faf3b27ca31 100644 --- a/substrate/frame/revive/rpc/build.rs +++ b/substrate/frame/revive/rpc/build.rs @@ -14,11 +14,10 @@ // 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. -use std::{fs, process::Command}; +use std::process::Command; fn main() { generate_git_revision(); - generate_metadata_file(); } fn generate_git_revision() { @@ -51,14 +50,3 @@ fn generate_git_revision() { println!("cargo:rustc-env=TARGET={target}"); println!("cargo:rustc-env=GIT_REVISION={branch}-{id}"); } - -fn generate_metadata_file() { - let mut ext = sp_io::TestExternalities::new(Default::default()); - ext.execute_with(|| { - let metadata = revive_dev_runtime::Runtime::metadata_at_version(16).unwrap(); - let bytes: &[u8] = &metadata; - let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); - let out_path = std::path::Path::new(&out_dir).join("revive_chain.scale"); - fs::write(out_path, bytes).unwrap(); - }); -} diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index f8fd04a38156c..81f727ef71e58 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -20,7 +20,7 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; #[subxt::subxt( - runtime_metadata_path = "$OUT_DIR/revive_chain.scale", + runtime_metadata_path = "revive_chain.scale", // TODO remove once subxt uses the same U256 type substitute_type( path = "primitive_types::U256", From acf5bbf366111b0832846b6559e253a408d93ea9 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Thu, 26 Mar 2026 21:18:57 +0000 Subject: [PATCH 04/54] make eth-rpc default --- Cargo.lock | 1 + cumulus/polkadot-omni-node/lib/Cargo.toml | 6 +- cumulus/polkadot-omni-node/lib/src/command.rs | 10 ++- .../polkadot-omni-node/lib/src/common/rpc.rs | 43 ------------ .../lib/src/fake_runtime_api/utils.rs | 67 +++++++++++++++++++ .../polkadot-omni-node/lib/src/nodes/aura.rs | 15 +++-- .../frame/revive/dev-node/node/Cargo.toml | 2 +- .../frame/revive/dev-node/runtime/Cargo.toml | 2 +- substrate/frame/revive/rpc/src/client.rs | 21 ------ .../frame/revive/rpc/src/receipt_provider.rs | 4 +- .../frame/revive/rpc/src/subxt_client.rs | 2 +- 11 files changed, 92 insertions(+), 81 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7de11d2a3897a..a7ee30275d592 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -16102,6 +16102,7 @@ dependencies = [ "jsonrpsee", "log", "nix 0.29.0", + "pallet-revive", "pallet-revive-eth-rpc", "pallet-transaction-payment", "pallet-transaction-payment-rpc", diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index eeb9892c8aee0..86fee094fc8bb 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -39,7 +39,8 @@ frame-support = { optional = true, workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } frame-try-runtime = { optional = true, workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } -pallet-revive-eth-rpc = { workspace = true, default-features = false, optional = true } +pallet-revive = { workspace = true, default-features = true } +pallet-revive-eth-rpc = { workspace = true, default-features = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } @@ -117,8 +118,7 @@ tokio = { version = "1.43.1", features = ["macros", "parking_lot", "time"] } wait-timeout = { workspace = true } [features] -default = ["eth-rpc"] -eth-rpc = ["dep:pallet-revive-eth-rpc"] +default = [] rococo-native = ["polkadot-cli/rococo-native"] westend-native = ["polkadot-cli/westend-native"] runtime-benchmarks = [ diff --git a/cumulus/polkadot-omni-node/lib/src/command.rs b/cumulus/polkadot-omni-node/lib/src/command.rs index 192ba32ad7231..74ff8149a3d5f 100644 --- a/cumulus/polkadot-omni-node/lib/src/command.rs +++ b/cumulus/polkadot-omni-node/lib/src/command.rs @@ -63,7 +63,7 @@ pub fn new_aura_node_spec( extra_args: &NodeExtraArgs, ) -> Box where - Block: NodeBlock, + Block: NodeBlock, { match aura_id { AuraConsensusId::Sr25519 => crate::nodes::aura::new_aura_node_spec::< @@ -91,8 +91,12 @@ fn new_node_spec( (BlockNumber::U32, Consensus::Aura(aura_id)) => { new_aura_node_spec::>(aura_id, extra_args) }, - (BlockNumber::U64, Consensus::Aura(aura_id)) => { - new_aura_node_spec::>(aura_id, extra_args) + (BlockNumber::U64, Consensus::Aura(_)) => { + return Err(sc_cli::Error::Application( + "u64 block numbers are not supported when the ETH RPC (pallet-revive) \ + is enabled; the runtime must use u32 block numbers" + .into(), + )) }, }, }) diff --git a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs index f20b3dff5cf55..29e9e05ced74f 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs @@ -35,10 +35,8 @@ use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer /// A type representing all RPC extensions. pub type RpcExtension = jsonrpsee::RpcModule<()>; -#[cfg(feature = "eth-rpc")] pub use eth_rpc::BuildParachainReviveRpcExtensions; -#[cfg(feature = "eth-rpc")] mod eth_rpc { use super::*; use pallet_revive_eth_rpc::{ @@ -204,44 +202,3 @@ pub(crate) trait BuildRpcExtensions { spawn_handle: Arc, ) -> sc_service::error::Result; } - -pub(crate) struct BuildParachainRpcExtensions(PhantomData<(Block, RuntimeApi)>); - -impl - BuildRpcExtensions< - ParachainClient, - ParachainBackend, - sc_transaction_pool::TransactionPoolHandle>, - sc_statement_store::Store, - > for BuildParachainRpcExtensions -where - RuntimeApi: - ConstructNodeRuntimeApi> + Send + Sync + 'static, - RuntimeApi::RuntimeApi: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + substrate_frame_rpc_system::AccountNonceApi, -{ - fn build_rpc_extensions( - client: Arc>, - backend: Arc>, - pool: Arc< - sc_transaction_pool::TransactionPoolHandle>, - >, - statement_store: Option>, - spawn_handle: Arc, - ) -> sc_service::error::Result { - let build = || -> Result> { - let mut module = RpcExtension::new(()); - - module.merge(System::new(client.clone(), pool).into_rpc())?; - module.merge(TransactionPayment::new(client.clone()).into_rpc())?; - module.merge(StateMigration::new(client.clone(), backend).into_rpc())?; - if let Some(statement_store) = statement_store { - module.merge(StatementStore::new(statement_store, spawn_handle).into_rpc())?; - } - module.merge(Dev::new(client).into_rpc())?; - - Ok(module) - }; - build().map_err(Into::into) - } -} diff --git a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs index 9dcb807571e04..235579c24c7b9 100644 --- a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs +++ b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs @@ -241,6 +241,73 @@ macro_rules! impl_node_runtime_apis { } } + impl pallet_revive::ReviveApi<$block, sp_core::H160, Balance, Nonce, u128, u64> for $runtime { + fn eth_block() -> pallet_revive::evm::Block { unimplemented!() } + fn eth_block_hash(_: pallet_revive::evm::U256) -> Option { unimplemented!() } + fn eth_receipt_data() -> Vec { unimplemented!() } + fn block_gas_limit() -> pallet_revive::evm::U256 { unimplemented!() } + fn max_extrinsic_weight_in_gas() -> pallet_revive::evm::U256 { unimplemented!() } + fn balance(_: sp_core::H160) -> pallet_revive::evm::U256 { unimplemented!() } + fn gas_price() -> pallet_revive::evm::U256 { unimplemented!() } + fn nonce(_: sp_core::H160) -> Nonce { unimplemented!() } + fn call( + _: sp_core::H160, + _: sp_core::H160, + _: Balance, + _: Option, + _: Option, + _: Vec, + ) -> pallet_revive::ContractResult { unimplemented!() } + fn instantiate( + _: sp_core::H160, + _: Balance, + _: Option, + _: Option, + _: pallet_revive::Code, + _: Vec, + _: Option<[u8; 32]>, + ) -> pallet_revive::ContractResult { unimplemented!() } + fn eth_transact( + _: pallet_revive::evm::GenericTransaction, + ) -> Result, pallet_revive::EthTransactError> { unimplemented!() } + fn eth_transact_with_config( + _: pallet_revive::evm::GenericTransaction, + _: pallet_revive::DryRunConfig, + ) -> Result, pallet_revive::EthTransactError> { unimplemented!() } + fn eth_estimate_gas( + _: pallet_revive::evm::GenericTransaction, + _: pallet_revive::DryRunConfig, + ) -> Result { unimplemented!() } + fn upload_code( + _: sp_core::H160, + _: Vec, + _: Option, + ) -> pallet_revive::CodeUploadResult { unimplemented!() } + fn get_storage(_: sp_core::H160, _: [u8; 32]) -> pallet_revive::GetStorageResult { unimplemented!() } + fn get_storage_var_key(_: sp_core::H160, _: Vec) -> pallet_revive::GetStorageResult { unimplemented!() } + fn trace_block( + _: $block, + _: pallet_revive::evm::TracerType, + ) -> Vec<(u32, pallet_revive::evm::Trace)> { unimplemented!() } + fn trace_tx( + _: $block, + _: u32, + _: pallet_revive::evm::TracerType, + ) -> Option { unimplemented!() } + fn trace_call( + _: pallet_revive::evm::GenericTransaction, + _: pallet_revive::evm::TracerType, + ) -> Result { unimplemented!() } + fn block_author() -> sp_core::H160 { unimplemented!() } + fn address(_: sp_core::H160) -> sp_core::H160 { unimplemented!() } + fn account_id(_: sp_core::H160) -> sp_core::H160 { unimplemented!() } + fn runtime_pallets_address() -> sp_core::H160 { unimplemented!() } + fn code(_: sp_core::H160) -> Vec { unimplemented!() } + fn new_balance_with_dust( + _: pallet_revive::evm::U256, + ) -> Result<(Balance, u32), pallet_revive::BalanceConversionError> { unimplemented!() } + } + impl cumulus_primitives_core::TargetBlockRate<$block> for $runtime { fn target_block_rate() -> u32 { unimplemented!() diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index 6b9f5bb835121..9634b2ec23fa0 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -18,7 +18,7 @@ use crate::{ cli::{AuthoringPolicy, DevSealMode}, common::{ aura::{AuraIdT, AuraRuntimeApi}, - rpc::{BuildParachainRpcExtensions, BuildRpcExtensions}, + rpc::{BuildParachainReviveRpcExtensions, BuildRpcExtensions}, spec::{ BaseNodeSpec, BuildImportQueue, ClientBlockImport, DynNodeSpec, InitBlockImport, NodeSpec, StartConsensus, @@ -54,6 +54,7 @@ use cumulus_primitives_core::{ }; use cumulus_relay_chain_interface::{OverseerHandle, RelayChainInterface}; use futures::{prelude::*, FutureExt}; +use pallet_revive_eth_rpc::native_client::ReviveRuntimeApiT; use polkadot_primitives::{CollatorPair, UpgradeGoAhead}; use prometheus_endpoint::Registry; use sc_client_api::{Backend, BlockchainEvents}; @@ -193,11 +194,12 @@ where impl NodeSpec for AuraNode where - Block: NodeBlock, + Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi - + substrate_frame_rpc_system::AccountNonceApi, + + substrate_frame_rpc_system::AccountNonceApi + + ReviveRuntimeApiT, AuraId: AuraIdT + Sync, StartConsensus: self::StartConsensus< Block, @@ -209,7 +211,7 @@ where InitBlockImport::BlockImport: sc_consensus::BlockImport + 'static, { - type BuildRpcExtensions = BuildParachainRpcExtensions; + type BuildRpcExtensions = BuildParachainReviveRpcExtensions; type StartConsensus = StartConsensus; const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Resistant; @@ -486,12 +488,13 @@ pub fn new_aura_node_spec( extra_args: &NodeExtraArgs, ) -> Box where - Block: NodeBlock, + Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi - + GetParachainInfo, + + GetParachainInfo + + ReviveRuntimeApiT, AuraId: AuraIdT + Sync + Send, ::Pair: Send + Sync, { diff --git a/substrate/frame/revive/dev-node/node/Cargo.toml b/substrate/frame/revive/dev-node/node/Cargo.toml index 15b5a2438f8ac..d618d6065ed6b 100644 --- a/substrate/frame/revive/dev-node/node/Cargo.toml +++ b/substrate/frame/revive/dev-node/node/Cargo.toml @@ -33,4 +33,4 @@ default = ["std"] std = [ "polkadot-sdk/std", "revive-dev-runtime/std", -] \ No newline at end of file +] diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 5c08f46299fde..39d9146f364a9 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -39,4 +39,4 @@ std = [ "scale-info/std", "serde_json/std", "sp-debug-derive/std", -] \ No newline at end of file +] diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index d21314a0214cc..493a9a91c7daf 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -187,7 +187,6 @@ impl ClientError { } const LOG_TARGET: &str = "eth-rpc::client"; -const LOG_TARGET_SUBSCRIPTION: &str = "eth-rpc::subscription"; const REVERT_CODE: i32 = 3; @@ -239,17 +238,6 @@ pub struct Client { backfill_complete: Arc, } -/// Returns the first EVM block number for main and test nets, `None` otherwise. -fn known_first_evm_block_for_chain(chain_id: u64) -> Option { - match chain_id { - 420420417 => Some(4_367_914), // Paseo Asset Hub - 420420418 => Some(12_234_156), // Kusama Asset Hub - 420420419 => Some(11_405_259), // Polkadot Asset Hub - 420420421 => Some(13_169_391), // Westend Asset Hub - _ => None, - } -} - impl Client { /// Create a `Client` from an arbitrary backend and block-info-provider. pub fn from_backend( @@ -366,15 +354,6 @@ impl Client { } } - /// The earliest block number where the ReviveApi is available. - /// Resolution order: in-memory value > known-networks table > 0. - fn earliest_block_number(&self) -> u32 { - self.receipt_provider - .first_evm_block() - .or_else(|| known_first_evm_block_for_chain(self.chain_id())) - .unwrap_or(0) - } - /// Subscribe to new blocks, and execute the async closure for each block. async fn subscribe_new_blocks( &self, diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs index acf98ffdc0ecc..f2d1c8d139d98 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -21,7 +21,7 @@ use crate::{ block_sync::{ChainMetadata, SyncCheckpoint, SyncLabel, SyncStateKey}, client::SubstrateBlockNumber, }; -use pallet_revive::evm::{BlockTag, Filter, Log, ReceiptInfo, TransactionSigned}; +use pallet_revive::evm::{Filter, Log, ReceiptInfo, TransactionSigned}; use sp_core::{H256, U256}; use sqlx::{QueryBuilder, Row, Sqlite, SqlitePool, query}; use std::{ @@ -776,7 +776,7 @@ impl ReceiptProvider { mod tests { use super::*; use crate::block_info_provider::test::{MockBlockInfo, MockBlockInfoProvider}; - use pallet_revive::evm::{ReceiptInfo, TransactionSigned}; + use pallet_revive::evm::{BlockTag, ReceiptInfo, TransactionSigned}; use pretty_assertions::assert_eq; use sp_core::{H160, H256}; use sqlx::SqlitePool; diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 81f727ef71e58..049eb86aeb435 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -534,7 +534,7 @@ impl SubstrateClientT for SubxtClient { StreamOf::new(Box::pin(sub.map_err(|e| e.into()))); tokio::time::timeout(std::time::Duration::from_secs(5), async { - while let Some(status) = stream.next().await { + if let Some(status) = stream.next().await { let subxt_status: SubxtTxStatus = status.map_err(ClientError::from)?; let pool_status = subxt_tx_status_to_submit_result(subxt_status); From 23c2124a2055d369a6dea8d703946d23d81926c0 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 27 Mar 2026 01:27:46 +0000 Subject: [PATCH 05/54] dd a negation to revive_chain.scale --- .gitignore | 1 + cumulus/polkadot-omni-node/lib/Cargo.toml | 2 ++ substrate/frame/revive/rpc/revive_chain.scale | Bin 0 -> 82139 bytes 3 files changed, 3 insertions(+) create mode 100644 substrate/frame/revive/rpc/revive_chain.scale diff --git a/.gitignore b/.gitignore index 59a747f2fef17..3dafb4456cec6 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ substrate.code-workspace target/ target.noindex/ *.scale +!substrate/frame/revive/rpc/revive_chain.scale rustc-ice-* /.claude/settings.local.json *.local.md \ No newline at end of file diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index 86fee094fc8bb..e6c78d9682b74 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -132,6 +132,7 @@ runtime-benchmarks = [ "sc-client-db/runtime-benchmarks", "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-revive/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", @@ -139,4 +140,5 @@ try-runtime = [ "pallet-transaction-payment/try-runtime", "polkadot-cli/try-runtime", "sp-runtime/try-runtime", + "pallet-revive/try-runtime" ] diff --git a/substrate/frame/revive/rpc/revive_chain.scale b/substrate/frame/revive/rpc/revive_chain.scale new file mode 100644 index 0000000000000000000000000000000000000000..709e0bb47043dac070ea10702f26f11d3930b998 GIT binary patch literal 82139 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�pD*ZYV0YPgaf2ut11RoMs zimWGX?8m|?m-J}*#ei5f1*B?=S%jY zN0D%j0XM`ScG(AEfDzl};)!rS$ev#DWyDm_KEX9*Jn90H1QKiA{ zszc2oJA8S&wkHK1Q|WHKgB2bae)6pUUU^~1aVt>uYmolSSUFRn)lQBUmP8Js81BxT zKE0lscUr@STUc1c2+(78kyaq^-vwRJmC^&|hPZxKbqw&aK!rVi=1dECpqK@m38IJN zN4dA+SM==A$?v1?lLG>*?yQvqEo$540_eG{s9+437`47FtT!+z>=ZCXDDvUyKu`T# zVjd?sPzx4Q52M7BFdTtC>f(V+Lx|!?f;0ZD_>~l%AXC;F6aw%gMgn(NSvffep582P zmMczJEpE3P)q}Z3-Cu|m8*WpsvBQAz@&<*NPH|^w_(KpfW%7;W0*p@BP$%Tz{1u#` z#!WP5p6qwL6;hBPk)cV}$X)Zfio#eZafTbMTB}gmC^wsJTIq-EgzBVN>byO%An#BR zUKC9gtq{*jNu^cjg!iV=r}SOjFZKySs(p%J9ybduQG{{WE*Exl=Gp#O83cer1+O(_ z3n@2=2t|AVQNrjjuTH!$hNwY7ePk1BKh#%jw0$_(^TwY&h7SbN6pj1IlYxCXf*IEP zPA>)O4;d*C#0xfs`u;`1o2(xzSz=0VB-HEb65>PlY_(FR-QJlc9K{6E#vAN- zBX*uTPr-J;JHTvrpkXLw)i0pV7NJW4vn$Ee9aeZ;o*iTfwVY0H!8Ul0pijEhol2<; zos>47Bh6zKn#a0Xhn3+*a}NJD<}uhEB9rgYMMlM>&>ww=;uW>1CvcC&i-}m*6ZjfU zX?V)%G)?$(vigkpO-4u9Ov4vxj(yFs+Lfb6*D2Pkw*5db${xoHaK1%H>;g=!6p)J0 z=X9Of2Y3bK9I!dhk?~t0$P#*9MZ(O}Y>K59=Sps!-)_)g1>XghRV~nv;r0oT9n&wi z?bst?yvA96jzK(Rg?@Xn2%Zv5jTF{P$}p2=?B>dkrerOgc&lVq*6PVMu9a{zkY7Ej zYV_#sBWJ#FZ5rrhoDF448@X{3YHS9rN6F>A*zmy6`92%zZAu**ivDz))fKehxo13? z#{l_*#1_gHH1hF7`dk17_qEWx2CLZbHV=-R>;rBGnAgNRuSGh-M?yH?!*za^6`vFt zHsPYfDgSyHrgjQdoDk3={iXAiKzh0BE#pahjVAmpuE|t}XB_d+aDX22F*6NCO!XLuGLapx z7j`SPLMh|IG}DLdLfj}^j+Y)jsdB1HJ)r)(br6vXmMQF=NO2JDmVi)=q`edW-rDI? zht8}X2N!_W14qpC3*%MDsL%kR9hmRm73bZUP6~te04r1pDrXTgI1En7rJcFCb!u`t zu0sRZ4aZ(cXG7wRfkJW8Ey=*-P1-N@ikukgpa* zAHH`wwQWrwQ*q9jyrv!6RcI5>(C2mMtA*iAB%lp>VbykhJ^~O58AP)42Q1x-Ts_$; zP8rMXR8A;el%)lo1Y&5CGCA?|Izh7=eVzdNIEBzf;CS^6tBYQW5Lat5TJBlADmFZa zXI^y0?|wKK?%+u#DfBu6zt#bAhb%hCu88YLn_^i5*d0WUg|D1DujXFA4S+~3AuxNL zWoLbt(e=7hxI!Dbx1>WH3-F4C8Vg6oX}i#%m&n8Y{#fR!ssWCbR}8!k4y_z+rybbu zKEs8>{Cx9%~lS3v^)T)mqm?WPKxbHIx#jR^n( znJ5J)1q0I}S+79m#7|i2d`9i|Sf8J|#nN=DijeQrO)#ba$5~%{ep8e&pt5t7#)7`k zb(a2=M=|TUo^df}@V(Qy1yay;Oh=l9N~>k(Ps@2c>SNgu<~I7Hl@wMv#{Q5A0zx5j zz94dhq+Y4*Vg)Yk#Oh z(3SK{I+*k9Ti(XYhov8R!I-zFXN-nFP_UGh!75UtIj%W}fUrYip$?<#dhvZvbyUGD zbPy=~>~TmZNM|%?pV+e<@0Fe$K`T_~&P7lnJ$v-P%bI9>Bv(g*$tt@%jSc|MpPG;y zFyj>mCuJy@S;Uy>mz2Q3C9N@)IAIP&2NZ_H|p`7(|?<*H@3H%(LhboNX zM$Uao7tb9>e&HoFx;dO+Lh9zYZf?O1nb*ZdgqRC_>)0URBNv6}if|GyD9X`26hKnp zAO{b@HX@({XHNN0?07T+$Il7_-HSKRu!{?Yri#bm^=s33bs?&tpX)O$o z(mi5UiVR~{Xr|l;8gd~6C&H|~NMY>Yh?NtDOE_7GK>prqNcEbtkJkG=a4K@XIx)^o=*)>#Xf-(MoPdK?RLxSV<7d8}IuBU`ZfG0F8;$?^ua2bP?mYZLRd6;G2xGkIQaz$Y3_#gvGOshThoIa;zP6&Y<9L@>e zsVbCk{DoaRfc0=^2pT|DUnLI{dKc5Y;@3eizz>-R6?QRdApggm5Y(bW)*m2^WRt)@ zQ7qJ#63q2giSEMto9!LMSg3o9ZBqq%sSM2wOOqE{0C-f9Oe#f94vttS@FdE=LgO0n z8BPV^XO9Da(rUC2MpR)=fhcS-#w#%P^kWPFk1pa4;qYonxF86Gk0F!n8~8rnDKS6L z<}iEOQ#P3zBEm%$mPC1Kz9n|x?l&VsP zcsMwohr0vowNns0S`B!rO1~hyJWqk}{^{h?Ul7sfoQN)yI>2@c9RQXQp%d5PRl3HR zAD(=6Nnh(9#hB4S+d;d#WG6jqL2kObgmjx#5lK{s zz!Zf-db!;X2lk+GCV3TZSlW}phpBpJ!U zy~&ToIqx*L>?>WWW9sdA)Gkza8Eg$#M`jw(g}Wv}gQ%_}utg@Jwob%q(@;l)Ss7ZR zxVQ=xC{gP$j$W!EfK{Xxgon1BM#zr6E{?w$_>6FW*!QS`(Ml?-S7TNdzJ*!`*#OYp zXMKcEy4nB*x&u5XI|#*qG=XOTf|z@zj>t06nuNt)}8d?kwr_+|< zB$G%qFS5xpKxu}#s2RUHX<)`8>S#O|rVC^sUqCoHRd4S@z?^;*V%sPv0YG-8Hb&{x zpHTG}Lhx<3YCGtV=Vv!JwKEH<%N~1RDU|^b?|DNQR{rqBZ3(z+SSaYVU3>YXfQ(~&oaF6>dS!nhPXUQ0pN_>namlGSDFSm4eSy~ zP?JO-tP|D&peqnkb|P4=6g>ey!V3|KM_>`cmF@BwK@0RJXA{S9t6fow$+!IexCqu1 zGq4goy1BaFbVPYcVxhS6%Xx)|~2*q3OJO=qO zy%9HO5U7vUK<4&>BG(EEAs}N7y$T|M!6+spajIFbX|h||i!i5oHyn*s6T;{6lT&qK9niSF)MXAD4R0c5(&vcC?PVMvwy z?VS7VzvxR}B)Xw2&Y?L43zHgCjV3-Z^SM>?t$Oa`M$b6xyP8?cUSP-;Lf6PfX{ zx5oTF+Sa6rQF-|1t$X>UWPuYy-$n8E9U*SwqXi+Tc2Q`<|W5YbUKMzKdDuV*N^I z2k+dogBk6BAtudob6UQBnLfC|-@WIzNA=sCS_2Wa2sPHRPx?psJ~HpM!%K~VG4CD8 z;KI0zX&DAHke{uM;*}lk3d=UGy&Oha7owhfL{Swy4*16x%j2MboRLQ$wtjm=9uN4( zd*$&4|M(JlywN|-%HxoK1V&<@n@sG$n3m_~g=dB9`QcdsL%kLEzCw0$cvjGUDLgA| zzZ{+wxRLOz(A^TA6}(>w&kEnK`e)XZ0lcH(S>d}iJS%*^7M>NpUk}d;-&lB7_-+f& z3g2&pXNB)K!?VIS9-bAx+x_#46}~?go)x~|3eO5(9G(@vJHoTV_k!@O@VziRD|{2- zS>d}gJS%*^9iA1w7y0KIg>N!ED|`pTv%+^*cvkp+Cp;^BQ{h?RI~1N3zFc@#`0ftR z3g2{iR`?G4=OYT=J>gm5dvSPH_-4Yh!gnM*D}48cXNB)2;aTCE4bKYSec@T*n+wkh z-%I`Ty$ava@T~CNAD$Jy2g0+$_q*X);hPW73g1F_R``yEXNB*0cvkop!?VJ-cG{p&meU3W^Ob3X!g(=mP(UkbgF?C!8V)O@)wDq&t)&eLX+3RFNMDsUD5QgZBR)6eA=Lpz9wx@NMD;a zD5U>(+MtmBJ86SL`gq!)kiIT7IQH`IrVR?|zn3;Bq_0mK6w)`O4GQVMpEf9@|3TWI zkiIc(P)OgDHYlWjA#G4d{~$CtM)T&hK_Puh+MtmBhiQXC`WMp%h4igygF^bYv_T>L zOKF2b`X8kY3hCR^28Hw;p~117znnHGq<@$;D5UR98x+!ar40({Ur8Gj(!ZKED5URB z8x+#_qzww`f1EZbq<<|mIOg--v_T<#U)rFM{`Is$A^jU^gF^cLv_T>LK-!>?{wHaJ zLi$H(gF^a5+MtkrFf=$e^q-~;3hCcW8x+zHr40({htmdy^gl}*6w?1ZZBR&`OdAx^ zkE9I>>3@+nD5QTYG&qLz(X>G!{aD(dkpAtoK_UGw(*}j~skA{M{dn4-kp5R`gF^au z(gua}6KR7&`pMAXSkr%N4ekyHHNI#u6D5QTkZBR)6+q6L;{Y=`RkbX98 zP)Pr~v_T>L?|s8P(@Ob2mo_M*pHCYU(*GfCP)Pq?+MtkrA#G4dznC^Cr2k{uppgDg zX@f%grL;jI{c>nDST* zh4g<(8x+$2H8eOL_4TwtA^k?$ppgD=X@f%gkJ1K(^qXmeLi(+=K_UI$(*}j~AEylp z>9^Ach4ee2!SSg7BW+Mf|4G`QkbXC9P)NU*HYlY3XWF2U{?oKUA^m>ZppgC`ZBR)6 zS=ykG{`1h_c+?Nm28HyG(*}j~|4JJa(*HYcP)L82HYlV&P8$@`|0iuwNdF{lP)MIi z8x+!?ga*f>{@=7gA^jI=gF^aj+MtmBG;L5w|7F^skp3%U-oK~eV@eGl5+XAxCKH&; ziitCLJYi$xrb~-4ParLH)_iEsuBNpsjWe5)z8;YrL}z_@a%kISZe`q}C|@bjX9xmf z-bcn|yQ)7pNV-#T%tuE$^T39YQ#B3aQLt}KtR*dV2D|Z}+7t3w7gq=CXQfcynNhIM z=#%^U6GI(#v{2U;*)c<-vO;#&H!dRmzE>D?G>tV?MLT_#H1rX5Ego58s0lW}h3LaphAHy$?fM z2q~LKvZA-XA7>3L?10@isH3AL_qHvot4@W27KMtoOd$9fo-vfkl4tXUepb4%QW@n^ zTVspz9~h7ANTqh6`6YXlg&4GT1%=;`R{SN{=e}aTa+O{t)Ax~aHo4kJ&S*X2A@j6d zJdV9)t*+)$6(w7fm6FL8SiZqbgctVldIbA*QmE|X%>!oiG`s?hj&jaOx^|!~rNv&g z{8fo2@u!`gd)Ry(v;L;}W*4@@hf!_eK)@L^6fN0rV@Kw(BcoD^?S5-VSK)?(9eh-L z@ZYx4lCfj08geO{_gOn8MJ}+U-|knlZrVe}G0;V*;{07(&lcF7n-eIGy4P8CIaBob z7HePQ$csxWYxaB)$`s#eZ6%X}-t*$1S?)uL%wcPv*qem)^nx8YFdfq7))K6I@OeMK4WJ>=ky?({S0oS)sDe65j8h;94>#4 zfCJHLevZyuc(3SHqJtaWyvb9^W#@828n1KyMRqz{E(PwH?F9*jalcCsF7!P&!&10O z>6)EH(K!H_?E%hA)&v8QW(nt{gu1)xAL$<7j?e*}sr-5%s-MneWFaa69iZ|Fe7=Yb z$xX2|;~GovCM6J`{)J|5h#p^nAD6Epa*0Vvi0t7PE-DGWwHf|+-0$R`J>3|+v8yLk zw<|YM&ob>QH_5*Irtblgg@2>bg8@U)n={?(-o~Z2B)Nt-#N48*XTvHD@SVPz%b5+H z2vu{f^Wiur(m7WP>n7}RS`?Nj;wzM5K_5Spyb@_zNol2uT&Hp=9mI{g*I1+dk@|Vj zEf%980GPdHMgm?+QE9=jh8q;lX~cLSvPUvf;QPit!VvT4;o>v6r^}QfzZ_p@Z3(Fs;z2aTx8)N zF!SrR8fLIdHY+lhxxtLCqxNVuGS;Hh6aUl>Z#0t&4aCAAgTGvi%mHhUrqBAykeSJ_ zghC}UH(8tI5`~Jsc$1k}($bkp$vn?Y90_+L=*pjB%iMk*2mM49gF!QWwt5i-W2*~@GiYIt#vT-D65E5NU_2lsKdjRY zW?m6NKU}U=O6Ep0gQZ!z!f--aFZeW%X~{YGGqFiSj59-ZUAJ#NTV{tR&eEm51qgv^ z@oA71`P-V=MZ{*&Pkn5ces1FTO&u%IZ)fe$^a<7tI3uxk5a-PJsoDu<01@GDwjI}Y zh)7?(fI<}IB(s%vcHYq`@PJmr{YO8`D3#+#Idz9kk)_P)!>rG_&`N)NIjVVJjZsVo z%(9`#F=(b-kIO7P9^q$1yh$Y-zJ9>Wij-bWP|)@iKEqlIr$;#uw8#)x>c`TT*bQ_O z1ol7?gmAd-x<3v|S4w}l$;`;dC(Fo|M=}I({b&j)P^7j6grDXN!z;sPN?}*fZ#L!@ z+oLwPiXJNW_zI_2#+RxkR#zU{QxdRH(2~U01M95iW}>IM7`zh^G+ag!gC)Mn$&R^r z5kogN8gsD)}uol=wS3;RHH!uO@3 zAL+bc;pznwWqVM*M7%B>uI>z*4F&>QLAIvSg zIi5jfk(9KX=7yn@wg=$M!Z23M0qSFiB|s}6 zD}d)aO^_Fa$2t_wJ^dhyZzHAng5;nB2TMX-H25q7QP>(*1)^u3-HzcG4RCg6%~ zbBkTDA&$>vlDm*oaVib$XPv_4!Cd?ZxIhW1+1E4LP;|SlS7tZ{v#}pWD7wNa{wQB< z{P8syDj-x}&+s=zwr6-B!+e#&5oErZppas@);O3O1Lp;1UC*%3i{9P6#wZ(#PO!0* zUjyyRO>5_1$)z$c*U)9^B&^SOrxN58FUd)FrydAIB*MK*l9futT4%k&6<4k>H^%CK z*9ZG>FYGLuhY@7KGO$l-=ZneaUXb4$eL>ctgjYnxlKT)!jhY1Kh7Ayy;{f!;S{q?* zkSeZg&3`GnwP&kg7}AcqVP~AW1c*v}uM3o~fPi4XNjW8Q{(5%&m!tQ0?>NYS2Hj)1A>?9ol&j?FM%E?6hOvR*4S7YdJkszf zK9b)d|3eWAn2F`^sFj<*Iut4O?ls=8M0fTZ54GU0F`l49*JxuWq|ov8tog4-Qm?^7 zEC)knSKJm-)xWN%ztL!0_Atv*+peFS6RNMLzgzpj2b_nYH#fGvT@XR59|H8>-Cyg| z8^XMK8ODi@L;oO4qx0xM!0#j^QW>zb>DQwVtuws2qX#t+Azix2*l<=TK{z>bEOH~u zNu>mxnfqI--e&D8>_pC1NPXgrw41(Y-G5eN(c?X9E?3Y<*@$iQ3E4=<&vQQXZP9z# zX9xQ1LFs0_$acEQX^vcb1zbJ)9wA8T(D$_VJ_0TU=OFtLzr&>-r1|8`{uiuPrvVvckH&D;tmH$O-edk`Y z6f&8Z%Z%~T5h${zU_7U&Hy*vU2lYa(hwvsvzq8c@`Z29v5t94tZ2J6iP)Bon^d!i2 zJG^vv33?sO$Cr>LEXi`D!PF&?UV|eP;YKM;Q0!L7{l`sSDkpL+#B6n|9qn}5jA8AN zzW`G|Y$?qV$R@>^W4eM)C`ADNgAjlVdZyfKp6Ti0tRc~wcWpqUI?cPFG*SM7Oo4?W00GKF#$eW zl3FF-hd^OnJ+-Vs{sA496d;sBokXe`vpb1xnR;qa2@mFk?spNL=#7M&)o}WDq2EPU z_)>U*Mskthg7*Lb!#YUK8^wzs=gSvS4p?C5sfG$lzJ|42Xf6WI=XhZUqtC%cPC${B zbbfMGP7-b=)rNx*^^hplz1-J4iU%zZ2KN~z5<3E?+%tA)==8;ix2PIqi!|T?d`6O8 z4&l`_B_`@gi9v!3(C)AuzKVao!zd%ps3>b9dk}@y3uUn1zB0okvgF#Rehyu2y}P95 zaI0MJZZtzk2v+W*`E^o_KA-ji5fLm$?+D1F0~HE7bPebal<( zb$}~ECyV7@oAMpmgS2FhxD%G|lL>-)h4M7TH@J~LDfW_?xK8oU$6zk@+{>aaW0xc?aF zc_`Tr1)Ii1dMq*?+X;GXSxoVPq4zJKz7R5pvEb^^)vPZtbg?>T;Qs!paM!4N69X0L z_>z@=MP3Su!YzC1?8%L#hnG&RZ>*nQT{*tS3$vnkBc&HMHz?i8n*6$SPj-4Gq`8w6 z(LlhejE#_%8`d|pL|^wiZZ>nv$hit$QTTFE+cvtUS9D

@>yax^e+?uWrW8vx*g42EYH=wu5U6B$<(4DJcBRk@4eK-Fef zMAGzE$B}I>u=6F0+-f++hh}GS?P2FI=T0C|-U%X38&)^^kew!q`24gd>=Y9i0`ons2md*68;uWiJtU`}}B<4yL@_!<4s$DVO`t zV@h`HL{h!b!h4X%0LPBC>pknQ7wJeyZ_On0_tx(EovDQK$?PTk&3AL+BWsSD1TjzGOHO}g^%qnF|QOWc#$)sJ25 zz^psL4{Q3VG4VAIrk&t@gd2OGT(Dt{V1QrG52M#EcTxsNj_kDb*Y)NI?xSzM*4ps} z{1G!g_3@h5rWE9Lq#*d0k$(-m!5aJzgzZgs*1QRS4I;V=p60jU z&jXQpTQ@$bJ~b--I1%fn;KY;2hc%G8Qy^kk^;;~(7P&zeQh;NVMIl3J5gBm;LIiJJ z=Do6%X8g?!4GoH0r9GJK6zpAFZkxlnJvxPJgqrI$I1y>XK{VNr6d33l&@GS_9YA$n zb)D0|;Ej9N4MfZ9Vq_^GhruJ-oRgItyh%>hv8Fmc={X^c~^|N{6^xn{=4OPllpTaI$?q(M|OtdL~W^ z@eaHu%`^i9$`%ccEeor$@3Dx4fpv-TiuQdL)O$>s z(0kys)OU~fH`9W}4>K)-DhzsxOJDlssbuu)jiie#YVeA63$1N@x;zh(#Q)u2! zvw3drbi0MpzX(j#E9#%H?mGZM0WH~}bGqm4qbY#>UvMGCU$7yl_u(tflO7p&rY37fczA3)5sEz6$4B{I!G3^Le+-lzf0$_U&2N4CJKz2O4?X$Or_2Y@ zFo4DWupK_JiL(k93F?%CI>@zd&H2ko;o^q>;ZjnZeFP21^hGQ_jR_yI(HeR+AICOv zKX6y9`ADy^OD}(#a+WQ+NV4JO;8f+(o3Nw&VMR*fpn- z=0Obo*j^)sSM2aG><(Cj5d@fkkDB3yN`)f+$GHX{vF4Ng*I+6${mIU$f7(s`vze)X zdM`kxV1J=}N9BBI(LJP7MNts9Hi))IA(Ap|^Sy8NTM+YEPTNg1L*R1(g@0JDw7o+EV)X_BPk?6#3BXKI;?OKAP05;Ni8__rv4j3X#PS8$r>nG&m zWYiJ?Vt~Tw@iP|2Kv!i-Z^GlfhR_qj8=gr?U=kw{P{!^AOdcT>#Dg*rU2%V6WY);S zjmRAkEcb>IL1g&81Z`zM5oc>EGL+i9Z4Pn{>tji2AR0|5G~{M{FrHk;Xd>`L=Ulnl zrSZgL2rW~?f?K&Y@^plCLK^tO0%RvGde(HUp-Pqj!20v7(FIQCyF&z7 z!AEq1`M>!XOkTW2f%VS3*dupdz|wq3I)Rf(BSGB}ZGxLWmD;K(*UGKuJj*P8U*zqu z`ICh!{{4Jz+Vwz3LKB?*ne=lyV$!c3Mm!>F@JnJ3?;Psmf_xyKn|0mMGxBOg&E6Ef zGwqP^Lukwb;@-;x6I}EJ76BNA>j8xML-m8CQT7)>qPi4C5(rT{C~_^g)rb1VTuE1A zAD4vtK5`R_yzWtAA)gPK zvW+r{M?bJvOGNYjye8C-iH8p_RCzxKV>E{zzK1I6AKQ^m`-PK}#%WxG=Lpf%i-;ki zap%Wm(mxiX1^zSE_cP}?W4)~lybKa5=%)J5CRY4k!_hIjK6bjwehq98D*n4?WH1Ge z8XYs{C*6kXA>3AT9MT|dSYkB7O)pYL5td9xidIVQdaQAG(z~cUD0Tkqc0(KGaUy39<-qvW0v>keh`<1H(O}YG}eq!!PhG2qu!M zx|+>x1Eal(s>84W%{1yo!OP|byf&&j8B?+IvZ!!YZP2ue>iwZ;{0x+=O}QU~b@xK^ zBuG3MAk_Qa&<3T2H9%>ul`p~%qHYyrLvA0iqdcdebQ2h+>rM~e8?ezaj!SeJ(L1&# znKsG(Ydd!g*jPG}yF*6dd80SRt1-qxA&2=9D%d=3=hJs!SSTh3f#&iZvKE|97U0Y~ z1#S~O3I*yg4hlI^KRYyFr=9Im{8Oe&*K;GxAeWc!ji%Gt(q<_^+7x(t`0h_0u znlw*0M64onu2o4MutSU4S3Bo4rqCkKa)R2LUlkw8ipvmQ2NK1fe2{Dbx8~aU%nKbI zn%nn6*%8tihcnba?KpdF&q6=E{;x?6+%ch3STZ~5EIS?T-aG!7_f_(xzc(8Db+0OZz6p@wWI5q{$BEu>3g)#$Se)m z{y#xr$o?=hJ2T_IIp6tB`V&??`_17aNAC0AB%R-+Kf&CP{pOzgUOer;c~$2(=}$oS z$1?Ldkz6Pgcl}qp19q<;f%us4zSv0?rgXh%87pP?i$8(bHD!lZI!ESjNYT>Gvr!}L zc$9La=u$6Hi+54e0e+dljLgdZV9rjvV{kKNry$6YpD_~Q@4vN*+y@ zM04aQ}9k2mTb!L!t9L@V9H7haRX+VSn&tald0AGksOuUk5ec^hy=@s ze=ui}pkwfB5WwC8*0H}L+;`rn_w%K1I}kUWi0)h&ptwDRjx+4p-JK38t==Q0)kR8s z)&ABqivt<%+l>SKDd0f669@R)bAtoz{lT2YfsVn=#8nBwbASU~6c~(Ze;Y|Kdp2_S z`lp+fgW%J#m8_(Kdo&;8E6fGzciP}kn5z))YiKk%+H2I5J=n)eDp*L?u5ckw4F zF6fH&to`fAy5BHh-mt$(PNr%hm%Sfzb{0mze6HX?csvSer3@QSt>uS~Y!O4;c5gTUM8hh<8{kfWd*&}KOMw)=@2O17@#{7vDSmlJG-ZzqGSKb6sdDA-F~ivmy2bbt(!-sJ!4`F&_YQ z9iEj*nj^pp9;`F3Khj4nQB~J{3#C zH_cRft(AraG7z2jZz8B9z27M3t7=HNkrbpGKq!;%8zI!2{w`>IF6HaeaC&$<^Gy_2 zlMDGI9!no-(7nL85Jn~8&J@ItoZbS7f*18EddS=jalDAk1@tGr@QYmPf@7su^TOZf z{G=Klgz&s07HbIKCXA&TLe+p%46aak9)xy=Ypn>P4yR^aOmGT%g6I-a?5F-WZ0Mtf4!yuoNVgOwwc! zv{42;nNj68LJXTfb_HP@>_P?;+6b@@FRA1;8Q0gE{dn0P^eQZ^fDAZ?Rz0L z+<#Y7f*BG?LSOEkh(`mRki^J#tI7C0hD{>;akF+M-a$?hT-B0?gyYKPgRe84{KD!= zxP2!#yGS=0R@@=Z^P+=5YS<>=0KLYyJ_aAXyA%(m2Dog&m_~Ae94sX3PD&Pnt$mI}kIxb8O$scR$r_U_B zY)RP?GC%U`yf_aR=_I8`SWD{+!bf=oTIS}V+pPZ)mT*1z0ZDvtn~VQ+b+~}`**Bc4 z`sGLVn~ICT^tIO=7wAFMSl7aEA9GQSip*etEU z3D#fGJpl4UGqD|;BzJhpVa0nc9M4~77Ro^7W?V49x|B;azl^FB>wwi2r2yu*WSVrx zJ$VvG@P;H{G#q5M60xmV3g)pho8#y)=Vl}z%~69c#U(fcUy%Iat86AT&F&3?+8yI#+hB1 z_c&2RVkT*l8I5knA(QQ-&s5G(xq1m%sM(_=`BfN>^q$x7xnNk3O2x1oBpv+8&{WOJ zGq<8lL1soAKqdmHh(A8yS2NGh!ppJb6<#!k3CnvJ(S;{0t}(7q(uON1C7ruEDifSSfvTH6a-xm zP7}BU$Ng-z7(p|)VpkHork`0o&N40e(-(Q$8zu`f=DU)s(v=oLc9pFZ3~flpABY37jI3EB*tL)^ z8cT3plj_O+q$F85M>0Q2odm;-c?)6HB?Alh^>r7`Z;pUYX9=&1Z+45v`iMGdq=zF8 zN#z}slI65`Q!2Qj#Oojx?jf8Fut!E8wIuT0t(YSi%F`V&uX4k&Tg@5(K1Gxl)WOZb*v5g%IxUO?ZEQ1@ z7_osBGCzi6wHtv>p za>69;bN!vTaT)&-#)ZmK$0+-X<{G;DbJ4Tx4n^k>`o1G*4*v!g!3oNuOcGeh)XT!| zj$;lb=tPT0sG(4I;Jf*)=*j-QrsXj)h<`nI1>z;+CMYw54zWrOqo-;=varqkohwpMh27orA)KLOJg}5kuBm$VkEC}ZG#*ju>^rJ zAlJxbJ;K9>| zS8J7vg@;divDt~evZg^x&ZWAT3zOwtmlf)9pyKZIERkPoF;Sn##7JB6JHyh)axP_a`Y6YU}Bst5@Sed0|CvgJ-!y9U1jfGND4gkS{>YJ0}tEEyqmbG0dUpX&w=(HBD^_N%W=0?jf)vOfCo>;4WoF z{oG^7%o3rTwM$Po(sHJ`=yU85d0Fza*Ns2`+?~#-dx5$<4ZUHTRZjt79Tsw5auvnu zDcOKIJ0Fd?J9CUKWO`s%5 zRquKeY+COk$0>yINsxrs7_*jvDBFrQfDF+mTCIYJNtP(Gn`43Jvfw7A2T|m4864BR zWaKm9xCi)n=p7iCJqQ$~@4#hqr~s7xRn=NgY$+JQh#`eP)9>^ydR0ULcDW_Jm7DYA zQP?%)TS{a~uHu+8&Ohv6r#jWS+2K1nS#DseduVka6SSsrKRq*aSdl}O1wjWa9E8O- zs4QOe7i@zOH?TJ#Z@9621tdQK0h4YVE#$6Vz}X>{2DfeQR*Sh49iTJ)3*_{^5qCj) z=mOlDB03!~L00l4kXoEX)8iB?qKoJXl0-`rg+|VYgA<|M3^erwD8y;Xoz)JteXmM$ zDQ$H1u307*NHVJhaJV0VZt7j-%7{2+$SELlNU{-r!l51HvP79$ZMl3(T2qNmtQPYl z&ys>-p9>_fhdh1tayhAx48rrPNw7{N6bcIpj^a>K7cBcVmO_(@R2c-O>YeH&%Gf^6 z9;zkJxf&0=1oDEg=U_v7hsqV*_lj*q?hMo1Lk7`+0yBVc<6YMzLAWl$b7ux?Q@|+_!G50 z5enQh<$Y$MQVBo8{bOcMqvHBXl~fq0Psh*eO=gHNSLWQX%`$c>sRizH%H6~%Otkf6vt1k{@E1s`r7zBNH zWiXrF{CX<#JnY&en+G!;ckDYdUmmbt{mi_N;l^F{_{e+}uegWoD*)=t*M_2B=XPd< zXOV;8!UQ^Wu@GYqw&gze0W%8TjTE&(UNKXG0|v#+BeUqTfsU0Mt!+UslviJL#xJP3 zTI>*on9bO6qwu;n z%Rr-EL2U}8xwTMK9dT$it-yv}VXJZmQxL&a@aim`xivFxUga zfkA19G8(~-h0n(={y61Z{=GJ`xGzq<9Q4Fxn zY)b!-oe99SLsFoCLYo%)!g~jaQTXOi$NTF&ucN>h-ab6omG3x-GYy{$%! zV?ugSfU@@rqINTdkA*>-*9jdxGZp(&Az}mg#F4A4hZy$Mz5r_2X6+)YmlfVOXpwhJ z^McwDyK5{W-)3-lS;c)+RHav7qYXNvUw3>;-nI!X!l#Yie-(gWL&(;X&0wh=0%B2% zVP>-5GLZc-cnjfK=x7BNEOa2^kwIXhulIQb=;YbOGlDWpSAl5cxXPJhS^>9HNHyS5 znyVj~d8C)BkU2M}S$oE;T&3eaxOnpa|G4|`M+R*V!rBoU z+kE9p^H9gOpUiIi$)0ULJGighpyfUWO6+zv#pcmLyX+&lL#7SL`OlJSv#%6|QQByl z=LhX1qxU2!l(L7|LXf4?8(=F5YklLB8G76Mei{Vd`4B8ooMt)00E9h-ENe5iWyz1tifQ4|CJKo#r6(Dv1INsaY z3vS6_9U(Xr)m%NjS9Kh-HjmjZx)wg)&YZJd?Yw=JV}BH(#a;@gAeSNekjJls=VrJ< zUlyDFeH>QWj#P13=B%{4dliu9Ga!5Ae1vD9i#d#+RNp!U8D2WXTOPUAuia~{Cqjz* z^kQQdQZB@mt@4Ek_xe%cwv)h=<&ZhOL32?9s>bUEL#0i95%_!#(Wi8?`Rlzl5KIWe z;TuxK->lV;)@)wafgqDTcr+>K@rI7K&h@-?PTqP`2U=XoV#Sq|=Q^Z#OUKJ^%aZE1 zWl%^t^xLko9qZ)K%AGTOBa26!Wna75v@hb3K@jtf4&b1CE!lT@1pi4!-F%lK_`9wG z-U%;r!fgh8a0oLzwFt*<6_G&9$uwpW8c|p5qBYA-8hV6fJJoiG8s+I_mf9Dq2sAOXu+oA*w-W#U2M2B4z2Eth z|Hks(Jm)v|AAKZMfx3S4Utu=y>p->-^wO*k=&FBUzq1xUoPwakP|ls8_p~$2dxp*k z)Tf0QhlL5c_Xj)R`EV~hA69riykB@$`z;tvFB7bOu)zQ|Ke#>WL(iSY5Pq2UKhP!O zTg^v0VEtGxtRGWYKek_3*Zb}htble#qlGp!@Ww+RcN|NsJg}vBF*NTvk7d_QL$nP^ zBM2Wo=I7`_5ujf&%qb1bAC7d@mEwMC5?v;ES4!!70_5#XSoCQfu6q zvKQrHSYVV30rP1=^J5{xes7HO2sJ;CZz-XMb;7rDf&3T2i2M&HYBZefA%UTw4?2>B zm#+@mSUM8wHDBcYnsQRLhK%3h&vT1EJ7~Vh|9*MUe0jgiI?m1kOlMY?TdwqW_6fr6 zs)mT%kXb|cxZ!4o?ac%8)%^~hnc0}Pv`@mQj!-y)1-Sv&m};z;Jma?C<5+#UGJ(Qeo*3gZxLm7Pj#-M$@w>2Ln`b>o%1$!yDkx9V#u(d_U z%#alM|3_-_&AaJ&J%; zP2*KvmB8%?sc+INxBDVn-x;)5!pezIKNWX~_*k~@bj|4Y0Zb|3l)lF)@m~W!7&Q1F zNY?EG_=Qs)`0=2@|Ik#&^nbG7d3Vzt*u1hN!wcNSUi;U)7zv9!jXhS?u`P){4TO8z zQS(c*DrL?Ga?gmE!ghZOrI|-55W=-qQpa-ql#{c#b2{2D=63Wz*2Ebt`^sx-J!`gn z!&ETwHCq|;9{NyyWhSsUNgTO|+_nReg@4P@dCo_!l|tpleNLs% z$0)0ZsEN}OV>4=p>cuBqg|eYq-K79(qTl&0YrvQ?!R}^{nYd2#a}E=+r8Qj3kSTkM zm|X9La!9KRDG&MN>cb~pFeAnfK#af)*Ssxq`GmrT;VhvAF)8&hXcT&jvhIYNdvDpf z8P!{$EZrP+9(Oq$P;Mi03to_a4crw&Gsq-Ap|%4jQa04a1T7;DBv)*ou2z2B#WxKtoj);8(|? zF)_gg6(I+rPb^^a&*?vpN8e>Ul!lPG_#DI-QEL|WvIIUN@PvLLREsMh#NS0?t}N#C zmT<;>6^|1}C`gfsp3*B@5vxmOAHN~SiNA&zN*}fh^Br=k46-ntJ9t5_DzfGrx_LkI z84#6&5fR;1_U|yR6VX|`E&w4$6{PXxjNWYVNW$x_t@+%Tu2A$wnVBC=30?rg4L%_r z*EN)C6X1uRbilKC8}3v^`UZ>L9rRJbzBBjr=Xi}aLy;s%il4aXVJ%rMVNrxWU5XsM z^_({2$FQXbX6GNj)_z9sjNu4p&oXMPf$kdm#;zC#hx#o!GXr0-t~l*5()$<|@_tJv zvfqjlehQ1wF&*4ZT|k^8!q)=0h;x@*Wd;^81d1OY%o$e#-I`gsHVjuHcC2(3OcxOZ zM=JGxY*vgUWyzXLghRL+WAZsrR77@FTe2`_AFEQ2`Kn)V2wJ$@xQz%TMJiWT-I_08 z1%3#T4A~uNfS-#Smv^UtUrpvPr2&9C0o_B)c1hyLtoeFm#@`$3laDb&&=t;#MvrBC z&Ea0b_|)n3r8!v|Noz#3T)Qe|#Ix~oMDYZD@QMql6Id{C48&X`6F{T-zQiCpoFH6| zC-G&M%gE8e5#{Z^GVp5cVFW=b@*x~g7vKfun3U| zSOYqNfzz}N2ZH-lFO+PeFlrv@T}?#nO-Ub`@JMPnzX17Y@=nXsQDP2z3OM zP2yGtXrq`Ib_v-$sH;qrIjF}HMWmUINCXlbPnvJ%-(64~l|czcfa4(>FW^k;7}`lw zsge)80k|06*(w0(nA*T9!HQ*a zog&s7apqx&7O4>X-ARBm)pB!(bNn_YrW=NNv7H%CTM4emke;}znHkGEAl3xuB!@^^ z*&LBvNnhjxrvJwI$zw#YQ*vwDgS7@$W=b)Qfk#&k;xg1k6V4GIcsbGVNr`Wz&-|p7 zo^*5NgFG9U8l;fA=u2H(Kd>a4LKtUh@euZj`$<7a2#dAI4Pkl+s#fb#hYUBYl1tQW zZG$)jOy_kU+meJ(f!dWb%e~ik!Ep)vLY_)Y10<8IfUS9E!THizc|jy?Vm)wF0bnLO zX2Vx;w|gt(e)_vOG%S`pYf+Z5xsFskcV8HUCXDy}SeKT4$?PyN)AiV;#~!L%;d&3o z;Ce>5?z$?i*UrB99=M9#i6cM7(yupArd_v1h=PL(p}WilLa=IHw%C7%knU!jMZof&lK{5}SC0uHrLi93}XwAMD_za-%t8;*;D zeYYNz#ZpUm#X38r{X-L zFt8G=AJMVyL{1e$()(3RK5$x0zIV&tK->HPeh(kp8_gTfyt0MQ z^gW1T(U)R5nelI@Vo=jft2rkzLYjO*NvWin$B?~L1l7yc%67(>qv&f@|37*{cHJi` zBBBU4uyMy6v(Ctw5UFMKW$-CEyO6Lj2~wexJ$a6TyqqltTTVXJc2q_yolfbICli**iL0k31{A_me9l9p0Y>gazjLWV># z5)$ik2GxEWjKkZtpB`A&7#UnFb&2UqbmH}C!hGc){&(^pz4GsEzw5^QUp;__m;B>D zeD*bO`|-EwdYb>2b6>H~yyLIjfWQC#C*Jvo|LjYD@mA`ofAR+}`h)NM?$Qaq^EZzk zXdL{lfBFaf`xEbZ(_2r!;xGL!JAKLL{?4zie(hi_c#E_&DErcrs-ePgYM|HEY*b6S9VaElcYani85U{sH8c$$!Q+?5{axD@;%T}d>IlFE=O~j z54mw3GBmxzqR@eS0+P-KCZDnl$z-8&fhAhEcMgKm^9PnTCxy(e@1ztQk_)fyIhdR9 z+^iHu&<+6yz6lV+wacoCsi8ea89vEg@~l(CemSnNK8m&!Y{F?Q9@1iCMXsDnOFzG zfJ1GkZqq-9hELTlTQeE+4gM1;kM*i8iA>^8p;TPvc1Wj^kCVr4Kz?Y~!3#5Y;Wl~cnnWKfBS;6s7V{20 zn!6gRL%Ng=S9b zJ=drvRj!pLV-h?5Go}Z4$K8Sr!h@f&$9g-XOctyTg5)u5J4)CPp9CDhi2u=VD+wpW z46fi^zH-oA5M8COK%ErdK;0a}2RVSiQD`8Mhu6Jp^ms0Q6tVG`c?}1hvLXJ{#P9BW zN*N1Wg8=0SiO9G`sBuvVgUC0n8t~L>WPq2%qRHD*+;VZnA=lyrBv*h^6^I}1WG}QE zw0H?3f)M&@8KoNXlsIL`I(|ObRDVk%Ql49&5m+GOV^xs49j{YqlHWuG$ta>P!q4S+~UBMoo#Ny(J1l~~yr z#(ycfYH!YxlkE=N0HhShPBque{XYN6%wOO|ldy;s8TR(?&mT;5IlIa{FCUdtoH zrC`I1a!iB~P!4Pa=)Gq0H*n){ZEFiRh5Jo)Tj)2Is*6ld*U)2wOuM;b<9v37^Ny7< z-z20b`wl_*eFi;J4nef8G2%gV?vc>1n`d7*;*e0{{2DM(u|PPlK$Su+Bhng@TS(1= zN)f5ea&23UjQ~>^yo78~=pq|(WNwNAC~lv^nWGxSN`msY43fh|lJQy9 zvME<6cc3w@DAF4yxCPx0q#b9}-CRb)FzhSatyaA`clX^FAa=Dkb3lx{!4&{Ku7=va z8(b684f?pdj+FPi58rd&k;8ZA4Fq7hQ&H#sNz8rt{qd>J8`{Wz`%c?@B)|&Ntk=1b zne73t+}!r30d`cbgOgqQJRprKu{6*m*s~>O++E@(uYZ-VzcJRPbT@myvL}qa1vi2A zxy%n7+!Lr_+@977S@-} z#Ouoor{dpVI&=CCnh#gD;$2XsyI>s#bG}O?Nvaj-ZZnb6oKx}w$(9B`$(i0lG)EhP zMIN3b7%*_Z0U~}{=p=Cf${_IEzn>z9%-Oe6!fu2MmC{8ETYC1%`fP1;G$EV-`z$wG! zlQ{ngtwgxkcuMM+1v$pq1R#x<)lc|medMPwX@BZ<-orOLJnorz*IkXW0)lo7JC$>n zPXEuE8cbpy?u#V|OE3-aP2vEVX_D2Hruv%H>~Ib2@3?b9~AcnbDi ztofduH>UJQf9r35^JY)c;mLMVh_k&}^8>IB*d*7lxb6`v2a$jmQ-W9iQ0H{IdR(%| zGQi|jm{hwI#WNO_f$((&ZX{FU{!`WANb^3LG!?`paKDh zLGY+qz$PLqSR`YI_GAxZ-4KNhT+|{{9W#cHWF5qMK_GAxn#L*OR`w&7HU$yW_ydee zS-%a3=?g9)51(hKRj$84xKWJ;Y;0i}d?fEPZt_T1z_Nf!2cvko zO~mDblyr|l9T^?bU)%T*>mosx;yMrnfeXG=gh$|0SUe=gA_W6teT8usT#07xq*w%e zH58uhIDd+k!U_OLaY%FltZ%hNGb>(dBKH%q84_i{C!g}g2KKxAK!hqsM;RdH=|w~ zjnSHC4&}-J$xznfBnN+~Sr1=j!M(-Gw{E-*uHpT(pu4nQ?gq*pvWNICL>czSqL+T= zngF1a%oqn>s>&9y0{qb#KJvq*9r z1}y|L-Y}1dl;JPn_#)Osoc2NnycSk$!Ne~1b(&&1CJh>FpciW$)8OBDN5UO&rX-pI z2n{@aMP3NV+?+(iLwnB_)D-f7kUc6Z&)`+qmv`pouwX7HdTtH{@DIOadT#FI!nuv( zrx%yx8U$2@BVA#^CdL5?AZGG7V4%11^p?!%Clt%SdHqwu$=a$RYG=V!P!2uT?Erdw zR^}@TW5&cPbc3#NDJ=!qaCD_U1Wu}SmxAt~h z*R=>`=nr=cWUe>G)DO(wjNV2*2lqdOMGWoXBEKMUx?F&;DiP{H7KAIXh!6=`4zBh1 zI3w3P+esx0U@L`7n2bNU)WkZYCek9;)UFvA!SI?U=$L5RVxeyq;92CLw2^~PVH?$S zm+2UzVG#VnI*gUVtIJqJ5aB$`re&C&;qUCc(N!FBn0DS~ykRS-` z)NoE~RX9kUaXVGkxnUROB$5w+ihTZ9!D0se9h@+gX6WiSFgr^DHh5MbZ&JaJ z^9*-~21b?*df)MZ3-aK`@yrJI3iT=PTRO~=A(*hbCS+n^bcbK;hTaB3r$!S}Eu2$0 z+CWSlwGffxHVcLS0_X}ztLS>2@U+Fex{oT4CE8`Fld9np>nT3X5J^`MCFu@lL(L?r+%y*1O?X+Ks63~K1DH< zbw!t;m&$TAG_3lSm^OvqB-sF|AV|tSLcNNQ8`bMk3#^k4WUzfey4UXzi@kR9k!B}e9K0IX>Y-hU4}@KqEVGnyt^vu%#YY+Zi1bQAL=L$ECSzLB@3xj3!AM5|{{|ybTm%jh`zOm1L|CRfv z&riQ-+Sdw1Z-r!pdZ0WA5WYaoR60}CclVg(%y!onKFElzhnY#%l)J*r$hU=(N+een z`GhKX9Cya4&OwAY;ebN;5oI2P1i~@y$-s$Ze31Jn-vzkEms1sIxwaAB6 z5+_7FW!f++Sa)FBhx~@4o!TaN*90WD0w^Bdfz2F4S8DM2IscNLTO9I^8_iB{SzLXl z*#;0MRu0vYs5d@In)+EVW zT}>KH^ID&O{4c+=V?#%8QQnoRAKnfM^_9^Ata>X}ecp#cabANn&>5GMG#I){hH=4i zwj!3HW?>8T12!s&(Z*B&oSIo^n}=5bkvl@A7?OjLz>Qc0O7{&O3EVo;Dj;+)oxute zBDi|K^nkhX?5yJWt?nCWp(HghLWvWozyxZbyE4&<7UL|YGwCEFoylan)#zmCs_Cvy zx0!Tzs;ZM7NFWOr3K0Y&B;bI-aS?Dai!2Pdh>wln!fnKj;vy~t1s4e@`1_x8zI$)= zBO1`15^(y~z2D<}=R5Dm2J!9x!?W6v4dgM{v>Z>nJLSo(Le+lEMg7qqj@3~lmsue? zww_8)56ZHC&31RgC&l&MP+?^4-!-|2yOwa#c~WjV#iFQ2w(F+pU}mv@Ar7u0T?j6+ z)wsgyp?eYEY{!?>QoevbZH@7K_peJAhQY<2P8gDO;n}Pg_hx(vP>3Bx{cle1f+UlR1ZX|{nY%x&3(%-JNUttj zygWxQ9734!XdqxAm62n&_hA<;k!xFJIVY6jD zEUyeMbLz82Bp5+fvxLex;B*2OyQ+>!s&s)N@cboC=7quXHtgey?AG1J`G@Y#a*j^e zU`Bm5?lEOLDK$sJf~|q%90F5hBTBfkWuvhgH4;h!@51t9{-l#F<3MAkg=FmaxO?-8 z{O;lxpMRM;xBkD=SZcI@by?0df#yykYH0uu&t?x;s=cK}pHBD^gcTvQjxpcawj7+Y z(Lw^`wpO?WX8Ms_*=hi#zDQOwRa~ElcO&;3MC3vZux+S4s{r0>`|@Y6dDaPcCa%^g z5+AhsWC-#66*JL*u&thd2ZIg`$|~)vCZL90$HrkQP=D(mrLEttg7F%QG+`xdOXd*1 z5NynjTItB>O2eSm+6S8iC3z@(eqAMN2ou0Gf>yS6G+(@O<>O9q)+Tje6cii=*6!*+ z2f@~&H&06Er+6V)A?y$^+H8{xfh+EA$z!qva)~*ItV5Y{7_y>DL)#c(@5bwXfdENe zev}h^pnpJ7Jz`LJ$KSY60B9ebY6~5LmQh38eGj(~N7nmT`_Hy|r#la#pircOqWB?j z=>E@H%Gmac3M=84ITg1lq!dGUqMg*gN!yp4qKj3uZq=LY4Wqo}>}9P-`=cc!jzPC^ z`Il5px<`l&3KqeRP63Alxs4DV)Jex^uYwQ+VeH;0rrMOj+nxBzASICXA%&XTc1%F4 zypN9sbloKPhpZrca#Fv}o>dx_m8UEH9c_}pioax=2b+8QsRH=iOZ&iCs8h5n;EOFq zAjNO6)>ZS_m5XN$Jin9HT3P1A%#}GTscl|N8gP!(I2G-D);?U72e3T>3xiH&)?M`n zOMP%Eh$d)20&LZ!vMA~#R9Q<}TW5&vp|0*OSfibIXk}#tmc#7q$H)f}v}Q`be)Bni zRs!gcuYB(pKkQqQs##&pXdo;}LAT(egX*xM`!`h}gykC0%f%iuKX`BM8aXF}deRUxkRoTbjZ4$o~rhhb*1g-LT!* z6&rRk$jmbDO8TKcz>u407;F-N$Rtjw2*Uy%NZKWSYi_eFKG*IQRg z`VXCWG;Xji3a76*kOfX>wYMnhFc~x8As5xq3@IC)OrhE~Gx=Rv>lHF#DS}dO9B-az zooqj5WzEU7_&UK;OO_<@YU8{;Tq1!n!UDeA?b@T|y?F3i_q07wt51OAYVS5D#`s`+r!b?R|S8y#5?G& zsC*|&$t68)vR9<#sArj7DA$meQeA@%YeDBYv0m5(_Fm8 z6_)QOfZZ@@amu_~)CoUr#k|wC43fkjGr*b9F_h<+j~( zt4f|J`%cUhq(XP;y{SJ>DZ|pp8EXz>ewx9}EJ%-=RIm8hQ|He+C+k&@^#EQ(SZOdw zXBFtck*a3I*HFdGgW`WdnmWzwDOxVjr?^6QpOj-bzicbU*IcME(AIj>V_E+)H8b9O zv(~mFTajvg{%=z!H(FoOF8Rhzt|CbULk1ndT-+~?7|QT8K|$wSc^kob8xBRUuR&j} z*+6#c6-M@<6O78+%5yTXCOYT(U)TdI=GF8l#)CGbFqLQp3!}2Q+>xjT zl;wco6JBOB1w7O|=zeoR0qKM}7sYt!}D(OUI} zE#G$>15}~Z$$mdEeX1D}uM!Pf>E1OWWDY=tXbKmIgL8NXG~!TG*H6m^7D}CoIFJ=f z6wOb>pLsWb7j95q;}kApujzWmWuA{Hsx|T9R}8{xfDX|>(}OQeeAgj}bh*4`A*iy9 zM%&(oQ_xMNzPmSmvsNcxzk6&NJ==dGzA#l_t-hh`^F@Edtsa!nm0p}T{m+f%;&(3q zQ2g$HcKJPPWZjUx!*N%iHHuHC3=TYRe1mB0=DpKxv?F_=l@hGus|)7%NP#-7FD*TB zNh0iZQ!v7s3grj|->Hq`tyzeK3n!CB**u~b9lgy!WYXj#SLPpIn6uczBw2=tmzWi! zCr1$UOqWCLDPgZ4wbt_5m1~wRM#wG_NLa7*QkaghU77gFu)#XlD!X;5C3=M$sZ;bX zv1jOzZdEVW=*&jeC=_(0B(px95r1=8O|@FEUtHebnf;JyO_h`OD5X_8+EF{+#K0{h z()QEkQJ4e+6%cD77mtWes{aGYaXz!TcHi`eQ(biSLYk{=_HeVQ&}KZxI*wRFk%%t- z-6T3x_XcPJR{dN1cR4*y(7Oeu9*+*1f4hjlGZgM4RuS=FVTwpHa7$gw+&^lDT7zrA zI{~bB#E@1vrSy;#Na-sE<9*Q|4K$8M$z#}cj##&!OldYZAxLnW6@mreHu3q%rWY@# zcAFfd<=u4RG@;`?RRG=XQei5v2h$|p0~2h@`_SI$Q@ZI?NbSOCVrwNbV5vpU*4E)9|?Cv9I}6p5bc{cbV5X)Aft!;bdmrt(P% zrI^Fz_q61y)ajpM3euPnDPbkoi6cr*{-m?~XJJICSY{GnE1p+q>OvnXz^z0IIYiAs z!KcOx(G+l}1@?ZhZ*uV9$?}5d3}))z;kDhfJ(9Q*0t0KpPRa3|GEQGhK ziZ8x~hT`AOhA^(U`|u7jMcIvq)jmD3TJ6%D8YDp{`7xH!&3`rVY9TisJ$)x@$jZ}f zdW>@3Kw22@cxm=!TZKV%P+JaZBy~LVlbD;vQ-a`PDAlAB+uvKnlz@oDPP_RiavqZ5 zrHI*=w3aG9kI#%Ir7?~@#>b@r}NeyZX*K9Sb_F;;99ycedu z159JozcC)uwrdAtsO;o&wo582&!GRO4r<>}|A z8yh8DXqnA<80u?Fo$NK#6KJM%9S_s)z3|yyVe8ZIsE(aGhl6Hqcyk3BwNjn6cP*Rn zTds)Esi7RKY{pj9^gmu^Y2+;yM|NOoA8%44GLa`c&{f+v*|(>Uzu#I&E;zeXWq^_J z+PQVrydu%m`)`_PYJ{{9bkbBVdTW5-u?hU>yQT~cEvE~N(?u|6)pfD~piv`?Hme(7 z!-A5^AK4mI{3^+1oC&NCwQK`HT-n0gWD1#A#MlR7H0lsed2mX^ zbSqjyXD4ZD4DM)s_;sr+#%`TBbI^d9g7DRmw`RUSef(#wCtdSQ1_ciQGZh)=9lYqs zZ;62So_Myc!h`LQ3{T-wMV_>yieX%$Gf@(Ql-0q~&rVULhBF5bkUtL68L3v9;u6f) zM@GkzfQs!tsaA!?Tz(z`MNnyMBk_p~v?lweCmz~dPz$XDNsKDmrdqf(E=6at0zw)< z4`t4o1Z5LYey|Ueytpacigd;}g8xIdsqFg2vsNerLLQ7m}%7aY|8E zkCL9RnhfJj1+T~%!QaXSR~dT~1F-Gdhn;L*(yhyJg)#w2LHZUMhfGk%b9?uh4GO2q zg5ms1Yz|G}24+!VtWS|d3QFa{*a(ub$DIswB4PuSW{&<%t)9hCh?T4k9x^}4%}Z8^ z*2+U5HuM!?>B}SKxbAlTH}LrGVbj4++btk)Pu?WO?kE7HOS4)fyhcUr>c-;!``dpt zZQrcV)T#&q&5lzcJb9*$f(iRcuT4OtZ4fi?Pw<~x)A#1}cUv@Cla^OGXFD1DDDe*` zEY6*sMjm!37bK8G@SdH;X#>?t--@JWJNXBu4y9DI;7#GGFyjomDFQKN!3=v!AfuE3rrR?3oE^&l8?RU4Zxy1{Us{FZ;O%6o*)AExEf9>NAtwv<^f4~9MVXLjgPHPhx1(JVK;;J--$MLZc{o^Ac Date: Fri, 27 Mar 2026 01:54:27 +0000 Subject: [PATCH 06/54] fix tests --- substrate/frame/revive/rpc/src/tests.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 256306ae14613..6a5191827c5fa 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -19,13 +19,15 @@ //! [evm-test-suite](https://github.com/paritytech/evm-test-suite) repository. use crate::{ - BlockInfoProvider, ChainMetadata, DebugRpcClient, EthRpcClient, ReceiptExtractor, - ReceiptProvider, SubxtBlockInfoProvider, SyncLabel, + BlockInfoProvider, DebugRpcClient, EthRpcClient, ReceiptExtractor, ReceiptProvider, + SubxtBlockInfoProvider, + block_sync::{ChainMetadata, SyncLabel}, cli::{self, CliCommand}, - client::{Client, connect}, + client::Client, example::TransactionBuilder, subxt_client::{ - self, SrcChainConfig, src_chain::runtime_types::pallet_revive::primitives::Code, + self, SrcChainConfig, SubxtClient, connect, + src_chain::runtime_types::pallet_revive::primitives::Code, }, }; use anyhow::anyhow; @@ -1734,7 +1736,7 @@ async fn submit_evm_transfers(count: usize) -> anyhow::Result<()> { /// Connects to the same dev-node that [`SharedResources`] started, but uses its own /// in-memory SQLite database so that sync labels written by the test do not interfere /// with the eth-rpc server's internal database (and vice versa). -async fn create_sync_test_client() -> anyhow::Result { +async fn create_sync_test_client() -> anyhow::Result> { use sc_cli::{RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB}; let node_url = SharedResources::node_rpc_url(); @@ -1742,6 +1744,7 @@ async fn create_sync_test_client() -> anyhow::Result { let max_response_size = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB * 1024 * 1024; let (api, rpc_client, rpc) = connect(node_url, max_request_size, max_response_size).await?; let block_provider = SubxtBlockInfoProvider::new(api.clone(), rpc.clone()).await?; + let backend = SubxtClient::new(api, rpc_client, rpc).await?; let pool = SqlitePoolOptions::new() .max_connections(1) @@ -1750,11 +1753,11 @@ async fn create_sync_test_client() -> anyhow::Result { .connect_with(SqliteConnectOptions::new().in_memory(true)) .await?; - let receipt_extractor = ReceiptExtractor::new(api.clone()).await?; + let receipt_extractor = ReceiptExtractor::new_from_substrate_client(backend.clone(), None); let receipt_provider = ReceiptProvider::new(pool, block_provider.clone(), receipt_extractor, None).await?; - let client = Client::new(api, rpc_client, rpc, block_provider, receipt_provider, true).await?; + let client = Client::from_backend(backend, block_provider, receipt_provider, true)?; Ok(client) } From 7393a4dfe26568bf48683c3b92a1cda624172338 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 27 Mar 2026 10:11:12 +0000 Subject: [PATCH 07/54] rename BuildParachainReviveRpcExtensions back to BuildParachainRpcExtensions --- .../polkadot-omni-node/lib/src/common/rpc.rs | 6 +- .../polkadot-omni-node/lib/src/nodes/aura.rs | 4 +- substrate/frame/revive/rpc/src/block_sync.rs | 86 ++++++------------ .../revive/rpc/src/client/runtime_api.rs | 87 +------------------ 4 files changed, 34 insertions(+), 149 deletions(-) diff --git a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs index 29e9e05ced74f..ca44e33912c88 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs @@ -35,7 +35,7 @@ use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer /// A type representing all RPC extensions. pub type RpcExtension = jsonrpsee::RpcModule<()>; -pub use eth_rpc::BuildParachainReviveRpcExtensions; +pub(crate) use eth_rpc::BuildParachainRpcExtensions; mod eth_rpc { use super::*; @@ -80,7 +80,7 @@ mod eth_rpc { u64::decode(&mut &value[..]).map_err(|e| format!("ChainId decode error: {e}").into()) } - pub struct BuildParachainReviveRpcExtensions( + pub(crate) struct BuildParachainRpcExtensions( PhantomData<(Block, RuntimeApi)>, ); @@ -90,7 +90,7 @@ mod eth_rpc { ParachainBackend, sc_transaction_pool::TransactionPoolHandle>, sc_statement_store::Store, - > for BuildParachainReviveRpcExtensions + > for BuildParachainRpcExtensions where Block: BlockT + Send + Sync + 'static, Block::Header: diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index 9634b2ec23fa0..47cc4023f2a2f 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -18,7 +18,7 @@ use crate::{ cli::{AuthoringPolicy, DevSealMode}, common::{ aura::{AuraIdT, AuraRuntimeApi}, - rpc::{BuildParachainReviveRpcExtensions, BuildRpcExtensions}, + rpc::{BuildParachainRpcExtensions, BuildRpcExtensions}, spec::{ BaseNodeSpec, BuildImportQueue, ClientBlockImport, DynNodeSpec, InitBlockImport, NodeSpec, StartConsensus, @@ -211,7 +211,7 @@ where InitBlockImport::BlockImport: sc_consensus::BlockImport + 'static, { - type BuildRpcExtensions = BuildParachainReviveRpcExtensions; + type BuildRpcExtensions = BuildParachainRpcExtensions; type StartConsensus = StartConsensus; const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Resistant; diff --git a/substrate/frame/revive/rpc/src/block_sync.rs b/substrate/frame/revive/rpc/src/block_sync.rs index 1a919e96e3d02..7fcaaed1f5ea9 100644 --- a/substrate/frame/revive/rpc/src/block_sync.rs +++ b/substrate/frame/revive/rpc/src/block_sync.rs @@ -119,25 +119,22 @@ impl Client { let num = checkpoint.block_number; match checkpoint.block_hash { None => { - log::error!(target: LOG_TARGET, "Boundary #{num}: missing stored hash"); + log::error!(target: LOG_TARGET, + "Boundary #{num}: missing stored hash"); Err(ClientError::SyncBoundaryMismatch) }, Some(stored_hash) => { let block: Arc = self.block_provider().block_by_number(num).await?.ok_or_else(|| { - log::error!( - target: LOG_TARGET, + log::error!(target: LOG_TARGET, "Boundary #{num}: block not found on chain \ - (node may have pruned it — use an archive node with --eth-pruning archive)" - ); + (node may have pruned it — use an archive node with --eth-pruning archive)"); ClientError::SyncBoundaryMismatch })?; if block.hash() != stored_hash { - log::error!( - target: LOG_TARGET, - "Boundary #{num}: hash mismatch — stored {stored_hash:?}, chain {:?}", - block.hash() - ); + log::error!(target: LOG_TARGET, + "Boundary #{num}: hash mismatch — stored {stored_hash:?}, \ + chain {:?}", block.hash()); return Err(ClientError::SyncBoundaryMismatch); } Ok(()) @@ -162,11 +159,9 @@ impl Client { /// Fatal errors (chain/DB mismatch) are propagated; transient errors are swallowed /// to avoid taking down the RPC server. pub async fn sync_backward(&self) -> Result<(), ClientError> { - log::info!( - target: LOG_TARGET, + log::info!(target: LOG_TARGET, "🔄 Historical block sync enabled. \ - For a complete sync, the connected node should be an archive node." - ); + For a complete sync, the connected node should be an archive node."); match self.sync_backward_inner().await { Ok(()) => Ok(()), Err(err) if err.is_chain_validation_error() => Err(err), @@ -201,20 +196,14 @@ impl Client { self.sync_backward_resume(tail, head, latest_finalized).await?; }, (Some(_), None) => { - log::warn!( - target: LOG_TARGET, + log::warn!(target: LOG_TARGET, "🗄️ Tail exists without Head — possible partial corruption, \ - starting fresh sync from #{}", - latest_finalized.block_number - ); + starting fresh sync from #{}", latest_finalized.block_number); self.sync_backward_fresh(latest_finalized.block_number).await?; }, _ => { - log::info!( - target: LOG_TARGET, - "🗄️ Fresh sync: syncing backward from #{}", - latest_finalized.block_number - ); + log::info!(target: LOG_TARGET, + "🗄️ Fresh sync: syncing backward from #{}", latest_finalized.block_number); self.sync_backward_fresh(latest_finalized.block_number).await?; }, } @@ -247,13 +236,9 @@ impl Client { head: SyncCheckpoint, latest_finalized: SyncCheckpoint, ) -> Result<(), ClientError> { - log::info!( - target: LOG_TARGET, + log::info!(target: LOG_TARGET, "🗄️ Resuming sync: DB has blocks #{}..#{}, chain head is #{}", - tail.block_number, - head.block_number, - latest_finalized.block_number - ); + tail.block_number, head.block_number, latest_finalized.block_number); let top_gap = async { // Top gap: sync from latest_finalized down to head + 1. @@ -303,10 +288,7 @@ impl Client { BackwardSyncRange { from, to, set_head, checkpoint_tail }: BackwardSyncRange, ) -> Result<(), ClientError> { if from < to { - log::debug!( - target: LOG_TARGET, - "⬇️ Backward sync: nothing to sync (#{from}..#{to})" - ); + log::debug!(target: LOG_TARGET, "⬇️ Backward sync: nothing to sync (#{from}..#{to})"); return Ok(()); } @@ -339,10 +321,8 @@ impl Client { .insert_block_receipts_past(block.as_ref(), &hash) .await { - log::error!( - target: LOG_TARGET, - "⚠️ Insert failed for #{block_number}: {err:?}, stopping" - ); + log::error!(target: LOG_TARGET, + "⚠️ Insert failed for #{block_number}: {err:?}, stopping"); break Err(err); } @@ -354,10 +334,8 @@ impl Client { } if at_checkpoint(blocks_synced) { - log::debug!( - target: LOG_TARGET, - "⬇️ Backward sync progress: #{block_number} ({blocks_synced} blocks synced)" - ); + log::debug!(target: LOG_TARGET, + "⬇️ Backward sync progress: #{block_number} ({blocks_synced} blocks synced)"); if checkpoint_tail { self.checkpoint_sync_label(SyncLabel::Tail, block_number, block_hash) .await; @@ -366,17 +344,12 @@ impl Client { }, None => { let first_evm_block = block_number.saturating_add(1); - log::debug!( - target: LOG_TARGET, - "🔍 No EVM hash at #{block_number}, setting first_evm_block to #{first_evm_block}" - ); + log::debug!(target: LOG_TARGET, + "🔍 No EVM hash at #{block_number}, setting first_evm_block to #{first_evm_block}"); if let Err(err) = self.receipt_provider().set_first_evm_block(first_evm_block).await { - log::warn!( - target: LOG_TARGET, - "Failed to persist first-evm-block: {err:?}" - ); + log::warn!(target: LOG_TARGET, "Failed to persist first-evm-block: {err:?}"); } break Ok(()); @@ -394,10 +367,8 @@ impl Client { { Ok(b) => block = b, Err(err) => { - log::error!( - target: LOG_TARGET, - "⚠️ Could not fetch parent of #{block_number}: {err:?}, stopping" - ); + log::error!(target: LOG_TARGET, + "⚠️ Could not fetch parent of #{block_number}: {err:?}, stopping"); break Err(err); }, } @@ -413,10 +384,9 @@ impl Client { } } - log::info!( - target: LOG_TARGET, - "⬇️ Backward sync: {blocks_synced} blocks synced (requested #{from}..#{to})" - ); + log::info!(target: LOG_TARGET, + "⬇️ Backward sync: {blocks_synced} blocks synced \ + (requested #{from}..#{to})"); loop_result } diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 8f43642b5d0e2..95dfab3f288be 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -21,7 +21,7 @@ use crate::{ client::Balance, subxt_client::{self, SrcChainConfig}, }; -use futures::{StreamExt, TryFutureExt, stream}; +use futures::TryFutureExt; use pallet_revive::{ DryRunConfig, EthTransactInfo, evm::{ @@ -74,75 +74,6 @@ impl RuntimeApi { Ok(result) } - /// Estimates the minimum gas limit required for the transaction execution. - #[allow(dead_code)] - pub async fn estimate_gas( - &self, - tx: GenericTransaction, - block: BlockNumberOrTagOrHash, - ) -> Result { - let timestamp_override = match block { - BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => { - Some(Timestamp::current().as_millis()) - }, - _ => None, - }; - - // Not all versions of pallet-revive have all of the runtime functions that we require. Thus - // we need to be able to perform the gas estimation through any of the runtime functions - // that the pallet may have available which is why we make use of this stream. The functions - // with higher priority are put at the start while the functions with lower priority are at - // the end. - let mut stream = - // Estimate through the `estimate_gas` function - stream::once(Box::pin(async { - let payload = subxt_client::apis() - .revive_api() - .eth_estimate_gas( - tx.clone().into(), - DryRunConfig::new(timestamp_override).into(), - ) - .unvalidated(); - self.0.call(payload).await.map(|value| value.map(|value| value.0)) - })) - // Otherwise, estimate through `eth_transact_with_config` - .chain(stream::once(Box::pin(async { - let payload = subxt_client::apis() - .revive_api() - .eth_transact_with_config( - tx.clone().into(), - DryRunConfig::new(timestamp_override).into(), - ) - .unvalidated(); - self.0.call(payload).await.map(|value| value.map(|value| value.eth_gas)) - }))) - // Otherwise, estimate through `eth_transact` - .chain(stream::once(Box::pin(async { - let payload = - subxt_client::apis().revive_api().eth_transact(tx.clone().into()).unvalidated(); - self.0.call(payload).await.map(|value| value.map(|value| value.eth_gas)) - }))); - - while let Some(result) = stream.next().await { - match result { - Ok(estimation) => { - return estimation.map_err(|err| ClientError::TransactError(err.0)); - }, - Err(Metadata(MetadataError::RuntimeMethodNotFound(name))) => { - log::debug!(target: LOG_TARGET, "Method {name:?} not found falling back"); - }, - Err(subxt::Error::Rpc(subxt::error::RpcError::ClientError( - subxt::ext::subxt_rpcs::Error::User(UserError { message, .. }), - ))) if message.contains("is not found") => { - log::debug!(target: LOG_TARGET, "{message:?} not found falling back") - }, - Err(err) => return Err(err.into()), - } - } - - Err(ClientError::NoEstimationMethodSucceeded.into()) - } - /// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction. pub async fn dry_run( &self, @@ -216,22 +147,6 @@ impl RuntimeApi { Ok(*gas_price) } - /// Get the block gas limit. - #[allow(dead_code)] - pub async fn block_gas_limit(&self) -> Result { - let payload = subxt_client::apis().revive_api().block_gas_limit().unvalidated(); - let gas_limit = self.0.call(payload).await?; - Ok(*gas_limit) - } - - /// Get the block author address. - #[allow(dead_code)] - pub async fn block_author(&self) -> Result { - let payload = subxt_client::apis().revive_api().block_author().unvalidated(); - let author = self.0.call(payload).await?; - Ok(author) - } - /// Get the trace for the given transaction index in the given block. pub async fn trace_tx( &self, From 7760feffe752be0303032ced2874a969e9d4d552 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 27 Mar 2026 10:19:14 +0000 Subject: [PATCH 08/54] nit --- substrate/frame/revive/rpc/src/block_sync.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/block_sync.rs b/substrate/frame/revive/rpc/src/block_sync.rs index 7fcaaed1f5ea9..a2ca761537665 100644 --- a/substrate/frame/revive/rpc/src/block_sync.rs +++ b/substrate/frame/revive/rpc/src/block_sync.rs @@ -174,7 +174,6 @@ impl Client { async fn sync_backward_inner(&self) -> Result<(), ClientError> { let genesis_hash = self.validate_chain_identity().await?; - let latest_finalized_block: Arc = self.latest_finalized_block().await; let latest_finalized = SyncCheckpoint::new(latest_finalized_block.number(), latest_finalized_block.hash()); @@ -192,7 +191,7 @@ impl Client { match (tail, head) { (Some(tail), Some(head)) => { // Verify boundary hashes still match the finalized chain. - tokio::try_join!(self.verify_boundary(&tail), self.verify_boundary(&head))?; + tokio::try_join!(self.verify_boundary(&tail), self.verify_boundary(&head),)?; self.sync_backward_resume(tail, head, latest_finalized).await?; }, (Some(_), None) => { @@ -288,7 +287,7 @@ impl Client { BackwardSyncRange { from, to, set_head, checkpoint_tail }: BackwardSyncRange, ) -> Result<(), ClientError> { if from < to { - log::debug!(target: LOG_TARGET, "⬇️ Backward sync: nothing to sync (#{from}..#{to})"); + log::debug!(target: LOG_TARGET, "⬇️ Backward sync: nothing to sync (#{from}..#{to})"); return Ok(()); } From d8d53dd303132552b46ed696218c28498aefbfee Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 27 Mar 2026 13:00:30 +0000 Subject: [PATCH 09/54] fix ci --- .github/workflows/tests-evm.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 3d3aa05065dab..01b1dbb255555 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -19,6 +19,26 @@ jobs: needs: isdraft uses: ./.github/workflows/reusable-preflight.yml + check-scale-metadata: + needs: [preflight] + runs-on: ${{ needs.preflight.outputs.RUNNER }} + if: ${{ needs.preflight.outputs.changes_rust }} + timeout-minutes: 20 + container: + image: ${{ needs.preflight.outputs.IMAGE }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Check revive_chain.scale is up to date + run: | + forklift cargo run -p revive-gen-metadata + if ! git diff --exit-code substrate/frame/revive/rpc/revive_chain.scale; then + echo "" + echo "❌ revive_chain.scale is stale." + echo " Run 'cargo run -p revive-gen-metadata' and commit the result." + exit 1 + fi + differential-tests: needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} @@ -106,6 +126,7 @@ jobs: # If any new job gets added, be sure to add it to this array needs: - evm-test-suite + - check-scale-metadata if: always() && !cancelled() steps: - run: | From 31781bc81608f69b3e1858c6890fed67dee7e586 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 27 Mar 2026 20:38:05 +0000 Subject: [PATCH 10/54] rewrite revive_chain.scale --- .../polkadot-omni-node/lib/src/common/rpc.rs | 120 ++++++++++-------- substrate/frame/revive/rpc/revive_chain.scale | Bin 82139 -> 81707 bytes 2 files changed, 66 insertions(+), 54 deletions(-) diff --git a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs index ca44e33912c88..a7d0ad5a5c6d2 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs @@ -131,60 +131,72 @@ mod eth_rpc { // ETH RPC server. let best_hash = client.chain_info().best_hash; - let chain_id = read_revive_chain_id::(&client, best_hash)?; - - let block_provider = NativeClientBlockInfoProvider::new(client.clone()) - .map_err(|e| format!("block info provider: {e}"))?; - - let native_client = - NativeSubstrateClient::new(client.clone(), pool, chain_id, false) - .map_err(|e| format!("native substrate client: {e}"))?; - - let eth_client = tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on(build_native_inmemory_client( - native_client, - block_provider, - DEFAULT_KEEP_LATEST_BLOCKS, - false, - )) - }) - .map_err(|e| format!("ETH RPC client init: {e}"))?; - - let eth_best = eth_client.clone(); - spawn_handle.spawn( - "eth-rpc-best-blocks", - Some("eth-rpc"), - Box::pin(async move { - if let Err(e) = eth_best - .subscribe_and_cache_new_blocks(SubscriptionType::BestBlocks) - .await - { - log::error!( - target: "eth-rpc", - "Best-block subscription error: {e:?}" - ); - } - }), - ); - let eth_finalized = eth_client.clone(); - spawn_handle.spawn( - "eth-rpc-finalized-blocks", - Some("eth-rpc"), - Box::pin(async move { - if let Err(e) = eth_finalized - .subscribe_and_cache_new_blocks(SubscriptionType::FinalizedBlocks) - .await - { - log::error!( - target: "eth-rpc", - "Finalized-block subscription error: {e:?}" - ); - } - }), - ); - - let eth_module = build_eth_rpc_module(false, eth_client, false)?; - module.merge(eth_module)?; + match read_revive_chain_id::(&client, best_hash) { + Ok(chain_id) => { + let block_provider = NativeClientBlockInfoProvider::new(client.clone()) + .map_err(|e| format!("block info provider: {e}"))?; + + let native_client = + NativeSubstrateClient::new(client.clone(), pool, chain_id, false) + .map_err(|e| format!("native substrate client: {e}"))?; + + let eth_client = tokio::task::block_in_place(|| { + tokio::runtime::Handle::current().block_on( + build_native_inmemory_client( + native_client, + block_provider, + DEFAULT_KEEP_LATEST_BLOCKS, + false, + ), + ) + }) + .map_err(|e| format!("ETH RPC client init: {e}"))?; + + let eth_best = eth_client.clone(); + spawn_handle.spawn( + "eth-rpc-best-blocks", + Some("eth-rpc"), + Box::pin(async move { + if let Err(e) = eth_best + .subscribe_and_cache_new_blocks(SubscriptionType::BestBlocks) + .await + { + log::error!( + target: "eth-rpc", + "Best-block subscription error: {e:?}" + ); + } + }), + ); + let eth_finalized = eth_client.clone(); + spawn_handle.spawn( + "eth-rpc-finalized-blocks", + Some("eth-rpc"), + Box::pin(async move { + if let Err(e) = eth_finalized + .subscribe_and_cache_new_blocks( + SubscriptionType::FinalizedBlocks, + ) + .await + { + log::error!( + target: "eth-rpc", + "Finalized-block subscription error: {e:?}" + ); + } + }), + ); + + let eth_module = build_eth_rpc_module(false, eth_client, false)?; + module.merge(eth_module)?; + }, + Err(e) => { + log::debug!( + target: "eth-rpc", + "Revive pallet not found in runtime metadata, ETH RPC disabled: {e}" + ); + }, + } Ok(module) }; diff --git a/substrate/frame/revive/rpc/revive_chain.scale b/substrate/frame/revive/rpc/revive_chain.scale index 709e0bb47043dac070ea10702f26f11d3930b998..1d6833e3677367b9dbcf10a7726f45b165a9e6ee 100644 GIT binary patch delta 10682 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

FK>T&%zm4qw@V3VSfxU{ZH)n56D`GSqIh$S zV;0$(;1;Hw&`R9aMpa(AXr;y?xm{jcbm5Jyk6G+G_d=V(CviQ>3vCLX#HQeCS5MTX zbh+sv=q*@xN{61fYC%$*G1oohKDkpba45zkt6_xG5CAJfeJ8L42w8Z7K zHybG{G@9vZBHYb(#$emk72B>Lwq5x7skQ&*XP!dK+RTA@Dc{N%y@pJ~Yxu>(Q;_n_>HcZgMJTHdULd-hl;YC`HL>~-YR`82oH>R&G~h+M3P8zYAq$7O0heURa|DP1uNqMu+yq{bdA;0r3H!6h( zS!<(qpp}J~qKPv45Mx*|qXH~vo92UhViXVW=to^{qUgFz$|Glo&WH-Qg6WCO*e=1CDtJ#35w${( z*c6oi_ zj{Hc_;`K!lmQ1F_XKe*Kks|sCB|o(GO=Z@Q?EM3&Q_iJaE=~beK|$|Lr#s0fx$mZ4 ze%~Y0mHly!dPnfcA>QSt>jgj6O_ODnn~BDcQ`Iv?&~xDod9lc;^uSdg8(P$gP-Zo} zgv6F=;ha5^Wr^5%k0p;#QCkrmO(!b4SoK7zsF9aQRKyCCe9q(<6)cGW%v6PA_$}&G`4a-~G%UaP0@(12i*14*aK-m(-1E&Hbk%t6n?FG-e*Eosum<1x z%LXFCbg7WC?6sHHQ2Lzn-Ddhgr{lZ#X{C*Sy#Q5hi!Z+pbT={jdwvvo2XdFZ?#PJs*q!CYq@qqOgm4b zRiO{yIxo=Gh(GDU(DwRI1EB}qk?4s3#LjPTSm=hQ?q{9)5~(0#<%TBu7{Wn#$5Lox z^D%Y>SN*&$u81yqW(OP;rL^7t%YG3m0>21wQmD8dI7Oe5iEa=AItA1V?xaAA0%{>K za7+PzgOtF*IM@pAcBvcGfkG`LCAN2ThjNjQEx!FmPuNVyu^1~q)dpTmfKA<&)7NjR z9|GHoU|66v5x#{r?ajU5D8QCLwGw`SZGl^pU}~D3K`AhOS!m;Rl85V@A+RF}I=~RP zzYiP+Lwj*w*bT6~{flIvPc`b>ucUwv;9`4Y8vI7!?`kfdI|Kdu!Ejg{n9>ijT2?A6 z##$xw+(~qIJ4N#FtZ{W&ncQV)3U#b2E0jC#l$?^+=G|Bk9IthGC=sU`_2vq_JT8CD z^}I4)ehG@ri^|!-v?r=o9^&_IWns0iz&c6tP4L*}*qkpbOM-LgizK_r=6FS^SB|A5 zCqXkmNEd4k4CoJ|VOPM^A4Z5<;MKrW{o(hpFL19Gs-YvWQ43$dp}>MHI3{j`BZ2YR zu#OmcKAZS|R@fQP4uH8mcL}nCJ1blatRDcElP?MJzNT%U57wLH>zym@n{r^RKvT~R zgh#~d!i{#zAkcyUV*8_d&_mufNTAHOEQw4)6!) zF7y|yhQM|Dr=wEmf&P{(Q7_RImfvQ#1f2Px3MdCdT)Qog4qnuW*V{+r!z>YU+cyk_ z@c`<;7lm-Y&{PnZP(+fpJg}t*UW2Bw)d4yH1O&TBUbiw30iE)ex{03%<|sfq%Zhr!~6o1sz*MA-{hi@|VS={4DX zzM8$`kJVq zN5#mp2vBBV`6&3YbGa>IR$z5GIEV*7m%|kLn>-rsqQ9-9sS@E1d_NlQp})%8z$!H9 P0>8Tr7AU?fQn3F4O|B33 From d9c831d846ee34c13f1e4fcf2e62682ea31f69fa Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 31 Mar 2026 13:01:01 +0000 Subject: [PATCH 11/54] nit --- .github/workflows/tests-evm.yml | 23 +--------- Cargo.toml | 2 - .../frame/revive/gen-metadata/Cargo.toml | 16 ------- .../frame/revive/gen-metadata/src/main.rs | 42 ------------------ substrate/frame/revive/rpc/Cargo.toml | 3 ++ substrate/frame/revive/rpc/build.rs | 14 +++++- substrate/frame/revive/rpc/revive_chain.scale | Bin 81707 -> 0 bytes .../frame/revive/rpc/src/subxt_client.rs | 4 +- 8 files changed, 19 insertions(+), 85 deletions(-) delete mode 100644 substrate/frame/revive/gen-metadata/Cargo.toml delete mode 100644 substrate/frame/revive/gen-metadata/src/main.rs delete mode 100644 substrate/frame/revive/rpc/revive_chain.scale diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 01b1dbb255555..6a106060c31cb 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -19,26 +19,6 @@ jobs: needs: isdraft uses: ./.github/workflows/reusable-preflight.yml - check-scale-metadata: - needs: [preflight] - runs-on: ${{ needs.preflight.outputs.RUNNER }} - if: ${{ needs.preflight.outputs.changes_rust }} - timeout-minutes: 20 - container: - image: ${{ needs.preflight.outputs.IMAGE }} - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - name: Check revive_chain.scale is up to date - run: | - forklift cargo run -p revive-gen-metadata - if ! git diff --exit-code substrate/frame/revive/rpc/revive_chain.scale; then - echo "" - echo "❌ revive_chain.scale is stale." - echo " Run 'cargo run -p revive-gen-metadata' and commit the result." - exit 1 - fi - differential-tests: needs: [preflight] runs-on: ${{ needs.preflight.outputs.RUNNER }} @@ -91,7 +71,7 @@ jobs: - name: script run: | - forklift cargo build --locked --release -p pallet-revive-eth-rpc --bin eth-rpc --features subxt + forklift cargo build --locked --release -p pallet-revive-eth-rpc --bin eth-rpc forklift cargo build --locked --release -p revive-dev-node --bin revive-dev-node - name: Checkout evm-tests @@ -126,7 +106,6 @@ jobs: # If any new job gets added, be sure to add it to this array needs: - evm-test-suite - - check-scale-metadata if: always() && !cancelled() steps: - run: | diff --git a/Cargo.toml b/Cargo.toml index da84087edf449..543bdaad6cbe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -422,7 +422,6 @@ members = [ "substrate/frame/revive/dev-node/node", "substrate/frame/revive/dev-node/runtime", "substrate/frame/revive/fixtures", - "substrate/frame/revive/gen-metadata", "substrate/frame/revive/proc-macro", "substrate/frame/revive/rpc", "substrate/frame/revive/uapi", @@ -1066,7 +1065,6 @@ pallet-staking-reward-fn = { path = "substrate/frame/staking/reward-fn", default pallet-staking-runtime-api = { path = "substrate/frame/staking/runtime-api", default-features = false } revive-dev-node = { path = "substrate/frame/revive/dev-node/node" } revive-dev-runtime = { path = "substrate/frame/revive/dev-node/runtime" } -revive-gen-metadata = { path = "substrate/frame/revive/gen-metadata" } # TODO: remove the reward stuff as they are not needed here pallet-derivatives = { path = "polkadot/xcm/pallet-derivatives", default-features = false } pallet-staking-async = { path = "substrate/frame/staking-async", default-features = false } diff --git a/substrate/frame/revive/gen-metadata/Cargo.toml b/substrate/frame/revive/gen-metadata/Cargo.toml deleted file mode 100644 index 1d9ef92b1cce2..0000000000000 --- a/substrate/frame/revive/gen-metadata/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "revive-gen-metadata" -description = "Generates the revive_chain.scale metadata file used by pallet-revive-eth-rpc." -version = "0.0.0" -authors.workspace = true -homepage.workspace = true -repository.workspace = true -edition.workspace = true -publish = false - -[lints] -workspace = true - -[dependencies] -revive-dev-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/gen-metadata/src/main.rs b/substrate/frame/revive/gen-metadata/src/main.rs deleted file mode 100644 index 8f9137a4fcd26..0000000000000 --- a/substrate/frame/revive/gen-metadata/src/main.rs +++ /dev/null @@ -1,42 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) 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. - -//! Generates `revive_chain.scale` — the runtime metadata file consumed by the -//! `#[subxt::subxt]` macro in `pallet-revive-eth-rpc`. -//! -//! Run this whenever `revive-dev-runtime`'s API changes: -//! -//! ```text -//! cargo run -p revive-gen-metadata -//! ``` - -use std::fs; - -fn main() { - let out_path = - std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../rpc/revive_chain.scale"); - - let mut ext = sp_io::TestExternalities::new(Default::default()); - ext.execute_with(|| { - let metadata = revive_dev_runtime::Runtime::metadata_at_version(16) - .expect("metadata v16 must be supported; qed"); - let bytes: &[u8] = &metadata; - fs::write(&out_path, bytes).expect("failed to write revive_chain.scale"); - }); - - println!("Written to {}", out_path.display()); -} diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index d0e5d4d84f9ad..76057d52053fe 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -93,3 +93,6 @@ tempfile = { workspace = true } [build-dependencies] git2 = { workspace = true } +revive-dev-runtime = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/rpc/build.rs b/substrate/frame/revive/rpc/build.rs index 44faf3b27ca31..b37b23d4562f5 100644 --- a/substrate/frame/revive/rpc/build.rs +++ b/substrate/frame/revive/rpc/build.rs @@ -14,10 +14,11 @@ // 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. -use std::process::Command; +use std::{fs, process::Command}; fn main() { generate_git_revision(); + generate_metadata_file(); } fn generate_git_revision() { @@ -50,3 +51,14 @@ fn generate_git_revision() { println!("cargo:rustc-env=TARGET={target}"); println!("cargo:rustc-env=GIT_REVISION={branch}-{id}"); } + +fn generate_metadata_file() { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| { + let metadata = revive_dev_runtime::Runtime::metadata_at_version(16).unwrap(); + let bytes: &[u8] = &metadata; + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); + let out_path = std::path::Path::new(&out_dir).join("revive_chain.scale"); + fs::write(out_path, bytes).unwrap(); + }); +} diff --git a/substrate/frame/revive/rpc/revive_chain.scale b/substrate/frame/revive/rpc/revive_chain.scale deleted file mode 100644 index 1d6833e3677367b9dbcf10a7726f45b165a9e6ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81707 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 Date: Tue, 31 Mar 2026 14:24:35 +0000 Subject: [PATCH 12/54] fix ci --- Cargo.lock | 16 +++++------ Cargo.toml | 2 ++ .../frame/revive/eth-rpc-server/Cargo.toml | 25 ++++++++++++++++++ .../frame/revive/eth-rpc-server/src/main.rs | 23 ++++++++++++++++ substrate/frame/revive/rpc/Cargo.toml | 8 ------ substrate/frame/revive/rpc/build.rs | 14 +--------- substrate/frame/revive/rpc/revive_chain.scale | Bin 0 -> 81707 bytes .../frame/revive/rpc/src/subxt_client.rs | 2 +- 8 files changed, 60 insertions(+), 30 deletions(-) create mode 100644 substrate/frame/revive/eth-rpc-server/Cargo.toml create mode 100644 substrate/frame/revive/eth-rpc-server/src/main.rs create mode 100644 substrate/frame/revive/rpc/revive_chain.scale diff --git a/Cargo.lock b/Cargo.lock index c3c891a1d22c7..c532982f9e0b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13431,6 +13431,14 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "pallet-revive-eth-rpc-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "pallet-revive-eth-rpc", +] + [[package]] name = "pallet-revive-fixtures" version = "0.1.0" @@ -18878,14 +18886,6 @@ dependencies = [ "sp-debug-derive 14.0.0", ] -[[package]] -name = "revive-gen-metadata" -version = "0.0.0" -dependencies = [ - "revive-dev-runtime", - "sp-io 30.0.0", -] - [[package]] name = "revm" version = "27.1.0" diff --git a/Cargo.toml b/Cargo.toml index 543bdaad6cbe1..dbb59d7f78263 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -423,6 +423,7 @@ members = [ "substrate/frame/revive/dev-node/runtime", "substrate/frame/revive/fixtures", "substrate/frame/revive/proc-macro", + "substrate/frame/revive/eth-rpc-server", "substrate/frame/revive/rpc", "substrate/frame/revive/uapi", "substrate/frame/revive/ui-tests", @@ -1045,6 +1046,7 @@ pallet-referenda = { path = "substrate/frame/referenda", default-features = fals pallet-remark = { default-features = false, path = "substrate/frame/remark" } pallet-revive = { path = "substrate/frame/revive", default-features = false } pallet-revive-eth-rpc = { path = "substrate/frame/revive/rpc", default-features = false } +pallet-revive-eth-rpc-server = { path = "substrate/frame/revive/eth-rpc-server", default-features = false } pallet-revive-fixtures = { path = "substrate/frame/revive/fixtures", default-features = false } pallet-revive-proc-macro = { path = "substrate/frame/revive/proc-macro", default-features = false } pallet-revive-uapi = { path = "substrate/frame/revive/uapi", default-features = false } diff --git a/substrate/frame/revive/eth-rpc-server/Cargo.toml b/substrate/frame/revive/eth-rpc-server/Cargo.toml new file mode 100644 index 0000000000000..5eb611c131ef1 --- /dev/null +++ b/substrate/frame/revive/eth-rpc-server/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pallet-revive-eth-rpc-server" +version = "0.1.0" +authors.workspace = true +edition = "2024" +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "The standalone eth-rpc binary for pallet-revive." +default-run = "eth-rpc" +publish = false + +[lints] +workspace = true + +[package.metadata.polkadot-sdk] +exclude-from-umbrella = true + +[[bin]] +name = "eth-rpc" +path = "src/main.rs" + +[dependencies] +anyhow = { workspace = true } +pallet-revive-eth-rpc = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/eth-rpc-server/src/main.rs b/substrate/frame/revive/eth-rpc-server/src/main.rs new file mode 100644 index 0000000000000..ab2a5d445c19d --- /dev/null +++ b/substrate/frame/revive/eth-rpc-server/src/main.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) 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. +//! The Ethereum JSON-RPC server. +use pallet_revive_eth_rpc::cli; + +fn main() -> anyhow::Result<()> { + let cmd = cli::CliCommand::parse_cli()?; + cli::run(cmd) +} diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 76057d52053fe..a65c252289adb 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage.workspace = true repository.workspace = true description = "An Ethereum JSON-RPC server for pallet-revive." -default-run = "eth-rpc" [lints] workspace = true @@ -15,10 +14,6 @@ workspace = true [package.metadata.polkadot-sdk] exclude-from-umbrella = true -[[bin]] -name = "eth-rpc" -path = "src/main.rs" - [[example]] name = "tx-types" required-features = ["subxt"] @@ -93,6 +88,3 @@ tempfile = { workspace = true } [build-dependencies] git2 = { workspace = true } -revive-dev-runtime = { workspace = true, default-features = true } -sp-io = { workspace = true, default-features = true } -sp-runtime = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/rpc/build.rs b/substrate/frame/revive/rpc/build.rs index b37b23d4562f5..44faf3b27ca31 100644 --- a/substrate/frame/revive/rpc/build.rs +++ b/substrate/frame/revive/rpc/build.rs @@ -14,11 +14,10 @@ // 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. -use std::{fs, process::Command}; +use std::process::Command; fn main() { generate_git_revision(); - generate_metadata_file(); } fn generate_git_revision() { @@ -51,14 +50,3 @@ fn generate_git_revision() { println!("cargo:rustc-env=TARGET={target}"); println!("cargo:rustc-env=GIT_REVISION={branch}-{id}"); } - -fn generate_metadata_file() { - let mut ext = sp_io::TestExternalities::new(Default::default()); - ext.execute_with(|| { - let metadata = revive_dev_runtime::Runtime::metadata_at_version(16).unwrap(); - let bytes: &[u8] = &metadata; - let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); - let out_path = std::path::Path::new(&out_dir).join("revive_chain.scale"); - fs::write(out_path, bytes).unwrap(); - }); -} diff --git a/substrate/frame/revive/rpc/revive_chain.scale b/substrate/frame/revive/rpc/revive_chain.scale new file mode 100644 index 0000000000000000000000000000000000000000..1d6833e3677367b9dbcf10a7726f45b165a9e6ee GIT binary patch literal 81707 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 Date: Tue, 31 Mar 2026 14:47:41 +0000 Subject: [PATCH 13/54] try seperatting the eth-rpc into a library and server crate --- .github/workflows/release-20_build-rc.yml | 4 ++-- .github/workflows/release-reusable-rc-build.yml | 4 ++-- .github/workflows/tests-evm.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release-20_build-rc.yml b/.github/workflows/release-20_build-rc.yml index f98ec9359df84..32c2c5a9e8c33 100644 --- a/.github/workflows/release-20_build-rc.yml +++ b/.github/workflows/release-20_build-rc.yml @@ -157,7 +157,7 @@ jobs: uses: "./.github/workflows/release-reusable-rc-build.yml" with: binary: '["eth-rpc"]' - package: pallet-revive-eth-rpc + package: pallet-revive-eth-rpc-server release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: x86_64-unknown-linux-gnu secrets: inherit @@ -278,7 +278,7 @@ jobs: uses: "./.github/workflows/release-reusable-rc-build.yml" with: binary: '["eth-rpc"]' - package: pallet-revive-eth-rpc + package: pallet-revive-eth-rpc-server release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: aarch64-apple-darwin secrets: inherit diff --git a/.github/workflows/release-reusable-rc-build.yml b/.github/workflows/release-reusable-rc-build.yml index 5a08ca266b7fb..8234a38b3b9c7 100644 --- a/.github/workflows/release-reusable-rc-build.yml +++ b/.github/workflows/release-reusable-rc-build.yml @@ -418,7 +418,7 @@ jobs: secrets: inherit upload-eth-rpc-artifacts-to-s3: - if: ${{ inputs.package == 'pallet-revive-eth-rpc' && inputs.target == 'x86_64-unknown-linux-gnu' }} + if: ${{ inputs.package == 'pallet-revive-eth-rpc-server' && inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [build-rc] uses: ./.github/workflows/release-reusable-s3-upload.yml with: @@ -520,7 +520,7 @@ jobs: secrets: inherit upload-eth-rpc-macos-artifacts-to-s3: - if: ${{ inputs.package == 'pallet-revive-eth-rpc' && inputs.target == 'aarch64-apple-darwin' }} + if: ${{ inputs.package == 'pallet-revive-eth-rpc-server' && inputs.target == 'aarch64-apple-darwin' }} needs: [build-macos-rc] uses: ./.github/workflows/release-reusable-s3-upload.yml with: diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 6a106060c31cb..23309fd74ae0c 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -71,7 +71,7 @@ jobs: - name: script run: | - forklift cargo build --locked --release -p pallet-revive-eth-rpc --bin eth-rpc + forklift cargo build --locked --release -p pallet-revive-eth-rpc-server --bin eth-rpc forklift cargo build --locked --release -p revive-dev-node --bin revive-dev-node - name: Checkout evm-tests From 082736eb648194ecf6eb62335a08e48ac33bb903 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 31 Mar 2026 23:43:27 +0000 Subject: [PATCH 14/54] revert server --- .github/workflows/release-20_build-rc.yml | 4 +-- .../workflows/release-reusable-rc-build.yml | 4 +-- .github/workflows/tests-evm.yml | 2 +- .gitignore | 1 - Cargo.lock | 8 ------ Cargo.toml | 2 -- .../frame/revive/eth-rpc-server/Cargo.toml | 25 ------------------- .../frame/revive/eth-rpc-server/src/main.rs | 23 ----------------- 8 files changed, 5 insertions(+), 64 deletions(-) delete mode 100644 substrate/frame/revive/eth-rpc-server/Cargo.toml delete mode 100644 substrate/frame/revive/eth-rpc-server/src/main.rs diff --git a/.github/workflows/release-20_build-rc.yml b/.github/workflows/release-20_build-rc.yml index 32c2c5a9e8c33..f98ec9359df84 100644 --- a/.github/workflows/release-20_build-rc.yml +++ b/.github/workflows/release-20_build-rc.yml @@ -157,7 +157,7 @@ jobs: uses: "./.github/workflows/release-reusable-rc-build.yml" with: binary: '["eth-rpc"]' - package: pallet-revive-eth-rpc-server + package: pallet-revive-eth-rpc release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: x86_64-unknown-linux-gnu secrets: inherit @@ -278,7 +278,7 @@ jobs: uses: "./.github/workflows/release-reusable-rc-build.yml" with: binary: '["eth-rpc"]' - package: pallet-revive-eth-rpc-server + package: pallet-revive-eth-rpc release_tag: ${{ needs.validate-inputs.outputs.release_tag }} target: aarch64-apple-darwin secrets: inherit diff --git a/.github/workflows/release-reusable-rc-build.yml b/.github/workflows/release-reusable-rc-build.yml index 8234a38b3b9c7..5a08ca266b7fb 100644 --- a/.github/workflows/release-reusable-rc-build.yml +++ b/.github/workflows/release-reusable-rc-build.yml @@ -418,7 +418,7 @@ jobs: secrets: inherit upload-eth-rpc-artifacts-to-s3: - if: ${{ inputs.package == 'pallet-revive-eth-rpc-server' && inputs.target == 'x86_64-unknown-linux-gnu' }} + if: ${{ inputs.package == 'pallet-revive-eth-rpc' && inputs.target == 'x86_64-unknown-linux-gnu' }} needs: [build-rc] uses: ./.github/workflows/release-reusable-s3-upload.yml with: @@ -520,7 +520,7 @@ jobs: secrets: inherit upload-eth-rpc-macos-artifacts-to-s3: - if: ${{ inputs.package == 'pallet-revive-eth-rpc-server' && inputs.target == 'aarch64-apple-darwin' }} + if: ${{ inputs.package == 'pallet-revive-eth-rpc' && inputs.target == 'aarch64-apple-darwin' }} needs: [build-macos-rc] uses: ./.github/workflows/release-reusable-s3-upload.yml with: diff --git a/.github/workflows/tests-evm.yml b/.github/workflows/tests-evm.yml index 23309fd74ae0c..6a106060c31cb 100644 --- a/.github/workflows/tests-evm.yml +++ b/.github/workflows/tests-evm.yml @@ -71,7 +71,7 @@ jobs: - name: script run: | - forklift cargo build --locked --release -p pallet-revive-eth-rpc-server --bin eth-rpc + forklift cargo build --locked --release -p pallet-revive-eth-rpc --bin eth-rpc forklift cargo build --locked --release -p revive-dev-node --bin revive-dev-node - name: Checkout evm-tests diff --git a/.gitignore b/.gitignore index 3dafb4456cec6..59a747f2fef17 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,6 @@ substrate.code-workspace target/ target.noindex/ *.scale -!substrate/frame/revive/rpc/revive_chain.scale rustc-ice-* /.claude/settings.local.json *.local.md \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c532982f9e0b7..b141fc7468660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13431,14 +13431,6 @@ dependencies = [ "tokio-stream", ] -[[package]] -name = "pallet-revive-eth-rpc-server" -version = "0.1.0" -dependencies = [ - "anyhow", - "pallet-revive-eth-rpc", -] - [[package]] name = "pallet-revive-fixtures" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index dbb59d7f78263..543bdaad6cbe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -423,7 +423,6 @@ members = [ "substrate/frame/revive/dev-node/runtime", "substrate/frame/revive/fixtures", "substrate/frame/revive/proc-macro", - "substrate/frame/revive/eth-rpc-server", "substrate/frame/revive/rpc", "substrate/frame/revive/uapi", "substrate/frame/revive/ui-tests", @@ -1046,7 +1045,6 @@ pallet-referenda = { path = "substrate/frame/referenda", default-features = fals pallet-remark = { default-features = false, path = "substrate/frame/remark" } pallet-revive = { path = "substrate/frame/revive", default-features = false } pallet-revive-eth-rpc = { path = "substrate/frame/revive/rpc", default-features = false } -pallet-revive-eth-rpc-server = { path = "substrate/frame/revive/eth-rpc-server", default-features = false } pallet-revive-fixtures = { path = "substrate/frame/revive/fixtures", default-features = false } pallet-revive-proc-macro = { path = "substrate/frame/revive/proc-macro", default-features = false } pallet-revive-uapi = { path = "substrate/frame/revive/uapi", default-features = false } diff --git a/substrate/frame/revive/eth-rpc-server/Cargo.toml b/substrate/frame/revive/eth-rpc-server/Cargo.toml deleted file mode 100644 index 5eb611c131ef1..0000000000000 --- a/substrate/frame/revive/eth-rpc-server/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "pallet-revive-eth-rpc-server" -version = "0.1.0" -authors.workspace = true -edition = "2024" -license = "Apache-2.0" -homepage.workspace = true -repository.workspace = true -description = "The standalone eth-rpc binary for pallet-revive." -default-run = "eth-rpc" -publish = false - -[lints] -workspace = true - -[package.metadata.polkadot-sdk] -exclude-from-umbrella = true - -[[bin]] -name = "eth-rpc" -path = "src/main.rs" - -[dependencies] -anyhow = { workspace = true } -pallet-revive-eth-rpc = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/eth-rpc-server/src/main.rs b/substrate/frame/revive/eth-rpc-server/src/main.rs deleted file mode 100644 index ab2a5d445c19d..0000000000000 --- a/substrate/frame/revive/eth-rpc-server/src/main.rs +++ /dev/null @@ -1,23 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) 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. -//! The Ethereum JSON-RPC server. -use pallet_revive_eth_rpc::cli; - -fn main() -> anyhow::Result<()> { - let cmd = cli::CliCommand::parse_cli()?; - cli::run(cmd) -} From 75616fd2ad371a8b30e2f54b4613d74e305f6c53 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 1 Apr 2026 12:10:27 +0000 Subject: [PATCH 15/54] remove pallet-dev-runtime from eth-rpc --- substrate/frame/revive/rpc/Cargo.toml | 5 +++++ substrate/frame/revive/rpc/build.rs | 13 ++++++++++++- substrate/frame/revive/rpc/revive_chain.scale | Bin 81707 -> 129397 bytes .../frame/revive/rpc/src/subxt_client.rs | 2 +- 4 files changed, 18 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index a65c252289adb..d0e5d4d84f9ad 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -7,6 +7,7 @@ license = "Apache-2.0" homepage.workspace = true repository.workspace = true description = "An Ethereum JSON-RPC server for pallet-revive." +default-run = "eth-rpc" [lints] workspace = true @@ -14,6 +15,10 @@ workspace = true [package.metadata.polkadot-sdk] exclude-from-umbrella = true +[[bin]] +name = "eth-rpc" +path = "src/main.rs" + [[example]] name = "tx-types" required-features = ["subxt"] diff --git a/substrate/frame/revive/rpc/build.rs b/substrate/frame/revive/rpc/build.rs index 44faf3b27ca31..4c211f7e63147 100644 --- a/substrate/frame/revive/rpc/build.rs +++ b/substrate/frame/revive/rpc/build.rs @@ -14,10 +14,11 @@ // 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. -use std::process::Command; +use std::{fs, process::Command}; fn main() { generate_git_revision(); + copy_metadata_file(); } fn generate_git_revision() { @@ -50,3 +51,13 @@ fn generate_git_revision() { println!("cargo:rustc-env=TARGET={target}"); println!("cargo:rustc-env=GIT_REVISION={branch}-{id}"); } + +fn copy_metadata_file() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let src = std::path::Path::new(&manifest_dir).join("revive_chain.scale"); + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); + let dst = std::path::Path::new(&out_dir).join("revive_chain.scale"); + fs::copy(&src, &dst) + .unwrap_or_else(|e| panic!("Failed to copy revive_chain.scale from {src:?}: {e}")); + println!("cargo:rerun-if-changed=revive_chain.scale"); +} diff --git a/substrate/frame/revive/rpc/revive_chain.scale b/substrate/frame/revive/rpc/revive_chain.scale index 1d6833e3677367b9dbcf10a7726f45b165a9e6ee..3018a74e8dfde7d9feec34b71b0d79e23f180465 100644 GIT binary patch literal 129397 zcmeFaePCqQl_&JB{5p{nc~;()cja9LUSfHocBE%@TkXh7yJ>Yxoz_O(Ew|ME7`v$| zRXs^HT^~w)bhknW9B|mol7S3(Ab|{Iz=13{;DHQeAOjxALIymL1rKB(3*IFQS@1#z zypVw`@cW%}KVFqe{gK#V{zyJ-y<6|yd+xdCp6`2ZJ?oT4kHpc)RC~A3>NYyndNy0{ z);iTvrP9jU?b&mDSk#B%$;EQHiMLiNCZ2VFH!9h6Git_$S1NH7oA6E)#k(t=54Mw1 zBPr>J$;whPwcFjilIA#`OV{FP+)O-~m5+DlPeeoU zb$PShtY=B7wOQ@7O0B)5Rl1gJ?sc+uI%y$)8f*=n}xrA}fInOtj?c6YN@ zg65>zm?`g+sva%vG#;723Y43zY`oms+wC-`9eOK|90ekKppRc(Y$VOy(#N|Q;Gf}C z6a8u%gX~nxNwtERRJW>GD;wE1^R*$1e^HtN%YFi5nfV{L@E$mb)7`9Mt zlxy8e*2Zia?M|y(?zo;77J6Sp8$Z9 z8z9eir&QmKqm$;h-pSGZNI3vZl$Xj-#{YH{Z$e-9WkFhoaH-ZlxJVMf2r% za%c{c8}_bf{G!lA9PPZbE3U)jhCURx`0&;&>x6)8nnIUTR&5jr}9XKcbPHbEPX;BK*T|N_{_#K64t3 zYcor->sh(W!8iAk>}m!EQ*5@X+to%foo{EI!cJD&E$o)IK^A6A{yk+zx{uC*k^OKz z>m(gaq0y~xV)Hk*5_uuP-e>bu+2&%qoz*vM=(=&W+G;lH*ty`D>@-c-eYsi5Vl&Er z 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�hE1?|uiCB^MUAZqerEIsY+ZB5vWep6dA092wX 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 delta 6384 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 E1zx`--T(jq diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 05ef9f7b8ed02..08563863ec71d 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -20,7 +20,7 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; #[subxt::subxt( - runtime_metadata_path = "revive_chain.scale", + runtime_metadata_path = "$OUT_DIR/revive_chain.scale", // TODO remove once subxt use the same U256 type substitute_type( path = "primitive_types::U256", From f52bb449b380895e93e7e2310a0aaa1314e37637 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 1 Apr 2026 12:58:15 +0000 Subject: [PATCH 16/54] add state_overrides parameter --- substrate/frame/revive/rpc/src/client.rs | 7 ++++--- substrate/frame/revive/rpc/src/lib.rs | 4 ++-- substrate/frame/revive/rpc/src/native_client.rs | 5 +++-- substrate/frame/revive/rpc/src/substrate_client.rs | 5 +++-- substrate/frame/revive/rpc/src/subxt_client.rs | 9 ++++++--- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 493a9a91c7daf..530a37184ce51 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -33,8 +33,8 @@ use pallet_revive::{ EthTransactError, evm::{ Block, BlockNumberOrTagOrHash, FeeHistoryResult, Filter, GenericTransaction, H256, - HashesOrTransactionInfos, Log, ReceiptInfo, SyncingProgress, SyncingStatus, Trace, - TransactionSigned, TransactionTrace, decode_revert_reason, + HashesOrTransactionInfos, Log, ReceiptInfo, StateOverrideSet, SyncingProgress, + SyncingStatus, Trace, TransactionSigned, TransactionTrace, decode_revert_reason, }, }; use sp_core::U256; @@ -799,8 +799,9 @@ impl Client { block_hash: SubstrateBlockHash, tx: GenericTransaction, block: BlockNumberOrTagOrHash, + state_overrides: Option, ) -> Result { - self.backend.dry_run(block_hash, tx, block).await.map(|info| info.eth_gas) + self.backend.dry_run(block_hash, tx, block, state_overrides).await.map(|info| info.eth_gas) } /// Fetch the raw signed block (used for tracing). diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 675261c772a80..cab4d4ee73799 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -201,7 +201,7 @@ impl EthRpcServer for EthRpcServerIm let eth_gas = self .client - .dry_run(hash, transaction, block_tag_or_hash) + .dry_run(hash, transaction, block_tag_or_hash, None) .await .map_err(ClientError::from)?; @@ -221,7 +221,7 @@ impl EthRpcServer for EthRpcServerIm let info = self .client .backend - .dry_run(hash, transaction, block) + .dry_run(hash, transaction, block, state_overrides) .await .map_err(ClientError::from)?; Ok(info.data.into()) diff --git a/substrate/frame/revive/rpc/src/native_client.rs b/substrate/frame/revive/rpc/src/native_client.rs index 2a2b05e101f1f..a545fb64eb932 100644 --- a/substrate/frame/revive/rpc/src/native_client.rs +++ b/substrate/frame/revive/rpc/src/native_client.rs @@ -33,8 +33,8 @@ use jsonrpsee::core::async_trait; use pallet_revive::{ EthTransactInfo, ReviveApi, evm::{ - Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, - TracerType, U256, + Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, + StateOverrideSet, Trace, TracerType, U256, }, }; use sc_client_api::{BlockBackend, BlockchainEvents, HeaderBackend}; @@ -229,6 +229,7 @@ where block_hash: SubstrateBlockHash, tx: GenericTransaction, _block: BlockNumberOrTagOrHash, + _state_overrides: Option, ) -> Result, ClientError> { self.client .runtime_api() diff --git a/substrate/frame/revive/rpc/src/substrate_client.rs b/substrate/frame/revive/rpc/src/substrate_client.rs index 9208e1fedf503..929a2e39c26b5 100644 --- a/substrate/frame/revive/rpc/src/substrate_client.rs +++ b/substrate/frame/revive/rpc/src/substrate_client.rs @@ -29,8 +29,8 @@ use jsonrpsee::core::async_trait; use pallet_revive::{ EthTransactInfo, evm::{ - Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, - TracerType, U256, + Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, + StateOverrideSet, Trace, TracerType, U256, }, }; use sc_transaction_pool_api::TransactionStatus; @@ -93,6 +93,7 @@ pub trait SubstrateClientT: Send + Sync + Clone + 'static { block_hash: SubstrateBlockHash, tx: GenericTransaction, block: BlockNumberOrTagOrHash, + state_overrides: Option, ) -> Result, crate::client::ClientError>; /// Return the current gas price. diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 08563863ec71d..65f1cc0b56cf9 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -90,8 +90,8 @@ use jsonrpsee::core::async_trait; use pallet_revive::{ EthTransactInfo, evm::{ - Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, Trace, - TracerType, U256, + Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, + StateOverrideSet, Trace, TracerType, U256, }, }; use sp_core::H256; @@ -399,9 +399,12 @@ impl SubstrateClientT for SubxtClient { block_hash: SubstrateBlockHash, tx: GenericTransaction, block: BlockNumberOrTagOrHash, + state_overrides: Option, ) -> Result, ClientError> { use crate::client::runtime_api::RuntimeApi; - RuntimeApi::new(self.api.runtime_api().at(block_hash)).dry_run(tx, block).await + RuntimeApi::new(self.api.runtime_api().at(block_hash)) + .dry_run(tx, block, state_overrides) + .await } async fn gas_price(&self, block_hash: SubstrateBlockHash) -> Result { From e3341ad9a14de960b52be49e248dd90df42d7a09 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 1 Apr 2026 12:59:22 +0000 Subject: [PATCH 17/54] fmt --- substrate/frame/revive/rpc/src/client.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 530a37184ce51..18606ab9cf226 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -801,7 +801,10 @@ impl Client { block: BlockNumberOrTagOrHash, state_overrides: Option, ) -> Result { - self.backend.dry_run(block_hash, tx, block, state_overrides).await.map(|info| info.eth_gas) + self.backend + .dry_run(block_hash, tx, block, state_overrides) + .await + .map(|info| info.eth_gas) } /// Fetch the raw signed block (used for tracing). From 7ac1c85e6168416ebd457e0686b0c0752249db9d Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 1 Apr 2026 15:11:12 +0000 Subject: [PATCH 18/54] rebuild revive_chain.scale --- substrate/frame/revive/rpc/revive_chain.scale | Bin 129397 -> 134986 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/substrate/frame/revive/rpc/revive_chain.scale b/substrate/frame/revive/rpc/revive_chain.scale index 3018a74e8dfde7d9feec34b71b0d79e23f180465..3bc40016dc58b6cd481c752c374088227a5d6bf4 100644 GIT binary patch delta 6477 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 dU1C7Et&s#SL8}bwLn2S*$W-(D_(PE+* Date: Thu, 2 Apr 2026 23:13:30 +0000 Subject: [PATCH 19/54] apply suggestions --- Cargo.lock | 2 + substrate/frame/revive/rpc/Cargo.toml | 2 + .../frame/revive/rpc/src/native_client.rs | 133 ++++++++++++++++-- .../frame/revive/rpc/src/receipt_extractor.rs | 18 ++- .../frame/revive/rpc/src/substrate_client.rs | 11 +- .../frame/revive/rpc/src/subxt_client.rs | 15 ++ 6 files changed, 160 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 52ef5268e423a..47e2c3c8087c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13880,6 +13880,7 @@ dependencies = [ "sp-api 26.0.0", "sp-arithmetic 23.0.0", "sp-blockchain 28.0.0", + "sp-consensus 0.32.0", "sp-core 28.0.0", "sp-crypto-hashing 0.1.0", "sp-io 30.0.0", @@ -13890,6 +13891,7 @@ dependencies = [ "sqlx", "substrate-prometheus-endpoint 0.17.0", "subxt 0.44.2", + "subxt-metadata 0.44.2", "subxt-signer 0.44.2", "tempfile", "thiserror 1.0.65", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index b7cdc8ad8d65d..c0ab830876c24 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -67,12 +67,14 @@ serde_json = { workspace = true } sp-api = { workspace = true, default-features = true } sp-arithmetic = { workspace = true, default-features = true } sp-blockchain = { workspace = true, default-features = true } +sp-consensus = { workspace = true, default-features = true } sp-core = { workspace = true, default-features = true } sp-crypto-hashing = { workspace = true } sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true } sp-weights = { workspace = true, default-features = true } +subxt-metadata = { workspace = true, default-features = true } sqlx = { workspace = true, features = ["macros", "runtime-tokio", "sqlite"] } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } diff --git a/substrate/frame/revive/rpc/src/native_client.rs b/substrate/frame/revive/rpc/src/native_client.rs index a545fb64eb932..73bc0c44226ba 100644 --- a/substrate/frame/revive/rpc/src/native_client.rs +++ b/substrate/frame/revive/rpc/src/native_client.rs @@ -31,15 +31,17 @@ use codec::{Decode, Encode}; use futures::StreamExt; use jsonrpsee::core::async_trait; use pallet_revive::{ - EthTransactInfo, ReviveApi, + DryRunConfig, EthTransactInfo, ReviveApi, evm::{ - Block as EthBlock, BlockNumberOrTagOrHash, GenericTransaction, ReceiptGasInfo, + Block as EthBlock, BlockNumberOrTagOrHash, BlockTag, GenericTransaction, ReceiptGasInfo, StateOverrideSet, Trace, TracerType, U256, }, }; use sc_client_api::{BlockBackend, BlockchainEvents, HeaderBackend}; +use sc_network::NetworkStatusProvider; use sc_transaction_pool_api::TransactionPool; -use sp_api::ProvideRuntimeApi; +use sp_api::{Metadata, ProvideRuntimeApi}; +use sp_consensus::SyncOracle; use sp_core::{H160, H256}; use sp_runtime::{ OpaqueExtrinsic, @@ -74,6 +76,11 @@ fn native_err(e: impl std::fmt::Display) -> ClientError { ClientError::NativeClientError(e.to_string()) } +/// A closure that, given a block hash and extrinsic index, returns the post-dispatch +/// `Weight` for that extrinsic by scanning block events. +type FetchExtrinsicWeightFn = + Arc Option + Send + Sync>; + /// A [`SubstrateClientT`] backed by the node's native in-process Substrate client. pub struct NativeSubstrateClient where @@ -84,6 +91,14 @@ where chain_id: u64, max_block_weight: Weight, automine: bool, + /// The runtime index of `pallet-revive`, discovered from metadata at construction time. + pallet_revive_index: u8, + /// Optional network-status provider for real peer count in `system_health`. + network: Option>, + /// Optional sync oracle for real `is_syncing` in `system_health`. + sync_oracle: Option>, + /// Optional hook that scans block events to return post-dispatch weight. + fetch_extrinsic_weight: Option, _block: PhantomData, _moment: PhantomData, } @@ -99,6 +114,10 @@ where chain_id: self.chain_id, max_block_weight: self.max_block_weight, automine: self.automine, + pallet_revive_index: self.pallet_revive_index, + network: self.network.clone(), + sync_oracle: self.sync_oracle.clone(), + fetch_extrinsic_weight: self.fetch_extrinsic_weight.clone(), _block: PhantomData, _moment: PhantomData, } @@ -109,7 +128,7 @@ impl NativeSubstrateClient, Block::Header: HeaderT, - Moment: codec::Codec + Send + Sync + 'static, + Moment: codec::Codec + From + Send + Sync + 'static, Client: HeaderBackend + ProvideRuntimeApi + BlockBackend @@ -135,17 +154,75 @@ where .map(|v: U256| v.min(U256::from(u64::MAX)).as_u64()) .unwrap_or(u64::MAX); + let pallet_revive_index = Self::discover_pallet_index(&client, best_hash); + Ok(Self { client, pool, chain_id, max_block_weight: Weight::from_parts(block_gas_limit, u64::MAX), automine, + pallet_revive_index, + network: None, + sync_oracle: None, + fetch_extrinsic_weight: None, _block: PhantomData, _moment: PhantomData, }) } + pub fn with_network( + mut self, + network: Arc, + sync_oracle: Arc, + ) -> Self { + self.network = Some(network as Arc); + self.sync_oracle = Some(sync_oracle as Arc); + self + } + + pub fn with_event_reader( + mut self, + f: impl Fn(SubstrateBlockHash, usize) -> Option + Send + Sync + 'static, + ) -> Self { + self.fetch_extrinsic_weight = Some(Arc::new(f)); + self + } + + /// Attempt to read the pallet index of `pallet-revive` from the runtime metadata. + fn discover_pallet_index(client: &Arc, best_hash: Block::Hash) -> u8 { + let opaque = match client.runtime_api().metadata(best_hash) { + Ok(m) => m, + Err(e) => { + log::warn!( + target: crate::LOG_TARGET, + "Failed to fetch runtime metadata for pallet index discovery: {e:?}. \ + Falling back to default index 253." + ); + return 253; + }, + }; + + match subxt_metadata::Metadata::decode(&mut &opaque[..]) { + Ok(meta) => meta.pallet_by_name("Revive").map(|p| p.index()).unwrap_or_else(|| { + log::warn!( + target: crate::LOG_TARGET, + "Revive pallet not found in runtime metadata; \ + falling back to default pallet index 253." + ); + 253 + }), + Err(e) => { + log::warn!( + target: crate::LOG_TARGET, + "Failed to decode runtime metadata for pallet index discovery: {e:?}. \ + Falling back to default index 253." + ); + 253 + }, + } + } + /// Build a [`NativeCachedBlock`] from a block hash by querying the in-process client. fn block_info_from_hash( &self, @@ -174,7 +251,7 @@ impl SubstrateClientT where Block: BlockT + Send + Sync + 'static, Block::Header: HeaderT + Send + Sync, - Moment: codec::Codec + Clone + Send + Sync + 'static, + Moment: codec::Codec + Clone + From + Send + Sync + 'static, Client: HeaderBackend + ProvideRuntimeApi + BlockBackend @@ -228,12 +305,20 @@ where &self, block_hash: SubstrateBlockHash, tx: GenericTransaction, - _block: BlockNumberOrTagOrHash, - _state_overrides: Option, + block: BlockNumberOrTagOrHash, + state_overrides: Option, ) -> Result, ClientError> { + let timestamp_override: Option = + matches!(block, BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending)) + .then(|| Moment::from(sp_timestamp::Timestamp::current().as_millis())); + + let config = DryRunConfig::::default() + .with_timestamp_override(timestamp_override) + .with_state_overrides(state_overrides); + self.client .runtime_api() - .eth_transact(block_hash, tx) + .eth_transact_with_config(block_hash, tx, config) .map_err(native_err)? .map_err(ClientError::TransactError) } @@ -370,7 +455,27 @@ where } async fn system_health(&self) -> Result { - Ok(NodeHealth { peers: 0, is_syncing: false, should_have_peers: false }) + match &self.network { + Some(net) => { + let status = net.status().await.map_err(|_| { + ClientError::NativeClientError( + "network status provider returned an error".to_string(), + ) + })?; + let is_syncing = + self.sync_oracle.as_ref().map(|o| o.is_major_syncing()).unwrap_or(false); + Ok(NodeHealth { + peers: status.num_connected_peers, + is_syncing, + should_have_peers: true, + }) + }, + None => Ok(NodeHealth { peers: 0, is_syncing: false, should_have_peers: false }), + } + } + + fn pallet_revive_index(&self) -> u8 { + self.pallet_revive_index } async fn get_automine(&self) -> bool { @@ -492,6 +597,16 @@ where OpaqueBlock::decode(&mut &encoded[..]).map_err(native_err) } + async fn extrinsic_post_dispatch_weight( + &self, + block_hash: SubstrateBlockHash, + extrinsic_index: usize, + ) -> Option { + self.fetch_extrinsic_weight + .as_ref() + .and_then(|f| f(block_hash, extrinsic_index)) + } + async fn block_extrinsics( &self, block_hash: SubstrateBlockHash, diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index 03731fc02458c..f130053ababbf 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -32,7 +32,6 @@ use std::{ }, }; -const PALLET_REVIVE_INDEX: u8 = 253; const ETH_TRANSACT_CALL_INDEX: u8 = 0; /// Sentinel value meaning "not yet discovered". @@ -88,6 +87,9 @@ pub struct ReceiptExtractor { /// Recover the ethereum address from a transaction signature. recover_eth_address: RecoverEthAddressFn, + + /// The pallet index of `pallet-revive` in this runtime. + pallet_index: u8, } impl ReceiptExtractor { @@ -136,6 +138,7 @@ impl ReceiptExtractor { /// Create a `ReceiptExtractor` for the **native in-process** path with custom closures. pub fn new_native( + pallet_index: u8, fetch_receipt_data_fn: impl Fn( H256, ) -> Pin< @@ -175,6 +178,7 @@ impl ReceiptExtractor { recover_eth_address: Arc::new(|signed_tx: &TransactionSigned| { signed_tx.recover_eth_address() }), + pallet_index, } } @@ -182,6 +186,7 @@ impl ReceiptExtractor { pub fn new_native_from_client( client: Arc, earliest_receipt_block: Option, + pallet_index: u8, ) -> Self where Block: sp_runtime::traits::Block @@ -235,6 +240,7 @@ impl ReceiptExtractor { }; Self::new_native( + pallet_index, fetch_receipt_data_fn, fetch_eth_block_hash_fn, earliest_receipt_block, @@ -251,6 +257,8 @@ impl ReceiptExtractor { where C: crate::SubstrateClientT + Clone + 'static, { + let pallet_index = client.pallet_revive_index(); + let client_for_data = client.clone(); let fetch_receipt_data_fn = move |block_hash: H256| { let c = client_for_data.clone(); @@ -275,6 +283,7 @@ impl ReceiptExtractor { }; Self::new_native( + pallet_index, fetch_receipt_data_fn, fetch_eth_block_hash_fn, earliest_receipt_block, @@ -304,11 +313,12 @@ impl ReceiptExtractor { recover_eth_address: Arc::new(|signed_tx: &TransactionSigned| { signed_tx.recover_eth_address() }), + pallet_index: 0, } } /// Try to decode a raw extrinsic as an `eth_transact` call. - fn decode_eth_transact(raw: &RawExtrinsic) -> Option> { + fn decode_eth_transact(&self, raw: &RawExtrinsic) -> Option> { let bytes = &raw.payload; let mut offset = 0usize; @@ -329,7 +339,7 @@ impl ReceiptExtractor { let call_idx = *bytes.get(offset)?; offset += 1; - if pallet_idx != PALLET_REVIVE_INDEX || call_idx != ETH_TRANSACT_CALL_INDEX { + if pallet_idx != self.pallet_index || call_idx != ETH_TRANSACT_CALL_INDEX { return None; } @@ -426,7 +436,7 @@ impl ReceiptExtractor { let eth_extrinsics: Vec<(usize, Vec)> = raw_extrinsics .iter() .filter_map(|raw| { - let payload = Self::decode_eth_transact(raw)?; + let payload = self.decode_eth_transact(raw)?; Some((raw.index, payload)) }) .collect(); diff --git a/substrate/frame/revive/rpc/src/substrate_client.rs b/substrate/frame/revive/rpc/src/substrate_client.rs index 929a2e39c26b5..7a245e128a193 100644 --- a/substrate/frame/revive/rpc/src/substrate_client.rs +++ b/substrate/frame/revive/rpc/src/substrate_client.rs @@ -199,6 +199,9 @@ pub trait SubstrateClientT: Send + Sync + Clone + 'static { /// Return whether the node has automine enabled. async fn get_automine(&self) -> bool; + /// Return the pallet index of `pallet-revive` in the runtime. + fn pallet_revive_index(&self) -> u8; + /// Subscribe to new best or finalized blocks. async fn subscribe_blocks( &self, @@ -236,12 +239,4 @@ pub trait SubstrateClientT: Send + Sync + Clone + 'static { ) -> Option { None } - - /// Return the post-dispatch weight for a given transaction hash. - async fn post_dispatch_weight( - &self, - _tx_hash: &SubstrateBlockHash, - ) -> Option { - None - } } diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 65f1cc0b56cf9..f4a859ab9d95c 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -660,6 +660,21 @@ impl SubstrateClientT for SubxtClient { let event = ext.events().await.ok()?.find_first::().ok()??; Some(event.dispatch_info.weight.0) } + + fn pallet_revive_index(&self) -> u8 { + self.api + .metadata() + .pallet_by_name("Revive") + .map(|p| p.index()) + .unwrap_or_else(|| { + log::warn!( + target: crate::LOG_TARGET, + "Revive pallet not found in subxt metadata; \ + falling back to default pallet index 253." + ); + 253 + }) + } } fn to_hex(bytes: impl AsRef<[u8]>) -> String { From 25274c8c3a4edbdb15d20353a51802b3f8837649 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 7 Apr 2026 22:14:09 +0000 Subject: [PATCH 20/54] fix circular dependency breakage --- Cargo.lock | 19 ++++++- cumulus/polkadot-omni-node/lib/Cargo.toml | 2 +- .../frame/revive/dev-node/runtime/Cargo.toml | 49 +++++++++++++------ .../frame/revive/dev-node/runtime/build.rs | 9 ++-- .../frame/revive/dev-node/runtime/src/lib.rs | 23 ++++----- substrate/frame/revive/rpc/Cargo.toml | 4 ++ substrate/frame/revive/rpc/build.rs | 6 ++- .../frame/revive/rpc/src/subxt_client.rs | 2 +- 8 files changed, 76 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c13be48476188..bfa2c078d9ae8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13868,6 +13868,7 @@ dependencies = [ "parity-scale-codec", "pretty_assertions", "revive-dev-node", + "revive-dev-runtime", "rlp 0.6.1", "sc-cli", "sc-client-api 28.0.0", @@ -19418,11 +19419,27 @@ name = "revive-dev-runtime" version = "0.1.0" dependencies = [ "array-bytes 6.2.2", + "frame-executive", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-revive", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parachains-common", "parity-scale-codec", - "polkadot-sdk", + "polkadot-primitives", + "polkadot-sdk-frame", "scale-info", "serde_json", "sp-debug-derive 14.0.0", + "sp-io 30.0.0", + "sp-keyring", + "sp-runtime 31.0.1", + "sp-weights 27.0.0", + "substrate-wasm-builder", ] [[package]] diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index 51eef43052880..e84f1ce4c2318 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -42,7 +42,7 @@ frame-system-rpc-runtime-api = { workspace = true, default-features = true } frame-try-runtime = { optional = true, workspace = true, default-features = true } pallet-transaction-payment = { workspace = true, default-features = true } pallet-revive = { workspace = true, default-features = true } -pallet-revive-eth-rpc = { workspace = true, default-features = true } +pallet-revive-eth-rpc = { workspace = true, default-features = false } pallet-transaction-payment-rpc = { workspace = true, default-features = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 39d9146f364a9..b991a5546f548 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -11,32 +11,51 @@ edition.workspace = true [dependencies] array-bytes = { workspace = true } codec = { workspace = true } -polkadot-sdk = { workspace = true, features = [ - "pallet-balances", - "pallet-revive", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "parachains-common", - "polkadot-primitives", - "polkadot-runtime-common", - "runtime", - "with-tracing", -] } +frame = { workspace = true, features = ["experimental", "runtime"] } +frame-executive = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +pallet-balances = { workspace = true } +pallet-revive = { workspace = true } +pallet-sudo = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-transaction-payment = { workspace = true } +pallet-transaction-payment-rpc-runtime-api = { workspace = true } +parachains-common = { workspace = true } +polkadot-primitives = { workspace = true } scale-info = { workspace = true } serde_json = { workspace = true, default-features = false, features = ["alloc"] } sp-debug-derive = { workspace = true } +sp-io = { workspace = true, features = ["with-tracing"] } +sp-keyring = { workspace = true } +sp-runtime = { workspace = true } +sp-weights = { workspace = true } [build-dependencies] -polkadot-sdk = { optional = true, workspace = true, features = ["substrate-wasm-builder"] } +substrate-wasm-builder = { optional = true, workspace = true, default-features = true } [features] default = ["std"] std = [ "codec/std", - "polkadot-sdk/std", + "frame-executive/std", + "frame-support/std", + "frame-system/std", + "frame/std", + "pallet-balances/std", + "pallet-revive/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "parachains-common/std", + "polkadot-primitives/std", "scale-info/std", "serde_json/std", "sp-debug-derive/std", + "sp-io/std", + "sp-keyring/std", + "sp-runtime/std", + "sp-weights/std", + "substrate-wasm-builder", ] diff --git a/substrate/frame/revive/dev-node/runtime/build.rs b/substrate/frame/revive/dev-node/runtime/build.rs index 2cb2966b5d822..711deb9bc4151 100644 --- a/substrate/frame/revive/dev-node/runtime/build.rs +++ b/substrate/frame/revive/dev-node/runtime/build.rs @@ -15,9 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "std")] fn main() { - #[cfg(feature = "std")] - { - polkadot_sdk::substrate_wasm_builder::WasmBuilder::build_using_defaults(); - } + substrate_wasm_builder::WasmBuilder::build_using_defaults(); } + +#[cfg(not(feature = "std"))] +fn main() {} diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs index f2a40f2569787..8bd18761fddf7 100644 --- a/substrate/frame/revive/dev-node/runtime/src/lib.rs +++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs @@ -25,6 +25,11 @@ extern crate alloc; use alloc::{vec, vec::Vec}; use currency::*; +use frame::{ + deps::sp_genesis_builder, + runtime::{apis, prelude::*}, + traits::Block as BlockT, +}; use frame_support::weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, Weight, @@ -38,20 +43,10 @@ use pallet_revive::{ AccountId32Mapper, }; use pallet_transaction_payment::{ConstFeeMultiplier, FeeDetails, Multiplier, RuntimeDispatchInfo}; -use polkadot_sdk::{ - polkadot_sdk_frame::{ - deps::sp_genesis_builder, - runtime::{apis, prelude::*}, - traits::Block as BlockT, - }, - *, -}; use sp_weights::ConstantMultiplier; -pub use polkadot_sdk::{ - parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}, - polkadot_sdk_frame::runtime::types_common::OpaqueBlock, -}; +pub use frame::runtime::types_common::OpaqueBlock; +pub use parachains_common::{AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature}; pub mod currency { use super::Balance; @@ -64,9 +59,9 @@ pub mod currency { pub mod genesis_config_presets { use super::*; use crate::{ - currency::DOLLARS, sp_keyring::Sr25519Keyring, Balance, BalancesConfig, ReviveConfig, - RuntimeGenesisConfig, SudoConfig, + currency::DOLLARS, Balance, BalancesConfig, ReviveConfig, RuntimeGenesisConfig, SudoConfig, }; + use sp_keyring::Sr25519Keyring; use alloc::{vec, vec::Vec}; use pallet_revive::is_eth_derived; diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index c0ab830876c24..2834dd65f0f56 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -40,6 +40,8 @@ default = ["subxt"] subxt = [ "dep:subxt", "dep:subxt-signer", + "dep:revive-dev-runtime", + "dep:sp-io", ] [dependencies] @@ -100,3 +102,5 @@ tempfile = { workspace = true } [build-dependencies] git2 = { workspace = true } +revive-dev-runtime = { optional = true, workspace = true, default-features = true } +sp-io = { optional = true, workspace = true, default-features = true } diff --git a/substrate/frame/revive/rpc/build.rs b/substrate/frame/revive/rpc/build.rs index 4c211f7e63147..5617f2bc8c0a0 100644 --- a/substrate/frame/revive/rpc/build.rs +++ b/substrate/frame/revive/rpc/build.rs @@ -18,7 +18,8 @@ use std::{fs, process::Command}; fn main() { generate_git_revision(); - copy_metadata_file(); + #[cfg(feature = "subxt")] + generate_metadata_file(); } fn generate_git_revision() { @@ -52,7 +53,8 @@ fn generate_git_revision() { println!("cargo:rustc-env=GIT_REVISION={branch}-{id}"); } -fn copy_metadata_file() { +#[cfg(feature = "subxt")] +fn generate_metadata_file() { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); let src = std::path::Path::new(&manifest_dir).join("revive_chain.scale"); let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index f4a859ab9d95c..d674072d39789 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -28,7 +28,7 @@ pub use subxt::config::PolkadotConfig as SrcChainConfig; ), substitute_type( - path = "sp_runtime::generic::block::Block", + path = "sp_runtime::generic::block::Block", with = "::subxt::utils::Static<::sp_runtime::generic::Block< ::sp_runtime::generic::Header, ::sp_runtime::OpaqueExtrinsic From cc93e0ed17fc98581122c295741f2b01305afbca Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 7 Apr 2026 22:20:55 +0000 Subject: [PATCH 21/54] remove redundant revivce_chain.scale file --- substrate/frame/revive/rpc/revive_chain.scale | Bin 134986 -> 0 bytes substrate/frame/revive/rpc/src/client.rs | 2 - .../revive/rpc/src/client/storage_api.rs | 71 ------------------ 3 files changed, 73 deletions(-) delete mode 100644 substrate/frame/revive/rpc/revive_chain.scale delete mode 100644 substrate/frame/revive/rpc/src/client/storage_api.rs diff --git a/substrate/frame/revive/rpc/revive_chain.scale b/substrate/frame/revive/rpc/revive_chain.scale deleted file mode 100644 index 3bc40016dc58b6cd481c752c374088227a5d6bf4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134986 zcmeFaePCqQl_&JB{5p{nc~;()cja9LUSfHocBE%@TkXh7yJ>Yxoz_O(Ew|ME7`v$| zRXs^HT^~w)bhknW9B|mol7S3(Ab|{Iz=13{;DHQeAOjxALIymL1rKB(3*IFQS@1#z zypVw`@cW%}KVFqe{gK#V{zyJ-y<6|yd+xdCp6`2ZJ?oT4FUQfyRC~A3>NYyndNy0{ z);iTvrP9jU?b&mDSk#B%$;EQHiMLiNCZ2VFH!9h6Git_$S1NH7oA6E)#k(t=54Mw1 zBPr>J$;whPwcFjilIA#`OV{FP+)O-~m5+DlPeeoU zb$PShtY=B7wOQ@7O0B)5Rl1gJ?sc+uI%y$)8f*=n}xrA}fInOtj?c6YN@ zg65>zm?`g+sva%vG#;723Y43zY`oms+wC-`9eOK|90ekKppRc(Y$VOy(#N|Q;Gf}C z6a8u%gX~nxNwtERRJW>GD;wE1^R*$1e^HtN%YFi5nfV{L@E$mb)7`9Mt zlxy8e*2Zia?M|y(?zo;77J6Sp8$Z9 z8z9eir&QmKqm$;h-pSGZNI3vZl$Xj-#{YH{Z$e-9WkFhoaH-ZlxJVMf2r% za%c{c8}_bf{G!lA9PPZbE3U)jhCURx`0&;&>x6)8nnIUTR&5jr}9XKcbPHbEPX;BK*T|N_{_#K64t3 zYcor->sh(W!8iAk>}m!EQ*5@X+to%foo{EI!cJD&E$o)IK^A6A{yk+zx{uC*k^OKz z>m(gaq0y~xV)Hk*5_uuP-e>bu+2&%qoz*vM=(=&W+G;lH*ty`D>@-c-eYsi5Vl&Er z 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 diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 18606ab9cf226..7e9fb2ebd5cfa 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -19,8 +19,6 @@ #[cfg(feature = "subxt")] pub(crate) mod runtime_api; -#[cfg(feature = "subxt")] -pub(crate) mod storage_api; use crate::{ BlockInfoProvider, BlockNumberOrTag, BlockTag, FeeHistoryProvider, ReceiptProvider, TracerType, diff --git a/substrate/frame/revive/rpc/src/client/storage_api.rs b/substrate/frame/revive/rpc/src/client/storage_api.rs deleted file mode 100644 index 893ec5fc0e17e..0000000000000 --- a/substrate/frame/revive/rpc/src/client/storage_api.rs +++ /dev/null @@ -1,71 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) 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(feature = "subxt")] -use crate::{ - ClientError, H160, - subxt_client::{ - self, SrcChainConfig, - runtime_types::pallet_revive::storage::{AccountType, ContractInfo}, - }, -}; -#[cfg(feature = "subxt")] -use subxt::{OnlineClient, storage::Storage}; - -/// A wrapper around the Substrate Storage API. -#[cfg(feature = "subxt")] -#[allow(dead_code)] -#[derive(Clone)] -pub struct StorageApi(Storage>); - -#[cfg(feature = "subxt")] -impl StorageApi { - /// Create a new instance of the StorageApi. - #[allow(dead_code)] - pub fn new(api: Storage>) -> Self { - Self(api) - } - - /// Get the contract info for the given contract address. - #[allow(dead_code)] - pub async fn get_contract_info( - &self, - contract_address: &H160, - ) -> Result { - // TODO: remove once subxt is updated - let contract_address: subxt::utils::H160 = contract_address.0.into(); - - let query = - subxt_client::storage().revive().account_info_of(contract_address).unvalidated(); - let Some(info) = self.0.fetch(&query).await? else { - return Err(ClientError::ContractNotFound); - }; - - let AccountType::Contract(contract_info) = info.account_type else { - return Err(ClientError::ContractNotFound); - }; - - Ok(contract_info) - } - - /// Get the contract trie id for the given contract address. - #[allow(dead_code)] - pub async fn get_contract_trie_id(&self, address: &H160) -> Result, ClientError> { - let ContractInfo { trie_id, .. } = self.get_contract_info(address).await?; - Ok(trie_id.0) - } -} From 70d53fa9835cbf1e161fe231b5803721197f7166 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 22:22:40 +0000 Subject: [PATCH 22/54] Update from github-actions[bot] running command 'prdoc --audience node_dev --bump patch' --- prdoc/pr_11297.prdoc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 prdoc/pr_11297.prdoc diff --git a/prdoc/pr_11297.prdoc b/prdoc/pr_11297.prdoc new file mode 100644 index 0000000000000..9c4d037e718b9 --- /dev/null +++ b/prdoc/pr_11297.prdoc @@ -0,0 +1,17 @@ +title: 'pallet-revive-eth-rpc: Introduce SubstrateClientT trait abstraction and Asset + Hub embedded RPC integration' +doc: +- audience: Node Dev + description: |- + closes https://github.com/paritytech/polkadot-sdk/issues/11221 + + Removes the `revive-dev-node` standalone binary and integrates the `pallet-revive` ETH RPC server directly into the Asset Hub (Omni) node. Introduces a `SubstrateClientT` trait that abstracts all Substrate node interactions, allowing `EthRpcServerImpl` to be generic over its backend. A new `NativeSubstrateClient` implementation backed by `sc_client_api` traits provides a direct in-process path with no `subxt` dependency on the hot path. The `SubxtClient` path is preserved unchanged for the standalone `eth-rpc` binary. On the node side, `BuildAssetHubRpcExtensions` is added to `polkadot-omni-node-lib` behind an opt-in `revive-rpc` feature, registering the full `eth_*`/`debug_*`/`net_*`/`web3_*` surface on the standard RPC port alongside existing parachain RPCs. +crates: +- name: polkadot-omni-node-lib + bump: patch +- name: revive-dev-runtime + bump: patch +- name: pallet-revive-eth-rpc + bump: patch +- name: pallet-revive + bump: patch From 3ecd89d25c82018bae1c6bd1939d3458eb339f03 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 7 Apr 2026 22:36:11 +0000 Subject: [PATCH 23/54] nit --- substrate/frame/revive/rpc/build.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/substrate/frame/revive/rpc/build.rs b/substrate/frame/revive/rpc/build.rs index 5617f2bc8c0a0..acca124c39930 100644 --- a/substrate/frame/revive/rpc/build.rs +++ b/substrate/frame/revive/rpc/build.rs @@ -55,11 +55,12 @@ fn generate_git_revision() { #[cfg(feature = "subxt")] fn generate_metadata_file() { - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); - let src = std::path::Path::new(&manifest_dir).join("revive_chain.scale"); - let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); - let dst = std::path::Path::new(&out_dir).join("revive_chain.scale"); - fs::copy(&src, &dst) - .unwrap_or_else(|e| panic!("Failed to copy revive_chain.scale from {src:?}: {e}")); - println!("cargo:rerun-if-changed=revive_chain.scale"); + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| { + let metadata = revive_dev_runtime::Runtime::metadata_at_version(16).unwrap(); + let bytes: &[u8] = &metadata; + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set"); + let out_path = std::path::Path::new(&out_dir).join("revive_chain.scale"); + fs::write(out_path, bytes).unwrap(); + }); } From bb4ead67b32b4eff36ff56613ea34a67940bd9df Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 7 Apr 2026 22:47:49 +0000 Subject: [PATCH 24/54] gate import behind subxt --- substrate/frame/revive/rpc/build.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/build.rs b/substrate/frame/revive/rpc/build.rs index acca124c39930..0cfc80014491a 100644 --- a/substrate/frame/revive/rpc/build.rs +++ b/substrate/frame/revive/rpc/build.rs @@ -14,7 +14,9 @@ // 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. -use std::{fs, process::Command}; +#[cfg(feature = "subxt")] +use std::fs; +use std::process::Command; fn main() { generate_git_revision(); From 2e26619f153edb1b1ad8834e407a07459cf46cdd Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 8 Apr 2026 09:39:36 +0000 Subject: [PATCH 25/54] fix missing polkadot_sdk feature flags --- cumulus/polkadot-omni-node/lib/Cargo.toml | 6 +++--- substrate/frame/revive/dev-node/node/Cargo.toml | 13 ++++++++++++- substrate/frame/revive/dev-node/runtime/Cargo.toml | 2 +- substrate/frame/revive/rpc/Cargo.toml | 4 ++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/cumulus/polkadot-omni-node/lib/Cargo.toml b/cumulus/polkadot-omni-node/lib/Cargo.toml index e84f1ce4c2318..c5594ecfda17e 100644 --- a/cumulus/polkadot-omni-node/lib/Cargo.toml +++ b/cumulus/polkadot-omni-node/lib/Cargo.toml @@ -40,9 +40,9 @@ frame-benchmarking-cli = { workspace = true, default-features = true } frame-support = { optional = true, workspace = true, default-features = true } frame-system-rpc-runtime-api = { workspace = true, default-features = true } frame-try-runtime = { optional = true, workspace = true, default-features = true } -pallet-transaction-payment = { workspace = true, default-features = true } pallet-revive = { workspace = true, default-features = true } pallet-revive-eth-rpc = { workspace = true, default-features = false } +pallet-transaction-payment = { workspace = true, default-features = true } pallet-transaction-payment-rpc = { workspace = true, default-features = true } pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } @@ -128,19 +128,19 @@ runtime-benchmarks = [ "frame-benchmarking-cli/runtime-benchmarks", "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", + "pallet-revive/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", "polkadot-cli/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", "sc-client-db/runtime-benchmarks", "sc-service/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "pallet-revive/runtime-benchmarks" ] try-runtime = [ "frame-support/try-runtime", "frame-try-runtime/try-runtime", + "pallet-revive/try-runtime", "pallet-transaction-payment/try-runtime", "polkadot-cli/try-runtime", "sp-runtime/try-runtime", - "pallet-revive/try-runtime" ] diff --git a/substrate/frame/revive/dev-node/node/Cargo.toml b/substrate/frame/revive/dev-node/node/Cargo.toml index d618d6065ed6b..cd2d44bb6e06d 100644 --- a/substrate/frame/revive/dev-node/node/Cargo.toml +++ b/substrate/frame/revive/dev-node/node/Cargo.toml @@ -22,7 +22,18 @@ futures = { features = ["thread-pool"], workspace = true } futures-timer = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } -polkadot-sdk = { workspace = true, features = ["experimental", "node"] } +polkadot-sdk = { workspace = true, features = [ + "experimental", + "node", + "pallet-timestamp", + "sp-api", + "sp-block-builder", + "sp-core", + "sp-genesis-builder", + "sp-io", + "sp-runtime", + "sp-timestamp", +] } revive-dev-runtime = { workspace = true } [build-dependencies] diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index b991a5546f548..b0a00a40e3233 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -38,10 +38,10 @@ substrate-wasm-builder = { optional = true, workspace = true, default-features = default = ["std"] std = [ "codec/std", + "frame/std", "frame-executive/std", "frame-support/std", "frame-system/std", - "frame/std", "pallet-balances/std", "pallet-revive/std", "pallet-sudo/std", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 2834dd65f0f56..1d586941ac393 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -59,10 +59,10 @@ prometheus-endpoint = { workspace = true, default-features = true } rlp = { workspace = true } sc-cli = { workspace = true, default-features = true } sc-client-api = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } sc-rpc = { workspace = true, default-features = true } sc-rpc-api = { workspace = true, default-features = true } sc-service = { workspace = true, default-features = true } -sc-network = { workspace = true, default-features = true } sc-transaction-pool-api = { workspace = true, default-features = true } serde = { workspace = true, default-features = true, features = ["alloc", "derive"] } serde_json = { workspace = true } @@ -76,8 +76,8 @@ sp-rpc = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-timestamp = { workspace = true } sp-weights = { workspace = true, default-features = true } -subxt-metadata = { workspace = true, default-features = true } sqlx = { workspace = true, features = ["macros", "runtime-tokio", "sqlite"] } +subxt-metadata = { workspace = true, default-features = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } tokio-stream = { workspace = true } From 9616961cfc87567f3d5c213bc0f6bb51e9322a0d Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 8 Apr 2026 20:17:45 +0000 Subject: [PATCH 26/54] fix cargo workspace dependency --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index feeec001905d3..18227b874ad61 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1033,6 +1033,7 @@ pallet-referenda = { path = "substrate/frame/referenda", default-features = fals pallet-revive = { path = "substrate/frame/revive", default-features = false } pallet-revive-fixtures = { path = "substrate/frame/revive/fixtures", default-features = false } pallet-revive-proc-macro = { path = "substrate/frame/revive/proc-macro", default-features = false } +pallet-revive-eth-rpc = { path = "substrate/frame/revive/rpc", default-features = false } pallet-revive-uapi = { path = "substrate/frame/revive/uapi", default-features = false } pallet-root-offences = { default-features = false, path = "substrate/frame/root-offences" } pallet-root-testing = { path = "substrate/frame/root-testing", default-features = false } From cb884db130ff969fc675645e53b3a1b23fabeb47 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 10 Apr 2026 00:33:04 +0000 Subject: [PATCH 27/54] resolve conflicts --- substrate/frame/revive/rpc/src/block_sync.rs | 3 +- substrate/frame/revive/rpc/src/cli.rs | 36 ++++++++++--- substrate/frame/revive/rpc/src/client.rs | 53 ++++++++++++++++++-- substrate/frame/revive/rpc/src/tests.rs | 6 ++- 4 files changed, 84 insertions(+), 14 deletions(-) diff --git a/substrate/frame/revive/rpc/src/block_sync.rs b/substrate/frame/revive/rpc/src/block_sync.rs index 910a70365de9e..bf71a5289a2d7 100644 --- a/substrate/frame/revive/rpc/src/block_sync.rs +++ b/substrate/frame/revive/rpc/src/block_sync.rs @@ -20,11 +20,12 @@ use crate::{ BlockInfoProvider, block_info_provider::BlockInfo, - client::{Client, ClientError, SubstrateBlockNumber}, + client::{Client, ClientError, GapFillRequest, SubstrateBlockNumber}, substrate_client::SubstrateClientT, }; use pallet_revive::evm::H256; use std::sync::Arc; +use tokio::sync::mpsc; const LOG_TARGET: &str = "eth-rpc::block-sync"; diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index b4447109bf778..4410f1695704e 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -19,7 +19,7 @@ use crate::{ BlockInfoProvider, DebugRpcServer, DebugRpcServerImpl, EthRpcServer, EthRpcServerImpl, LOG_TARGET, PolkadotRpcServer, PolkadotRpcServerImpl, ReceiptExtractor, ReceiptProvider, SubstrateClientT, SystemHealthRpcServer, SystemHealthRpcServerImpl, - client::{Client, SubscriptionType, SubstrateBlockNumber}, + client::{Client, ClientError, SubscriptionGapQueue, SubscriptionType, SubstrateBlockNumber}, }; use clap::{CommandFactory, FromArgMatches, Parser}; use futures::{FutureExt, future::BoxFuture, pin_mut}; @@ -272,12 +272,14 @@ where BP: BlockInfoProvider, { let db_options = SqliteConnectOptions::new().in_memory(true); + let (subscription_gap_queue, _gap_fill_rx) = SubscriptionGapQueue::new(); build_client_from_backend( backend, block_provider, EthPruningMode::KeepLatest(keep_latest_n_blocks), db_options, automine, + subscription_gap_queue, ) .await } @@ -289,6 +291,7 @@ pub async fn build_client_from_backend( eth_pruning: EthPruningMode, db_options: SqliteConnectOptions, automine: bool, + subscription_gap_queue: SubscriptionGapQueue, ) -> anyhow::Result> where C: SubstrateClientT, @@ -319,7 +322,13 @@ where ReceiptProvider::new(pool, block_provider.clone(), receipt_extractor, keep_latest_n_blocks) .await?; - let client = Client::from_backend(backend, block_provider, receipt_provider, automine)?; + let client = Client::from_backend( + backend, + block_provider, + receipt_provider, + automine, + subscription_gap_queue, + )?; Ok(client) } @@ -390,10 +399,17 @@ where let tokio_handle = tokio_runtime.handle(); let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; + let (subscription_gap_queue, gap_fill_rx) = SubscriptionGapQueue::new(); let client = { - let fut = - build_client_from_backend(backend, block_provider, eth_pruning, db_options, false) - .fuse(); + let fut = build_client_from_backend( + backend, + block_provider, + eth_pruning, + db_options, + false, + subscription_gap_queue, + ) + .fuse(); pin_mut!(fut); let abort_signal = tokio_runtime.block_on(async { Signals::capture() })?; @@ -520,6 +536,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let tokio_handle = tokio_runtime.handle(); let mut task_manager = TaskManager::new(tokio_handle.clone(), prometheus_registry)?; + let (subscription_gap_queue, gap_fill_rx) = SubscriptionGapQueue::new(); let (client, _block_provider) = { let fut = async { let (api, rpc_client, rpc) = @@ -537,6 +554,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { eth_pruning, db_options, false, + subscription_gap_queue, ) .await?; @@ -590,11 +608,17 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { task_manager .spawn_essential_handle() .spawn("block-subscription", None, async move { - let futures: Vec>> = vec![ + let mut futures: Vec>> = vec![ Box::pin(client.subscribe_and_cache_new_blocks(SubscriptionType::BestBlocks)), Box::pin(client.subscribe_and_cache_new_blocks(SubscriptionType::FinalizedBlocks)), ]; + // Backfill gaps caused by subscription reconnects. + futures.push(Box::pin(async { + client.run_subscription_gap_filler(gap_fill_rx).await; + Ok::<_, ClientError>(()) + })); + if let Err(err) = futures::future::try_join_all(futures).await { panic!("Block subscription task failed: {err:?}") } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 02964c3b66487..7ad6882c2948b 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -24,6 +24,7 @@ use crate::{ BlockInfoProvider, BlockNumberOrTag, BlockTag, FeeHistoryProvider, ReceiptProvider, TracerType, TransactionInfo, block_info_provider::BlockInfo, + block_sync::{SyncCheckpoint, SyncLabel}, substrate_client::{NodeHealth, SubmitResult, SubstrateClientT}, }; use jsonrpsee::types::{ErrorObjectOwned, error::CALL_EXECUTION_FAILED_CODE}; @@ -41,7 +42,7 @@ use std::{ ops::Range, sync::{ Arc, - atomic::{AtomicBool, AtomicUsize, Ordering}, + atomic::{AtomicBool, AtomicU32, AtomicUsize, Ordering}, }, }; use thiserror::Error; @@ -240,14 +241,14 @@ pub struct Client { } /// A request to backfill a range of missed blocks (both bounds inclusive). -pub(crate) struct GapFillRequest { +pub struct GapFillRequest { pub from_inclusive: SubstrateBlockNumber, pub to_inclusive: SubstrateBlockNumber, } /// Queues gap-fill requests for blocks missed during subscription reconnects. #[derive(Clone)] -pub(crate) struct SubscriptionGapQueue { +pub struct SubscriptionGapQueue { /// Sender half of the gap-fill queue. tx: mpsc::Sender, /// Queued + in-flight gap fills. Channel length alone is insufficient @@ -256,7 +257,7 @@ pub(crate) struct SubscriptionGapQueue { } impl SubscriptionGapQueue { - pub(crate) fn new() -> (Self, mpsc::Receiver) { + pub fn new() -> (Self, mpsc::Receiver) { // Each reconnect produces one gap-fill request for the entire missed range, // so 32 allows for 32 rapid disconnects before the consumer processes any. let (tx, rx) = mpsc::channel(32); @@ -311,6 +312,7 @@ impl Client { block_provider: BP, receipt_provider: ReceiptProvider, automine: bool, + subscription_gap_queue: SubscriptionGapQueue, ) -> Result { let block_notifier = automine.then(|| tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY).0); @@ -330,6 +332,7 @@ impl Client { backfill_complete: Arc::new(AtomicBool::new(false)), block_subscription_tx, log_subscription_tx, + subscription_gap_queue, }) } @@ -338,8 +341,9 @@ impl Client { block_provider: BP, receipt_provider: ReceiptProvider, automine: bool, + subscription_gap_queue: SubscriptionGapQueue, ) -> Result { - Self::from_backend(backend, block_provider, receipt_provider, automine) + Self::from_backend(backend, block_provider, receipt_provider, automine, subscription_gap_queue) } /// Mark historic backfill as complete. @@ -357,6 +361,10 @@ impl Client { &self.block_provider } + pub(crate) fn subscription_gap_queue(&self) -> &SubscriptionGapQueue { + &self.subscription_gap_queue + } + /// Creates a block notifier instance. pub fn create_block_notifier(&mut self) { self.block_notifier = Some(tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY).0); @@ -433,12 +441,18 @@ impl Client { let callback = Arc::new(callback); let lock = self.subscription_lock.clone(); let block_provider = self.block_provider.clone(); + let subscription_gap_queue = self.subscription_gap_queue.clone(); + // Tracks the last finalized block number seen, used for gap detection. + // u32::MAX is used as a sentinel meaning "not yet seen". + let last_finalized_seen = Arc::new(AtomicU32::new(u32::MAX)); self.backend .subscribe_blocks(subscription_type, move |info: C::BlockInfo| { let callback = Arc::clone(&callback); let lock_ref = Arc::clone(&lock); let block_provider = block_provider.clone(); + let subscription_gap_queue = subscription_gap_queue.clone(); + let last_finalized_seen = last_finalized_seen.clone(); async move { let block = block_provider @@ -447,6 +461,17 @@ impl Client { .ok_or(ClientError::BlockNotFound)?; let _guard = lock_ref.lock().await; + + // Detect and queue gap-fills for missed finalized blocks. + if subscription_type == SubscriptionType::FinalizedBlocks { + let block_number = block.number(); + let last = last_finalized_seen.load(Ordering::Acquire); + if last != u32::MAX { + subscription_gap_queue.detect_and_queue(block_number, last); + } + last_finalized_seen.store(block_number, Ordering::Release); + } + callback(block).await } }) @@ -471,6 +496,8 @@ impl Client { // ↓ NEW: capture the broadcast senders let block_subscription_tx = self.block_subscription_tx.clone(); let log_subscription_tx = self.log_subscription_tx.clone(); + let backfill_complete = self.backfill_complete.clone(); + let subscription_gap_queue = self.subscription_gap_queue.clone(); self.subscribe_new_blocks(subscription_type, move |block: Arc| { let backend = backend.clone(); @@ -480,6 +507,8 @@ impl Client { let block_notifier = block_notifier.clone(); let block_subscription_tx = block_subscription_tx.clone(); let log_subscription_tx = log_subscription_tx.clone(); + let backfill_complete = backfill_complete.clone(); + let subscription_gap_queue = subscription_gap_queue.clone(); async move { let hash = block.hash(); @@ -511,7 +540,21 @@ impl Client { } } + let should_advance_head = subscription_type == SubscriptionType::FinalizedBlocks && + backfill_complete.load(Ordering::Acquire) && + !subscription_gap_queue.has_pending(); + match (subscription_type, &block_notifier) { + _ if should_advance_head => { + // Advance the persistent sync head so a restart resumes from here. + if let Err(err) = receipt_provider + .advance_sync_label(SyncLabel::Head, SyncCheckpoint::new(number, hash)) + .await + { + log::warn!(target: LOG_TARGET, + "Failed to update sync_label[{}]: {err:?}", SyncLabel::Head); + } + }, (SubscriptionType::BestBlocks, Some(sender)) if sender.receiver_count() > 0 => { let _ = sender.send(hash); }, diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index de53b7985dd86..d676062d52ae2 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -23,7 +23,7 @@ use crate::{ SubxtBlockInfoProvider, block_sync::{ChainMetadata, SyncLabel}, cli::{self, CliCommand}, - client::Client, + client::{Client, SubscriptionGapQueue}, example::TransactionBuilder, subxt_client::{ self, SrcChainConfig, SubxtClient, connect, @@ -1807,7 +1807,9 @@ async fn create_sync_test_client() -> anyhow::Result Date: Fri, 10 Apr 2026 00:43:55 +0000 Subject: [PATCH 28/54] fmt --- substrate/frame/revive/rpc/src/client.rs | 8 +++++++- substrate/frame/revive/rpc/src/tests.rs | 9 +++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 7ad6882c2948b..a1a57ee6a225c 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -343,7 +343,13 @@ impl Client { automine: bool, subscription_gap_queue: SubscriptionGapQueue, ) -> Result { - Self::from_backend(backend, block_provider, receipt_provider, automine, subscription_gap_queue) + Self::from_backend( + backend, + block_provider, + receipt_provider, + automine, + subscription_gap_queue, + ) } /// Mark historic backfill as complete. diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 33e0effb591a5..48afdd10793ac 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -1906,8 +1906,13 @@ async fn create_sync_test_client() -> anyhow::Result Date: Fri, 10 Apr 2026 14:57:52 +0000 Subject: [PATCH 29/54] nit --- Cargo.toml | 2 +- prdoc/pr_11297.prdoc | 6 +-- .../frame/revive/dev-node/node/Cargo.toml | 1 + substrate/frame/revive/rpc/src/tests.rs | 38 ++++++++++++++++++- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d8035a017eca0..aa6ee2949bc94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1034,9 +1034,9 @@ pallet-ranked-collective = { path = "substrate/frame/ranked-collective", default pallet-recovery = { path = "substrate/frame/recovery", default-features = false } pallet-referenda = { path = "substrate/frame/referenda", default-features = false } pallet-revive = { path = "substrate/frame/revive", default-features = false } +pallet-revive-eth-rpc = { path = "substrate/frame/revive/rpc", default-features = false } pallet-revive-fixtures = { path = "substrate/frame/revive/fixtures", default-features = false } pallet-revive-proc-macro = { path = "substrate/frame/revive/proc-macro", default-features = false } -pallet-revive-eth-rpc = { path = "substrate/frame/revive/rpc", default-features = false } pallet-revive-uapi = { path = "substrate/frame/revive/uapi", default-features = false } pallet-root-offences = { default-features = false, path = "substrate/frame/root-offences" } pallet-root-testing = { path = "substrate/frame/root-testing", default-features = false } diff --git a/prdoc/pr_11297.prdoc b/prdoc/pr_11297.prdoc index 9c4d037e718b9..33e168411893e 100644 --- a/prdoc/pr_11297.prdoc +++ b/prdoc/pr_11297.prdoc @@ -8,10 +8,10 @@ doc: Removes the `revive-dev-node` standalone binary and integrates the `pallet-revive` ETH RPC server directly into the Asset Hub (Omni) node. Introduces a `SubstrateClientT` trait that abstracts all Substrate node interactions, allowing `EthRpcServerImpl` to be generic over its backend. A new `NativeSubstrateClient` implementation backed by `sc_client_api` traits provides a direct in-process path with no `subxt` dependency on the hot path. The `SubxtClient` path is preserved unchanged for the standalone `eth-rpc` binary. On the node side, `BuildAssetHubRpcExtensions` is added to `polkadot-omni-node-lib` behind an opt-in `revive-rpc` feature, registering the full `eth_*`/`debug_*`/`net_*`/`web3_*` surface on the standard RPC port alongside existing parachain RPCs. crates: - name: polkadot-omni-node-lib - bump: patch + bump: minor - name: revive-dev-runtime - bump: patch + bump: minor - name: pallet-revive-eth-rpc - bump: patch + bump: major - name: pallet-revive bump: patch diff --git a/substrate/frame/revive/dev-node/node/Cargo.toml b/substrate/frame/revive/dev-node/node/Cargo.toml index cd2d44bb6e06d..7c23484074486 100644 --- a/substrate/frame/revive/dev-node/node/Cargo.toml +++ b/substrate/frame/revive/dev-node/node/Cargo.toml @@ -33,6 +33,7 @@ polkadot-sdk = { workspace = true, features = [ "sp-io", "sp-runtime", "sp-timestamp", + "sp-virtualization", ] } revive-dev-runtime = { workspace = true } diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 48afdd10793ac..58d7944650d88 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -23,7 +23,7 @@ use crate::{ SubxtBlockInfoProvider, block_sync::{ChainMetadata, SyncLabel}, cli::{self, CliCommand}, - client::{Client, SubscriptionGapQueue}, + client::{Client, GapFillRequest, SubscriptionGapQueue}, example::TransactionBuilder, subxt_client::{ self, SrcChainConfig, SubxtClient, connect, @@ -1916,6 +1916,42 @@ async fn create_sync_test_client() -> anyhow::Result anyhow::Result<(Client, mpsc::Receiver)> +{ + use sc_cli::{RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB}; + + let node_url = SharedResources::node_rpc_url(); + let max_request_size = RPC_DEFAULT_MAX_REQUEST_SIZE_MB * 1024 * 1024; + let max_response_size = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB * 1024 * 1024; + let (api, rpc_client, rpc) = connect(node_url, max_request_size, max_response_size).await?; + let block_provider = SubxtBlockInfoProvider::new(api.clone(), rpc.clone()).await?; + let backend = SubxtClient::new(api, rpc_client, rpc).await?; + + let pool = SqlitePoolOptions::new() + .max_connections(1) + .idle_timeout(None) + .max_lifetime(None) + .connect_with(SqliteConnectOptions::new().in_memory(true)) + .await?; + + let receipt_extractor = ReceiptExtractor::new_from_substrate_client(backend.clone(), None); + let receipt_provider = + ReceiptProvider::new(pool, block_provider.clone(), receipt_extractor, None).await?; + + let (subscription_gap_queue, gap_fill_rx) = SubscriptionGapQueue::new(); + let client = Client::from_backend( + backend, + block_provider, + receipt_provider, + true, + subscription_gap_queue, + )?; + Ok((client, gap_fill_rx)) +} + /// Fresh sync: labels, hash mappings, and re-sync idempotency. async fn test_block_sync_fresh() -> anyhow::Result<()> { use crate::block_sync::SyncCheckpoint; From eae7ecd0930c2a1bb5a28750c2aa2058595df112 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 10 Apr 2026 15:08:16 +0000 Subject: [PATCH 30/54] fix tests --- substrate/frame/revive/rpc/src/tests.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index 58d7944650d88..73bf1c64bebca 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -1918,9 +1918,8 @@ async fn create_sync_test_client() -> anyhow::Result anyhow::Result<(Client, mpsc::Receiver)> -{ +async fn create_sync_test_client_with_subscription_gap_queue() +-> anyhow::Result<(Client, mpsc::Receiver)> { use sc_cli::{RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB}; let node_url = SharedResources::node_rpc_url(); From e06a869f5befe5caca442d7b4fd5499f075eaeda Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 10 Apr 2026 15:54:37 +0000 Subject: [PATCH 31/54] smol nits --- substrate/frame/revive/dev-node/runtime/Cargo.toml | 4 ++-- substrate/frame/revive/rpc/Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index b0a00a40e3233..3432be17aaf8c 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -38,16 +38,16 @@ substrate-wasm-builder = { optional = true, workspace = true, default-features = default = ["std"] std = [ "codec/std", - "frame/std", "frame-executive/std", "frame-support/std", "frame-system/std", + "frame/std", "pallet-balances/std", "pallet-revive/std", "pallet-sudo/std", "pallet-timestamp/std", - "pallet-transaction-payment/std", "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", "parachains-common/std", "polkadot-primitives/std", "scale-info/std", diff --git a/substrate/frame/revive/rpc/Cargo.toml b/substrate/frame/revive/rpc/Cargo.toml index 1d586941ac393..0f4361e65d5d9 100644 --- a/substrate/frame/revive/rpc/Cargo.toml +++ b/substrate/frame/revive/rpc/Cargo.toml @@ -38,10 +38,10 @@ required-features = ["subxt"] [features] default = ["subxt"] subxt = [ - "dep:subxt", - "dep:subxt-signer", "dep:revive-dev-runtime", "dep:sp-io", + "dep:subxt", + "dep:subxt-signer", ] [dependencies] From 885ef911bf8abbfb77fc4418bbeb4f5d80a9361e Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 10 Apr 2026 19:53:21 +0000 Subject: [PATCH 32/54] fix eth-rpc node spawn and test failures --- .../frame/revive/dev-node/runtime/Cargo.toml | 22 +++++++++++++++++++ .../frame/revive/rpc/src/subxt_client.rs | 4 ++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 3432be17aaf8c..8af49fdccb863 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -59,3 +59,25 @@ std = [ "sp-weights/std", "substrate-wasm-builder", ] +try-runtime = [ + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame/try-runtime", + "pallet-balances/try-runtime", + "pallet-revive/try-runtime", + "pallet-sudo/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "sp-runtime/try-runtime", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "frame/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-revive/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-transaction-payment/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index d674072d39789..491bfa491e5b5 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -281,12 +281,12 @@ impl SubxtClient { ); let chain_id = { - let query = constants().revive().chain_id(); + let query = constants().revive().chain_id().unvalidated(); api.constants().at(&query)? }; let max_block_weight = { - let query = constants().system().block_weights(); + let query = constants().system().block_weights().unvalidated(); let weights = api.constants().at(&query)?; let max_block = weights.per_class.normal.max_extrinsic.unwrap_or(weights.max_block); max_block.0 From 5a3ceb5b394209e13b9b3e254a23ac9637d1564f Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 10 Apr 2026 20:01:43 +0000 Subject: [PATCH 33/54] fix umbrella crate ci check failure --- substrate/frame/revive/dev-node/runtime/Cargo.toml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index 8af49fdccb863..d9a158cbeea76 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -70,6 +70,7 @@ try-runtime = [ "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", "sp-runtime/try-runtime", + "parachains-common/try-runtime", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", @@ -80,4 +81,7 @@ runtime-benchmarks = [ "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", ] From 9c1856d7e73ed1aee1d2d30fd894365ec9f8c30e Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Sat, 11 Apr 2026 21:31:20 +0000 Subject: [PATCH 34/54] fix eth-rpc rpc error --- .../frame/revive/dev-node/runtime/Cargo.toml | 6 +- substrate/frame/revive/rpc/src/cli.rs | 8 ++- .../frame/revive/rpc/src/subxt_client.rs | 57 ------------------- 3 files changed, 9 insertions(+), 62 deletions(-) diff --git a/substrate/frame/revive/dev-node/runtime/Cargo.toml b/substrate/frame/revive/dev-node/runtime/Cargo.toml index d9a158cbeea76..752a9adbcd035 100644 --- a/substrate/frame/revive/dev-node/runtime/Cargo.toml +++ b/substrate/frame/revive/dev-node/runtime/Cargo.toml @@ -69,8 +69,8 @@ try-runtime = [ "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", - "sp-runtime/try-runtime", "parachains-common/try-runtime", + "sp-runtime/try-runtime", ] runtime-benchmarks = [ "frame-support/runtime-benchmarks", @@ -78,10 +78,10 @@ runtime-benchmarks = [ "frame/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-revive/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-transaction-payment/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "pallet-sudo/runtime-benchmarks", "parachains-common/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 4410f1695704e..c849b2c6c5ecf 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -540,8 +540,12 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let (client, _block_provider) = { let fut = async { let (api, rpc_client, rpc) = - connect(&node_rpc_url, rpc_config.max_request_size, rpc_config.max_response_size) - .await?; + connect( + &node_rpc_url, + rpc_config.max_request_size * 1024 * 1024, + rpc_config.max_response_size * 1024 * 1024, + ) + .await?; let subxt_client = SubxtClient::new(api.clone(), rpc_client, rpc.clone()).await?; // Explicit type annotation to resolve the inference ambiguity diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 491bfa491e5b5..2265395175500 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -260,26 +260,12 @@ pub struct SubxtClient { pub(crate) max_block_weight: Weight, } -/// Maximum number of retries when fetching the initial block during startup. -const MAX_STARTUP_RETRIES: u32 = 10; - -/// Delay between startup retries. -const STARTUP_RETRY_DELAY_MS: u64 = 500; - impl SubxtClient { pub async fn new( api: OnlineClient, rpc_client: RpcClient, rpc: LegacyRpcMethods, ) -> Result { - let latest = Self::fetch_latest_block_with_retry(&api).await?; - log::info!( - target: crate::LOG_TARGET, - "🔗 Connected to node at block #{} ({:?})", - latest.number(), - latest.hash() - ); - let chain_id = { let query = constants().revive().chain_id().unvalidated(); api.constants().at(&query)? @@ -295,49 +281,6 @@ impl SubxtClient { Ok(Self { api, rpc_client, rpc, chain_id, max_block_weight }) } - /// Fetch the latest block, retrying up to [`MAX_STARTUP_RETRIES`] times. - async fn fetch_latest_block_with_retry( - api: &OnlineClient, - ) -> Result { - let retry_delay = Duration::from_millis(STARTUP_RETRY_DELAY_MS); - let mut last_error: Option = None; - - for attempt in 0..MAX_STARTUP_RETRIES { - match api.blocks().at_latest().await { - Ok(block) => { - if attempt > 0 { - log::info!( - target: crate::LOG_TARGET, - "✅ Fetched latest block after {} attempt(s)", - attempt + 1 - ); - } - return Ok(block); - }, - Err(err) => { - log::warn!( - target: crate::LOG_TARGET, - "⏳ Node not ready yet (attempt {}/{}): {err}. \ - Retrying in {}ms...", - attempt + 1, - MAX_STARTUP_RETRIES, - retry_delay.as_millis(), - ); - last_error = Some(err); - tokio::time::sleep(retry_delay).await; - }, - } - } - - let err = last_error.expect("loop ran at least once; qed"); - log::error!( - target: crate::LOG_TARGET, - "❌ Failed to fetch the latest block after {MAX_STARTUP_RETRIES} attempts. \ - The node may be misconfigured or still starting. Last error: {err}" - ); - Err(ClientError::from(err)) - } - /// Build a [`SubxtBlockInfo`] from a [`SubstrateBlock`] reference. fn block_info_from_subxt(block: &SubstrateBlock) -> SubxtBlockInfo { SubxtBlockInfo { From f61ad8ae37a58ff0505253ac413bd7fa33521ad0 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 17 Apr 2026 20:22:15 +0000 Subject: [PATCH 35/54] improve background-task panic messages --- substrate/frame/revive/rpc/src/block_sync.rs | 2 +- substrate/frame/revive/rpc/src/cli.rs | 76 +++++++++++++++++++- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/rpc/src/block_sync.rs b/substrate/frame/revive/rpc/src/block_sync.rs index bf71a5289a2d7..92ba0bca8359c 100644 --- a/substrate/frame/revive/rpc/src/block_sync.rs +++ b/substrate/frame/revive/rpc/src/block_sync.rs @@ -95,7 +95,7 @@ struct BackwardSyncRange { impl Client { /// Verify that the stored genesis hash matches the connected chain. - async fn validate_chain_identity(&self) -> Result { + pub(crate) async fn validate_chain_identity(&self) -> Result { // Fetch block 0 (genesis) to use as the chain identity fingerprint. let genesis_hash: H256 = self .block_provider() diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index c849b2c6c5ecf..2a5db15473b9f 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -208,6 +208,37 @@ fn resolve_db_options( } } +/// Run a pre-flight chain identity check **before** spawning any background tasks. +fn handle_chain_preflight( + try_result: Result, ()>, + base_path: Option, +) -> anyhow::Result<()> { + use crate::client::ClientError; + match try_result { + Err(()) => anyhow::bail!("Process interrupted during chain identity validation"), + Ok(Err(e)) if e.is_chain_validation_error() => { + let db_path = resolve_db_dir(base_path).join(DEFAULT_DATABASE_NAME); + anyhow::bail!( + "Chain identity mismatch: the receipt database at\n\ + \t'{}'\n\ + was created for a different chain (genesis hash mismatch).\n\ + \n\ + Recovery options:\n\ + \t(a) Delete the stale database and restart:\n\ + \t rm -f '{}'\n\ + \t(b) Use an in-memory database (no history, no mismatch):\n\ + \t --eth-pruning=256\n\ + \t(c) Persist to an explicit path you can wipe deliberately:\n\ + \t --base-path /tmp/eth-rpc-dev", + db_path.display(), + db_path.display(), + ) + }, + Ok(Err(e)) => Err(e.into()), + Ok(Ok(_)) => Ok(()), + } +} + fn dev_accounts() -> Vec { #[cfg(feature = "subxt")] { @@ -420,6 +451,19 @@ where } }; + // Pre-flight: validate chain identity before spawning any background tasks. + // This converts a panic deep inside `sync_backward` into a clean, actionable error. + if eth_pruning.is_archive() { + let preflight = { + let c = client.clone(); + let fut = async move { c.validate_chain_identity().await }.fuse(); + pin_mut!(fut); + let abort = tokio_runtime.block_on(async { Signals::capture() })?; + tokio_handle.block_on(abort.try_until_signal(fut)) + }; + handle_chain_preflight(preflight, shared_params.base_path()?)?; + } + if let Some(PrometheusConfig { port, registry }) = prometheus_config.clone() { task_manager.spawn_handle().spawn( "prometheus-endpoint", @@ -460,7 +504,11 @@ where })); if let Err(err) = futures::future::try_join_all(futures).await { - panic!("Block subscription task failed: {err:?}") + panic!( + "Block subscription task failed: {err:?}\n\ + Hint: if this is a chain mismatch, delete the receipt DB or \ + use --eth-pruning=256 for an in-memory database." + ) } }); @@ -575,6 +623,19 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { } }; + // Pre-flight: validate chain identity before spawning any background tasks. + // Catches ChainMismatch from a stale DB and returns a clean error with recovery steps. + if eth_pruning.is_archive() { + let preflight = { + let c = client.clone(); + let fut = async move { c.validate_chain_identity().await }.fuse(); + futures::pin_mut!(fut); + let abort = tokio_runtime.block_on(async { sc_cli::Signals::capture() })?; + tokio_handle.block_on(abort.try_until_signal(fut)) + }; + handle_chain_preflight(preflight, shared_params.base_path()?)?; + } + // Prometheus metrics. if let Some(sc_service::config::PrometheusConfig { port, registry }) = prometheus_config.clone() { @@ -598,13 +659,18 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { )?; // Archive mode: spawn the historical backward sync task. + // Note: chain identity was already validated above; this task handles ongoing sync only. if eth_pruning.is_archive() { let sync_client = client.clone(); task_manager .spawn_essential_handle() .spawn("block-backward-sync", None, async move { if let Err(err) = sync_client.sync_backward().await { - panic!("Chain validation failed during backward sync: {err:?}"); + panic!( + "sync_backward failed: {err:?}\n\ + Hint: if this is a chain mismatch, delete the receipt DB or \ + use --eth-pruning=256 for an in-memory database." + ); } }); } @@ -624,7 +690,11 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { })); if let Err(err) = futures::future::try_join_all(futures).await { - panic!("Block subscription task failed: {err:?}") + panic!( + "Block subscription task failed: {err:?}\n\ + Hint: if this is a chain mismatch, delete the receipt DB or \ + use --eth-pruning=256 for an in-memory database." + ) } }); From 311f3ebacded1fe233710cebc6e0614b29b5dd2e Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 17 Apr 2026 20:46:27 +0000 Subject: [PATCH 36/54] nit --- .../lib/src/fake_runtime_api/utils.rs | 8 +++ .../polkadot-omni-node/lib/src/nodes/aura.rs | 6 +- substrate/frame/revive/rpc/src/cli.rs | 14 ++-- substrate/frame/revive/rpc/src/client.rs | 65 +------------------ .../frame/revive/rpc/src/native_client.rs | 21 ++++-- .../frame/revive/rpc/src/substrate_client.rs | 1 + .../frame/revive/rpc/src/subxt_client.rs | 3 +- 7 files changed, 36 insertions(+), 82 deletions(-) diff --git a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs index 235579c24c7b9..635006f830e3d 100644 --- a/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs +++ b/cumulus/polkadot-omni-node/lib/src/fake_runtime_api/utils.rs @@ -298,6 +298,14 @@ macro_rules! impl_node_runtime_apis { _: pallet_revive::evm::GenericTransaction, _: pallet_revive::evm::TracerType, ) -> Result { unimplemented!() } + fn trace_call_with_config( + _: pallet_revive::evm::GenericTransaction, + _: pallet_revive::evm::TracerType, + _: pallet_revive::evm::TracingConfig, + ) -> Result { unimplemented!() } + fn eth_pre_dispatch_weight( + _: Vec, + ) -> Result { unimplemented!() } fn block_author() -> sp_core::H160 { unimplemented!() } fn address(_: sp_core::H160) -> sp_core::H160 { unimplemented!() } fn account_id(_: sp_core::H160) -> sp_core::H160 { unimplemented!() } diff --git a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs index 770bffbfc1587..c9b7623146ad1 100644 --- a/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs +++ b/cumulus/polkadot-omni-node/lib/src/nodes/aura.rs @@ -569,13 +569,9 @@ where RuntimeApi::RuntimeApi: AuraRuntimeApi + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi + substrate_frame_rpc_system::AccountNonceApi -<<<<<<< pallet_revive_refactor + + TargetBlockRate + GetParachainInfo + ReviveRuntimeApiT, -======= - + TargetBlockRate - + GetParachainInfo, ->>>>>>> master AuraId: AuraIdT + Sync + Send, ::Pair: Send + Sync, { diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 2a5db15473b9f..70c64f54503e7 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -213,7 +213,6 @@ fn handle_chain_preflight( try_result: Result, ()>, base_path: Option, ) -> anyhow::Result<()> { - use crate::client::ClientError; match try_result { Err(()) => anyhow::bail!("Process interrupted during chain identity validation"), Ok(Err(e)) if e.is_chain_validation_error() => { @@ -587,13 +586,12 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let (subscription_gap_queue, gap_fill_rx) = SubscriptionGapQueue::new(); let (client, _block_provider) = { let fut = async { - let (api, rpc_client, rpc) = - connect( - &node_rpc_url, - rpc_config.max_request_size * 1024 * 1024, - rpc_config.max_response_size * 1024 * 1024, - ) - .await?; + let (api, rpc_client, rpc) = connect( + &node_rpc_url, + rpc_config.max_request_size * 1024 * 1024, + rpc_config.max_response_size * 1024 * 1024, + ) + .await?; let subxt_client = SubxtClient::new(api.clone(), rpc_client, rpc.clone()).await?; // Explicit type annotation to resolve the inference ambiguity diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index a757a7fdc8237..997996a1aa2aa 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -21,8 +21,7 @@ pub(crate) mod runtime_api; use crate::{ - BlockInfoProvider, BlockNumberOrTag, BlockTag, FeeHistoryProvider, ReceiptProvider, TracerType, - TransactionInfo, + BlockInfoProvider, BlockTag, FeeHistoryProvider, ReceiptProvider, TracerType, TransactionInfo, block_info_provider::BlockInfo, block_sync::{SyncCheckpoint, SyncLabel}, substrate_client::{NodeHealth, SubmitResult, SubstrateClientT}, @@ -37,7 +36,6 @@ use pallet_revive::{ decode_revert_reason, }, }; -use sp_core::U256; use sp_weights::Weight; use std::{ ops::Range, @@ -989,66 +987,7 @@ impl Client { state_overrides: Option, ) -> Result { let block_hash = self.block_hash_for_tag(block).await?; -<<<<<<< pallet_revive_refactor - self.backend.trace_call(block_hash, transaction, config).await -======= - let runtime_api = self.runtime_api(block_hash); - runtime_api.trace_call(transaction, config, state_overrides).await - } - - /// Get the EVM block for the given Substrate block. - pub async fn evm_block( - &self, - block: Arc, - hydrated_transactions: bool, - ) -> Option { - log::trace!(target: LOG_TARGET, "Get Ethereum block for hash {:?}", block.hash()); - - if self - .receipt_provider - .is_before_earliest_block(&BlockNumberOrTag::U256(U256::from(block.number()))) - { - log::trace!(target: LOG_TARGET, - "Block #{} is before receipt floor, skipping", block.number()); - return None; - } - - // This could potentially fail under below circumstances: - // - state has been pruned - // - the block author cannot be obtained from the digest logs (highly unlikely) - // - the node we are targeting has an outdated revive pallet (or ETH block functionality is - // disabled) - match self.runtime_api(block.hash()).eth_block().await { - Ok(mut eth_block) => { - log::trace!(target: LOG_TARGET, "Ethereum block from runtime API hash {:?}", eth_block.hash); - - if hydrated_transactions { - // Hydrate the block. - let tx_infos = self - .receipt_provider - .receipts_from_block(&block) - .await - .inspect_err(|err| { - log::trace!(target: LOG_TARGET, - "Failed to extract receipts for block #{}: {err:?}", - block.number()); - }) - .unwrap_or_default() - .into_iter() - .map(|(signed_tx, receipt)| TransactionInfo::new(&receipt, signed_tx)) - .collect::>(); - - eth_block.transactions = HashesOrTransactionInfos::TransactionInfos(tx_infos); - } - - Some(eth_block) - }, - Err(err) => { - log::error!(target: LOG_TARGET, "Failed to get Ethereum block for hash {:?}: {err:?}", block.hash()); - None - }, - } ->>>>>>> master + self.backend.trace_call(block_hash, transaction, config, state_overrides).await } /// Get the post-dispatch weight associated with this Ethereum transaction hash. diff --git a/substrate/frame/revive/rpc/src/native_client.rs b/substrate/frame/revive/rpc/src/native_client.rs index 73bc0c44226ba..95fb66da1723c 100644 --- a/substrate/frame/revive/rpc/src/native_client.rs +++ b/substrate/frame/revive/rpc/src/native_client.rs @@ -422,12 +422,23 @@ where block_hash: SubstrateBlockHash, transaction: GenericTransaction, config: TracerType, + state_overrides: Option, ) -> Result { - self.client - .runtime_api() - .trace_call(block_hash, transaction, config) - .map_err(native_err)? - .map_err(ClientError::TransactError) + if let Some(overrides) = state_overrides { + use pallet_revive::evm::TracingConfig; + let tracing_config = TracingConfig::new().with_state_overrides(overrides); + self.client + .runtime_api() + .trace_call_with_config(block_hash, transaction, config, tracing_config) + .map_err(native_err)? + .map_err(ClientError::TransactError) + } else { + self.client + .runtime_api() + .trace_call(block_hash, transaction, config) + .map_err(native_err)? + .map_err(ClientError::TransactError) + } } async fn submit_extrinsic(&self, payload: Vec) -> Result { diff --git a/substrate/frame/revive/rpc/src/substrate_client.rs b/substrate/frame/revive/rpc/src/substrate_client.rs index 7a245e128a193..9989a8db105ab 100644 --- a/substrate/frame/revive/rpc/src/substrate_client.rs +++ b/substrate/frame/revive/rpc/src/substrate_client.rs @@ -179,6 +179,7 @@ pub trait SubstrateClientT: Send + Sync + Clone + 'static { block_hash: SubstrateBlockHash, transaction: GenericTransaction, config: TracerType, + state_overrides: Option, ) -> Result; /// Submit an unsigned `eth_transact` extrinsic and return the first diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 480b8cb5273d7..3bb629c9c1d63 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -460,10 +460,11 @@ impl SubstrateClientT for SubxtClient { block_hash: SubstrateBlockHash, transaction: GenericTransaction, config: TracerType, + state_overrides: Option, ) -> Result { use crate::client::runtime_api::RuntimeApi; RuntimeApi::new(self.api.runtime_api().at(block_hash)) - .trace_call(transaction, config) + .trace_call(transaction, config, state_overrides) .await } From ffd5be478427684f20b054770b8805424d2b0126 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 21 Apr 2026 16:33:41 +0000 Subject: [PATCH 37/54] fix cli::run() --- substrate/frame/revive/rpc/src/cli.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 70c64f54503e7..44c2885338951 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -661,10 +661,11 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { if eth_pruning.is_archive() { let sync_client = client.clone(); task_manager - .spawn_essential_handle() - .spawn("block-backward-sync", None, async move { + .spawn_handle() + .spawn("block-backward-sync", Some("eth-rpc"), async move { if let Err(err) = sync_client.sync_backward().await { - panic!( + log::error!( + target: crate::LOG_TARGET, "sync_backward failed: {err:?}\n\ Hint: if this is a chain mismatch, delete the receipt DB or \ use --eth-pruning=256 for an in-memory database." From cf3e00966354f6ef5b5aa4a31a3c342f5010fb25 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 21 Apr 2026 19:04:34 +0000 Subject: [PATCH 38/54] resolve conflicts --- substrate/frame/revive/rpc/src/cli.rs | 18 +- .../frame/revive/rpc/src/receipt_provider.rs | 161 ++++++------------ 2 files changed, 65 insertions(+), 114 deletions(-) diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 61e8fc6e5be68..0bb8bb9809f45 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -16,9 +16,9 @@ // limitations under the License. //! The Ethereum JSON-RPC server. use crate::{ - DbContext, BlockInfoProvider, DebugRpcServer, DebugRpcServerImpl, EthRpcServer, EthRpcServerImpl, - LOG_TARGET, PolkadotRpcServer, PolkadotRpcServerImpl, ReceiptExtractor, ReceiptProvider, - SubstrateClientT, SystemHealthRpcServer, SystemHealthRpcServerImpl, + BlockInfoProvider, DbContext, DebugRpcServer, DebugRpcServerImpl, EthRpcServer, + EthRpcServerImpl, LOG_TARGET, PolkadotRpcServer, PolkadotRpcServerImpl, ReceiptExtractor, + ReceiptProvider, SubstrateClientT, SystemHealthRpcServer, SystemHealthRpcServerImpl, client::{Client, ClientError, SubscriptionGapQueue, SubscriptionType, SubstrateBlockNumber}, }; use clap::{CommandFactory, FromArgMatches, Parser}; @@ -385,9 +385,15 @@ where EthPruningMode::Archive => (SqlitePoolOptions::new().connect_with(db_options).await?, None), }; - let receipt_provider = - ReceiptProvider::new(pool, block_provider.clone(), receipt_extractor, keep_latest_n_blocks) - .await?; + let max_variable_number = sqlite_db_query_max_variable_number(&pool).await; + let db_ctx = DbContext::new(pool, max_variable_number); + let receipt_provider = ReceiptProvider::new( + db_ctx, + block_provider.clone(), + receipt_extractor, + keep_latest_n_blocks, + ) + .await?; let client = Client::from_backend( backend, diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs index 8a19c49ac67a7..85bd3fb524244 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -70,11 +70,10 @@ impl DbContext { /// ReceiptProvider stores transaction receipts and logs in a SQLite database. #[derive(Clone)] pub struct ReceiptProvider { - /// The database pool. - pool: SqlitePool, + /// The database context. + db_ctx: DbContext, /// The block provider used to fetch blocks and reconstruct receipts. block_provider: BP, - db_ctx: DbContext, /// A means to extract receipts from extrinsics. receipt_extractor: ReceiptExtractor, /// When `Some`, old blocks will be pruned. @@ -100,15 +99,36 @@ impl BlockHashMap { /// persistent-DB mode). const MAX_CACHED_BLOCKS: usize = 256; +async fn insert_block_mapping<'e, E: sqlx::Executor<'e, Database = Sqlite>>( + executor: E, + block_map: &BlockHashMap, +) -> Result { + let ethereum_hash_ref = block_map.ethereum_hash.as_ref(); + let substrate_hash_ref = block_map.substrate_hash.as_ref(); + query!( + r#" + INSERT OR REPLACE INTO eth_to_substrate_blocks (ethereum_block_hash, substrate_block_hash) + VALUES ($1, $2) + "#, + ethereum_hash_ref, + substrate_hash_ref, + ) + .execute(executor) + .await +} + impl ReceiptProvider { /// Create a new `ReceiptProvider`. pub async fn new( - pool: SqlitePool, + db_ctx: DbContext, block_provider: BP, receipt_extractor: ReceiptExtractor, keep_latest_n_blocks: Option, - ) -> Result { - sqlx::migrate!().run(&pool).await.map_err(|e| sqlx::Error::Migrate(e.into()))?; + ) -> Result { + sqlx::migrate!() + .run(&db_ctx.pool) + .await + .map_err(|e| sqlx::Error::Migrate(e.into()))?; let provider = Self { db_ctx, @@ -118,10 +138,7 @@ impl ReceiptProvider { block_number_to_hashes: Default::default(), }; - provider.restore_first_evm_block().await.map_err(|e| match e { - ClientError::SqlxError(sqlx_err) => sqlx_err, - other => sqlx::Error::Io(std::io::Error::other(other.to_string())), - })?; + provider.restore_first_evm_block().await?; Ok(provider) } @@ -158,7 +175,7 @@ impl ReceiptProvider { ); sqlx::query("DELETE FROM sync_state WHERE label = ?1") .bind(ChainMetadata::FirstEvmBlock.to_string()) - .execute(&self.pool) + .execute(&self.db_ctx.pool) .await?; } @@ -215,7 +232,7 @@ impl ReceiptProvider { .bind(&key_str) .bind(block_number) .bind(block_hash) - .execute(&self.pool) + .execute(&self.db_ctx.pool) .await?; Ok(()) @@ -233,7 +250,7 @@ impl ReceiptProvider { let row = sqlx::query("SELECT block_number, block_hash FROM sync_state WHERE label = ?1") .bind(&key_str) - .fetch_optional(&self.pool) + .fetch_optional(&self.db_ctx.pool) .await?; Ok(row.map(|r| { @@ -385,7 +402,7 @@ impl ReceiptProvider { let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM transaction_hashes WHERE block_hash = ?") .bind(block_hash_ref) - .fetch_one(&self.pool) + .fetch_one(&self.db_ctx.pool) .await .ok()?; @@ -402,7 +419,7 @@ impl ReceiptProvider { "SELECT transaction_index, transaction_hash FROM transaction_hashes WHERE block_hash = ?", ) .bind(block_hash_ref) - .fetch_all(&self.pool) + .fetch_all(&self.db_ctx.pool) .await .ok()?; @@ -487,31 +504,6 @@ impl ReceiptProvider { .await } - /// Insert a block mapping from Ethereum block hash to Substrate block hash. - async fn insert_block_mapping(&self, block_map: &BlockHashMap) -> Result<(), ClientError> { - let ethereum_hash_ref = block_map.ethereum_hash.as_ref(); - let substrate_hash_ref = block_map.substrate_hash.as_ref(); - - query!( - r#" - INSERT OR REPLACE INTO eth_to_substrate_blocks (ethereum_block_hash, substrate_block_hash) - VALUES ($1, $2) - "#, - ethereum_hash_ref, - substrate_hash_ref, - ) - .execute(&self.pool) - .await?; - - log::trace!( - target: LOG_TARGET, - "Insert block mapping ethereum block: {:?} -> substrate block: {:?}", - block_map.ethereum_hash, - block_map.substrate_hash - ); - Ok(()) - } - /// Deletes older records from the database. async fn remove(&self, block_mappings: &[BlockHashMap]) -> Result<(), ClientError> { if block_mappings.is_empty() { @@ -519,14 +511,20 @@ impl ReceiptProvider { } log::debug!(target: LOG_TARGET, "Removing block hashes: {block_mappings:?}"); - let placeholders = vec!["?"; block_mappings.len()].join(", "); - let sql = format!("DELETE FROM transaction_hashes WHERE block_hash in ({placeholders})"); - let mut delete_tx_query = sqlx::query(&sql); + let mut db_tx = self.db_ctx.pool.begin().await?; - let sql = format!( - "DELETE FROM eth_to_substrate_blocks WHERE substrate_block_hash in ({placeholders})" - ); - let mut delete_mappings_query = sqlx::query(&sql); + for chunk in block_mappings.chunks(self.db_ctx.max_variable_number) { + let placeholders = vec!["?"; chunk.len()].join(", "); + let sql_tx = + format!("DELETE FROM transaction_hashes WHERE block_hash in ({placeholders})"); + let sql_logs = format!("DELETE FROM logs WHERE block_hash in ({placeholders})"); + let sql_mappings = format!( + "DELETE FROM eth_to_substrate_blocks WHERE substrate_block_hash in ({placeholders})" + ); + + let mut delete_tx_query = sqlx::query(&sql_tx); + let mut delete_logs_query = sqlx::query(&sql_logs); + let mut delete_mappings_query = sqlx::query(&sql_mappings); for block_map in chunk { delete_tx_query = delete_tx_query.bind(block_map.substrate_hash.as_ref()); @@ -620,69 +618,13 @@ impl ReceiptProvider { "SELECT EXISTS(SELECT 1 FROM eth_to_substrate_blocks WHERE substrate_block_hash = ?)", ) .bind(substrate_hash_ref) - .fetch_one(&self.pool) + .fetch_one(&self.db_ctx.pool) .await?; // Assuming that if no mapping exists then no relevant entries in transaction_hashes and // logs exist - if !exists { - for (_, receipt) in receipts { - let transaction_hash: &[u8] = receipt.transaction_hash.as_ref(); - let transaction_index = receipt.transaction_index.as_u32() as i32; - - query!( - r#" - INSERT OR REPLACE INTO transaction_hashes (transaction_hash, block_hash, transaction_index) - VALUES ($1, $2, $3) - "#, - transaction_hash, - substrate_hash_ref, - transaction_index - ) - .execute(&self.pool) - .await?; - - for log in &receipt.logs { - let log_index = log.log_index.as_u32() as i32; - let address: &[u8] = log.address.as_ref(); - - let topic_0 = log.topics.first().as_ref().map(|v| &v[..]); - let topic_1 = log.topics.get(1).as_ref().map(|v| &v[..]); - let topic_2 = log.topics.get(2).as_ref().map(|v| &v[..]); - let topic_3 = log.topics.get(3).as_ref().map(|v| &v[..]); - let data = log.data.as_ref().map(|v| &v.0[..]); - - query!( - r#" - INSERT INTO logs( - block_hash, - transaction_index, - log_index, - address, - block_number, - transaction_hash, - topic_0, topic_1, topic_2, topic_3, - data) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) - "#, - ethereum_hash_ref, - transaction_index, - log_index, - address, - block_number_i64, - transaction_hash, - topic_0, - topic_1, - topic_2, - topic_3, - data - ) - .execute(&self.pool) - .await?; - } - } - - self.insert_block_mapping(&block_map).await?; + if exists { + return Ok(()); } let mut db_tx = self.db_ctx.pool.begin().await?; @@ -895,7 +837,10 @@ mod tests { fn mock_provider() -> ReceiptProvider { ReceiptProvider { - pool: SqlitePool::connect_lazy("sqlite::memory:").unwrap(), + db_ctx: DbContext::new( + SqlitePool::connect_lazy("sqlite::memory:").unwrap(), + DbContext::DEFAULT_MAX_VARIABLE_NUMBER, + ), block_provider: MockBlockInfoProvider, receipt_extractor: ReceiptExtractor::new_mock(), keep_latest_n_blocks: None, @@ -1599,7 +1544,7 @@ mod tests { async fn persistent_mode_caps_in_memory_map(pool: SqlitePool) -> anyhow::Result<()> { // Persistent DB mode: keep_latest_n_blocks = None let provider = ReceiptProvider { - pool, + db_ctx: DbContext::new(pool, DbContext::DEFAULT_MAX_VARIABLE_NUMBER), block_provider: MockBlockInfoProvider, receipt_extractor: ReceiptExtractor::new_mock(), keep_latest_n_blocks: None, From b3f96d0e85bdce391873b5e66617e4a462a7cdc4 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 21 Apr 2026 19:23:39 +0000 Subject: [PATCH 39/54] resolve new conflict again --- substrate/frame/revive/rpc/src/receipt_extractor.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index 914dd527177b3..488758b82ddff 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -24,7 +24,6 @@ use pallet_revive::{ }; use sp_core::keccak_256; use std::{ - collections::{BTreeMap, HashMap, HashSet}, future::Future, pin::Pin, sync::{ @@ -359,9 +358,6 @@ impl ReceiptExtractor { eth_payload: &[u8], receipt_gas_info: &ReceiptGasInfo, transaction_index: usize, - receipt_gas_info: ReceiptGasInfo, - reverted: bool, - logs: Vec, ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> { let transaction_hash = H256(keccak_256(eth_payload)); let signed_tx = From 4278d019551d02ecaaca85d7d16536b6d08b95a7 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 21 Apr 2026 21:02:09 +0000 Subject: [PATCH 40/54] nit --- .../frame/revive/rpc/src/receipt_extractor.rs | 79 ++++++++----------- .../frame/revive/rpc/src/receipt_provider.rs | 12 ++- substrate/frame/revive/rpc/src/tests.rs | 8 +- 3 files changed, 48 insertions(+), 51 deletions(-) diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index 488758b82ddff..58b7f722c14d8 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -546,10 +546,10 @@ mod tests { use pallet_revive::evm::{Account, TransactionLegacyUnsigned, TransactionUnsigned}; - fn signed_call(account: &Account, tx: TransactionUnsigned) -> (EthTransact, H256) { + fn signed_call(account: &Account, tx: TransactionUnsigned) -> (Vec, H256) { let payload = account.sign_transaction(tx).signed_payload(); let hash = H256(keccak_256(&payload)); - (EthTransact { payload }, hash) + (payload, hash) } fn legacy_call_tx(to: H160) -> TransactionUnsigned { @@ -573,20 +573,18 @@ mod tests { let extractor = ReceiptExtractor::new_mock(); let account = Account::default(); let eth_block_hash = H256::from([0xAB; 32]); - let block_number = U256::from(42); - let (call, tx_hash) = signed_call(&account, legacy_call_tx(account.address())); + let block_number = 42u32; + let (payload, tx_hash) = signed_call(&account, legacy_call_tx(account.address())); // Successful call let (signed_tx, receipt) = extractor - .decode_transaction_and_build_receipt( - eth_block_hash, + .build_receipt( block_number, - call, - tx_hash, + eth_block_hash, + &ExtrinsicEvents { success: true, logs: vec![] }, + &payload, + &gas_info(), 3, - gas_info(), - false, - vec![], ) .unwrap(); @@ -595,24 +593,22 @@ mod tests { assert_eq!(receipt.to, Some(account.address())); assert_eq!(receipt.contract_address, None); assert_eq!(receipt.block_hash, eth_block_hash); - assert_eq!(receipt.block_number, block_number); + assert_eq!(receipt.block_number, U256::from(block_number)); assert_eq!(receipt.transaction_hash, tx_hash); assert_eq!(receipt.transaction_index, U256::from(3)); assert_eq!(receipt.gas_used, U256::from(21_000)); assert_eq!(signed_tx.recover_eth_address().unwrap(), account.address()); // Same call, but reverted - let (call, tx_hash) = signed_call(&account, legacy_call_tx(account.address())); + let (payload, _tx_hash) = signed_call(&account, legacy_call_tx(account.address())); let (_, receipt) = extractor - .decode_transaction_and_build_receipt( - eth_block_hash, + .build_receipt( block_number, - call, - tx_hash, + eth_block_hash, + &ExtrinsicEvents { success: false, logs: vec![] }, + &payload, + &gas_info(), 3, - gas_info(), - true, - vec![], ) .unwrap(); @@ -630,18 +626,16 @@ mod tests { nonce: U256::from(0), ..Default::default() }); - let (call, tx_hash) = signed_call(&account, deploy_tx); + let (payload, _) = signed_call(&account, deploy_tx); let (_, receipt) = extractor - .decode_transaction_and_build_receipt( + .build_receipt( + 1u32, H256::zero(), - U256::from(1), - call, - tx_hash, + &ExtrinsicEvents { success: true, logs: vec![] }, + &payload, + &gas_info(), 0, - gas_info(), - false, - vec![], ) .unwrap(); @@ -656,18 +650,15 @@ mod tests { let extractor = ReceiptExtractor::new_mock(); // Corrupt payload - let call = EthTransact { payload: vec![0xde, 0xad] }; - let hash = H256(keccak_256(&call.payload)); + let payload = vec![0xde, 0xadu8]; let err = extractor - .decode_transaction_and_build_receipt( + .build_receipt( + 1u32, H256::zero(), - U256::from(1), - call, - hash, + &ExtrinsicEvents { success: true, logs: vec![] }, + &payload, + &gas_info(), 0, - gas_info(), - false, - vec![], ) .unwrap_err(); assert!(matches!(err, ClientError::TxDecodingFailed)); @@ -678,17 +669,15 @@ mod tests { ..ReceiptExtractor::new_mock() }; let account = Account::default(); - let (call, hash) = signed_call(&account, legacy_call_tx(account.address())); + let (payload, _) = signed_call(&account, legacy_call_tx(account.address())); let err = extractor - .decode_transaction_and_build_receipt( + .build_receipt( + 1u32, H256::zero(), - U256::from(1), - call, - hash, + &ExtrinsicEvents { success: true, logs: vec![] }, + &payload, + &gas_info(), 0, - gas_info(), - false, - vec![], ) .unwrap_err(); assert!(matches!(err, ClientError::RecoverEthAddressFailed)); diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs index 85bd3fb524244..1808b123c8ef9 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -1675,7 +1675,9 @@ mod tests { let receipts = make_receipts(0, 5, 3); // First insert. - provider.insert_into_db(&block, &receipts, ðereum_hash).await?; + provider + .insert_with_hashes(block.hash(), block.number(), &receipts, ðereum_hash) + .await?; assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 5); assert_eq!(count(&provider.db_ctx.pool, "logs", Some(ethereum_hash)).await, 15); assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 1); @@ -1687,7 +1689,9 @@ mod tests { assert_eq!(count(&provider.db_ctx.pool, "eth_to_substrate_blocks", None).await, 0); // Second insert hits the actual INSERT OR REPLACE statements. - provider.insert_into_db(&block, &receipts, ðereum_hash).await?; + provider + .insert_with_hashes(block.hash(), block.number(), &receipts, ðereum_hash) + .await?; // Row counts unchanged — no duplicates. assert_eq!(count(&provider.db_ctx.pool, "transaction_hashes", None).await, 5); @@ -1735,7 +1739,9 @@ mod tests { let block = MockBlockInfo { hash: make_hash(i, 0xAA), number: i as u32 + 1 }; let ethereum_hash = make_hash(i, 0xBB); let receipts = make_receipts(i * n_tx_per_block, n_tx_per_block, n_logs_per_receipt); - provider.insert_into_db(&block, &receipts, ðereum_hash).await?; + provider + .insert_with_hashes(block.hash(), block.number(), &receipts, ðereum_hash) + .await?; block_mappings.push(BlockHashMap::new(block.hash, ethereum_hash)); } diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index d07f7cca41a7f..b98c0298ac27d 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -19,7 +19,7 @@ //! [evm-test-suite](https://github.com/paritytech/evm-test-suite) repository. use crate::{ - BlockInfoProvider, DebugRpcClient, EthRpcClient, ReceiptExtractor, ReceiptProvider, + BlockInfoProvider, DbContext, DebugRpcClient, EthRpcClient, ReceiptExtractor, ReceiptProvider, SubxtBlockInfoProvider, block_sync::{ChainMetadata, SyncLabel}, cli::{self, CliCommand}, @@ -1925,8 +1925,9 @@ async fn create_sync_test_client() -> anyhow::Result Date: Tue, 21 Apr 2026 21:18:43 +0000 Subject: [PATCH 41/54] nit --- .../frame/revive/rpc/src/receipt_extractor.rs | 152 ------------------ 1 file changed, 152 deletions(-) diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index 58b7f722c14d8..9021e6c82095f 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -700,156 +700,4 @@ mod tests { extractor.set_first_evm_block(100); assert_eq!(extractor.first_evm_block(), Some(50)); } - - use codec::{Compact, Decode, Encode}; - use frame_system::EventRecord; - use revive_dev_runtime::{Runtime, RuntimeEvent}; - use subxt::{events::Events, metadata::Metadata}; - - /// Build `Events` by SCALE-encoding revive events against the generated runtime metadata. - struct EventsBuilder { - metadata: Metadata, - bytes: Vec, - count: u32, - } - - impl EventsBuilder { - fn new() -> Self { - let metadata_bytes: &[u8] = - include_bytes!(concat!(env!("OUT_DIR"), "/revive_chain.scale")); - let metadata = Metadata::decode(&mut &metadata_bytes[..]).unwrap(); - Self { metadata, bytes: Vec::new(), count: 0 } - } - - fn push_event( - mut self, - phase: frame_system::Phase, - event: pallet_revive::Event, - ) -> Self { - EventRecord:: { - phase, - event: RuntimeEvent::Revive(event), - topics: vec![], - } - .encode_to(&mut self.bytes); - self.count += 1; - self - } - - fn build(self) -> Events { - let mut encoded_events = Vec::new(); - Compact(self.count).encode_to(&mut encoded_events); - encoded_events.extend(self.bytes); - Events::decode_from(encoded_events, self.metadata) - } - } - - #[test] - fn extract_revive_events_decodes_contract_emitted_log() { - let contract = H160::from([0x11; 20]); - let topics = vec![H256::from([0x22; 32]), H256::from([0x33; 32])]; - let data = vec![0xde, 0xad, 0xbe, 0xef]; - let events = EventsBuilder::new() - .push_event( - frame_system::Phase::ApplyExtrinsic(5), - pallet_revive::Event::ContractEmitted { - contract, - data: data.clone(), - topics: topics.clone(), - }, - ) - .build(); - - let tx_hash = H256::from([0xAA; 32]); - let eth_block_hash = H256::from([0xBB; 32]); - let substrate_block_number = 42u32; - let eth_block_number = U256::from(substrate_block_number); - - let (reverts, logs) = extract_revive_events( - &events, - substrate_block_number, - eth_block_number, - eth_block_hash, - |idx| (idx == 5).then_some(tx_hash), - ); - - assert!(reverts.is_empty()); - assert_eq!(logs.len(), 1); - let log = &logs[&5][0]; - assert_eq!(log.address, contract); - assert_eq!(log.topics, topics); - assert_eq!(log.data.as_ref().unwrap().0, data); - assert_eq!(log.block_hash, eth_block_hash); - assert_eq!(log.block_number, eth_block_number); - assert_eq!(log.transaction_hash, tx_hash); - assert_eq!(log.transaction_index, U256::from(5)); - } - - #[test] - fn extract_revive_events_skips_irrelevant_events() { - // Events outside `ApplyExtrinsic` and events for extrinsics the tx-hash closure - // doesn't resolve are both dropped. - let empty_contract_emitted = pallet_revive::Event::ContractEmitted { - contract: H160::zero(), - data: vec![], - topics: vec![], - }; - let revert = pallet_revive::Event::EthExtrinsicRevert { - dispatch_error: sp_runtime::DispatchError::Other("skipped-phase revert"), - }; - let events = EventsBuilder::new() - .push_event(frame_system::Phase::Finalization, empty_contract_emitted.clone()) - .push_event(frame_system::Phase::Initialization, revert.clone()) - .push_event(frame_system::Phase::ApplyExtrinsic(5), empty_contract_emitted) - .push_event(frame_system::Phase::ApplyExtrinsic(5), revert) - .build(); - - // The tx-hash closure returns `Some` only for extrinsic 7 (not present) - let (reverts, logs) = - extract_revive_events(&events, 0, U256::zero(), H256::zero(), |idx| { - (idx == 7).then_some(H256::zero()) - }); - - assert!(reverts.is_empty()); - assert!(logs.is_empty()); - } - - #[test] - fn extract_revive_events_accumulates_per_extrinsic() { - let tx0 = H256::from([0x01; 32]); - let tx1 = H256::from([0x02; 32]); - let tx2 = H256::from([0x03; 32]); - let emitted_by = |contract: H160| pallet_revive::Event::ContractEmitted { - contract, - data: vec![], - topics: vec![], - }; - let events = EventsBuilder::new() - .push_event(frame_system::Phase::ApplyExtrinsic(0), emitted_by(H160::from([0xaa; 20]))) - .push_event(frame_system::Phase::ApplyExtrinsic(0), emitted_by(H160::from([0xbb; 20]))) - .push_event( - frame_system::Phase::ApplyExtrinsic(1), - pallet_revive::Event::EthExtrinsicRevert { - dispatch_error: sp_runtime::DispatchError::Other("tx-1 revert"), - }, - ) - .push_event(frame_system::Phase::ApplyExtrinsic(2), emitted_by(H160::from([0xcc; 20]))) - .build(); - - let (reverts, logs) = - extract_revive_events(&events, 0, U256::zero(), H256::zero(), |idx| match idx { - 0 => Some(tx0), - 1 => Some(tx1), - 2 => Some(tx2), - _ => None, - }); - - assert_eq!(reverts, [1usize].into_iter().collect::>()); - assert_eq!(logs[&0].len(), 2); - assert_eq!(logs[&2].len(), 1); - // log_index is block-wide - assert_eq!(logs[&0][0].log_index, U256::from(0)); - assert_eq!(logs[&0][1].log_index, U256::from(1)); - assert_eq!(logs[&2][0].log_index, U256::from(3)); - } } From 2e800c99290deeb43885ba22f3b2d8f7fd1b3577 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Tue, 21 Apr 2026 22:33:42 +0000 Subject: [PATCH 42/54] fix pallet-revive tests --- .../frame/revive/rpc/src/receipt_extractor.rs | 9 ++- .../frame/revive/rpc/src/substrate_client.rs | 9 +++ .../frame/revive/rpc/src/subxt_client.rs | 56 +++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/substrate/frame/revive/rpc/src/receipt_extractor.rs b/substrate/frame/revive/rpc/src/receipt_extractor.rs index 9021e6c82095f..0c1b73981d147 100644 --- a/substrate/frame/revive/rpc/src/receipt_extractor.rs +++ b/substrate/frame/revive/rpc/src/receipt_extractor.rs @@ -282,12 +282,19 @@ impl ReceiptExtractor { Box::pin(fut) as Pin>> + Send>> }; + let client_for_events = client.clone(); + let fetch_block_events_fn = move |block_hash: H256| { + let c = client_for_events.clone(); + let fut = async move { c.block_events(block_hash).await.ok().flatten() }; + Box::pin(fut) as Pin> + Send>> + }; + Self::new_native( pallet_index, fetch_receipt_data_fn, fetch_eth_block_hash_fn, earliest_receipt_block, - None:: Pin> + Send>>>, + Some(fetch_block_events_fn), Some(fetch_block_extrinsics_fn), ) } diff --git a/substrate/frame/revive/rpc/src/substrate_client.rs b/substrate/frame/revive/rpc/src/substrate_client.rs index 9989a8db105ab..0b4cc12298972 100644 --- a/substrate/frame/revive/rpc/src/substrate_client.rs +++ b/substrate/frame/revive/rpc/src/substrate_client.rs @@ -231,6 +231,15 @@ pub trait SubstrateClientT: Send + Sync + Clone + 'static { block_hash: SubstrateBlockHash, ) -> Result, crate::client::ClientError>; + /// Return per-extrinsic event data (success flag + EVM logs) for the given block. + /// Returns `Ok(None)` when event scanning is not supported by this client. + async fn block_events( + &self, + _block_hash: SubstrateBlockHash, + ) -> Result, crate::client::ClientError> { + Ok(None) + } + /// Scan block events for the given extrinsic index and return its /// post-dispatch weight (if available). async fn extrinsic_post_dispatch_weight( diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 3bb629c9c1d63..7138f04d4c786 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -597,6 +597,62 @@ impl SubstrateClientT for SubxtClient { .collect()) } + async fn block_events( + &self, + block_hash: SubstrateBlockHash, + ) -> Result, ClientError> { + use revive::events::{ContractEmitted, EthExtrinsicRevert}; + use subxt::events::{Phase, StaticEvent as _}; + + let block = match self.api.blocks().at(block_hash).await { + Ok(b) => b, + Err(subxt::Error::Block(subxt::error::BlockError::NotFound(_))) => return Ok(None), + Err(e) => return Err(e.into()), + }; + + let extrinsics = block.extrinsics().await?; + let num_extrinsics = extrinsics.iter().count(); + let events = block.events().await?; + + let mut block_events: crate::receipt_extractor::BlockEvents = (0..num_extrinsics) + .map(|_| crate::receipt_extractor::ExtrinsicEvents { success: true, logs: vec![] }) + .collect(); + + for event_result in events.iter() { + let event = match event_result { + Ok(e) => e, + Err(_) => continue, + }; + + let extrinsic_idx = match event.phase() { + Phase::ApplyExtrinsic(idx) => idx as usize, + _ => continue, + }; + + if extrinsic_idx >= block_events.len() { + continue; + } + + if event.pallet_name() != ContractEmitted::PALLET { + continue; + } + + if event.variant_name() == EthExtrinsicRevert::EVENT { + block_events[extrinsic_idx].success = false; + } else if event.variant_name() == ContractEmitted::EVENT { + if let Ok(Some(evt)) = event.as_event::() { + block_events[extrinsic_idx].logs.push(( + evt.contract, + evt.topics, + Some(evt.data), + )); + } + } + } + + Ok(Some(block_events)) + } + async fn extrinsic_post_dispatch_weight( &self, block_hash: SubstrateBlockHash, From f820dbd067c2cba3121452c752b060072e59a7a7 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 06:24:08 +0000 Subject: [PATCH 43/54] resolve new conflicts --- substrate/frame/revive/rpc/src/client.rs | 39 ------------------------ 1 file changed, 39 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 74c91057c2c91..997996a1aa2aa 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -483,45 +483,6 @@ impl Client { .await } - /// Extract receipts from a block, persist them and update fee history. - async fn process_block( - &self, - block: &SubstrateBlock, - ) -> Result<(Block, Vec), ClientError> { - let block_number = block.number(); - let hash = block.hash(); - - macro_rules! time { - ($label:expr, $expr:expr) => {{ - let t = std::time::Instant::now(); - let r = $expr; - log::trace!( - target: LOG_TARGET, - "⏱️ #{block_number} {}: {:?}", - $label, t.elapsed(), - ); - r - }}; - } - - let eth_block = time!("eth_block", self.runtime_api(hash).eth_block().await?); - let receipts = time!( - "receipts_from_block", - self.receipt_provider.receipts_from_block(block, eth_block.hash).await? - ); - time!( - "insert_block_receipts", - self.receipt_provider - .insert_block_receipts(block, &receipts, ð_block.hash) - .await? - ); - - let (_, receipt_infos): (Vec<_>, Vec<_>) = receipts.into_iter().unzip(); - self.fee_history_provider.update_fee_history(ð_block, &receipt_infos).await; - - Ok((eth_block, receipt_infos)) - } - /// Start the block subscription, and populate the block cache. pub async fn subscribe_and_cache_new_blocks( &self, From 65df8e8fbf0d5ee1de6ee29167ec604892c5e710 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 06:37:38 +0000 Subject: [PATCH 44/54] nit --- substrate/frame/revive/rpc/src/receipt_provider.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs index 708df3b858abb..105589c7eae8c 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -428,6 +428,20 @@ impl ReceiptProvider { Some(H256::from_slice(&result.ethereum_block_hash[..])) } + /// Look up the ethereum block hash for a previously processed block from the in-memory cache. + pub async fn get_processed_eth_block_hash( + &self, + block_number: SubstrateBlockNumber, + substrate_hash: H256, + ) -> Option { + self.block_number_to_hashes + .lock() + .await + .get(&block_number) + .filter(|entry| entry.substrate_hash == substrate_hash) + .map(|entry| entry.ethereum_hash) + } + /// Get the number of receipts for a given substrate block hash. pub async fn receipts_count_per_block(&self, block_hash: &H256) -> Option { let block_hash_ref = block_hash.as_ref(); From 43a13036cf39c2ba2b29cea75c428579b40697f5 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 09:25:52 +0000 Subject: [PATCH 45/54] nit --- substrate/frame/revive/rpc/src/lib.rs | 38 ++++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index cab4d4ee73799..a287b462f1754 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -132,7 +132,7 @@ pub enum EthRpcError { #[error("Account not found for address {0:?}")] AccountNotFound(H160), /// Received an invalid transaction - #[error("Invalid transaction")] + #[error("Invalid Transaction")] InvalidTransaction, /// Received an invalid transaction #[error("Invalid transaction {0:?}")] @@ -244,30 +244,42 @@ impl EthRpcServer for EthRpcServerIm EthRpcError::InvalidTransaction })?; - let is_chain_id_provided = match signed_transaction { + let expected_chain_id = U256::from(self.client.chain_id()); + let tx_chain_id = match signed_transaction { TransactionSigned::Transaction7702Signed(tx) => { - tx.transaction_7702_unsigned.chain_id != U256::zero() + Some(tx.transaction_7702_unsigned.chain_id) }, TransactionSigned::Transaction4844Signed(tx) => { - tx.transaction_4844_unsigned.chain_id != U256::zero() + Some(tx.transaction_4844_unsigned.chain_id) }, TransactionSigned::Transaction1559Signed(tx) => { - tx.transaction_1559_unsigned.chain_id != U256::zero() + Some(tx.transaction_1559_unsigned.chain_id) }, TransactionSigned::Transaction2930Signed(tx) => { - tx.transaction_2930_unsigned.chain_id != U256::zero() + Some(tx.transaction_2930_unsigned.chain_id) }, TransactionSigned::TransactionLegacySigned(tx) => { - tx.transaction_legacy_unsigned.chain_id.is_some() + tx.transaction_legacy_unsigned.chain_id }, }; - if !is_chain_id_provided { - log::trace!( - target: LOG_TARGET, - "Invalid Transaction: no chain-id. ethereum_hash: {hash:?}" - ); - Err(EthRpcError::InvalidTransaction)?; + match tx_chain_id { + None => { + log::trace!( + target: LOG_TARGET, + "Invalid Transaction: no chain-id. ethereum_hash: {hash:?}" + ); + Err(EthRpcError::InvalidTransaction)?; + }, + Some(id) if id != expected_chain_id => { + log::trace!( + target: LOG_TARGET, + "Invalid Transaction: wrong chain-id {id}, expected {expected_chain_id}. \ + ethereum_hash: {hash:?}" + ); + Err(EthRpcError::InvalidTransaction)?; + }, + _ => {}, } } From 977d75b5e29faf2492716f77ceefd58a54bb6249 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 11:27:48 +0000 Subject: [PATCH 46/54] fix run_all_eth_roc_tests --- .github/assets/revive-dev-node-polkavm-resolc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/assets/revive-dev-node-polkavm-resolc.json b/.github/assets/revive-dev-node-polkavm-resolc.json index 39df8f87df177..c003a6583fdc3 100644 --- a/.github/assets/revive-dev-node-polkavm-resolc.json +++ b/.github/assets/revive-dev-node-polkavm-resolc.json @@ -1,4 +1,5 @@ { + "fixtures/solidity/complex/create/create2_many/test.json::0::Y M3 S-": "Failed", "fixtures/solidity/complex/create/create2_many/test.json::1::Y M3 S+": "Failed", "fixtures/solidity/complex/create/create_many/test.json::1::Y M3 S+": "Failed", "fixtures/solidity/complex/defi/UniswapV4/test.json::0::Y M3 S+": "Failed", From 629ca5564a5ae6664db62846104a1bd714d670d7 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 11:27:59 +0000 Subject: [PATCH 47/54] fix run_all_eth_roc_tests --- .github/assets/revive-dev-node-polkavm-resolc.json | 1 - substrate/frame/revive/rpc/src/client.rs | 4 ++++ substrate/frame/revive/rpc/src/fee_history_provider.rs | 8 ++++++-- substrate/frame/revive/rpc/src/tests.rs | 1 - 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/assets/revive-dev-node-polkavm-resolc.json b/.github/assets/revive-dev-node-polkavm-resolc.json index c003a6583fdc3..39df8f87df177 100644 --- a/.github/assets/revive-dev-node-polkavm-resolc.json +++ b/.github/assets/revive-dev-node-polkavm-resolc.json @@ -1,5 +1,4 @@ { - "fixtures/solidity/complex/create/create2_many/test.json::0::Y M3 S-": "Failed", "fixtures/solidity/complex/create/create2_many/test.json::1::Y M3 S+": "Failed", "fixtures/solidity/complex/create/create_many/test.json::1::Y M3 S+": "Failed", "fixtures/solidity/complex/defi/UniswapV4/test.json::0::Y M3 S+": "Failed", diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 997996a1aa2aa..95bddb881f682 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -613,6 +613,9 @@ impl Client { let hash = self.get_block_hash(n).await?.ok_or(ClientError::BlockNotFound)?; Ok(hash) }, + BlockNumberOrTagOrHash::BlockTag(BlockTag::Earliest) => { + self.get_block_hash(0).await?.ok_or(ClientError::BlockNotFound) + }, BlockNumberOrTagOrHash::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { Ok(self.latest_finalized_block().await.hash()) }, @@ -653,6 +656,7 @@ impl Client { let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?; self.block_by_number(n).await }, + BlockNumberOrTag::BlockTag(BlockTag::Earliest) => self.block_by_number(0).await, BlockNumberOrTag::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { Ok(Some(self.block_provider.latest_finalized_block().await)) }, diff --git a/substrate/frame/revive/rpc/src/fee_history_provider.rs b/substrate/frame/revive/rpc/src/fee_history_provider.rs index c727b5f6735f7..5dd53793f7e82 100644 --- a/substrate/frame/revive/rpc/src/fee_history_provider.rs +++ b/substrate/frame/revive/rpc/src/fee_history_provider.rs @@ -105,7 +105,11 @@ impl FeeHistoryProvider { }); }; - let lowest = highest.saturating_sub(block_count.saturating_sub(1)).max(lowest_in_cache); + // When the requested newest block is older than our oldest cached block, clamp + // to the oldest available so callers always get at least one entry. + let effective_highest = highest.max(lowest_in_cache); + let lowest = + effective_highest.saturating_sub(block_count.saturating_sub(1)).max(lowest_in_cache); let mut response = FeeHistoryResult { oldest_block: U256::from(lowest), @@ -116,7 +120,7 @@ impl FeeHistoryProvider { let rewards = &mut response.reward; // Iterate over the requested block range. - for n in lowest..=highest { + for n in lowest..=effective_highest { if let Some(block) = cache.get(&n) { response.base_fee_per_gas.push(U256::from(block.base_fee)); response.gas_used_ratio.push(block.gas_used_ratio); diff --git a/substrate/frame/revive/rpc/src/tests.rs b/substrate/frame/revive/rpc/src/tests.rs index b98c0298ac27d..f107e47c38665 100644 --- a/substrate/frame/revive/rpc/src/tests.rs +++ b/substrate/frame/revive/rpc/src/tests.rs @@ -970,7 +970,6 @@ async fn test_earliest_block_tag() -> anyhow::Result<()> { // eth_feeHistory let fee = client.fee_history(U256::from(1), BlockTag::Earliest.into(), None).await?; - assert_eq!(fee.oldest_block, U256::zero(), "feeHistory oldest_block should be 0"); assert!(!fee.base_fee_per_gas.is_empty(), "feeHistory should include base fee"); // eth_getLogs From cb033853e5147988c451d551d41e74296f0643c6 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 12:33:11 +0000 Subject: [PATCH 48/54] fix get_storage --- substrate/frame/revive/rpc/src/client/runtime_api.rs | 6 ++++-- substrate/frame/revive/rpc/src/native_client.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 3f0da2c0617f0..9409485a06675 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -70,8 +70,10 @@ impl RuntimeApi { .revive_api() .get_storage(contract_address, key) .unvalidated(); - let result = self.0.call(payload).await?.map_err(|_| ClientError::ContractNotFound)?; - Ok(result) + match self.0.call(payload).await? { + Err(_) => Ok(None), + Ok(value) => Ok(value), + } } /// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction. diff --git a/substrate/frame/revive/rpc/src/native_client.rs b/substrate/frame/revive/rpc/src/native_client.rs index 95fb66da1723c..1eb4745a7b011 100644 --- a/substrate/frame/revive/rpc/src/native_client.rs +++ b/substrate/frame/revive/rpc/src/native_client.rs @@ -361,11 +361,15 @@ where address: H160, key: [u8; 32], ) -> Result>, ClientError> { - self.client + match self + .client .runtime_api() .get_storage(block_hash, address, key) .map_err(native_err)? - .map_err(|_| ClientError::ContractNotFound) + { + Err(_) => Ok(None), + Ok(value) => Ok(value), + } } async fn eth_block(&self, block_hash: SubstrateBlockHash) -> Result { From e193dfeff760b72134aa3ebf86b68811ebb78851 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 12:34:21 +0000 Subject: [PATCH 49/54] cargo fmt --- substrate/frame/revive/rpc/src/fee_history_provider.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/substrate/frame/revive/rpc/src/fee_history_provider.rs b/substrate/frame/revive/rpc/src/fee_history_provider.rs index 5dd53793f7e82..c534cba97ff22 100644 --- a/substrate/frame/revive/rpc/src/fee_history_provider.rs +++ b/substrate/frame/revive/rpc/src/fee_history_provider.rs @@ -108,8 +108,9 @@ impl FeeHistoryProvider { // When the requested newest block is older than our oldest cached block, clamp // to the oldest available so callers always get at least one entry. let effective_highest = highest.max(lowest_in_cache); - let lowest = - effective_highest.saturating_sub(block_count.saturating_sub(1)).max(lowest_in_cache); + let lowest = effective_highest + .saturating_sub(block_count.saturating_sub(1)) + .max(lowest_in_cache); let mut response = FeeHistoryResult { oldest_block: U256::from(lowest), From 37c828ab1b8e6728e20e7551a92aa257b9eaed9f Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Wed, 22 Apr 2026 13:21:23 +0000 Subject: [PATCH 50/54] Fix block_hash_for_tag to use earliest_block_number() --- substrate/frame/revive/rpc/src/client.rs | 31 +++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 95bddb881f682..0db35823c23ea 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -211,6 +211,17 @@ impl From for ErrorObjectOwned { } } +/// Returns the first EVM block number for main and test nets, `None` otherwise. +fn known_first_evm_block_for_chain(chain_id: u64) -> Option { + match chain_id { + 420420417 => Some(4_367_914), // Paseo Asset Hub + 420420418 => Some(12_234_156), // Kusama Asset Hub + 420420419 => Some(11_405_259), // Polkadot Asset Hub + 420420421 => Some(13_169_391), // Westend Asset Hub + _ => None, + } +} + /// A client that connects to a Substrate node and maintains a receipt/block cache. #[derive(Clone)] pub struct Client { @@ -614,7 +625,11 @@ impl Client { Ok(hash) }, BlockNumberOrTagOrHash::BlockTag(BlockTag::Earliest) => { - self.get_block_hash(0).await?.ok_or(ClientError::BlockNotFound) + let hash = self + .get_block_hash(self.earliest_block_number()) + .await? + .ok_or(ClientError::BlockNotFound)?; + Ok(hash) }, BlockNumberOrTagOrHash::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { Ok(self.latest_finalized_block().await.hash()) @@ -656,7 +671,9 @@ impl Client { let n = (*n).try_into().map_err(|_| ClientError::ConversionFailed)?; self.block_by_number(n).await }, - BlockNumberOrTag::BlockTag(BlockTag::Earliest) => self.block_by_number(0).await, + BlockNumberOrTag::BlockTag(BlockTag::Earliest) => { + self.block_by_number(self.earliest_block_number()).await + }, BlockNumberOrTag::BlockTag(BlockTag::Finalized | BlockTag::Safe) => { Ok(Some(self.block_provider.latest_finalized_block().await)) }, @@ -765,6 +782,13 @@ impl Client { self.backend.chain_id() } + fn earliest_block_number(&self) -> u32 { + self.receipt_provider + .first_evm_block() + .or_else(|| known_first_evm_block_for_chain(self.backend.chain_id())) + .unwrap_or(0) + } + /// Get the max block weight. pub fn max_block_weight(&self) -> Weight { self.backend.max_block_weight() @@ -833,12 +857,13 @@ impl Client { /// Get the logs matching the given filter. pub async fn logs(&self, filter: Option) -> Result, ClientError> { + let earliest = U256::from(self.earliest_block_number()); let latest_block_number = self.block_number().await?; let resolve_block_number = move |block: BlockNumberOrTag| -> anyhow::Result { match block { BlockNumberOrTag::U256(v) => Ok(v), - BlockNumberOrTag::BlockTag(BlockTag::Earliest) => Ok(U256::zero()), + BlockNumberOrTag::BlockTag(BlockTag::Earliest) => Ok(earliest), BlockNumberOrTag::BlockTag( BlockTag::Latest | BlockTag::Pending | BlockTag::Safe | BlockTag::Finalized, ) => Ok(U256::from(latest_block_number)), From 9f9d3bae01968efa54383cf83918f89a09add77f Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Thu, 23 Apr 2026 15:47:24 +0000 Subject: [PATCH 51/54] fix trace_block_by_number --- substrate/frame/revive/rpc/src/client.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 0db35823c23ea..2ed01d22012e8 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -976,6 +976,9 @@ impl Client { let block_hash = self.block_hash_for_tag(at.into()).await?; let block = self.tracing_block(block_hash).await?; + if block.header.parent_hash == Default::default() { + return Ok(vec![]); + } let traces = self.backend.trace_block(block_hash, block, config).await?; let mut hashes = self From f91b8d9b4b31b1e5ddd66a5a254c3732174de013 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Thu, 23 Apr 2026 17:56:59 +0000 Subject: [PATCH 52/54] add estimate_gas back to runtime_api --- substrate/frame/revive/rpc/src/client.rs | 17 +++++- .../revive/rpc/src/client/runtime_api.rs | 61 ++++++++++++++++++- substrate/frame/revive/rpc/src/lib.rs | 2 +- .../frame/revive/rpc/src/native_client.rs | 19 ++++++ .../frame/revive/rpc/src/substrate_client.rs | 8 +++ .../frame/revive/rpc/src/subxt_client.rs | 12 ++++ 6 files changed, 114 insertions(+), 5 deletions(-) diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 2ed01d22012e8..5915c3459bee7 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -329,7 +329,7 @@ impl Client { let (block_subscription_tx, _) = tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY); - let (log_subscription_tx, _) = tokio::sync::broadcast::channel::(NOTIFIER_CAPACITY); + let (log_subscription_tx, _) = tokio::sync::broadcast::channel::(1000); Ok(Self { backend, @@ -547,8 +547,9 @@ impl Client { let _ = block_subscription_tx.send(evm_block.clone()); } - // Broadcast logs to eth_subscribe("logs") subscribers. - if log_subscription_tx.receiver_count() > 0 { + if subscription_type == SubscriptionType::FinalizedBlocks && + log_subscription_tx.receiver_count() > 0 + { for receipt in &receipts { for log in &receipt.logs { let _ = log_subscription_tx.send(log.clone()); @@ -936,6 +937,16 @@ impl Client { self.backend.get_storage(block_hash, address, key).await } + /// Estimate the gas for the given transaction using binary search. + pub async fn estimate_gas( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result { + self.backend.estimate_gas(block_hash, tx, block).await + } + /// Dry-run a transaction and return the estimated gas as `U256`. pub async fn dry_run( &self, diff --git a/substrate/frame/revive/rpc/src/client/runtime_api.rs b/substrate/frame/revive/rpc/src/client/runtime_api.rs index 9409485a06675..fdac15752e44e 100644 --- a/substrate/frame/revive/rpc/src/client/runtime_api.rs +++ b/substrate/frame/revive/rpc/src/client/runtime_api.rs @@ -21,7 +21,7 @@ use crate::{ client::Balance, subxt_client::{self, SrcChainConfig}, }; -use futures::TryFutureExt; +use futures::{StreamExt, TryFutureExt, stream}; use pallet_revive::{ DryRunConfig, EthTransactInfo, TracingConfig, evm::{ @@ -76,6 +76,65 @@ impl RuntimeApi { } } + /// Estimate the gas for the given transaction. + pub async fn estimate_gas( + &self, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result { + let timestamp_override = match block { + BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending) => { + Some(Timestamp::current().as_millis()) + }, + _ => None, + }; + + let mut stream = stream::once(Box::pin(async { + let payload = subxt_client::apis() + .revive_api() + .eth_estimate_gas( + tx.clone().into(), + DryRunConfig::default().with_timestamp_override(timestamp_override).into(), + ) + .unvalidated(); + self.0.call(payload).await.map(|value| value.map(|value| value.0)) + })) + .chain(stream::once(Box::pin(async { + let payload = subxt_client::apis() + .revive_api() + .eth_transact_with_config( + tx.clone().into(), + DryRunConfig::default().with_timestamp_override(timestamp_override).into(), + ) + .unvalidated(); + self.0.call(payload).await.map(|value| value.map(|value| value.eth_gas)) + }))) + .chain(stream::once(Box::pin(async { + let payload = + subxt_client::apis().revive_api().eth_transact(tx.clone().into()).unvalidated(); + self.0.call(payload).await.map(|value| value.map(|value| value.eth_gas)) + }))); + + while let Some(result) = stream.next().await { + match result { + Ok(estimation) => { + return estimation.map_err(|err| ClientError::TransactError(err.0)); + }, + Err(Metadata(MetadataError::RuntimeMethodNotFound(name))) => { + log::debug!(target: LOG_TARGET, "Method {name:?} not found falling back"); + }, + Err(subxt::Error::Rpc(subxt::error::RpcError::ClientError( + subxt::ext::subxt_rpcs::Error::User(UserError { message, .. }), + ))) if message.contains("is not found") => { + log::debug!(target: LOG_TARGET, "{message:?} not found falling back") + }, + Err(err) => return Err(err.into()), + } + } + + Err(ClientError::NativeClientError("No gas estimation method succeeded".to_string())) + } + /// Dry run a transaction and returns the [`EthTransactInfo`] for the transaction. pub async fn dry_run( &self, diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index a287b462f1754..ef93f2f35f115 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -201,7 +201,7 @@ impl EthRpcServer for EthRpcServerIm let eth_gas = self .client - .dry_run(hash, transaction, block_tag_or_hash, None) + .estimate_gas(hash, transaction, block_tag_or_hash) .await .map_err(ClientError::from)?; diff --git a/substrate/frame/revive/rpc/src/native_client.rs b/substrate/frame/revive/rpc/src/native_client.rs index 1eb4745a7b011..3df55523b2202 100644 --- a/substrate/frame/revive/rpc/src/native_client.rs +++ b/substrate/frame/revive/rpc/src/native_client.rs @@ -323,6 +323,25 @@ where .map_err(ClientError::TransactError) } + async fn estimate_gas( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result { + let timestamp_override: Option = + matches!(block, BlockNumberOrTagOrHash::BlockTag(BlockTag::Pending)) + .then(|| Moment::from(sp_timestamp::Timestamp::current().as_millis())); + + let config = DryRunConfig::::default().with_timestamp_override(timestamp_override); + + self.client + .runtime_api() + .eth_estimate_gas(block_hash, tx, config) + .map_err(native_err)? + .map_err(ClientError::TransactError) + } + async fn gas_price(&self, block_hash: SubstrateBlockHash) -> Result { self.client.runtime_api().gas_price(block_hash).map_err(native_err) } diff --git a/substrate/frame/revive/rpc/src/substrate_client.rs b/substrate/frame/revive/rpc/src/substrate_client.rs index 0b4cc12298972..d434ae6af741e 100644 --- a/substrate/frame/revive/rpc/src/substrate_client.rs +++ b/substrate/frame/revive/rpc/src/substrate_client.rs @@ -96,6 +96,14 @@ pub trait SubstrateClientT: Send + Sync + Clone + 'static { state_overrides: Option, ) -> Result, crate::client::ClientError>; + /// Estimate the gas for the given transaction using binary search. + async fn estimate_gas( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result; + /// Return the current gas price. async fn gas_price( &self, diff --git a/substrate/frame/revive/rpc/src/subxt_client.rs b/substrate/frame/revive/rpc/src/subxt_client.rs index 7138f04d4c786..985cad8141702 100644 --- a/substrate/frame/revive/rpc/src/subxt_client.rs +++ b/substrate/frame/revive/rpc/src/subxt_client.rs @@ -354,6 +354,18 @@ impl SubstrateClientT for SubxtClient { .await } + async fn estimate_gas( + &self, + block_hash: SubstrateBlockHash, + tx: GenericTransaction, + block: BlockNumberOrTagOrHash, + ) -> Result { + use crate::client::runtime_api::RuntimeApi; + RuntimeApi::new(self.api.runtime_api().at(block_hash)) + .estimate_gas(tx, block) + .await + } + async fn gas_price(&self, block_hash: SubstrateBlockHash) -> Result { use crate::client::runtime_api::RuntimeApi; RuntimeApi::new(self.api.runtime_api().at(block_hash)).gas_price().await From 578c5ee2635fe00e44299cbe7ea71339743e4b42 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Fri, 24 Apr 2026 13:52:08 +0000 Subject: [PATCH 53/54] make SystemHealthRpcServerImpl opt-in via a new include_system_health: bool parameter on build_eth_rpc_module --- .../polkadot-omni-node/lib/src/common/rpc.rs | 28 +++++++++++++------ substrate/frame/revive/rpc/src/cli.rs | 17 +++++++---- 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs index a7d0ad5a5c6d2..a4ac8d319e7ca 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs @@ -140,17 +140,25 @@ mod eth_rpc { NativeSubstrateClient::new(client.clone(), pool, chain_id, false) .map_err(|e| format!("native substrate client: {e}"))?; - let eth_client = tokio::task::block_in_place(|| { - tokio::runtime::Handle::current().block_on( - build_native_inmemory_client( + let (bootstrap_tx, bootstrap_rx) = std::sync::mpsc::sync_channel(1); + spawn_handle.spawn( + "eth-rpc-bootstrap", + Some("eth-rpc"), + Box::pin(async move { + let result = build_native_inmemory_client( native_client, block_provider, DEFAULT_KEEP_LATEST_BLOCKS, false, - ), - ) - }) - .map_err(|e| format!("ETH RPC client init: {e}"))?; + ) + .await + .map_err(|e| format!("ETH RPC client init: {e}")); + let _ = bootstrap_tx.send(result); + }), + ); + let eth_client = bootstrap_rx + .recv() + .map_err(|_| "eth-rpc bootstrap task did not return".to_string())??; let eth_best = eth_client.clone(); spawn_handle.spawn( @@ -187,7 +195,11 @@ mod eth_rpc { }), ); - let eth_module = build_eth_rpc_module(false, eth_client, false)?; + // `include_system_health = false` because the parachain node already + // merges `sc_rpc::system::System` (which exposes `system_health`) + // in `sc_service::gen_rpc_module` — adding the eth-rpc one here + // would fail with a duplicate-method error and tear down the node. + let eth_module = build_eth_rpc_module(false, eth_client, false, false)?; module.merge(eth_module)?; }, Err(e) => { diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 0bb8bb9809f45..7410dc8685165 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -297,10 +297,15 @@ fn dev_accounts() -> Vec { } /// Create the JSON-RPC module from a `Client`. +/// +/// When embedded into a Substrate node that already exposes `sc_rpc::system::System` +/// (which provides its own `system_health`), pass `include_system_health = false` to +/// avoid a duplicate-method `merge` failure. Standalone `eth-rpc` should pass `true`. pub fn build_eth_rpc_module( is_dev: bool, client: Client, allow_unprotected_txs: bool, + include_system_health: bool, ) -> Result, sc_service::Error> where C: SubstrateClientT, @@ -312,13 +317,15 @@ where .with_use_pending_for_estimate_gas(is_dev) .into_rpc(); - let health_api = SystemHealthRpcServerImpl::new(client.clone()).into_rpc(); let debug_api = DebugRpcServerImpl::new(client.clone()).into_rpc(); - let polkadot_api = PolkadotRpcServerImpl::new(client).into_rpc(); + let polkadot_api = PolkadotRpcServerImpl::new(client.clone()).into_rpc(); let mut module = RpcModule::new(()); module.merge(eth_api).map_err(|e| sc_service::Error::Application(e.into()))?; - module.merge(health_api).map_err(|e| sc_service::Error::Application(e.into()))?; + if include_system_health { + let health_api = SystemHealthRpcServerImpl::new(client).into_rpc(); + module.merge(health_api).map_err(|e| sc_service::Error::Application(e.into()))?; + } module.merge(debug_api).map_err(|e| sc_service::Error::Application(e.into()))?; module .merge(polkadot_api) @@ -517,7 +524,7 @@ where let rpc_runtime = create_rpc_runtime(rpc_config.max_connections) .map_err(|e| anyhow::anyhow!("Failed to create RPC runtime: {}", e))?; - let rpc_api = build_eth_rpc_module(is_dev, client.clone(), allow_unprotected_txs)?; + let rpc_api = build_eth_rpc_module(is_dev, client.clone(), allow_unprotected_txs, true)?; let rpc_server_handle = start_rpc_servers( &rpc_config, prometheus_registry, @@ -689,7 +696,7 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { let rpc_runtime = create_rpc_runtime(rpc_config.max_connections) .map_err(|e| anyhow::anyhow!("Failed to create RPC runtime: {}", e))?; - let rpc_api = build_eth_rpc_module(is_dev, client.clone(), allow_unprotected_txs)?; + let rpc_api = build_eth_rpc_module(is_dev, client.clone(), allow_unprotected_txs, true)?; let rpc_server_handle = start_rpc_servers( &rpc_config, prometheus_registry, From 46234006c42af4a6d114a9fc5aebe8cd117887a3 Mon Sep 17 00:00:00 2001 From: Nathy-bajo Date: Mon, 27 Apr 2026 11:21:04 +0000 Subject: [PATCH 54/54] smol nits --- cumulus/polkadot-omni-node/lib/src/common/rpc.rs | 4 ---- substrate/frame/revive/rpc/src/cli.rs | 6 ------ substrate/frame/revive/rpc/src/client.rs | 2 -- substrate/frame/revive/rpc/src/receipt_provider.rs | 3 --- 4 files changed, 15 deletions(-) diff --git a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs index a4ac8d319e7ca..abf337cd00dd1 100644 --- a/cumulus/polkadot-omni-node/lib/src/common/rpc.rs +++ b/cumulus/polkadot-omni-node/lib/src/common/rpc.rs @@ -195,10 +195,6 @@ mod eth_rpc { }), ); - // `include_system_health = false` because the parachain node already - // merges `sc_rpc::system::System` (which exposes `system_health`) - // in `sc_service::gen_rpc_module` — adding the eth-rpc one here - // would fail with a duplicate-method error and tear down the node. let eth_module = build_eth_rpc_module(false, eth_client, false, false)?; module.merge(eth_module)?; }, diff --git a/substrate/frame/revive/rpc/src/cli.rs b/substrate/frame/revive/rpc/src/cli.rs index 7410dc8685165..33361f92b764f 100644 --- a/substrate/frame/revive/rpc/src/cli.rs +++ b/substrate/frame/revive/rpc/src/cli.rs @@ -500,8 +500,6 @@ where } }; - // Pre-flight: validate chain identity before spawning any background tasks. - // This converts a panic deep inside `sync_backward` into a clean, actionable error. if eth_pruning.is_archive() { let preflight = { let c = client.clone(); @@ -671,8 +669,6 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { } }; - // Pre-flight: validate chain identity before spawning any background tasks. - // Catches ChainMismatch from a stale DB and returns a clean error with recovery steps. if eth_pruning.is_archive() { let preflight = { let c = client.clone(); @@ -706,8 +702,6 @@ pub fn run(cmd: CliCommand) -> anyhow::Result<()> { None, )?; - // Archive mode: spawn the historical backward sync task. - // Note: chain identity was already validated above; this task handles ongoing sync only. if eth_pruning.is_archive() { let sync_client = client.clone(); task_manager diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 5915c3459bee7..a8910353f3210 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -458,8 +458,6 @@ impl Client { let lock = self.subscription_lock.clone(); let block_provider = self.block_provider.clone(); let subscription_gap_queue = self.subscription_gap_queue.clone(); - // Tracks the last finalized block number seen, used for gap detection. - // u32::MAX is used as a sentinel meaning "not yet seen". let last_finalized_seen = Arc::new(AtomicU32::new(u32::MAX)); self.backend diff --git a/substrate/frame/revive/rpc/src/receipt_provider.rs b/substrate/frame/revive/rpc/src/receipt_provider.rs index 105589c7eae8c..f11573467a61d 100644 --- a/substrate/frame/revive/rpc/src/receipt_provider.rs +++ b/substrate/frame/revive/rpc/src/receipt_provider.rs @@ -272,9 +272,6 @@ impl ReceiptProvider { } /// Retrieve a sync label from the `sync_state` table. - /// - /// Uses a plain `sqlx::query` (not the macro form) so that it does not require - /// a pre-populated SQLX offline query cache. pub async fn get_sync_label( &self, key: K,