Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions prdoc/pr_9914.prdoc
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion substrate/frame/revive/dev-node/node/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

use polkadot_sdk::{sc_cli::RunCmd, *};

#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub enum Consensus {
ManualSeal(u64),
InstantSeal,
Expand Down
38 changes: 36 additions & 2 deletions substrate/frame/revive/dev-node/node/src/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -37,6 +38,38 @@ pub struct FullDeps<C, P> {
pub client: Arc<C>,
/// Transaction pool instance.
pub pool: Arc<P>,
/// The consensus type of the node.
pub consensus: Consensus,
}

/// AutoMine JSON-RPC api.
Comment thread
marian-radu marked this conversation as resolved.
/// 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<bool>;
}

/// 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<bool> {
Comment thread
pgherveou marked this conversation as resolved.
Ok(self.is_auto_mine)
}
}

#[docify::export]
Expand All @@ -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)
Expand Down
3 changes: 2 additions & 1 deletion substrate/frame/revive/dev-node/node/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,8 @@ pub fn new_full<Network: sc_network::NetworkBackend<Block, <Block as BlockT>::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)
})
};
Expand Down
7 changes: 7 additions & 0 deletions substrate/frame/revive/rpc/src/apis/debug_apis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub trait DebugRpc {
block: BlockNumberOrTagOrHash,
tracer_config: TracerConfig,
) -> RpcResult<Trace>;

#[method(name = "debug_getAutomine")]
async fn get_automine(&self) -> RpcResult<bool>;
}

