diff --git a/Cargo.lock b/Cargo.lock index 4814175e44..cc40cec7a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5322,6 +5322,7 @@ dependencies = [ "fc-rpc", "fc-rpc-core", "fc-storage", + "fp-consensus", "fp-dynamic-fee", "fp-rpc", "frame-benchmarking", @@ -5382,6 +5383,7 @@ dependencies = [ "substrate-prometheus-endpoint", "subtensor-custom-rpc", "subtensor-custom-rpc-runtime-api", + "thiserror", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 8f3e35e03f..cfc12df31a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ syn = { version = "2", features = [ ] } quote = "1" proc-macro2 = { version = "1", features = ["span-locations"] } +thiserror = "1.0" walkdir = "2" subtensor-macros = { path = "support/macros" } @@ -173,6 +174,7 @@ fp-account = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", de fc-storage = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } fc-db = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } fc-consensus = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } +fp-consensus = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } fp-dynamic-fee = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } fc-api = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } fc-rpc = { git = "https://github.com/gztensor/frontier", rev = "b8e3025", default-features = false } diff --git a/node/Cargo.toml b/node/Cargo.toml index dcd3cdc0f7..0206413425 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -96,6 +96,8 @@ fc-rpc = { workspace = true } fc-rpc-core = { workspace = true } fp-rpc = { workspace = true } fc-mapping-sync = { workspace = true } +fp-consensus = { workspace = true } +thiserror = { workspace = true } # Local Dependencies node-subtensor-runtime = { path = "../runtime" } diff --git a/node/src/service.rs b/node/src/service.rs index 22e1679493..060d972b2b 100644 --- a/node/src/service.rs +++ b/node/src/service.rs @@ -1,9 +1,13 @@ //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. +use fp_consensus::{ensure_log, FindLogError}; +use fp_rpc::EthereumRuntimeRPCApi; use futures::{channel::mpsc, future, FutureExt}; use node_subtensor_runtime::{opaque::Block, RuntimeApi, TransactionConverter}; use sc_client_api::{Backend as BackendT, BlockBackend}; -use sc_consensus::{BasicQueue, BoxBlockImport}; +use sc_consensus::{ + BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxBlockImport, ImportResult, +}; use sc_consensus_grandpa::BlockNumberOps; use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging; use sc_network_sync::strategy::warp::{WarpSyncConfig, WarpSyncProvider}; @@ -11,11 +15,14 @@ use sc_service::{error::Error as ServiceError, Configuration, PartialComponents, use sc_telemetry::{log, Telemetry, TelemetryHandle, TelemetryWorker}; use sc_transaction_pool::FullPool; use sc_transaction_pool_api::OffchainTransactionPoolFactory; +use sp_api::ProvideRuntimeApi; +use sp_block_builder::BlockBuilder as BlockBuilderApi; +use sp_consensus::Error as ConsensusError; use sp_consensus_aura::sr25519::AuthorityPair as AuraPair; use sp_core::U256; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::{cell::RefCell, path::Path}; -use std::{sync::Arc, time::Duration}; +use std::{marker::PhantomData, sync::Arc, time::Duration}; use substrate_prometheus_endpoint::Registry; use crate::cli::Sealing; @@ -167,6 +174,106 @@ where }) } +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Multiple runtime Ethereum blocks, rejecting!")] + MultipleRuntimeLogs, + #[error("Runtime Ethereum block not found, rejecting!")] + NoRuntimeLog, + #[error("Cannot access the runtime at genesis, rejecting!")] + RuntimeApiCallFailed, +} + +impl From for String { + fn from(error: Error) -> String { + error.to_string() + } +} + +impl From for Error { + fn from(error: FindLogError) -> Error { + match error { + FindLogError::NotFound => Error::NoRuntimeLog, + FindLogError::MultipleLogs => Error::MultipleRuntimeLogs, + } + } +} + +impl From for ConsensusError { + fn from(error: Error) -> ConsensusError { + ConsensusError::ClientImport(error.to_string()) + } +} + +pub struct ConditionalEVMBlockImport { + inner: I, + frontier_block_import: F, + client: Arc, + _marker: PhantomData, +} + +impl Clone for ConditionalEVMBlockImport +where + B: BlockT, + I: Clone + BlockImport, + F: Clone + BlockImport, +{ + fn clone(&self) -> Self { + ConditionalEVMBlockImport { + inner: self.inner.clone(), + frontier_block_import: self.frontier_block_import.clone(), + client: self.client.clone(), + _marker: PhantomData, + } + } +} + +impl ConditionalEVMBlockImport +where + B: BlockT, + I: BlockImport, + I::Error: Into, + F: BlockImport, + F::Error: Into, + C: ProvideRuntimeApi, + C::Api: BlockBuilderApi + EthereumRuntimeRPCApi, +{ + pub fn new(inner: I, frontier_block_import: F, client: Arc) -> Self { + Self { + inner, + frontier_block_import, + client, + _marker: PhantomData, + } + } +} + +#[async_trait::async_trait] +impl BlockImport for ConditionalEVMBlockImport +where + B: BlockT, + I: BlockImport + Send + Sync, + I::Error: Into, + F: BlockImport + Send + Sync, + F::Error: Into, + C: ProvideRuntimeApi + Send + Sync, + C::Api: BlockBuilderApi + EthereumRuntimeRPCApi, +{ + type Error = ConsensusError; + + async fn check_block(&self, block: BlockCheckParams) -> Result { + self.inner.check_block(block).await.map_err(Into::into) + } + + async fn import_block(&self, block: BlockImportParams) -> Result { + // Import like Frontier, but fallback to grandpa import for errors + match ensure_log(block.header.digest()).map_err(Error::from) { + Ok(()) => self.inner.import_block(block).await.map_err(Into::into), + _ => self.inner.import_block(block).await.map_err(Into::into), + } + } +} + /// Build the import queue for the template runtime (aura + grandpa). pub fn build_aura_grandpa_import_queue( client: Arc, @@ -179,8 +286,11 @@ pub fn build_aura_grandpa_import_queue( where NumberFor: BlockNumberOps, { - let frontier_block_import = - FrontierBlockImport::new(grandpa_block_import.clone(), client.clone()); + let conditional_block_import = ConditionalEVMBlockImport::new( + grandpa_block_import.clone(), + FrontierBlockImport::new(grandpa_block_import.clone(), client.clone()), + client.clone(), + ); let slot_duration = sc_consensus_aura::slot_duration(&*client)?; let target_gas_price = eth_config.target_gas_price; @@ -197,8 +307,8 @@ where let import_queue = sc_consensus_aura::import_queue::( sc_consensus_aura::ImportQueueParams { - block_import: frontier_block_import.clone(), - justification_import: Some(Box::new(grandpa_block_import)), + block_import: conditional_block_import.clone(), + justification_import: Some(Box::new(grandpa_block_import.clone())), client, create_inherent_data_providers, spawner: &task_manager.spawn_essential_handle(), @@ -210,7 +320,7 @@ where ) .map_err::(Into::into)?; - Ok((import_queue, Box::new(frontier_block_import))) + Ok((import_queue, Box::new(conditional_block_import))) } /// Build the import queue for the template runtime (manual seal). @@ -220,16 +330,20 @@ pub fn build_manual_seal_import_queue( _eth_config: &EthConfiguration, task_manager: &TaskManager, _telemetry: Option, - _grandpa_block_import: GrandpaBlockImport, + grandpa_block_import: GrandpaBlockImport, ) -> Result<(BasicQueue, BoxBlockImport), ServiceError> { - let frontier_block_import = FrontierBlockImport::new(client.clone(), client); + let conditional_block_import = ConditionalEVMBlockImport::new( + grandpa_block_import.clone(), + FrontierBlockImport::new(grandpa_block_import.clone(), client.clone()), + client, + ); Ok(( sc_consensus_manual_seal::import_queue( - Box::new(frontier_block_import.clone()), + Box::new(conditional_block_import.clone()), &task_manager.spawn_essential_handle(), config.prometheus_registry(), ), - Box::new(frontier_block_import), + Box::new(conditional_block_import), )) }