-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Changes from all commits
8e51391
7283297
c48453b
0a923a6
c86ac57
3e2c702
3f6fa81
ccbfa3a
0ae07c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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; | ||
|
@@ -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. | ||
|
@@ -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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. still need to call 4788 etc here right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. good catch |
||
&mut db, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
@@ -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, | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.