pub struct DebugRpcServerImpl {
Expand Down Expand Up @@ -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<bool> {
sc_service::Result::Ok(self.client.get_automine().await)
}
}
89 changes: 80 additions & 9 deletions substrate/frame/revive/rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 std::{collections::HashSet, ops::Range, sync::Arc, time::Duration};
use storage_api::StorageApi;
use subxt::{
backend::{
legacy::{rpc_methods::SystemHealth, LegacyRpcMethods},
Expand Down Expand Up @@ -69,8 +68,10 @@ pub type SubstrateBlockHash = HashFor<SrcChainConfig>;
/// The runtime balance type.
pub type Balance = u128;

pub type TransactHashes = HashSet<H256>;

/// 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,
Expand Down Expand Up @@ -160,6 +161,8 @@ pub struct Client {
fee_history_provider: FeeHistoryProvider,
chain_id: u64,
max_block_weight: Weight,
automine: bool,
transact_hashes_sender: Option<tokio::sync::broadcast::Sender<TransactHashes>>,
}

/// Fetch the chain ID from the substrate chain.
Expand All @@ -176,6 +179,17 @@ async fn max_block_weight(api: &OnlineClient<SrcChainConfig>) -> Result<Weight,
Ok(max_block.0)
}

/// Get the automine status from the node.
async fn get_automine(rpc_client: &RpcClient) -> bool {
match rpc_client.request::<bool>("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<u64> {
let extrinsics = block.extrinsics().await.ok()?;
Expand Down Expand Up @@ -205,6 +219,26 @@ pub async fn connect(
Ok((api, rpc_client, rpc))
}

/// Broadcast transaction hashes to subscribers.
async fn broadcast_transact_hashes(
transact_hashes_sender: &tokio::sync::broadcast::Sender<TransactHashes>,
receipts: &[ReceiptInfo],
) {
if transact_hashes_sender.receiver_count() == 0 || receipts.is_empty() {
return;
}

let transact_hashes: HashSet<_> =
receipts.iter().map(|receipt| receipt.transaction_hash).collect();

if let Err(err) = transact_hashes_sender.send(transact_hashes) {
log::warn!(
target: LOG_TARGET,
"Failed to broadcast transaction hashes. error: {err:?}",
);
}
}

impl Client {
/// Create a new client instance.
pub async fn new(
Expand All @@ -214,10 +248,19 @@ impl Client {
block_provider: SubxtBlockInfoProvider,
receipt_provider: ReceiptProvider,
) -> Result<Self, ClientError> {
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)
},)?;

// Create the transaction hashes sender if automine is enabled.
let transact_hashes_sender = automine.then(|| {
// Channel used to notify when a transaction hash is included in a block.
// Capacity of 32 should be enough since blocks are mined at a relatively low rate.
tokio::sync::broadcast::channel::<TransactHashes>(32).0
});
Comment thread
pgherveou marked this conversation as resolved.
Outdated

Ok(Self {
let client = Self {
api,
rpc_client,
rpc,
Expand All @@ -226,7 +269,11 @@ impl Client {
fee_history_provider: FeeHistoryProvider::default(),
chain_id,
max_block_weight,
})
automine,
transact_hashes_sender,
};

Ok(client)
}

/// Subscribe to past blocks executing the callback for each block in `range`.
Expand Down Expand Up @@ -324,6 +371,14 @@ impl Client {
let (signed_txs, receipts): (Vec<_>, Vec<_>) =
self.receipt_provider.insert_block_receipts(&block).await?.into_iter().unzip();

// Only broadcast for best blocks to avoid duplicate notifications.
match (subscription_type, &self.transact_hashes_sender) {
(SubscriptionType::BestBlocks, Some(sender)) => {
broadcast_transact_hashes(sender, &receipts).await;
},
_ => {},
}

Comment thread
pgherveou marked this conversation as resolved.
Outdated
let evm_block =
self.evm_block_from_receipts(&block, &receipts, signed_txs, false).await;
self.block_provider.update_latest(block, subscription_type).await;
Expand Down Expand Up @@ -682,6 +737,12 @@ impl Client {
self.max_block_weight
}

pub fn get_transact_hashes_sender(
&self,
) -> Option<tokio::sync::broadcast::Sender<TransactHashes>> {
self.transact_hashes_sender.clone()
}

/// Get the logs matching the given filter.
pub async fn logs(&self, filter: Option<Filter>) -> Result<Vec<Log>, ClientError> {
let logs =
Expand All @@ -703,4 +764,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
}
}
50 changes: 49 additions & 1 deletion substrate/frame/revive/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! The [`EthRpcServer`] RPC server implementation
#![cfg_attr(docsrs, feature(doc_cfg))]

use client::ClientError;
use client::{ClientError, TransactHashes};
use jsonrpsee::{
core::{async_trait, RpcResult},
types::{ErrorCode, ErrorObjectOwned},
Expand All @@ -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;
Expand All @@ -50,6 +51,8 @@ pub use receipt_extractor::*;
mod apis;
pub use apis::*;

use tokio::sync::broadcast;

pub const LOG_TARGET: &str = "eth-rpc";

/// An EVM RPC server implementation.
Expand Down Expand Up @@ -110,6 +113,34 @@ impl From<EthRpcError> for ErrorObjectOwned {
}
}

async fn wait_for_transaction_to_be_mined(
hash: H256,
mut transact_hashes_receiver: broadcast::Receiver<TransactHashes>,

timeout: Duration,
) -> Result<bool, String> {
tokio::time::timeout(timeout, async {
loop {
match transact_hashes_receiver.recv().await {
Ok(hashes) => {
if hashes.contains(&hash) {
return Ok(true);
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
log::warn!(target: LOG_TARGET, "Missed {n} notifications while waiting for transaction {hash:?} to be mined.");
continue;
}
Err(err) => {
return Err(format!("Failed to receive transaction hashes: error: {err}.").into());
}
}
}
})
.await
.map_err(|_| format!("Timed out waiting for transaction {hash:?} to be mined."))?
}

#[async_trait]
impl EthRpcServer for EthRpcServerImpl {
async fn net_version(&self) -> RpcResult<String> {
Expand Down Expand Up @@ -164,11 +195,28 @@ impl EthRpcServer for EthRpcServerImpl {
async fn send_raw_transaction(&self, transaction: Bytes) -> RpcResult<H256> {
let hash = H256(keccak_256(&transaction.0));
let call = subxt_client::tx().revive().eth_transact(transaction.0);

// Subscribe to transaction hashes only when automine is enabled.
let transact_hashes_receiver =
self.client.get_transact_hashes_sender().map(|transact_hashes_sender| {
transact_hashes_sender.subscribe() // each task gets its own receiver
});

// 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(hashes_receiver) = transact_hashes_receiver {
let _ = wait_for_transaction_to_be_mined(hash, hashes_receiver, Duration::from_secs(1))
.await
Comment thread
pgherveou marked this conversation as resolved.
Outdated
.map_err(|err| {
log::warn!(target : LOG_TARGET, "Waiting for tx receipt failed: {err}");
});
}

log::debug!(target: LOG_TARGET, "send_raw_transaction hash: {hash:?}");
Ok(hash)
}
Expand Down
Loading