Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): debug_executionWitness #9249

Merged
merged 9 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions crates/rpc/rpc-api/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use reth_rpc_types::{
},
Bundle, RichBlock, StateContext, TransactionRequest,
};
use std::collections::HashMap;

/// Debug rpc interface.
#[cfg_attr(not(feature = "client"), rpc(server, namespace = "debug"))]
Expand Down Expand Up @@ -132,6 +133,18 @@ pub trait DebugApi {
opts: Option<GethDebugTracingCallOptions>,
) -> RpcResult<Vec<Vec<GethTrace>>>;

/// The `debug_executionWitness` method allows for re-execution of a block with the purpose of
/// generating an execution witness. The witness comprises of a map of all hashed trie nodes
/// to their preimages that were required during the execution of the block, including during
/// state root recomputation.
///
/// The first and only argument is the block number or block hash.
#[method(name = "executionWitness")]
async fn debug_execution_witness(
&self,
block: BlockNumberOrTag,
) -> RpcResult<HashMap<B256, Bytes>>;

/// Sets the logging backtrace location. When a backtrace location is set and a log message is
/// emitted at that location, the stack of the goroutine executing the log statement will
/// be printed to stderr.
Expand Down
1 change: 1 addition & 0 deletions crates/rpc/rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ reth-rpc-eth-types.workspace = true
reth-rpc-server-types.workspace = true
reth-node-api.workspace = true
reth-network-types.workspace = true
reth-trie.workspace = true

# eth
alloy-dyn-abi.workspace = true
Expand Down
120 changes: 114 additions & 6 deletions crates/rpc/rpc/src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
use std::sync::Arc;

use alloy_rlp::{Decodable, Encodable};
use async_trait::async_trait;
use jsonrpsee::core::RpcResult;
use reth_chainspec::EthereumHardforks;
use reth_evm::ConfigureEvmEnv;
use reth_evm::{system_calls::pre_block_beacon_root_contract_call, ConfigureEvmEnv};
use reth_primitives::{
Address, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSignedEcRecovered, B256, U256,
};
use reth_provider::{
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory,
TransactionVariant,
BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProofProvider,
StateProviderFactory, TransactionVariant,
};
use reth_revm::database::StateProviderDatabase;
use reth_rpc_api::DebugApiServer;
Expand All @@ -29,14 +27,18 @@ use reth_rpc_types::{
BlockError, Bundle, RichBlock, StateContext, TransactionRequest,
};
use reth_tasks::pool::BlockingTaskGuard;
use reth_trie::{HashedPostState, HashedStorage};
use revm::{
db::CacheDB,
db::{states::bundle_state::BundleRetention, CacheDB},
primitives::{db::DatabaseCommit, BlockEnv, CfgEnvWithHandlerCfg, Env, EnvWithHandlerCfg},
StateBuilder,
};
use revm_inspectors::tracing::{
js::{JsInspector, TransactionContext},
FourByteInspector, MuxInspector, TracingInspector, TracingInspectorConfig,
};
use revm_primitives::{keccak256, HashMap};
use std::sync::Arc;
use tokio::sync::{AcquireError, OwnedSemaphorePermit};

/// `debug` API implementation.
Expand Down Expand Up @@ -550,6 +552,103 @@ where
.await
}

/// The `debug_executionWitness` method allows for re-execution of a block with the purpose of
/// generating an execution witness. The witness comprises of a map of all hashed trie nodes
/// to their preimages that were required during the execution of the block, including during
/// state root recomputation.
pub async fn debug_execution_witness(
&self,
block_id: BlockNumberOrTag,
) -> Result<HashMap<B256, Bytes>, Eth::Error> {
let ((cfg, block_env, _), maybe_block) = futures::try_join!(
self.inner.eth_api.evm_env_at(block_id.into()),
self.inner.eth_api.block_with_senders(block_id.into()),
)?;
let block = maybe_block.ok_or(EthApiError::UnknownBlockNumber)?;

let this = self.clone();

self.inner
.eth_api
.spawn_with_state_at_block(block.parent_hash.into(), move |state| {
let evm_config = Call::evm_config(this.eth_api()).clone();
let mut db = StateBuilder::new()
.with_database(StateProviderDatabase::new(state))
.with_bundle_update()
.build();

pre_block_beacon_root_contract_call(
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reminder to self: Need 4788 call here

Copy link
Member

@Rjected Rjected Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wondering if we can eventually use the block executor traits here, as there will be more system contract calls and maintaining a copy of block execution logic in debug APIs seems brittle

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I also specifically need this for Optimism execution, so that would be ideal. Glad to circle back around to this before we merge, just have to get it working first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still need to call 4788 etc here right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

&mut db,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one more thing, this doesn't check if the target block is post merge

do we need to call any other systemcalls or just 4788?

&evm_config,
&this.inner.provider.chain_spec(),
&cfg,
&block_env,
block.timestamp,
block.number,
block.parent_beacon_block_root,
)
.map_err(|err| EthApiError::Internal(err.into()))?;

// Re-execute all of the transactions in the block to load all touched accounts into
// the cache DB.
for tx in block.raw_transactions() {
let tx_envelope = TransactionSignedEcRecovered::decode(&mut tx.as_ref())
.map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?;
let env = EnvWithHandlerCfg {
env: Env::boxed(
cfg.cfg_env.clone(),
block_env.clone(),
evm_config.tx_env(&tx_envelope),
),
handler_cfg: cfg.handler_cfg,
};

let (res, _) = this.inner.eth_api.transact(&mut db, env)?;
db.commit(res.state);
}

// Merge all state transitions
db.merge_transitions(BundleRetention::Reverts);

// Take the bundle state
let bundle_state = db.take_bundle();

// Grab all account proofs for the data accessed during block execution.
//
// Note: We grab *all* accounts in the cache here, as the `BundleState` prunes
// referenced accounts + storage slots.
let mut hashed_state = HashedPostState::from_bundle_state(&bundle_state.state);
for (address, account) in db.cache.accounts {
let hashed_address = keccak256(address);
hashed_state.accounts.insert(
hashed_address,
account.account.as_ref().map(|a| a.info.clone().into()),
);

let storage = hashed_state
.storages
.entry(hashed_address)
.or_insert_with(|| HashedStorage::new(account.status.was_destroyed()));

if let Some(account) = account.account {
for (slot, value) in account.storage {
let hashed_slot = keccak256(B256::from(slot));
storage.storage.insert(hashed_slot, value);
}
}
}

// Generate an execution witness for the aggregated state of accessed accounts.
// Destruct the cache database to retrieve the state provider.
let state_provider = db.database.into_inner();
let witness = state_provider
.witness(HashedPostState::default(), hashed_state)
.map_err(Into::into)?;
Ok(witness)
rkrasiuk marked this conversation as resolved.
Show resolved Hide resolved
})
.await
}

/// Executes the configured transaction with the environment on the given database.
///
/// Returns the trace frame and the state that got updated after executing the transaction.
Expand Down Expand Up @@ -806,6 +905,15 @@ where
.map_err(Into::into)
}

/// Handler for `debug_executionWitness`
async fn debug_execution_witness(
&self,
block: BlockNumberOrTag,
) -> RpcResult<HashMap<B256, Bytes>> {
let _permit = self.acquire_trace_permit().await;
Self::debug_execution_witness(self, block).await.map_err(Into::into)
}

/// Handler for `debug_traceCall`
async fn debug_trace_call(
&self,
Expand Down
Loading