diff --git a/prdoc/pr_9914.prdoc b/prdoc/pr_9914.prdoc new file mode 100644 index 0000000000000..b5414b287faf9 --- /dev/null +++ b/prdoc/pr_9914.prdoc @@ -0,0 +1,12 @@ +title: ' Wait for transaction receipt if instant seal is enabled' +doc: +- audience: Runtime Dev + description: "Fixes https://github.com/paritytech/contract-issues/issues/165\n\n\ + The main changes in this PR are:\n\n1. Add a new API to revive-dev-node to check\ + \ whether the node has instant seal enabled.\n2. Add a new debug API to eth-rpc\ + \ to check whether the node has instant seal enabled. (optional)\n3. Query and\ + \ cache the node\u2019s instant seal status during eth-rpc initialization.\n4.\ + \ If instant seal is enabled, wait for the transaction receipt to be available" +crates: +- name: pallet-revive-eth-rpc + bump: patch diff --git a/substrate/frame/revive/dev-node/node/src/cli.rs b/substrate/frame/revive/dev-node/node/src/cli.rs index a2d84e1069575..1d480067465d0 100644 --- a/substrate/frame/revive/dev-node/node/src/cli.rs +++ b/substrate/frame/revive/dev-node/node/src/cli.rs @@ -17,7 +17,7 @@ use polkadot_sdk::{sc_cli::RunCmd, *}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum Consensus { ManualSeal(u64), InstantSeal, diff --git a/substrate/frame/revive/dev-node/node/src/rpc.rs b/substrate/frame/revive/dev-node/node/src/rpc.rs index 1369cf9381763..48e435825f49f 100644 --- a/substrate/frame/revive/dev-node/node/src/rpc.rs +++ b/substrate/frame/revive/dev-node/node/src/rpc.rs @@ -22,7 +22,8 @@ #![warn(missing_docs)] -use jsonrpsee::RpcModule; +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}, @@ -37,6 +38,38 @@ pub struct FullDeps { pub client: Arc, /// Transaction pool instance. pub pool: Arc

, + /// The consensus type of the node. + pub consensus: Consensus, +} + +/// AutoMine JSON-RPC api. +/// Automine is a feature of the Hardhat Network where a new block is automatically mined after each +/// transaction. +#[rpc(server, client)] +pub trait AutoMineRpc { + /// API to get the automine status. + #[method(name = "getAutomine")] + fn get_automine(&self) -> RpcResult; +} + +/// Implementation of the AutoMine RPC api. +pub struct AutoMineRpcImpl { + /// Whether the node is running in auto-mine mode. + is_auto_mine: bool, +} + +impl AutoMineRpcImpl { + /// Create new `AutoMineRpcImpl` instance. + pub fn new(consensus: Consensus) -> Self { + Self { is_auto_mine: matches!(consensus, Consensus::InstantSeal) } + } +} + +impl AutoMineRpcServer for AutoMineRpcImpl { + /// Returns `true` if block production is set to `instant`. + fn get_automine(&self) -> RpcResult { + Ok(self.is_auto_mine) + } } #[docify::export] @@ -58,8 +91,9 @@ where { use polkadot_sdk::substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); - let FullDeps { client, pool } = deps; + let FullDeps { client, pool, consensus } = deps; + module.merge(AutoMineRpcImpl::new(consensus).into_rpc())?; module.merge(System::new(client.clone(), pool.clone()).into_rpc())?; Ok(module) diff --git a/substrate/frame/revive/dev-node/node/src/service.rs b/substrate/frame/revive/dev-node/node/src/service.rs index 4f53173627d70..27cd09269b784 100644 --- a/substrate/frame/revive/dev-node/node/src/service.rs +++ b/substrate/frame/revive/dev-node/node/src/service.rs @@ -168,7 +168,8 @@ pub fn new_full::Ha let pool = transaction_pool.clone(); Box::new(move |_| { - let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone() }; + let deps = + crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), consensus }; crate::rpc::create_full(deps).map_err(Into::into) }) }; diff --git a/substrate/frame/revive/rpc/src/apis/debug_apis.rs b/substrate/frame/revive/rpc/src/apis/debug_apis.rs index 5d2c61458c55a..6704d74b13f8d 100644 --- a/substrate/frame/revive/rpc/src/apis/debug_apis.rs +++ b/substrate/frame/revive/rpc/src/apis/debug_apis.rs @@ -56,6 +56,9 @@ pub trait DebugRpc { block: BlockNumberOrTagOrHash, tracer_config: TracerConfig, ) -> RpcResult; + + #[method(name = "debug_getAutomine")] + async fn get_automine(&self) -> RpcResult; } pub struct DebugRpcServerImpl { @@ -115,4 +118,8 @@ impl DebugRpcServer for DebugRpcServerImpl { let TracerConfig { config, timeout } = tracer_config; with_timeout(timeout, self.client.trace_call(transaction, block, config)).await } + + async fn get_automine(&self) -> RpcResult { + sc_service::Result::Ok(self.client.get_automine().await) + } } diff --git a/substrate/frame/revive/rpc/src/client.rs b/substrate/frame/revive/rpc/src/client.rs index 36945ba5bc1bf..0477f3d0b7f5d 100644 --- a/substrate/frame/revive/rpc/src/client.rs +++ b/substrate/frame/revive/rpc/src/client.rs @@ -20,9 +20,6 @@ mod runtime_api; mod storage_api; -use runtime_api::RuntimeApi; -use storage_api::StorageApi; - use crate::{ subxt_client::{self, revive::calls::types::EthTransact, SrcChainConfig}, BlockInfoProvider, BlockTag, FeeHistoryProvider, ReceiptProvider, SubxtBlockInfoProvider, @@ -37,9 +34,11 @@ use pallet_revive::{ }, EthTransactError, }; +use runtime_api::RuntimeApi; use sp_runtime::traits::Block as BlockT; use sp_weights::Weight; use std::{ops::Range, sync::Arc, time::Duration}; +use storage_api::StorageApi; use subxt::{ backend::{ legacy::{rpc_methods::SystemHealth, LegacyRpcMethods}, @@ -70,7 +69,7 @@ pub type SubstrateBlockHash = HashFor; pub type Balance = u128; /// The subscription type used to listen to new blocks. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub enum SubscriptionType { /// Subscribe to best blocks. BestBlocks, @@ -160,6 +159,11 @@ pub struct Client { 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 transaction hashes that are included in a + /// block, when automine is enabled. + tx_notifier: Option>, } /// Fetch the chain ID from the substrate chain. @@ -176,6 +180,17 @@ async fn max_block_weight(api: &OnlineClient) -> Result 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 + }, + } +} + /// Extract the block timestamp. async fn extract_block_timestamp(block: &SubstrateBlock) -> Option { let extrinsics = block.extrinsics().await.ok()?; @@ -214,10 +229,12 @@ impl Client { block_provider: SubxtBlockInfoProvider, receipt_provider: ReceiptProvider, ) -> Result { - let (chain_id, max_block_weight) = - tokio::try_join!(chain_id(&api), max_block_weight(&api))?; + let (chain_id, max_block_weight, automine) = + tokio::try_join!(chain_id(&api), max_block_weight(&api), async { + Ok(get_automine(&rpc_client).await) + },)?; - Ok(Self { + let client = Self { api, rpc_client, rpc, @@ -226,7 +243,11 @@ impl Client { fee_history_provider: FeeHistoryProvider::default(), chain_id, max_block_weight, - }) + automine, + tx_notifier: automine.then(|| tokio::sync::broadcast::channel::(10).0), + }; + + Ok(client) } /// Subscribe to past blocks executing the callback for each block in `range`. @@ -329,6 +350,15 @@ impl Client { self.block_provider.update_latest(block, subscription_type).await; self.fee_history_provider.update_fee_history(&evm_block, &receipts).await; + + // Only broadcast for best blocks to avoid duplicate notifications. + match (subscription_type, &self.tx_notifier) { + (SubscriptionType::BestBlocks, Some(sender)) if sender.receiver_count() > 0 => + for receipt in &receipts { + let _ = sender.send(receipt.transaction_hash); + }, + _ => {}, + } Ok(()) }) .await @@ -682,6 +712,11 @@ impl Client { self.max_block_weight } + /// Get the block notifier, if automine is enabled. + pub fn tx_notifier(&self) -> Option> { + self.tx_notifier.clone() + } + /// Get the logs matching the given filter. pub async fn logs(&self, filter: Option) -> Result, ClientError> { let logs = @@ -703,4 +738,14 @@ impl Client { .fee_history(block_count, latest_block.number(), reward_percentiles) .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 + } } diff --git a/substrate/frame/revive/rpc/src/lib.rs b/substrate/frame/revive/rpc/src/lib.rs index 3cdb55f6f081a..686a2549b8822 100644 --- a/substrate/frame/revive/rpc/src/lib.rs +++ b/substrate/frame/revive/rpc/src/lib.rs @@ -26,6 +26,7 @@ use pallet_revive::evm::*; use sp_arithmetic::Permill; use sp_core::{keccak_256, H160, H256, U256}; use thiserror::Error; +use tokio::time::Duration; pub mod cli; pub mod client; @@ -164,11 +165,34 @@ impl EthRpcServer for EthRpcServerImpl { async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult { let hash = H256(keccak_256(&transaction.0)); let call = subxt_client::tx().revive().eth_transact(transaction.0); + + // Subscribe to new block only when automine is enabled. + let receiver = self.client.tx_notifier().map(|sender| sender.subscribe()); + + // Submit the transaction self.client.submit(call).await.map_err(|err| { log::debug!(target: LOG_TARGET, "submit call failed: {err:?}"); err })?; + // Wait for the transaction to be included in a block if automine is enabled + if let Some(mut receiver) = receiver { + if let Err(err) = tokio::time::timeout(Duration::from_millis(500), async { + loop { + if let Ok(mined_hash) = receiver.recv().await { + if mined_hash == hash { + log::debug!(target: LOG_TARGET, "{hash:} was included in a block"); + break; + } + } + } + }) + .await + { + log::debug!(target: LOG_TARGET, "timeout waiting for new block: {err:?}"); + } + } + log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}"); Ok(hash) }