fix(rpc): use parent block state when tracing blocks (#2574)

This commit is contained in:
Matthias Seitz
2023-05-05 19:21:09 +02:00
committed by GitHub
parent 35e2421022
commit f37d102820
5 changed files with 115 additions and 61 deletions

View File

@@ -41,15 +41,28 @@ pub fn fill_cfg_env(
cfg_env.perf_all_precompiles_have_balance = false;
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse;
}
/// Fill block environment from Block.
pub fn fill_block_env(
block_env: &mut BlockEnv,
chain_spec: &ChainSpec,
header: &Header,
after_merge: bool,
) {
let coinbase = block_coinbase(chain_spec, header, after_merge);
fill_block_env_with_coinbase(block_env, header, after_merge, coinbase);
}
/// Fill block environment with coinbase.
#[inline]
pub fn fill_block_env_with_coinbase(
block_env: &mut BlockEnv,
header: &Header,
after_merge: bool,
coinbase: Address,
) {
block_env.number = U256::from(header.number);
block_env.coinbase = block_coinbase(chain_spec, header, after_merge);
block_env.coinbase = coinbase;
block_env.timestamp = U256::from(header.timestamp);
if after_merge {
block_env.prevrandao = Some(header.mix_hash);

View File

@@ -162,11 +162,8 @@ where
.await
.err()
.unwrap();
TraceApiClient::trace_block(client, block_id).await.err().unwrap();
TraceApiClient::replay_block_transactions(client, block_id, HashSet::default())
.await
.err()
.unwrap();
TraceApiClient::trace_block(client, block_id).await.unwrap();
TraceApiClient::replay_block_transactions(client, block_id, HashSet::default()).await.unwrap();
assert!(is_unimplemented(
TraceApiClient::trace_filter(client, trace_filter).await.err().unwrap()

View File

@@ -9,7 +9,7 @@ use crate::{
};
use async_trait::async_trait;
use jsonrpsee::core::RpcResult;
use reth_primitives::{Block, BlockId, BlockNumberOrTag, Bytes, H256, U256};
use reth_primitives::{Block, BlockId, BlockNumberOrTag, Bytes, TransactionSigned, H256, U256};
use reth_provider::{BlockProvider, HeaderProvider, StateProviderBox};
use reth_revm::{
database::{State, SubState},
@@ -27,6 +27,7 @@ use reth_rpc_types::{
BlockError, CallRequest, RichBlock,
};
use revm::primitives::Env;
use revm_primitives::{BlockEnv, CfgEnv};
/// `debug` API implementation.
///
@@ -37,7 +38,6 @@ pub struct DebugApi<Client, Eth> {
client: Client,
/// The implementation of `eth` API
eth_api: Eth,
// restrict the number of concurrent calls to `debug_traceTransaction`
tracing_call_guard: TracingCallGuard,
}
@@ -58,42 +58,15 @@ where
Client: BlockProvider + HeaderProvider + 'static,
Eth: EthTransactions + 'static,
{
/// Replays the given block and returns the trace of each transaction.
///
/// This expects a rlp encoded block
///
/// Note, the parent of this block must be present, or it will fail.
pub async fn debug_trace_raw_block(
/// Trace the entire block
fn trace_block_with(
&self,
rlp_block: Bytes,
_opts: GethDebugTracingOptions,
) -> EthResult<Vec<TraceResult>> {
let block =
Block::decode(&mut rlp_block.as_ref()).map_err(BlockError::RlpDecodeRawBlock)?;
let _parent = block.parent_hash;
// TODO we need the state after the parent block
todo!()
}
/// Replays a block and returns the trace of each transaction.
pub async fn debug_trace_block(
&self,
block_id: BlockId,
at: BlockId,
transactions: Vec<TransactionSigned>,
cfg: CfgEnv,
block_env: BlockEnv,
opts: GethDebugTracingOptions,
) -> EthResult<Vec<TraceResult>> {
let block_hash = self
.client
.block_hash_for_id(block_id)?
.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
let ((cfg, block_env, at), transactions) = futures::try_join!(
self.eth_api.evm_env_at(block_hash.into()),
self.eth_api.transactions_by_block(block_hash),
)?;
let transactions = transactions.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
// replay all transactions of the block
self.eth_api.with_state_at_block(at, move |state| {
let mut results = Vec::with_capacity(transactions.len());
@@ -112,6 +85,50 @@ where
})
}
/// Replays the given block and returns the trace of each transaction.
///
/// This expects a rlp encoded block
///
/// Note, the parent of this block must be present, or it will fail.
pub async fn debug_trace_raw_block(
&self,
rlp_block: Bytes,
opts: GethDebugTracingOptions,
) -> EthResult<Vec<TraceResult>> {
let block =
Block::decode(&mut rlp_block.as_ref()).map_err(BlockError::RlpDecodeRawBlock)?;
let (cfg, block_env) = self.eth_api.evm_env_for_raw_block(&block.header).await?;
// we trace on top the block's parent block
let parent = block.parent_hash;
self.trace_block_with(parent.into(), block.body, cfg, block_env, opts)
}
/// Replays a block and returns the trace of each transaction.
pub async fn debug_trace_block(
&self,
block_id: BlockId,
opts: GethDebugTracingOptions,
) -> EthResult<Vec<TraceResult>> {
let block_hash = self
.client
.block_hash_for_id(block_id)?
.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
let ((cfg, block_env, _), block) = futures::try_join!(
self.eth_api.evm_env_at(block_hash.into()),
self.eth_api.block_by_id(block_id),
)?;
let block = block.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
// we need to get the state of the parent block because we're replaying this block on top of
// its parent block's state
let state_at = block.parent_hash;
self.trace_block_with(state_at.into(), block.body, cfg, block_env, opts)
}
/// Trace the transaction according to the provided options.
///
/// Ref: <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
@@ -128,10 +145,10 @@ where
// we need to get the state of the parent block because we're essentially replaying the
// block the transaction is included in
let parent_block = block.parent_hash;
let state_at = block.parent_hash;
let block_txs = block.body;
self.eth_api.with_state_at_block(parent_block.into(), |state| {
self.eth_api.with_state_at_block(state_at.into(), |state| {
// configure env for the target transaction
let tx = transaction.into_recovered();

View File

@@ -11,15 +11,15 @@ use async_trait::async_trait;
use reth_network_api::NetworkInfo;
use reth_primitives::{
Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredTransaction, IntoRecoveredTransaction,
Receipt, SealedBlock,
Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredTransaction, Header,
IntoRecoveredTransaction, Receipt, SealedBlock,
TransactionKind::{Call, Create},
TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, H256, U128, U256, U64,
};
use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory};
use reth_revm::{
database::{State, SubState},
env::tx_env_with_recovered,
env::{fill_block_env_with_coinbase, tx_env_with_recovered},
tracing::{TracingInspector, TracingInspectorConfig},
};
use reth_rpc_types::{
@@ -32,7 +32,7 @@ use revm::{
primitives::{BlockEnv, CfgEnv},
Inspector,
};
use revm_primitives::{utilities::create_address, Env, ResultAndState};
use revm_primitives::{utilities::create_address, Env, ResultAndState, SpecId};
/// Commonly used transaction related functions for the [EthApi] type in the `eth_` namespace
#[async_trait::async_trait]
@@ -51,12 +51,22 @@ pub trait EthTransactions: Send + Sync {
/// for.
async fn evm_env_at(&self, at: BlockId) -> EthResult<(CfgEnv, BlockEnv, BlockId)>;
/// Returns the revm evm env for the raw block header
///
/// This is used for tracing raw blocks
async fn evm_env_for_raw_block(&self, at: &Header) -> EthResult<(CfgEnv, BlockEnv)>;
/// Get all transactions in the block with the given hash.
///
/// Returns `None` if block does not exist.
async fn transactions_by_block(&self, block: H256)
-> EthResult<Option<Vec<TransactionSigned>>>;
/// Get the entire block for the given id.
///
/// Returns `None` if block does not exist.
async fn block_by_id(&self, id: BlockId) -> EthResult<Option<SealedBlock>>;
/// Get all transactions in the block with the given hash.
///
/// Returns `None` if block does not exist.
@@ -205,6 +215,16 @@ where
}
}
async fn evm_env_for_raw_block(&self, header: &Header) -> EthResult<(CfgEnv, BlockEnv)> {
// get the parent config first
let (cfg, mut block_env, _) = self.evm_env_at(header.parent_hash.into()).await?;
let after_merge = cfg.spec_id >= SpecId::MERGE;
fill_block_env_with_coinbase(&mut block_env, header, after_merge, header.beneficiary);
Ok((cfg, block_env))
}
async fn transactions_by_block(
&self,
block: H256,
@@ -212,14 +232,15 @@ where
Ok(self.cache().get_block_transactions(block).await?)
}
async fn block_by_id(&self, id: BlockId) -> EthResult<Option<SealedBlock>> {
self.block(id).await
}
async fn transactions_by_block_id(
&self,
block: BlockId,
) -> EthResult<Option<Vec<TransactionSigned>>> {
match self.client().block_hash_for_id(block)? {
None => Ok(None),
Some(hash) => self.transactions_by_block(hash).await,
}
self.block_by_id(block).await.map(|block| block.map(|block| block.body))
}
async fn transaction_by_hash(&self, hash: H256) -> EthResult<Option<TransactionSource>> {
@@ -248,9 +269,9 @@ where
async fn transaction_by_hash_at(
&self,
hash: H256,
transaction_hash: H256,
) -> EthResult<Option<(TransactionSource, BlockId)>> {
match self.transaction_by_hash(hash).await? {
match self.transaction_by_hash(transaction_hash).await? {
None => return Ok(None),
Some(tx) => {
let res = match tx {

View File

@@ -213,20 +213,26 @@ where
where
F: Fn(TransactionInfo, TracingInspector, ResultAndState) -> EthResult<R> + Send,
{
let block_hash = match self.client.block_hash_for_id(block_id)? {
Some(hash) => hash,
let ((cfg, block_env, _), block) = futures::try_join!(
self.eth_api.evm_env_at(block_id),
self.eth_api.block_by_id(block_id),
)?;
let block = match block {
Some(block) => block,
None => return Ok(None),
};
let ((cfg, block_env, at), transactions) = futures::try_join!(
self.eth_api.evm_env_at(block_hash.into()),
self.eth_api.transactions_by_block(block_hash),
)?;
let transactions = transactions.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
// we need to get the state of the parent block because we're replaying this block on top of
// its parent block's state
let state_at = block.parent_hash;
let block_hash = block.hash;
let transactions = block.body;
// replay all transactions of the block
self.eth_api
.with_state_at_block(at, move |state| {
.with_state_at_block(state_at.into(), move |state| {
let mut results = Vec::with_capacity(transactions.len());
let mut db = SubState::new(State::new(state));