From d867be141b100ff7ddc22f32de876351c57bf294 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Sat, 4 May 2024 12:23:31 +0100 Subject: [PATCH 1/9] feat(examples): evm execution exex --- Cargo.lock | 18 ++++++++++ Cargo.toml | 1 + examples/exex/evm/Cargo.toml | 21 ++++++++++++ examples/exex/evm/src/main.rs | 62 +++++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 examples/exex/evm/Cargo.toml create mode 100644 examples/exex/evm/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 1828b2a2faef..75ef8fdac654 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2911,6 +2911,24 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "exex-evm" +version = "0.0.0" +dependencies = [ + "eyre", + "futures", + "reth", + "reth-evm", + "reth-evm-ethereum", + "reth-exex", + "reth-node-api", + "reth-node-core", + "reth-node-ethereum", + "reth-primitives", + "reth-tracing", + "tokio", +] + [[package]] name = "exex-minimal" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 0aca2afbbaaa..20d913300c4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ members = [ "examples/txpool-tracing/", "examples/polygon-p2p/", "examples/custom-inspector/", + "examples/exex/evm/", "examples/exex/minimal/", "examples/exex/op-bridge/", "examples/exex/rollup/", diff --git a/examples/exex/evm/Cargo.toml b/examples/exex/evm/Cargo.toml new file mode 100644 index 000000000000..2f556d02bbb0 --- /dev/null +++ b/examples/exex/evm/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "exex-evm" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth.workspace = true +reth-evm.workspace = true +reth-evm-ethereum.workspace = true +reth-exex.workspace = true +reth-node-api.workspace = true +reth-node-core.workspace = true +reth-node-ethereum.workspace = true +reth-primitives.workspace = true +reth-tracing.workspace = true + +eyre.workspace = true +tokio.workspace = true +futures.workspace = true diff --git a/examples/exex/evm/src/main.rs b/examples/exex/evm/src/main.rs new file mode 100644 index 000000000000..7e181b385d81 --- /dev/null +++ b/examples/exex/evm/src/main.rs @@ -0,0 +1,62 @@ +use eyre::OptionExt; +use reth::{ + providers::{DatabaseProviderFactory, HistoricalStateProviderRef}, + revm::database::StateProviderDatabase, +}; +use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; +use reth_evm_ethereum::EthEvmConfig; +use reth_exex::{ExExContext, ExExEvent}; +use reth_node_api::FullNodeComponents; +use reth_node_ethereum::{EthExecutorProvider, EthereumNode}; +use reth_tracing::tracing::info; + +async fn exex(mut ctx: ExExContext) -> eyre::Result<()> { + while let Some(notification) = ctx.notifications.recv().await { + if let Some(chain) = notification.committed_chain() { + // TODO(alexey): use custom EVM config with tracer + let evm_config = EthEvmConfig::default(); + let executor_provider = EthExecutorProvider::new(ctx.config.chain.clone(), evm_config); + + let database_provider = ctx.provider().database_provider_ro()?; + let db = StateProviderDatabase::new(HistoricalStateProviderRef::new( + database_provider.tx_ref(), + chain.first().number.checked_sub(1).ok_or_eyre("block number underflow")?, + database_provider.static_file_provider().clone(), + )); + + let mut executor = executor_provider.batch_executor( + db, + ctx.config.prune_config().map(|config| config.segments).unwrap_or_default(), + ); + + for block in chain.blocks_iter() { + let td = block.header().difficulty; + executor.execute_one((&block.clone().unseal(), td).into())?; + } + + let output = executor.finalize(); + + let same_state = chain.state() == &output.into(); + info!( + chain = ?chain.range(), + %same_state, + "Executed chain" + ); + + ctx.events.send(ExExEvent::FinishedHeight(chain.tip().number))?; + } + } + Ok(()) +} + +fn main() -> eyre::Result<()> { + reth::cli::Cli::parse_args().run(|builder, _| async move { + let handle = builder + .node(EthereumNode::default()) + .install_exex("EVM", |ctx| async move { Ok(exex(ctx)) }) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) +} From e9919de3e33c5d098f4919ff45790a8acd542cdf Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 7 May 2024 13:46:56 +0100 Subject: [PATCH 2/9] add to readme --- examples/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 4c135f880feb..e13b697fe0e5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,11 +24,12 @@ to make a PR! ## ExEx | Example | Description | -|-------------------------------------------|-----------------------------------------------------------------------------------| +| ----------------------------------------- | --------------------------------------------------------------------------------- | | [Minimal ExEx](./exex/minimal) | Illustrates how to build a simple ExEx | | [OP Bridge ExEx](./exex/op-bridge) | Illustrates an ExEx that decodes Optimism deposit and withdrawal receipts from L1 | | [Rollup](./exex/rollup) | Illustrates a rollup ExEx that derives the state from L1 | | [In Memory State](./exex/in-memory-state) | Illustrates an ExEx that tracks the plain state in memory | +| [EVM](./exex/evm) | Illustrates an ExEx that re-executes every block with a custom tracing inspector | ## RPC From 16cfc46f7ad3f7106418bcd1bbaca300801bb282 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 15 May 2024 16:44:10 +0300 Subject: [PATCH 3/9] feat(storage): implement `BundleStateProvider` for `BundleStateWithReceipts` --- crates/blockchain-tree/src/bundle.rs | 6 +++- crates/blockchain-tree/src/chain.rs | 4 +-- crates/blockchain-tree/src/noop.rs | 6 ++-- crates/blockchain-tree/src/shareable.rs | 4 +-- .../bundle_state_with_receipts.rs | 19 +++++++++-- crates/storage/provider/src/providers/mod.rs | 16 ++++----- .../storage/provider/src/test_utils/mock.rs | 8 ++--- .../storage/provider/src/test_utils/noop.rs | 2 +- crates/storage/provider/src/traits/mod.rs | 5 +-- crates/storage/provider/src/traits/state.rs | 34 ++++++++++++++----- 10 files changed, 70 insertions(+), 34 deletions(-) diff --git a/crates/blockchain-tree/src/bundle.rs b/crates/blockchain-tree/src/bundle.rs index d8c93439e5b1..ccfbc2adc31c 100644 --- a/crates/blockchain-tree/src/bundle.rs +++ b/crates/blockchain-tree/src/bundle.rs @@ -1,7 +1,7 @@ //! [BundleStateDataProvider] implementations used by the tree. use reth_primitives::{BlockHash, BlockNumber, ForkBlock}; -use reth_provider::{BundleStateDataProvider, BundleStateWithReceipts}; +use reth_provider::{BundleStateDataProvider, BundleStateForkProvider, BundleStateWithReceipts}; use std::collections::BTreeMap; /// Structure that combines references of required data to be a [`BundleStateDataProvider`]. @@ -30,7 +30,9 @@ impl<'a> BundleStateDataProvider for BundleStateDataRef<'a> { self.canonical_block_hashes.get(&block_number).cloned() } +} +impl<'a> BundleStateForkProvider for BundleStateDataRef<'a> { fn canonical_fork(&self) -> ForkBlock { self.canonical_fork } @@ -57,7 +59,9 @@ impl BundleStateDataProvider for BundleStateData { fn block_hash(&self, block_number: BlockNumber) -> Option { self.parent_block_hashes.get(&block_number).cloned() } +} +impl BundleStateForkProvider for BundleStateData { fn canonical_fork(&self) -> ForkBlock { self.canonical_fork } diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs index db4b4627abe7..ce6487a060b9 100644 --- a/crates/blockchain-tree/src/chain.rs +++ b/crates/blockchain-tree/src/chain.rs @@ -21,7 +21,7 @@ use reth_primitives::{ }; use reth_provider::{ providers::{BundleStateProvider, ConsistentDbView}, - BundleStateDataProvider, BundleStateWithReceipts, Chain, ProviderError, StateRootProvider, + BundleStateWithReceipts, Chain, FullBundleStateDataProvider, ProviderError, StateRootProvider, }; use reth_revm::database::StateProviderDatabase; use reth_trie::updates::TrieUpdates; @@ -178,7 +178,7 @@ impl AppendableChain { block_validation_kind: BlockValidationKind, ) -> RethResult<(BundleStateWithReceipts, Option)> where - BSDP: BundleStateDataProvider, + BSDP: FullBundleStateDataProvider, DB: Database + Clone, E: BlockExecutorProvider, { diff --git a/crates/blockchain-tree/src/noop.rs b/crates/blockchain-tree/src/noop.rs index 776a153250bc..18423d3bb7e6 100644 --- a/crates/blockchain-tree/src/noop.rs +++ b/crates/blockchain-tree/src/noop.rs @@ -12,8 +12,8 @@ use reth_primitives::{ SealedHeader, }; use reth_provider::{ - BlockchainTreePendingStateProvider, BundleStateDataProvider, CanonStateNotificationSender, - CanonStateNotifications, CanonStateSubscriptions, + BlockchainTreePendingStateProvider, CanonStateNotificationSender, CanonStateNotifications, + CanonStateSubscriptions, FullBundleStateDataProvider, }; use std::collections::{BTreeMap, HashSet}; @@ -138,7 +138,7 @@ impl BlockchainTreePendingStateProvider for NoopBlockchainTree { fn find_pending_state_provider( &self, _block_hash: BlockHash, - ) -> Option> { + ) -> Option> { None } } diff --git a/crates/blockchain-tree/src/shareable.rs b/crates/blockchain-tree/src/shareable.rs index 77cc53c2d309..66f76b0916f1 100644 --- a/crates/blockchain-tree/src/shareable.rs +++ b/crates/blockchain-tree/src/shareable.rs @@ -17,7 +17,7 @@ use reth_primitives::{ SealedHeader, }; use reth_provider::{ - BlockchainTreePendingStateProvider, BundleStateDataProvider, CanonStateSubscriptions, + BlockchainTreePendingStateProvider, CanonStateSubscriptions, FullBundleStateDataProvider, ProviderError, }; use std::{ @@ -199,7 +199,7 @@ where fn find_pending_state_provider( &self, block_hash: BlockHash, - ) -> Option> { + ) -> Option> { trace!(target: "blockchain_tree", ?block_hash, "Finding pending state provider"); let provider = self.tree.read().post_state_data(block_hash)?; Some(Box::new(provider)) diff --git a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs index 5f6d4af3f843..a313428a14d1 100644 --- a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs +++ b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs @@ -1,4 +1,7 @@ -use crate::{providers::StaticFileProviderRWRefMut, StateChanges, StateReverts, StateWriter}; +use crate::{ + providers::StaticFileProviderRWRefMut, BundleStateDataProvider, StateChanges, StateReverts, + StateWriter, +}; use reth_db::{ cursor::{DbCursorRO, DbCursorRW}, tables, @@ -9,8 +12,8 @@ use reth_interfaces::provider::{ProviderError, ProviderResult}; use reth_primitives::{ logs_bloom, revm::compat::{into_reth_acc, into_revm_acc}, - Account, Address, BlockNumber, Bloom, Bytecode, Log, Receipt, Receipts, StaticFileSegment, - StorageEntry, B256, U256, + Account, Address, BlockHash, BlockNumber, Bloom, Bytecode, Log, Receipt, Receipts, + StaticFileSegment, StorageEntry, B256, U256, }; use reth_trie::HashedPostState; pub use revm::db::states::OriginalValuesKnown; @@ -365,6 +368,16 @@ impl StateWriter for BundleStateWithReceipts { } } +impl BundleStateDataProvider for BundleStateWithReceipts { + fn state(&self) -> &BundleStateWithReceipts { + self + } + + fn block_hash(&self, _block_number: BlockNumber) -> Option { + None + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 8a06f0c0d204..f9969a9500dc 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -1,11 +1,11 @@ use crate::{ AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, - BlockSource, BlockchainTreePendingStateProvider, BundleStateDataProvider, CanonChainTracker, - CanonStateNotifications, CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, - DatabaseProviderFactory, EvmEnvProvider, HeaderProvider, ProviderError, PruneCheckpointReader, - ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateProviderBox, - StateProviderFactory, StaticFileProviderFactory, TransactionVariant, TransactionsProvider, - TreeViewer, WithdrawalsProvider, + BlockSource, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotifications, + CanonStateSubscriptions, ChainSpecProvider, ChangeSetReader, DatabaseProviderFactory, + EvmEnvProvider, FullBundleStateDataProvider, HeaderProvider, ProviderError, + PruneCheckpointReader, ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, + StateProviderBox, StateProviderFactory, StaticFileProviderFactory, TransactionVariant, + TransactionsProvider, TreeViewer, WithdrawalsProvider, }; use reth_db::{ database::Database, @@ -638,7 +638,7 @@ where fn pending_with_provider( &self, - bundle_state_data: Box, + bundle_state_data: Box, ) -> ProviderResult { let canonical_fork = bundle_state_data.canonical_fork(); trace!(target: "providers::blockchain", ?canonical_fork, "Returning post state provider"); @@ -871,7 +871,7 @@ where fn find_pending_state_provider( &self, block_hash: BlockHash, - ) -> Option> { + ) -> Option> { self.tree.find_pending_state_provider(block_hash) } } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 96e137ac6fdf..893bd052d0b3 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -1,9 +1,9 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, - BundleStateDataProvider, ChainSpecProvider, ChangeSetReader, EvmEnvProvider, HeaderProvider, - ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, - TransactionVariant, TransactionsProvider, WithdrawalsProvider, + ChainSpecProvider, ChangeSetReader, EvmEnvProvider, FullBundleStateDataProvider, + HeaderProvider, ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, + StateRootProvider, TransactionVariant, TransactionsProvider, WithdrawalsProvider, }; use parking_lot::Mutex; use reth_db::models::{AccountBeforeTx, StoredBlockBodyIndices}; @@ -660,7 +660,7 @@ impl StateProviderFactory for MockEthProvider { fn pending_with_provider<'a>( &'a self, - _bundle_state_data: Box, + _bundle_state_data: Box, ) -> ProviderResult { Ok(Box::new(self.clone())) } diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 373dc4d7e5f0..02890eaf1935 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -419,7 +419,7 @@ impl StateProviderFactory for NoopProvider { fn pending_with_provider<'a>( &'a self, - _bundle_state_data: Box, + _bundle_state_data: Box, ) -> ProviderResult { Ok(Box::new(*self)) } diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index c966cd9efa09..10984240a5d8 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -35,8 +35,9 @@ pub use receipts::{ReceiptProvider, ReceiptProviderIdExt}; mod state; pub use state::{ - BlockchainTreePendingStateProvider, BundleStateDataProvider, StateProvider, StateProviderBox, - StateProviderFactory, StateWriter, + BlockchainTreePendingStateProvider, BundleStateDataProvider, BundleStateForkProvider, + FullBundleStateDataProvider, StateProvider, StateProviderBox, StateProviderFactory, + StateWriter, }; mod trie; diff --git a/crates/storage/provider/src/traits/state.rs b/crates/storage/provider/src/traits/state.rs index 4cb74dec61cb..ac72d52f9f44 100644 --- a/crates/storage/provider/src/traits/state.rs +++ b/crates/storage/provider/src/traits/state.rs @@ -187,7 +187,7 @@ pub trait StateProviderFactory: BlockIdReader + Send + Sync { /// Used to inspect or execute transaction on the pending state. fn pending_with_provider( &self, - bundle_state_data: Box, + bundle_state_data: Box, ) -> ProviderResult; } @@ -201,7 +201,7 @@ pub trait BlockchainTreePendingStateProvider: Send + Sync { fn pending_state_provider( &self, block_hash: BlockHash, - ) -> ProviderResult> { + ) -> ProviderResult> { self.find_pending_state_provider(block_hash) .ok_or(ProviderError::StateForHashNotFound(block_hash)) } @@ -210,28 +210,46 @@ pub trait BlockchainTreePendingStateProvider: Send + Sync { fn find_pending_state_provider( &self, block_hash: BlockHash, - ) -> Option>; + ) -> Option>; } -/// Post state data needs for execution on it. -/// This trait is used to create a state provider over pending state. +/// Post state data needed for execution on it. /// -/// Pending state contains: +/// State contains: /// * [`BundleStateWithReceipts`] contains all changed of accounts and storage of pending chain /// * block hashes of pending chain and canonical blocks. -/// * canonical fork, the block on what pending chain was forked from. #[auto_impl(&, Box)] pub trait BundleStateDataProvider: Send + Sync { /// Return post state fn state(&self) -> &BundleStateWithReceipts; /// Return block hash by block number of pending or canonical chain. fn block_hash(&self, block_number: BlockNumber) -> Option; - /// return canonical fork, the block on what post state was forked from. +} + +/// Fork data needed for execution on it. +/// +/// It contains a canonical fork, the block on what pending chain was forked from. +#[auto_impl(&, Box)] +pub trait BundleStateForkProvider { + /// Return canonical fork, the block on what post state was forked from. /// /// Needed to create state provider. fn canonical_fork(&self) -> BlockNumHash; } +/// Full post state data needed for execution on it. +/// This trait is used to create a state provider over pending state. +/// +/// This trait is a combination of [`BundleStateDataProvider`] and [`BundleStateForkProvider`]. +/// +/// Pending state contains: +/// * [`BundleStateWithReceipts`] contains all changed of accounts and storage of pending chain +/// * block hashes of pending chain and canonical blocks. +/// * canonical fork, the block on what pending chain was forked from. +pub trait FullBundleStateDataProvider: BundleStateDataProvider + BundleStateForkProvider {} + +impl FullBundleStateDataProvider for T where T: BundleStateDataProvider + BundleStateForkProvider {} + /// A helper trait for [BundleStateWithReceipts] to write state and receipts to storage. pub trait StateWriter { /// Write the data and receipts to the database or static files if `static_file_producer` is From 78f09bdd9941894ac0f0f03d8a6de40891265e60 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 15 May 2024 16:48:29 +0300 Subject: [PATCH 4/9] add a comment about None --- .../provider/src/bundle_state/bundle_state_with_receipts.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs index a313428a14d1..947c6609b961 100644 --- a/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs +++ b/crates/storage/provider/src/bundle_state/bundle_state_with_receipts.rs @@ -373,6 +373,7 @@ impl BundleStateDataProvider for BundleStateWithReceipts { self } + /// Always returns [None] because we don't have any information about the block header. fn block_hash(&self, _block_number: BlockNumber) -> Option { None } From 000caa6a4f78b9c31590c64914aef77dedb83322 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 15 May 2024 17:28:05 +0300 Subject: [PATCH 5/9] use bundle state provider --- examples/exex/evm/src/main.rs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/examples/exex/evm/src/main.rs b/examples/exex/evm/src/main.rs index 7e181b385d81..744bb347eefa 100644 --- a/examples/exex/evm/src/main.rs +++ b/examples/exex/evm/src/main.rs @@ -1,6 +1,8 @@ use eyre::OptionExt; use reth::{ - providers::{DatabaseProviderFactory, HistoricalStateProviderRef}, + providers::{ + providers::BundleStateProvider, DatabaseProviderFactory, HistoricalStateProviderRef, + }, revm::database::StateProviderDatabase, }; use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; @@ -18,11 +20,15 @@ async fn exex(mut ctx: ExExContext) -> eyre::Res let executor_provider = EthExecutorProvider::new(ctx.config.chain.clone(), evm_config); let database_provider = ctx.provider().database_provider_ro()?; - let db = StateProviderDatabase::new(HistoricalStateProviderRef::new( - database_provider.tx_ref(), - chain.first().number.checked_sub(1).ok_or_eyre("block number underflow")?, - database_provider.static_file_provider().clone(), - )); + let provider = BundleStateProvider::new( + HistoricalStateProviderRef::new( + database_provider.tx_ref(), + chain.first().number.checked_sub(1).ok_or_eyre("block number underflow")?, + database_provider.static_file_provider().clone(), + ), + chain.state(), + ); + let db = StateProviderDatabase::new(&provider); let mut executor = executor_provider.batch_executor( db, From ab23869a16fabbc7f973b487a524904b2eb1c0e8 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 10 Jun 2024 18:39:42 +0100 Subject: [PATCH 6/9] call balanceOf --- Cargo.lock | 1 + crates/primitives/src/transaction/mod.rs | 6 + examples/exex/evm/Cargo.toml | 2 + examples/exex/evm/erc20.json | 222 +++++++++++++++++++++++ examples/exex/evm/src/main.rs | 84 ++++++--- 5 files changed, 294 insertions(+), 21 deletions(-) create mode 100644 examples/exex/evm/erc20.json diff --git a/Cargo.lock b/Cargo.lock index a4ffa13abbe5..28fd84e369de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2928,6 +2928,7 @@ dependencies = [ name = "exex-evm" version = "0.0.0" dependencies = [ + "alloy-sol-types", "eyre", "futures", "reth", diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index c441a3277411..54624bd7b648 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -132,6 +132,12 @@ pub enum Transaction { Deposit(TxDeposit), } +impl AsRef for Transaction { + fn as_ref(&self) -> &Self { + self + } +} + // === impl Transaction === impl Transaction { diff --git a/examples/exex/evm/Cargo.toml b/examples/exex/evm/Cargo.toml index 2f556d02bbb0..89dd4e1323d7 100644 --- a/examples/exex/evm/Cargo.toml +++ b/examples/exex/evm/Cargo.toml @@ -16,6 +16,8 @@ reth-node-ethereum.workspace = true reth-primitives.workspace = true reth-tracing.workspace = true +alloy-sol-types = { workspace = true, features = ["json"] } + eyre.workspace = true tokio.workspace = true futures.workspace = true diff --git a/examples/exex/evm/erc20.json b/examples/exex/evm/erc20.json new file mode 100644 index 000000000000..06b572ddc211 --- /dev/null +++ b/examples/exex/evm/erc20.json @@ -0,0 +1,222 @@ +[ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_spender", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_from", + "type": "address" + }, + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "name": "balance", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_to", + "type": "address" + }, + { + "name": "_value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_owner", + "type": "address" + }, + { + "name": "_spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "from", + "type": "address" + }, + { + "indexed": true, + "name": "to", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } +] diff --git a/examples/exex/evm/src/main.rs b/examples/exex/evm/src/main.rs index 744bb347eefa..8235db602a40 100644 --- a/examples/exex/evm/src/main.rs +++ b/examples/exex/evm/src/main.rs @@ -1,3 +1,6 @@ +use std::collections::{HashMap, HashSet}; + +use alloy_sol_types::{sol, SolEventInterface, SolInterface}; use eyre::OptionExt; use reth::{ providers::{ @@ -5,20 +8,22 @@ use reth::{ }, revm::database::StateProviderDatabase, }; -use reth_evm::execute::{BatchExecutor, BlockExecutorProvider}; +use reth_evm::ConfigureEvm; use reth_evm_ethereum::EthEvmConfig; use reth_exex::{ExExContext, ExExEvent}; use reth_node_api::FullNodeComponents; -use reth_node_ethereum::{EthExecutorProvider, EthereumNode}; +use reth_node_ethereum::EthereumNode; +use reth_primitives::{revm::env::fill_tx_env, Address, Transaction, TxKind, TxLegacy, U256}; use reth_tracing::tracing::info; +sol!(ERC20, "erc20.json"); +use crate::ERC20::{ERC20Calls, ERC20Events}; + async fn exex(mut ctx: ExExContext) -> eyre::Result<()> { + let evm_config = EthEvmConfig::default(); + while let Some(notification) = ctx.notifications.recv().await { if let Some(chain) = notification.committed_chain() { - // TODO(alexey): use custom EVM config with tracer - let evm_config = EthEvmConfig::default(); - let executor_provider = EthExecutorProvider::new(ctx.config.chain.clone(), evm_config); - let database_provider = ctx.provider().database_provider_ro()?; let provider = BundleStateProvider::new( HistoricalStateProviderRef::new( @@ -30,24 +35,61 @@ async fn exex(mut ctx: ExExContext) -> eyre::Res ); let db = StateProviderDatabase::new(&provider); - let mut executor = executor_provider.batch_executor( - db, - ctx.config.prune_config().map(|config| config.segments).unwrap_or_default(), - ); + let mut evm = evm_config.evm(db); - for block in chain.blocks_iter() { - let td = block.header().difficulty; - executor.execute_one((&block.clone().unseal(), td).into())?; - } + // Collect all ERC20 contract addresses and addresses that had balance changes + let erc20_contracts_and_addresses = chain + .block_receipts_iter() + .flatten() + .flatten() + .flat_map(|receipt| receipt.logs.iter()) + .fold(HashMap::>::new(), |mut acc, log| { + if let Ok(ERC20Events::Transfer(ERC20::Transfer { from, to, value: _ })) = + ERC20Events::decode_raw_log(log.topics(), &log.data.data, true) + { + acc.entry(log.address).or_default().extend([from, to]); + } - let output = executor.finalize(); + acc + }); - let same_state = chain.state() == &output.into(); - info!( - chain = ?chain.range(), - %same_state, - "Executed chain" - ); + let txs = erc20_contracts_and_addresses.into_iter().map(|(contract, addresses)| { + ( + contract, + addresses.into_iter().map(move |address| { + ( + address, + Transaction::Legacy(TxLegacy { + gas_limit: 50_000_000, + to: TxKind::Call(contract), + input: ERC20Calls::balanceOf(ERC20::balanceOfCall { + _owner: address, + }) + .abi_encode() + .into(), + ..Default::default() + }), + ) + }), + ) + }); + + for (contract, balance_txs) in txs { + for (address, balance_tx) in balance_txs { + fill_tx_env(evm.tx_mut(), &balance_tx, Address::ZERO); + let result = evm.transact()?.result; + let output = result.output().ok_or_eyre("no output for balance tx")?; + if let Some(balance) = U256::try_from_be_slice(output) { + info!(?contract, ?address, %balance, "Balance updated"); + } else { + info!( + ?contract, + ?address, + "Balance updated but too large to fit into U256" + ); + } + } + } ctx.events.send(ExExEvent::FinishedHeight(chain.tip().number))?; } From 7ebc608321cb4fc5d672e3a6106c62a5aafa4280 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 11 Jun 2024 19:50:34 +0100 Subject: [PATCH 7/9] calculate the decimals --- examples/exex/evm/src/main.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/examples/exex/evm/src/main.rs b/examples/exex/evm/src/main.rs index 8235db602a40..431b5baafa03 100644 --- a/examples/exex/evm/src/main.rs +++ b/examples/exex/evm/src/main.rs @@ -31,7 +31,7 @@ async fn exex(mut ctx: ExExContext) -> eyre::Res chain.first().number.checked_sub(1).ok_or_eyre("block number underflow")?, database_provider.static_file_provider().clone(), ), - chain.state(), + chain.execution_outcome(), ); let db = StateProviderDatabase::new(&provider); @@ -53,9 +53,17 @@ async fn exex(mut ctx: ExExContext) -> eyre::Res acc }); + // Construct transactions to check the decimals of ERC20 contracts and balances of + //addresses that had balance changes let txs = erc20_contracts_and_addresses.into_iter().map(|(contract, addresses)| { ( contract, + Transaction::Legacy(TxLegacy { + gas_limit: 50_000_000, + to: TxKind::Call(contract), + input: ERC20Calls::decimals(ERC20::decimalsCall {}).abi_encode().into(), + ..Default::default() + }), addresses.into_iter().map(move |address| { ( address, @@ -74,12 +82,22 @@ async fn exex(mut ctx: ExExContext) -> eyre::Res ) }); - for (contract, balance_txs) in txs { + for (contract, decimals_tx, balance_txs) in txs { + fill_tx_env(evm.tx_mut(), &decimals_tx, Address::ZERO); + let result = evm.transact()?.result; + let output = result.output().ok_or_eyre("no output for decimals tx")?; + let decimals = U256::try_from_be_slice(output); + for (address, balance_tx) in balance_txs { fill_tx_env(evm.tx_mut(), &balance_tx, Address::ZERO); let result = evm.transact()?.result; let output = result.output().ok_or_eyre("no output for balance tx")?; - if let Some(balance) = U256::try_from_be_slice(output) { + let balance = U256::try_from_be_slice(output); + + if let Some((balance, decimals)) = balance.zip(decimals) { + let divisor = U256::from(10).pow(decimals); + let (balance, rem) = balance.div_rem(divisor); + let balance = f64::from(balance) + f64::from(rem) / f64::from(divisor); info!(?contract, ?address, %balance, "Balance updated"); } else { info!( From bf1e7e79e092b550290488413838f23cd5173c64 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 11 Jun 2024 19:53:01 +0100 Subject: [PATCH 8/9] update readme --- examples/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/README.md b/examples/README.md index 14e264398cff..fb75740e83dc 100644 --- a/examples/README.md +++ b/examples/README.md @@ -29,7 +29,7 @@ to make a PR! | [OP Bridge ExEx](./exex/op-bridge) | Illustrates an ExEx that decodes Optimism deposit and withdrawal receipts from L1 | | [Rollup](./exex/rollup) | Illustrates a rollup ExEx that derives the state from L1 | | [In Memory State](./exex/in-memory-state) | Illustrates an ExEx that tracks the plain state in memory | -| [EVM](./exex/evm) | Illustrates an ExEx that re-executes every block with a custom tracing inspector | +| [EVM](./exex/evm) | Illustrates an ExEx that checks ERC20 balances | ## RPC @@ -58,11 +58,11 @@ to make a PR! ## P2P -| Example | Description | -| --------------------------- | ----------------------------------------------------------------- | -| [Manual P2P](./manual-p2p) | Illustrates how to connect and communicate with a peer | -| [Polygon P2P](./polygon-p2p) | Illustrates how to connect and communicate with a peer on Polygon | -| [BSC P2P](./bsc-p2p) | Illustrates how to connect and communicate with a peer on Binance Smart Chain | +| Example | Description | +| ---------------------------- | ----------------------------------------------------------------------------- | +| [Manual P2P](./manual-p2p) | Illustrates how to connect and communicate with a peer | +| [Polygon P2P](./polygon-p2p) | Illustrates how to connect and communicate with a peer on Polygon | +| [BSC P2P](./bsc-p2p) | Illustrates how to connect and communicate with a peer on Binance Smart Chain | ## Misc From 45cb19c65f1901be2fc8bf33380f324debef5bbe Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 11 Jun 2024 19:53:50 +0100 Subject: [PATCH 9/9] minify erc20.json --- examples/exex/evm/erc20.json | 223 +---------------------------------- 1 file changed, 1 insertion(+), 222 deletions(-) diff --git a/examples/exex/evm/erc20.json b/examples/exex/evm/erc20.json index 06b572ddc211..a3de7b8f3898 100644 --- a/examples/exex/evm/erc20.json +++ b/examples/exex/evm/erc20.json @@ -1,222 +1 @@ -[ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_spender", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_from", - "type": "address" - }, - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [ - { - "name": "", - "type": "uint8" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "name": "balance", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [ - { - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "name": "_to", - "type": "address" - }, - { - "name": "_value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "name": "_owner", - "type": "address" - }, - { - "name": "_spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "payable": true, - "stateMutability": "payable", - "type": "fallback" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "name": "from", - "type": "address" - }, - { - "indexed": true, - "name": "to", - "type": "address" - }, - { - "indexed": false, - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - } -] +[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]