From 63db481c2a1459ae2e56a38fff3cf3e87f7940ea Mon Sep 17 00:00:00 2001 From: Tim Beiko Date: Mon, 12 Jun 2023 10:54:31 -0700 Subject: [PATCH 001/216] Small typo (#3112) --- book/intro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/intro.md b/book/intro.md index 51090d198a52..a1325e4bc624 100644 --- a/book/intro.md +++ b/book/intro.md @@ -80,7 +80,7 @@ We intend to also audit / fuzz the EVM & parts of the codebase. Please reach out Here are some useful sections to jump to: - Install Reth by following the [guide](./installation/installation.md). -- Sync your node on any [official network](./run/run_a_node.md). +- Sync your node on any [official network](./run/run-a-node.md). - View [statistics and metrics](./run/observability.md) about your node. - Query the [JSON-RPC](./api/api.md) using Foundry's `cast` or `curl`. - Set up your [development environment and contribute](./contribute.md)! From adf4ae1246509f436189d3ea80c7ed273e0b5083 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 12 Jun 2023 13:55:15 -0400 Subject: [PATCH 002/216] book: fix book installation link (#3113) --- book/run/mainnet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/run/mainnet.md b/book/run/mainnet.md index 5ef6a17a026a..1a7c01b6d07b 100644 --- a/book/run/mainnet.md +++ b/book/run/mainnet.md @@ -53,7 +53,7 @@ In the meantime, consider setting up [observability](./observability.md) to moni -[installation]: ./../installation//installation.md +[installation]: ./../installation/installation.md [docs]: https://github.com/paradigmxyz/reth/tree/main/docs [metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics.md#current-metrics From cfdd88d3923afa55a1aaad47af9621b9d97bce5c Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 12 Jun 2023 20:13:11 +0200 Subject: [PATCH 003/216] feat(rpc): add `BlockOverrides` in `trace_*` (#3102) --- crates/rpc/rpc-api/src/trace.rs | 5 ++++- crates/rpc/rpc/src/trace.rs | 35 ++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/crates/rpc/rpc-api/src/trace.rs b/crates/rpc/rpc-api/src/trace.rs index 745aacf8b2b2..ab4f36758dee 100644 --- a/crates/rpc/rpc-api/src/trace.rs +++ b/crates/rpc/rpc-api/src/trace.rs @@ -1,8 +1,9 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{BlockId, Bytes, H256}; use reth_rpc_types::{ + state::StateOverride, trace::{filter::TraceFilter, parity::*}, - CallRequest, Index, + BlockOverrides, CallRequest, Index, }; use std::collections::HashSet; @@ -17,6 +18,8 @@ pub trait TraceApi { call: CallRequest, trace_types: HashSet, block_id: Option, + state_overrides: Option, + block_overrides: Option>, ) -> RpcResult; /// Performs multiple call traces on top of the same block. i.e. transaction n will be executed diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 77cf0e3bdefd..81d0e39e520e 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -2,7 +2,7 @@ use crate::{ eth::{ cache::EthStateCache, error::{EthApiError, EthResult}, - revm_utils::{inspect, prepare_call_env}, + revm_utils::{inspect, prepare_call_env, EvmOverrides}, utils::recover_raw_transaction, EthTransactions, }, @@ -20,8 +20,9 @@ use reth_revm::{ }; use reth_rpc_api::TraceApiServer; use reth_rpc_types::{ + state::StateOverride, trace::{filter::TraceFilter, parity::*}, - BlockError, CallRequest, Index, TransactionInfo, + BlockError, BlockOverrides, CallRequest, Index, TransactionInfo, }; use reth_tasks::TaskSpawner; use revm::primitives::Env; @@ -100,9 +101,17 @@ where call: CallRequest, trace_types: HashSet, block_id: Option, + state_overrides: Option, + block_overrides: Option>, ) -> EthResult { self.on_blocking_task(|this| async move { - this.try_trace_call(call, trace_types, block_id).await + this.try_trace_call( + call, + trace_types, + block_id, + EvmOverrides::new(state_overrides, block_overrides), + ) + .await }) .await } @@ -112,16 +121,14 @@ where call: CallRequest, trace_types: HashSet, block_id: Option, + overrides: EvmOverrides, ) -> EthResult { let at = block_id.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); let config = tracing_config(&trace_types); let mut inspector = TracingInspector::new(config); - let (res, _) = self - .inner - .eth_api - .inspect_call_at(call, at, Default::default(), &mut inspector) - .await?; + let (res, _) = + self.inner.eth_api.inspect_call_at(call, at, overrides, &mut inspector).await?; let trace_res = inspector.into_parity_builder().into_trace_results(res.result, &trace_types); @@ -388,9 +395,19 @@ where call: CallRequest, trace_types: HashSet, block_id: Option, + state_overrides: Option, + block_overrides: Option>, ) -> Result { let _permit = self.acquire_trace_permit().await; - Ok(TraceApi::trace_call(self, call, trace_types, block_id).await?) + Ok(TraceApi::trace_call( + self, + call, + trace_types, + block_id, + state_overrides, + block_overrides, + ) + .await?) } /// Handler for `trace_callMany` From f55d88b8c4c78c6a42c2d31dc08659e7a608e31e Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 12 Jun 2023 23:37:58 +0100 Subject: [PATCH 004/216] refactor: remove `Transaction` and add `DatabaseProvider` to stages (#3034) Co-authored-by: Georgios Konstantopoulos --- .gitignore | 5 +- Cargo.lock | 1 + bin/reth/src/chain/import.rs | 2 +- bin/reth/src/db/mod.rs | 2 +- bin/reth/src/debug_cmd/execution.rs | 18 +- bin/reth/src/debug_cmd/merkle.rs | 33 +- bin/reth/src/node/mod.rs | 2 +- bin/reth/src/stage/drop.rs | 2 +- bin/reth/src/stage/dump/execution.rs | 47 +- bin/reth/src/stage/dump/hashing_account.rs | 35 +- bin/reth/src/stage/dump/hashing_storage.rs | 39 +- bin/reth/src/stage/dump/merkle.rs | 62 +- bin/reth/src/stage/dump/mod.rs | 2 +- bin/reth/src/stage/run.rs | 19 +- bin/reth/src/stage/unwind.rs | 18 +- bin/reth/src/utils.rs | 8 +- crates/blockchain-tree/src/blockchain_tree.rs | 23 +- crates/consensus/beacon/src/engine/mod.rs | 76 +- crates/staged-sync/Cargo.toml | 1 + crates/staged-sync/src/utils/init.rs | 21 +- crates/stages/benches/criterion.rs | 10 +- .../stages/benches/setup/account_hashing.rs | 5 +- crates/stages/benches/setup/mod.rs | 32 +- crates/stages/src/error.rs | 6 + crates/stages/src/lib.rs | 4 +- crates/stages/src/pipeline/builder.rs | 7 +- crates/stages/src/pipeline/mod.rs | 52 +- crates/stages/src/sets.rs | 2 +- crates/stages/src/stage.rs | 17 +- crates/stages/src/stages/bodies.rs | 20 +- crates/stages/src/stages/execution.rs | 196 ++- crates/stages/src/stages/finish.rs | 6 +- crates/stages/src/stages/hashing_account.rs | 50 +- crates/stages/src/stages/hashing_storage.rs | 30 +- crates/stages/src/stages/headers.rs | 44 +- .../src/stages/index_account_history.rs | 59 +- .../src/stages/index_storage_history.rs | 60 +- crates/stages/src/stages/merkle.rs | 78 +- crates/stages/src/stages/sender_recovery.rs | 32 +- crates/stages/src/stages/total_difficulty.rs | 21 +- crates/stages/src/stages/tx_lookup.rs | 22 +- crates/stages/src/test_utils/runner.rs | 21 +- crates/stages/src/test_utils/stage.rs | 6 +- crates/stages/src/test_utils/test_db.rs | 25 +- crates/storage/db/src/abstraction/database.rs | 6 +- crates/storage/db/src/abstraction/mock.rs | 2 +- crates/storage/provider/src/lib.rs | 16 +- .../provider/src/providers/database/mod.rs | 9 +- .../src/providers/database/provider.rs | 1441 +++++++++++++++- .../storage/provider/src/test_utils/blocks.rs | 6 +- crates/storage/provider/src/traits/account.rs | 22 +- crates/storage/provider/src/traits/mod.rs | 2 +- crates/storage/provider/src/transaction.rs | 1509 +---------------- crates/trie/src/trie.rs | 139 +- crates/trie/src/trie_cursor/account_cursor.rs | 10 +- crates/trie/src/trie_cursor/storage_cursor.rs | 13 +- crates/trie/src/walker.rs | 19 +- testing/ef-tests/src/cases/blockchain_test.rs | 20 +- 58 files changed, 2326 insertions(+), 2109 deletions(-) diff --git a/.gitignore b/.gitignore index 96bb78b92e3a..3184275d0fa1 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,7 @@ data/ proptest-regressions/ # Release artifacts -dist/ \ No newline at end of file +dist/ + +# VSCode +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 93eb307168ac..222fe94c252c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5428,6 +5428,7 @@ dependencies = [ "reth-db", "reth-discv4", "reth-downloaders", + "reth-interfaces", "reth-net-nat", "reth-network", "reth-network-api", diff --git a/bin/reth/src/chain/import.rs b/bin/reth/src/chain/import.rs index 6233eca86c72..ca4904fccb62 100644 --- a/bin/reth/src/chain/import.rs +++ b/bin/reth/src/chain/import.rs @@ -177,7 +177,7 @@ impl ImportCommand { }, )), ) - .build(db); + .build(db, self.chain.clone()); let events = pipeline.events().map(Into::into); diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index 30b0b2ec45a0..eff705a164a1 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -100,7 +100,7 @@ impl Command { reth_db::mdbx::EnvKind::RW, )?; - let mut tool = DbTool::new(&db)?; + let mut tool = DbTool::new(&db, self.chain.clone())?; match self.command { // TODO: We'll need to add this on the DB trait. diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index 0e1db2ab98d1..bb2a1cecfb4e 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -1,6 +1,6 @@ //! Command for debugging execution. use crate::{ - args::{get_secret_key, NetworkArgs}, + args::{get_secret_key, utils::genesis_value_parser, NetworkArgs}, dirs::{DataDirPath, MaybePlatformPath}, node::events, runner::CliContext, @@ -26,17 +26,15 @@ use reth_interfaces::{ use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256}; -use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase, Transaction}; +use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase}; use reth_staged_sync::utils::init::{init_db, init_genesis}; - -use crate::args::utils::genesis_value_parser; use reth_stages::{ sets::DefaultStages, stages::{ ExecutionStage, ExecutionStageThresholds, HeaderSyncMode, SenderRecoveryStage, TotalDifficultyStage, }, - Pipeline, StageSet, + Pipeline, PipelineError, StageSet, }; use reth_tasks::TaskExecutor; use std::{ @@ -146,7 +144,7 @@ impl Command { ExecutionStageThresholds { max_blocks: None, max_changes: None }, )), ) - .build(db); + .build(db, self.chain.clone()); Ok(pipeline) } @@ -252,6 +250,8 @@ impl Command { } let mut current_max_block = latest_block_number; + let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); + while current_max_block < self.to { let next_block = current_max_block + 1; let target_block = self.to.min(current_max_block + self.interval); @@ -266,8 +266,10 @@ impl Command { // Unwind the pipeline without committing. { - let tx = Transaction::new(db.as_ref())?; - tx.take_block_and_execution_range(&self.chain, next_block..=target_block)?; + shareable_db + .provider_rw() + .map_err(PipelineError::Interface)? + .take_block_and_execution_range(&self.chain, next_block..=target_block)?; } // Update latest block diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index 193f4f331c85..c05fc6c54b94 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -9,14 +9,14 @@ use reth_primitives::{ stage::{StageCheckpoint, StageId}, ChainSpec, }; -use reth_provider::Transaction; +use reth_provider::ShareableDatabase; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ AccountHashingStage, ExecutionStage, ExecutionStageThresholds, MerkleStage, StorageHashingStage, }, - ExecInput, Stage, + ExecInput, PipelineError, Stage, }; use std::sync::Arc; @@ -68,10 +68,11 @@ impl Command { std::fs::create_dir_all(&db_path)?; let db = Arc::new(init_db(db_path)?); - let mut tx = Transaction::new(db.as_ref())?; + let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); + let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; let execution_checkpoint_block = - tx.get_stage_checkpoint(StageId::Execution)?.unwrap_or_default().block_number; + provider_rw.get_stage_checkpoint(StageId::Execution)?.unwrap_or_default().block_number; assert!(execution_checkpoint_block < self.to, "Nothing to run"); // Check if any of hashing or merkle stages aren't on the same block number as @@ -79,7 +80,7 @@ impl Command { let should_reset_stages = [StageId::AccountHashing, StageId::StorageHashing, StageId::MerkleExecute] .into_iter() - .map(|stage_id| tx.get_stage_checkpoint(stage_id)) + .map(|stage_id| provider_rw.get_stage_checkpoint(stage_id)) .collect::, _>>()? .into_iter() .map(Option::unwrap_or_default) @@ -109,7 +110,7 @@ impl Command { execution_stage .execute( - &mut tx, + &mut provider_rw, ExecInput { target: Some(block), checkpoint: block.checked_sub(1).map(StageCheckpoint::new), @@ -121,7 +122,7 @@ impl Command { while !account_hashing_done { let output = account_hashing_stage .execute( - &mut tx, + &mut provider_rw, ExecInput { target: Some(block), checkpoint: progress.map(StageCheckpoint::new), @@ -135,7 +136,7 @@ impl Command { while !storage_hashing_done { let output = storage_hashing_stage .execute( - &mut tx, + &mut provider_rw, ExecInput { target: Some(block), checkpoint: progress.map(StageCheckpoint::new), @@ -147,7 +148,7 @@ impl Command { let incremental_result = merkle_stage .execute( - &mut tx, + &mut provider_rw, ExecInput { target: Some(block), checkpoint: progress.map(StageCheckpoint::new), @@ -157,29 +158,33 @@ impl Command { if incremental_result.is_err() { tracing::warn!(target: "reth::cli", block, "Incremental calculation failed, retrying from scratch"); - let incremental_account_trie = tx + let incremental_account_trie = provider_rw + .tx_ref() .cursor_read::()? .walk_range(..)? .collect::, _>>()?; - let incremental_storage_trie = tx + let incremental_storage_trie = provider_rw + .tx_ref() .cursor_dup_read::()? .walk_range(..)? .collect::, _>>()?; let clean_input = ExecInput { target: Some(block), checkpoint: None }; loop { - let clean_result = merkle_stage.execute(&mut tx, clean_input).await; + let clean_result = merkle_stage.execute(&mut provider_rw, clean_input).await; assert!(clean_result.is_ok(), "Clean state root calculation failed"); if clean_result.unwrap().done { break } } - let clean_account_trie = tx + let clean_account_trie = provider_rw + .tx_ref() .cursor_read::()? .walk_range(..)? .collect::, _>>()?; - let clean_storage_trie = tx + let clean_storage_trie = provider_rw + .tx_ref() .cursor_dup_read::()? .walk_range(..)? .collect::, _>>()?; diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 8877e81ab5fb..17163ed09c6f 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -689,7 +689,7 @@ impl Command { }, )), ) - .build(db); + .build(db, self.chain.clone()); Ok(pipeline) } diff --git a/bin/reth/src/stage/drop.rs b/bin/reth/src/stage/drop.rs index 482f17c3c2c1..8b4bf28365c8 100644 --- a/bin/reth/src/stage/drop.rs +++ b/bin/reth/src/stage/drop.rs @@ -59,7 +59,7 @@ impl Command { let db = Env::::open(db_path.as_ref(), reth_db::mdbx::EnvKind::RW)?; - let tool = DbTool::new(&db)?; + let tool = DbTool::new(&db, self.chain.clone())?; tool.db.update(|tx| { match &self.stage { diff --git a/bin/reth/src/stage/dump/execution.rs b/bin/reth/src/stage/dump/execution.rs index c4c313541ed3..8af0e225e8ec 100644 --- a/bin/reth/src/stage/dump/execution.rs +++ b/bin/reth/src/stage/dump/execution.rs @@ -4,10 +4,11 @@ use eyre::Result; use reth_db::{ cursor::DbCursorRO, database::Database, table::TableImporter, tables, transaction::DbTx, }; -use reth_primitives::{stage::StageCheckpoint, MAINNET}; -use reth_provider::Transaction; +use reth_primitives::{stage::StageCheckpoint, ChainSpec}; +use reth_provider::ShareableDatabase; +use reth_revm::Factory; use reth_stages::{stages::ExecutionStage, Stage, UnwindInput}; -use std::{ops::DerefMut, path::PathBuf}; +use std::{path::PathBuf, sync::Arc}; use tracing::info; pub(crate) async fn dump_execution_stage( @@ -17,14 +18,14 @@ pub(crate) async fn dump_execution_stage( output_db: &PathBuf, should_run: bool, ) -> Result<()> { - let (output_db, tip_block_number) = setup::(from, to, output_db, db_tool)?; + let (output_db, tip_block_number) = setup(from, to, output_db, db_tool)?; - import_tables_with_range::(&output_db, db_tool, from, to)?; + import_tables_with_range(&output_db, db_tool, from, to)?; - unwind_and_copy::(db_tool, from, tip_block_number, &output_db).await?; + unwind_and_copy(db_tool, from, tip_block_number, &output_db).await?; if should_run { - dry_run(output_db, to, from).await?; + dry_run(db_tool.chain.clone(), output_db, to, from).await?; } Ok(()) @@ -93,13 +94,14 @@ async fn unwind_and_copy( tip_block_number: u64, output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { - let mut unwind_tx = Transaction::new(db_tool.db)?; + let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); + let mut provider = shareable_db.provider_rw()?; - let mut exec_stage = ExecutionStage::new_with_factory(reth_revm::Factory::new(MAINNET.clone())); + let mut exec_stage = ExecutionStage::new_with_factory(Factory::new(db_tool.chain.clone())); exec_stage .unwind( - &mut unwind_tx, + &mut provider, UnwindInput { unwind_to: from, checkpoint: StageCheckpoint::new(tip_block_number), @@ -108,31 +110,32 @@ async fn unwind_and_copy( ) .await?; - let unwind_inner_tx = unwind_tx.deref_mut(); + let unwind_inner_tx = provider.into_tx(); - output_db.update(|tx| tx.import_dupsort::(unwind_inner_tx))??; - output_db.update(|tx| tx.import_table::(unwind_inner_tx))??; - output_db.update(|tx| tx.import_table::(unwind_inner_tx))??; - - unwind_tx.drop()?; + output_db + .update(|tx| tx.import_dupsort::(&unwind_inner_tx))??; + output_db.update(|tx| tx.import_table::(&unwind_inner_tx))??; + output_db.update(|tx| tx.import_table::(&unwind_inner_tx))??; Ok(()) } /// Try to re-execute the stage without committing -async fn dry_run( - output_db: reth_db::mdbx::Env, +async fn dry_run( + chain: Arc, + output_db: DB, to: u64, from: u64, ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage. [dry-run]"); - let mut tx = Transaction::new(&output_db)?; - let mut exec_stage = ExecutionStage::new_with_factory(reth_revm::Factory::new(MAINNET.clone())); + let shareable_db = ShareableDatabase::new(&output_db, chain.clone()); + let mut provider = shareable_db.provider_rw()?; + let mut exec_stage = ExecutionStage::new_with_factory(Factory::new(chain.clone())); exec_stage .execute( - &mut tx, + &mut provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), @@ -140,8 +143,6 @@ async fn dry_run( ) .await?; - tx.drop()?; - info!(target: "reth::cli", "Success."); Ok(()) diff --git a/bin/reth/src/stage/dump/hashing_account.rs b/bin/reth/src/stage/dump/hashing_account.rs index 642fa525a663..d63a14cc82c1 100644 --- a/bin/reth/src/stage/dump/hashing_account.rs +++ b/bin/reth/src/stage/dump/hashing_account.rs @@ -2,10 +2,10 @@ use super::setup; use crate::utils::DbTool; use eyre::Result; use reth_db::{database::Database, table::TableImporter, tables}; -use reth_primitives::{stage::StageCheckpoint, BlockNumber}; -use reth_provider::Transaction; +use reth_primitives::{stage::StageCheckpoint, BlockNumber, ChainSpec}; +use reth_provider::ShareableDatabase; use reth_stages::{stages::AccountHashingStage, Stage, UnwindInput}; -use std::{ops::DerefMut, path::PathBuf}; +use std::{path::PathBuf, sync::Arc}; use tracing::info; pub(crate) async fn dump_hashing_account_stage( @@ -15,17 +15,17 @@ pub(crate) async fn dump_hashing_account_stage( output_db: &PathBuf, should_run: bool, ) -> Result<()> { - let (output_db, tip_block_number) = setup::(from, to, output_db, db_tool)?; + let (output_db, tip_block_number) = setup(from, to, output_db, db_tool)?; // Import relevant AccountChangeSets output_db.update(|tx| { tx.import_table_with_range::(&db_tool.db.tx()?, Some(from), to) })??; - unwind_and_copy::(db_tool, from, tip_block_number, &output_db).await?; + unwind_and_copy(db_tool, from, tip_block_number, &output_db).await?; if should_run { - dry_run(output_db, to, from).await?; + dry_run(db_tool.chain.clone(), output_db, to, from).await?; } Ok(()) @@ -38,12 +38,13 @@ async fn unwind_and_copy( tip_block_number: u64, output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { - let mut unwind_tx = Transaction::new(db_tool.db)?; + let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); + let mut provider = shareable_db.provider_rw()?; let mut exec_stage = AccountHashingStage::default(); exec_stage .unwind( - &mut unwind_tx, + &mut provider, UnwindInput { unwind_to: from, checkpoint: StageCheckpoint::new(tip_block_number), @@ -51,24 +52,24 @@ async fn unwind_and_copy( }, ) .await?; - let unwind_inner_tx = unwind_tx.deref_mut(); + let unwind_inner_tx = provider.into_tx(); - output_db.update(|tx| tx.import_table::(unwind_inner_tx))??; - - unwind_tx.drop()?; + output_db.update(|tx| tx.import_table::(&unwind_inner_tx))??; Ok(()) } /// Try to re-execute the stage straightaway -async fn dry_run( - output_db: reth_db::mdbx::Env, +async fn dry_run( + chain: Arc, + output_db: DB, to: u64, from: u64, ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage."); - let mut tx = Transaction::new(&output_db)?; + let shareable_db = ShareableDatabase::new(&output_db, chain); + let mut provider = shareable_db.provider_rw()?; let mut exec_stage = AccountHashingStage { clean_threshold: 1, // Forces hashing from scratch ..Default::default() @@ -78,7 +79,7 @@ async fn dry_run( while !exec_output { exec_output = exec_stage .execute( - &mut tx, + &mut provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), @@ -88,8 +89,6 @@ async fn dry_run( .done; } - tx.drop()?; - info!(target: "reth::cli", "Success."); Ok(()) diff --git a/bin/reth/src/stage/dump/hashing_storage.rs b/bin/reth/src/stage/dump/hashing_storage.rs index 6529541be813..6e717544c7ad 100644 --- a/bin/reth/src/stage/dump/hashing_storage.rs +++ b/bin/reth/src/stage/dump/hashing_storage.rs @@ -2,10 +2,10 @@ use super::setup; use crate::utils::DbTool; use eyre::Result; use reth_db::{database::Database, table::TableImporter, tables}; -use reth_primitives::stage::StageCheckpoint; -use reth_provider::Transaction; +use reth_primitives::{stage::StageCheckpoint, ChainSpec}; +use reth_provider::ShareableDatabase; use reth_stages::{stages::StorageHashingStage, Stage, UnwindInput}; -use std::{ops::DerefMut, path::PathBuf}; +use std::{path::PathBuf, sync::Arc}; use tracing::info; pub(crate) async fn dump_hashing_storage_stage( @@ -15,12 +15,12 @@ pub(crate) async fn dump_hashing_storage_stage( output_db: &PathBuf, should_run: bool, ) -> Result<()> { - let (output_db, tip_block_number) = setup::(from, to, output_db, db_tool)?; + let (output_db, tip_block_number) = setup(from, to, output_db, db_tool)?; - unwind_and_copy::(db_tool, from, tip_block_number, &output_db).await?; + unwind_and_copy(db_tool, from, tip_block_number, &output_db).await?; if should_run { - dry_run(output_db, to, from).await?; + dry_run(db_tool.chain.clone(), output_db, to, from).await?; } Ok(()) @@ -33,12 +33,14 @@ async fn unwind_and_copy( tip_block_number: u64, output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { - let mut unwind_tx = Transaction::new(db_tool.db)?; + let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); + let mut provider = shareable_db.provider_rw()?; + let mut exec_stage = StorageHashingStage::default(); exec_stage .unwind( - &mut unwind_tx, + &mut provider, UnwindInput { unwind_to: from, checkpoint: StageCheckpoint::new(tip_block_number), @@ -46,26 +48,27 @@ async fn unwind_and_copy( }, ) .await?; - let unwind_inner_tx = unwind_tx.deref_mut(); + let unwind_inner_tx = provider.into_tx(); // TODO optimize we can actually just get the entries we need for both these tables - output_db.update(|tx| tx.import_dupsort::(unwind_inner_tx))??; - output_db.update(|tx| tx.import_dupsort::(unwind_inner_tx))??; - - unwind_tx.drop()?; + output_db + .update(|tx| tx.import_dupsort::(&unwind_inner_tx))??; + output_db.update(|tx| tx.import_dupsort::(&unwind_inner_tx))??; Ok(()) } /// Try to re-execute the stage straightaway -async fn dry_run( - output_db: reth_db::mdbx::Env, +async fn dry_run( + chain: Arc, + output_db: DB, to: u64, from: u64, ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage."); - let mut tx = Transaction::new(&output_db)?; + let shareable_db = ShareableDatabase::new(&output_db, chain); + let mut provider = shareable_db.provider_rw()?; let mut exec_stage = StorageHashingStage { clean_threshold: 1, // Forces hashing from scratch ..Default::default() @@ -75,7 +78,7 @@ async fn dry_run( while !exec_output { exec_output = exec_stage .execute( - &mut tx, + &mut provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), @@ -85,8 +88,6 @@ async fn dry_run( .done; } - tx.drop()?; - info!(target: "reth::cli", "Success."); Ok(()) diff --git a/bin/reth/src/stage/dump/merkle.rs b/bin/reth/src/stage/dump/merkle.rs index 385afd3a2a45..3eb38283be12 100644 --- a/bin/reth/src/stage/dump/merkle.rs +++ b/bin/reth/src/stage/dump/merkle.rs @@ -2,8 +2,8 @@ use super::setup; use crate::utils::DbTool; use eyre::Result; use reth_db::{database::Database, table::TableImporter, tables}; -use reth_primitives::{stage::StageCheckpoint, BlockNumber, MAINNET}; -use reth_provider::Transaction; +use reth_primitives::{stage::StageCheckpoint, BlockNumber, ChainSpec}; +use reth_provider::ShareableDatabase; use reth_stages::{ stages::{ AccountHashingStage, ExecutionStage, ExecutionStageThresholds, MerkleStage, @@ -11,7 +11,7 @@ use reth_stages::{ }, Stage, UnwindInput, }; -use std::{ops::DerefMut, path::PathBuf}; +use std::{path::PathBuf, sync::Arc}; use tracing::info; pub(crate) async fn dump_merkle_stage( @@ -21,7 +21,7 @@ pub(crate) async fn dump_merkle_stage( output_db: &PathBuf, should_run: bool, ) -> Result<()> { - let (output_db, tip_block_number) = setup::(from, to, output_db, db_tool)?; + let (output_db, tip_block_number) = setup(from, to, output_db, db_tool)?; output_db.update(|tx| { tx.import_table_with_range::(&db_tool.db.tx()?, Some(from), to) @@ -31,10 +31,10 @@ pub(crate) async fn dump_merkle_stage( tx.import_table_with_range::(&db_tool.db.tx()?, Some(from), to) })??; - unwind_and_copy::(db_tool, (from, to), tip_block_number, &output_db).await?; + unwind_and_copy(db_tool, (from, to), tip_block_number, &output_db).await?; if should_run { - dry_run(output_db, to, from).await?; + dry_run(db_tool.chain.clone(), output_db, to, from).await?; } Ok(()) @@ -48,7 +48,9 @@ async fn unwind_and_copy( output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { let (from, to) = range; - let mut unwind_tx = Transaction::new(db_tool.db)?; + let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); + let mut provider = shareable_db.provider_rw()?; + let unwind = UnwindInput { unwind_to: from, checkpoint: StageCheckpoint::new(tip_block_number), @@ -58,20 +60,21 @@ async fn unwind_and_copy( reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)) }; // Unwind hashes all the way to FROM - StorageHashingStage::default().unwind(&mut unwind_tx, unwind).await.unwrap(); - AccountHashingStage::default().unwind(&mut unwind_tx, unwind).await.unwrap(); - MerkleStage::default_unwind().unwind(&mut unwind_tx, unwind).await?; + StorageHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); + AccountHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); + + MerkleStage::default_unwind().unwind(&mut provider, unwind).await?; // Bring Plainstate to TO (hashing stage execution requires it) let mut exec_stage = ExecutionStage::new( - reth_revm::Factory::new(MAINNET.clone()), + reth_revm::Factory::new(db_tool.chain.clone()), ExecutionStageThresholds { max_blocks: Some(u64::MAX), max_changes: None }, ); exec_stage .unwind( - &mut unwind_tx, + &mut provider, UnwindInput { unwind_to: to, checkpoint: StageCheckpoint::new(tip_block_number), @@ -81,47 +84,48 @@ async fn unwind_and_copy( .await?; // Bring hashes to TO + AccountHashingStage { clean_threshold: u64::MAX, commit_threshold: u64::MAX } - .execute(&mut unwind_tx, execute_input) + .execute(&mut provider, execute_input) .await .unwrap(); StorageHashingStage { clean_threshold: u64::MAX, commit_threshold: u64::MAX } - .execute(&mut unwind_tx, execute_input) + .execute(&mut provider, execute_input) .await .unwrap(); - let unwind_inner_tx = unwind_tx.deref_mut(); + let unwind_inner_tx = provider.into_tx(); // TODO optimize we can actually just get the entries we need - output_db.update(|tx| tx.import_dupsort::(unwind_inner_tx))??; + output_db.update(|tx| tx.import_dupsort::(&unwind_inner_tx))??; - output_db.update(|tx| tx.import_table::(unwind_inner_tx))??; - output_db.update(|tx| tx.import_dupsort::(unwind_inner_tx))??; - output_db.update(|tx| tx.import_table::(unwind_inner_tx))??; - output_db.update(|tx| tx.import_dupsort::(unwind_inner_tx))??; - - unwind_tx.drop()?; + output_db.update(|tx| tx.import_table::(&unwind_inner_tx))??; + output_db.update(|tx| tx.import_dupsort::(&unwind_inner_tx))??; + output_db.update(|tx| tx.import_table::(&unwind_inner_tx))??; + output_db.update(|tx| tx.import_dupsort::(&unwind_inner_tx))??; Ok(()) } /// Try to re-execute the stage straightaway -async fn dry_run( - output_db: reth_db::mdbx::Env, +async fn dry_run( + chain: Arc, + output_db: DB, to: u64, from: u64, ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage."); - - let mut tx = Transaction::new(&output_db)?; + let shareable_db = ShareableDatabase::new(&output_db, chain); + let mut provider = shareable_db.provider_rw()?; let mut exec_output = false; while !exec_output { exec_output = MerkleStage::Execution { - clean_threshold: u64::MAX, /* Forces updating the root instead of calculating from + clean_threshold: u64::MAX, /* Forces updating the root instead of calculating + * from * scratch */ } .execute( - &mut tx, + &mut provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), @@ -131,8 +135,6 @@ async fn dry_run( .done; } - tx.drop()?; - info!(target: "reth::cli", "Success."); Ok(()) diff --git a/bin/reth/src/stage/dump/mod.rs b/bin/reth/src/stage/dump/mod.rs index 116a777ce3c3..c749eb68ec35 100644 --- a/bin/reth/src/stage/dump/mod.rs +++ b/bin/reth/src/stage/dump/mod.rs @@ -106,7 +106,7 @@ impl Command { reth_db::mdbx::EnvKind::RW, )?; - let mut tool = DbTool::new(&db)?; + let mut tool = DbTool::new(&db, self.chain.clone())?; match &self.command { Stages::Execution(StageCommand { output_db, from, to, dry_run, .. }) => { diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 9f4291830579..4bfcea361a2a 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -12,7 +12,7 @@ use reth_beacon_consensus::BeaconConsensus; use reth_config::Config; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_primitives::ChainSpec; -use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase, Transaction}; +use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase}; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ @@ -20,9 +20,9 @@ use reth_stages::{ IndexAccountHistoryStage, IndexStorageHistoryStage, MerkleStage, SenderRecoveryStage, StorageHashingStage, TransactionLookupStage, }, - ExecInput, ExecOutput, Stage, UnwindInput, + ExecInput, ExecOutput, PipelineError, Stage, UnwindInput, }; -use std::{any::Any, net::SocketAddr, ops::Deref, path::PathBuf, sync::Arc}; +use std::{any::Any, net::SocketAddr, path::PathBuf, sync::Arc}; use tracing::*; /// `reth stage` command @@ -120,7 +120,8 @@ impl Command { info!(target: "reth::cli", path = ?db_path, "Opening database"); let db = Arc::new(init_db(db_path)?); - let mut tx = Transaction::new(db.as_ref())?; + let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); + let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; if let Some(listen_addr) = self.metrics { info!(target: "reth::cli", "Starting metrics endpoint at {}", listen_addr); @@ -214,7 +215,8 @@ impl Command { assert!(exec_stage.type_id() == unwind_stage.type_id()); } - let checkpoint = get_stage_checkpoint(tx.deref(), exec_stage.id())?.unwrap_or_default(); + let checkpoint = + get_stage_checkpoint(provider_rw.tx_ref(), exec_stage.id())?.unwrap_or_default(); let unwind_stage = unwind_stage.as_mut().unwrap_or(&mut exec_stage); @@ -226,7 +228,7 @@ impl Command { if !self.skip_unwind { while unwind.checkpoint.block_number > self.from { - let unwind_output = unwind_stage.unwind(&mut tx, unwind).await?; + let unwind_output = unwind_stage.unwind(&mut provider_rw, unwind).await?; unwind.checkpoint = unwind_output.checkpoint; } } @@ -237,12 +239,13 @@ impl Command { }; while let ExecOutput { checkpoint: stage_progress, done: false } = - exec_stage.execute(&mut tx, input).await? + exec_stage.execute(&mut provider_rw, input).await? { input.checkpoint = Some(stage_progress); if self.commit { - tx.commit()?; + provider_rw.commit()?; + provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; } } diff --git a/bin/reth/src/stage/unwind.rs b/bin/reth/src/stage/unwind.rs index 42904bac016b..12ba4e7edee5 100644 --- a/bin/reth/src/stage/unwind.rs +++ b/bin/reth/src/stage/unwind.rs @@ -1,20 +1,21 @@ //! Unwinding a certain block range -use crate::dirs::{DataDirPath, MaybePlatformPath}; +use crate::{ + args::utils::genesis_value_parser, + dirs::{DataDirPath, MaybePlatformPath}, +}; use clap::{Parser, Subcommand}; use reth_db::{ + cursor::DbCursorRO, database::Database, mdbx::{Env, WriteMap}, tables, transaction::DbTx, }; use reth_primitives::{BlockHashOrNumber, ChainSpec}; -use reth_provider::Transaction; +use reth_provider::ShareableDatabase; use std::{ops::RangeInclusive, sync::Arc}; -use crate::args::utils::genesis_value_parser; -use reth_db::cursor::DbCursorRO; - /// `reth stage unwind` command #[derive(Debug, Parser)] pub struct Command { @@ -68,13 +69,14 @@ impl Command { eyre::bail!("Cannot unwind genesis block") } - let mut tx = Transaction::new(&db)?; + let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); + let provider = shareable_db.provider_rw()?; - let blocks_and_execution = tx + let blocks_and_execution = provider .take_block_and_execution_range(&self.chain, range) .map_err(|err| eyre::eyre!("Transaction error on unwind: {err:?}"))?; - tx.commit()?; + provider.commit()?; println!("Unwound {} blocks", blocks_and_execution.len()); diff --git a/bin/reth/src/utils.rs b/bin/reth/src/utils.rs index 87fb8fda790f..fc659cec3c76 100644 --- a/bin/reth/src/utils.rs +++ b/bin/reth/src/utils.rs @@ -11,10 +11,11 @@ use reth_interfaces::p2p::{ headers::client::{HeadersClient, HeadersRequest}, priority::Priority, }; -use reth_primitives::{BlockHashOrNumber, HeadersDirection, SealedHeader}; +use reth_primitives::{BlockHashOrNumber, ChainSpec, HeadersDirection, SealedHeader}; use std::{ env::VarError, path::{Path, PathBuf}, + sync::Arc, }; use tracing::info; @@ -58,12 +59,13 @@ where /// Wrapper over DB that implements many useful DB queries. pub struct DbTool<'a, DB: Database> { pub(crate) db: &'a DB, + pub(crate) chain: Arc, } impl<'a, DB: Database> DbTool<'a, DB> { /// Takes a DB where the tables have already been created. - pub(crate) fn new(db: &'a DB) -> eyre::Result { - Ok(Self { db }) + pub(crate) fn new(db: &'a DB, chain: Arc) -> eyre::Result { + Ok(Self { db, chain }) } /// Grabs the contents of the table within a certain index range and places the diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index f3a428f0067b..034275b33afe 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -22,7 +22,7 @@ use reth_provider::{ chain::{ChainSplit, SplitAt}, post_state::PostState, BlockNumProvider, CanonStateNotification, CanonStateNotificationSender, - CanonStateNotifications, Chain, ExecutorFactory, HeaderProvider, Transaction, + CanonStateNotifications, Chain, DatabaseProvider, ExecutorFactory, HeaderProvider, }; use std::{ collections::{BTreeMap, HashMap}, @@ -993,14 +993,18 @@ impl BlockchainTree /// Canonicalize the given chain and commit it to the database. fn commit_canonical(&mut self, chain: Chain) -> Result<(), Error> { - let mut tx = Transaction::new(&self.externals.db)?; + let mut provider = DatabaseProvider::new_rw( + self.externals.db.tx_mut()?, + self.externals.chain_spec.clone(), + ); let (blocks, state) = chain.into_inner(); - tx.append_blocks_with_post_state(blocks.into_blocks().collect(), state) + provider + .append_blocks_with_post_state(blocks.into_blocks().collect(), state) .map_err(|e| BlockExecutionError::CanonicalCommit { inner: e.to_string() })?; - tx.commit()?; + provider.commit()?; Ok(()) } @@ -1030,17 +1034,20 @@ impl BlockchainTree fn revert_canonical(&mut self, revert_until: BlockNumber) -> Result, Error> { // read data that is needed for new sidechain - let mut tx = Transaction::new(&self.externals.db)?; + let provider = DatabaseProvider::new_rw( + self.externals.db.tx_mut()?, + self.externals.chain_spec.clone(), + ); - let tip = tx.tip_number()?; + let tip = provider.last_block_number()?; let revert_range = (revert_until + 1)..=tip; info!(target: "blockchain_tree", "Unwinding canonical chain blocks: {:?}", revert_range); // read block and execution result from database. and remove traces of block from tables. - let blocks_and_execution = tx + let blocks_and_execution = provider .take_block_and_execution_range(self.externals.chain_spec.as_ref(), revert_range) .map_err(|e| BlockExecutionError::CanonicalRevert { inner: e.to_string() })?; - tx.commit()?; + provider.commit()?; if blocks_and_execution.is_empty() { Ok(None) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 54d9f992bd23..70f555555256 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1287,7 +1287,6 @@ mod tests { use reth_primitives::{stage::StageCheckpoint, ChainSpec, ChainSpecBuilder, H256, MAINNET}; use reth_provider::{ providers::BlockchainProvider, test_utils::TestExecutorFactory, ShareableDatabase, - Transaction, }; use reth_stages::{test_utils::TestStages, ExecOutput, PipelineError, StageError}; use reth_tasks::TokioTaskExecutor; @@ -1384,7 +1383,7 @@ mod tests { let pipeline = Pipeline::builder() .add_stages(TestStages::new(pipeline_exec_outputs, Default::default())) .with_tip_sender(tip_tx) - .build(db.clone()); + .build(db.clone(), chain_spec.clone()); // Setup blockchain tree let externals = @@ -1436,7 +1435,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Err(StageError::ChannelClosed)]), Vec::default(), ); @@ -1465,7 +1464,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Err(StageError::ChannelClosed)]), Vec::default(), ); @@ -1505,7 +1504,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([ Ok(ExecOutput { checkpoint: StageCheckpoint::new(1), done: true }), Err(StageError::ChannelClosed), @@ -1538,7 +1537,7 @@ mod tests { .build(), ); let (mut consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(max_block), done: true, @@ -1557,12 +1556,15 @@ mod tests { assert_matches!(rx.await, Ok(Ok(()))); } - fn insert_blocks<'a, DB: Database>(db: &DB, mut blocks: impl Iterator) { - let mut transaction = Transaction::new(db).unwrap(); - blocks - .try_for_each(|b| transaction.insert_block(b.clone(), None)) - .expect("failed to insert"); - transaction.commit().unwrap(); + fn insert_blocks<'a, DB: Database>( + db: &DB, + chain: Arc, + mut blocks: impl Iterator, + ) { + let factory = ShareableDatabase::new(db, chain); + let mut provider = factory.provider_rw().unwrap(); + blocks.try_for_each(|b| provider.insert_block(b.clone(), None)).expect("failed to insert"); + provider.commit().unwrap(); } mod fork_choice_updated { @@ -1581,7 +1583,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0), @@ -1612,7 +1614,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0), @@ -1622,7 +1624,7 @@ mod tests { let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); - insert_blocks(env.db.as_ref(), [&genesis, &block1].into_iter()); + insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); env.db .update(|tx| { tx.put::( @@ -1660,7 +1662,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([ Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), @@ -1670,7 +1672,7 @@ mod tests { let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); - insert_blocks(env.db.as_ref(), [&genesis, &block1].into_iter()); + insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1686,7 +1688,7 @@ mod tests { let invalid_rx = env.send_forkchoice_updated(next_forkchoice_state).await; // Insert next head immediately after sending forkchoice update - insert_blocks(env.db.as_ref(), [&next_head].into_iter()); + insert_blocks(env.db.as_ref(), chain_spec.clone(), [&next_head].into_iter()); let expected_result = ForkchoiceUpdated::from_status(PayloadStatusEnum::Syncing); assert_matches!(invalid_rx, Ok(result) => assert_eq!(result, expected_result)); @@ -1709,7 +1711,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0), @@ -1719,7 +1721,7 @@ mod tests { let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); - insert_blocks(env.db.as_ref(), [&genesis, &block1].into_iter()); + insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); let engine = spawn_consensus_engine(consensus_engine); @@ -1746,7 +1748,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([ Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), @@ -1766,7 +1768,11 @@ mod tests { let mut block3 = random_block(1, Some(genesis.hash), None, Some(0)); block3.header.difficulty = U256::from(1); - insert_blocks(env.db.as_ref(), [&genesis, &block1, &block2, &block3].into_iter()); + insert_blocks( + env.db.as_ref(), + chain_spec.clone(), + [&genesis, &block1, &block2, &block3].into_iter(), + ); let _engine = spawn_consensus_engine(consensus_engine); @@ -1795,7 +1801,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([ Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), @@ -1806,7 +1812,7 @@ mod tests { let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); - insert_blocks(env.db.as_ref(), [&genesis, &block1].into_iter()); + insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); let _engine = spawn_consensus_engine(consensus_engine); @@ -1842,7 +1848,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0), @@ -1875,7 +1881,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0), @@ -1886,7 +1892,11 @@ mod tests { let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); let block2 = random_block(2, Some(block1.hash), None, Some(0)); - insert_blocks(env.db.as_ref(), [&genesis, &block1, &block2].into_iter()); + insert_blocks( + env.db.as_ref(), + chain_spec.clone(), + [&genesis, &block1, &block2].into_iter(), + ); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1921,7 +1931,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0), @@ -1931,7 +1941,7 @@ mod tests { let genesis = random_block(0, None, None, Some(0)); - insert_blocks(env.db.as_ref(), [&genesis].into_iter()); + insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis].into_iter()); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1978,7 +1988,7 @@ mod tests { .build(), ); let (consensus_engine, env) = setup_consensus_engine( - chain_spec, + chain_spec.clone(), VecDeque::from([Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0), @@ -1986,7 +1996,11 @@ mod tests { Vec::from([exec_result2]), ); - insert_blocks(env.db.as_ref(), [&data.genesis, &block1].into_iter()); + insert_blocks( + env.db.as_ref(), + chain_spec.clone(), + [&data.genesis, &block1].into_iter(), + ); let mut engine_rx = spawn_consensus_engine(consensus_engine); diff --git a/crates/staged-sync/Cargo.toml b/crates/staged-sync/Cargo.toml index 70d5e2ed5741..b224a4a9de38 100644 --- a/crates/staged-sync/Cargo.toml +++ b/crates/staged-sync/Cargo.toml @@ -19,6 +19,7 @@ reth-primitives = { path = "../../crates/primitives" } reth-provider = { path = "../../crates/storage/provider", features = ["test-utils"] } reth-net-nat = { path = "../../crates/net/nat" } reth-stages = { path = "../stages" } +reth-interfaces = { path = "../interfaces" } # io serde = "1.0" diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index e0a2342acc9c..323858049fe3 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -6,7 +6,7 @@ use reth_db::{ transaction::{DbTx, DbTxMut}, }; use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, H256, U256}; -use reth_provider::{PostState, Transaction, TransactionError}; +use reth_provider::{DatabaseProviderRW, PostState, ShareableDatabase, TransactionError}; use std::{path::Path, sync::Arc}; use tracing::debug; @@ -39,6 +39,10 @@ pub enum InitDatabaseError { /// Low-level database error. #[error(transparent)] DBError(#[from] reth_db::DatabaseError), + + /// Internal error. + #[error(transparent)] + InternalError(#[from] reth_interfaces::Error), } /// Write the genesis block if it has not already been written @@ -66,11 +70,11 @@ pub fn init_genesis( drop(tx); debug!("Writing genesis block."); - let tx = db.tx_mut()?; // use transaction to insert genesis header - let transaction = Transaction::new_raw(&db, tx); - insert_genesis_hashes(transaction, genesis)?; + let shareable_db = ShareableDatabase::new(&db, chain.clone()); + let provider_rw = shareable_db.provider_rw()?; + insert_genesis_hashes(provider_rw, genesis)?; // Insert header let tx = db.tx_mut()?; @@ -123,20 +127,21 @@ pub fn insert_genesis_state( /// Inserts hashes for the genesis state. pub fn insert_genesis_hashes( - mut transaction: Transaction<'_, DB>, + provider: DatabaseProviderRW<'_, &DB>, genesis: &reth_primitives::Genesis, ) -> Result<(), InitDatabaseError> { // insert and hash accounts to hashing table let alloc_accounts = genesis.alloc.clone().into_iter().map(|(addr, account)| (addr, Some(account.into()))); - transaction.insert_account_for_hashing(alloc_accounts)?; + provider.insert_account_for_hashing(alloc_accounts)?; let alloc_storage = genesis.alloc.clone().into_iter().filter_map(|(addr, account)| { // only return Some if there is storage account.storage.map(|storage| (addr, storage.into_iter().map(|(k, v)| (k, v.into())))) }); - transaction.insert_storage_for_hashing(alloc_storage)?; - transaction.commit()?; + provider.insert_storage_for_hashing(alloc_storage)?; + provider.commit()?; + Ok(()) } diff --git a/crates/stages/benches/criterion.rs b/crates/stages/benches/criterion.rs index aa5a000fcd31..fb252ed2f2b0 100644 --- a/crates/stages/benches/criterion.rs +++ b/crates/stages/benches/criterion.rs @@ -5,7 +5,8 @@ use criterion::{ use pprof::criterion::{Output, PProfProfiler}; use reth_db::mdbx::{Env, WriteMap}; use reth_interfaces::test_utils::TestConsensus; -use reth_primitives::stage::StageCheckpoint; +use reth_primitives::{stage::StageCheckpoint, MAINNET}; +use reth_provider::ShareableDatabase; use reth_stages::{ stages::{MerkleStage, SenderRecoveryStage, TotalDifficultyStage, TransactionLookupStage}, test_utils::TestTransaction, @@ -135,9 +136,10 @@ fn measure_stage_with_path( }, |_| async { let mut stage = stage.clone(); - let mut db_tx = tx.inner(); - stage.execute(&mut db_tx, input).await.unwrap(); - db_tx.commit().unwrap(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); + stage.execute(&mut provider, input).await.unwrap(); + provider.commit().unwrap(); }, ) }); diff --git a/crates/stages/benches/setup/account_hashing.rs b/crates/stages/benches/setup/account_hashing.rs index 893e2c931a79..fa3492adff87 100644 --- a/crates/stages/benches/setup/account_hashing.rs +++ b/crates/stages/benches/setup/account_hashing.rs @@ -63,8 +63,9 @@ fn generate_testdata_db(num_blocks: u64) -> (PathBuf, StageRange) { std::fs::create_dir_all(&path).unwrap(); println!("Account Hashing testdata not found, generating to {:?}", path.display()); let tx = TestTransaction::new(&path); - let mut tx = tx.inner(); - let _accounts = AccountHashingStage::seed(&mut tx, opts); + let mut provider = tx.inner(); + let _accounts = AccountHashingStage::seed(&mut provider, opts); + provider.commit().expect("failed to commit"); } (path, (ExecInput { target: Some(num_blocks), ..Default::default() }, UnwindInput::default())) } diff --git a/crates/stages/benches/setup/mod.rs b/crates/stages/benches/setup/mod.rs index ef7ce811a8c1..5639a0983ef9 100644 --- a/crates/stages/benches/setup/mod.rs +++ b/crates/stages/benches/setup/mod.rs @@ -9,7 +9,8 @@ use reth_interfaces::test_utils::generators::{ random_block_range, random_contract_account_range, random_eoa_account_range, random_transition_range, }; -use reth_primitives::{Account, Address, SealedBlock, H256}; +use reth_primitives::{Account, Address, SealedBlock, H256, MAINNET}; +use reth_provider::ShareableDatabase; use reth_stages::{ stages::{AccountHashingStage, StorageHashingStage}, test_utils::TestTransaction, @@ -18,7 +19,6 @@ use reth_stages::{ use reth_trie::StateRoot; use std::{ collections::BTreeMap, - ops::Deref, path::{Path, PathBuf}, }; @@ -38,11 +38,12 @@ pub(crate) fn stage_unwind>>( tokio::runtime::Runtime::new().unwrap().block_on(async { let mut stage = stage.clone(); - let mut db_tx = tx.inner(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); // Clear previous run stage - .unwind(&mut db_tx, unwind) + .unwind(&mut provider, unwind) .await .map_err(|e| { format!( @@ -52,7 +53,7 @@ pub(crate) fn stage_unwind>>( }) .unwrap(); - db_tx.commit().unwrap(); + provider.commit().unwrap(); }); } @@ -65,18 +66,19 @@ pub(crate) fn unwind_hashes>>( tokio::runtime::Runtime::new().unwrap().block_on(async { let mut stage = stage.clone(); - let mut db_tx = tx.inner(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); - StorageHashingStage::default().unwind(&mut db_tx, unwind).await.unwrap(); - AccountHashingStage::default().unwind(&mut db_tx, unwind).await.unwrap(); + StorageHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); + AccountHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); // Clear previous run - stage.unwind(&mut db_tx, unwind).await.unwrap(); + stage.unwind(&mut provider, unwind).await.unwrap(); - AccountHashingStage::default().execute(&mut db_tx, input).await.unwrap(); - StorageHashingStage::default().execute(&mut db_tx, input).await.unwrap(); + AccountHashingStage::default().execute(&mut provider, input).await.unwrap(); + StorageHashingStage::default().execute(&mut provider, input).await.unwrap(); - db_tx.commit().unwrap(); + provider.commit().unwrap(); }); } @@ -121,7 +123,7 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> PathBuf { tx.insert_accounts_and_storages(start_state.clone()).unwrap(); // make first block after genesis have valid state root - let (root, updates) = StateRoot::new(tx.inner().deref()).root_with_updates().unwrap(); + let (root, updates) = StateRoot::new(tx.inner().tx_ref()).root_with_updates().unwrap(); let second_block = blocks.get_mut(1).unwrap(); let cloned_second = second_block.clone(); let mut updated_header = cloned_second.header.unseal(); @@ -142,8 +144,8 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> PathBuf { // make last block have valid state root let root = { - let mut tx_mut = tx.inner(); - let root = StateRoot::new(tx_mut.deref()).root().unwrap(); + let tx_mut = tx.inner(); + let root = StateRoot::new(tx_mut.tx_ref()).root().unwrap(); tx_mut.commit().unwrap(); root }; diff --git a/crates/stages/src/error.rs b/crates/stages/src/error.rs index dd2ca86c9be9..b05b091db5ba 100644 --- a/crates/stages/src/error.rs +++ b/crates/stages/src/error.rs @@ -66,6 +66,9 @@ pub enum StageError { /// rely on external downloaders #[error("Invalid download response: {0}")] Download(#[from] DownloadError), + /// Internal error + #[error(transparent)] + Internal(#[from] reth_interfaces::Error), /// The stage encountered a recoverable error. /// /// These types of errors are caught by the [Pipeline][crate::Pipeline] and trigger a restart @@ -104,6 +107,9 @@ pub enum PipelineError { /// The pipeline encountered a database error. #[error("A database error occurred.")] Database(#[from] DbError), + /// The pipeline encountered an irrecoverable error in one of the stages. + #[error("An interface error occurred.")] + Interface(#[from] reth_interfaces::Error), /// The pipeline encountered an error while trying to send an event. #[error("The pipeline encountered an error while trying to send an event.")] Channel(#[from] SendError), diff --git a/crates/stages/src/lib.rs b/crates/stages/src/lib.rs index 80da43b248d4..14b31668e40d 100644 --- a/crates/stages/src/lib.rs +++ b/crates/stages/src/lib.rs @@ -20,7 +20,7 @@ //! //! ``` //! # use std::sync::Arc; -//! use reth_db::mdbx::test_utils::create_test_rw_db; +//! # use reth_db::mdbx::test_utils::create_test_rw_db; //! # use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; //! # use reth_downloaders::headers::reverse_headers::ReverseHeadersDownloaderBuilder; //! # use reth_interfaces::consensus::Consensus; @@ -51,7 +51,7 @@ //! .add_stages( //! DefaultStages::new(HeaderSyncMode::Tip(tip_rx), consensus, headers_downloader, bodies_downloader, factory) //! ) -//! .build(db); +//! .build(db, MAINNET.clone()); //! ``` mod error; mod pipeline; diff --git a/crates/stages/src/pipeline/builder.rs b/crates/stages/src/pipeline/builder.rs index 6994ebe57c2a..3cedb35b0ad2 100644 --- a/crates/stages/src/pipeline/builder.rs +++ b/crates/stages/src/pipeline/builder.rs @@ -1,6 +1,8 @@ +use std::sync::Arc; + use crate::{pipeline::BoxedStage, Pipeline, Stage, StageSet}; use reth_db::database::Database; -use reth_primitives::{stage::StageId, BlockNumber, H256}; +use reth_primitives::{stage::StageId, BlockNumber, ChainSpec, H256}; use tokio::sync::watch; /// Builds a [`Pipeline`]. @@ -61,10 +63,11 @@ where /// Builds the final [`Pipeline`] using the given database. /// /// Note: it's expected that this is either an [Arc](std::sync::Arc) or an Arc wrapper type. - pub fn build(self, db: DB) -> Pipeline { + pub fn build(self, db: DB, chain_spec: Arc) -> Pipeline { let Self { stages, max_block, tip_tx } = self; Pipeline { db, + chain_spec, stages, max_block, tip_tx, diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index 2fd3611bd3bd..6586365e84fa 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -4,10 +4,10 @@ use reth_db::database::Database; use reth_interfaces::executor::BlockExecutionError; use reth_primitives::{ constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH, listener::EventListeners, stage::StageId, - BlockNumber, H256, + BlockNumber, ChainSpec, H256, }; -use reth_provider::{providers::get_stage_checkpoint, Transaction}; -use std::pin::Pin; +use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase}; +use std::{pin::Pin, sync::Arc}; use tokio::sync::watch; use tokio_stream::wrappers::UnboundedReceiverStream; use tracing::*; @@ -93,6 +93,8 @@ pub type PipelineWithResult = (Pipeline, Result { /// The Database db: DB, + /// Chain spec + chain_spec: Arc, /// All configured stages in the order they will be executed. stages: Vec>, /// The maximum block number to sync to. @@ -245,14 +247,15 @@ where // Unwind stages in reverse order of execution let unwind_pipeline = self.stages.iter_mut().rev(); - let mut tx = Transaction::new(&self.db)?; + let shareable_db = ShareableDatabase::new(&self.db, self.chain_spec.clone()); + let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; for stage in unwind_pipeline { let stage_id = stage.id(); let span = info_span!("Unwinding", stage = %stage_id); let _enter = span.enter(); - let mut checkpoint = tx.get_stage_checkpoint(stage_id)?.unwrap_or_default(); + let mut checkpoint = provider_rw.get_stage_checkpoint(stage_id)?.unwrap_or_default(); if checkpoint.block_number < to { debug!(target: "sync::pipeline", from = %checkpoint, %to, "Unwind point too far for stage"); self.listeners.notify(PipelineEvent::Skipped { stage_id }); @@ -264,7 +267,7 @@ where let input = UnwindInput { checkpoint, unwind_to: to, bad_block }; self.listeners.notify(PipelineEvent::Unwinding { stage_id, input }); - let output = stage.unwind(&mut tx, input).await; + let output = stage.unwind(&mut provider_rw, input).await; match output { Ok(unwind_output) => { checkpoint = unwind_output.checkpoint; @@ -282,12 +285,14 @@ where // doesn't change when we unwind. None, ); - tx.save_stage_checkpoint(stage_id, checkpoint)?; + provider_rw.save_stage_checkpoint(stage_id, checkpoint)?; self.listeners .notify(PipelineEvent::Unwound { stage_id, result: unwind_output }); - tx.commit()?; + provider_rw.commit()?; + provider_rw = + shareable_db.provider_rw().map_err(PipelineError::Interface)?; } Err(err) => { self.listeners.notify(PipelineEvent::Error { stage_id }); @@ -312,10 +317,11 @@ where let mut made_progress = false; let target = self.max_block.or(previous_stage); - loop { - let mut tx = Transaction::new(&self.db)?; + let shareable_db = ShareableDatabase::new(&self.db, self.chain_spec.clone()); + let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; - let prev_checkpoint = tx.get_stage_checkpoint(stage_id)?; + loop { + let prev_checkpoint = provider_rw.get_stage_checkpoint(stage_id)?; let stage_reached_max_block = prev_checkpoint .zip(self.max_block) @@ -343,7 +349,10 @@ where checkpoint: prev_checkpoint, }); - match stage.execute(&mut tx, ExecInput { target, checkpoint: prev_checkpoint }).await { + match stage + .execute(&mut provider_rw, ExecInput { target, checkpoint: prev_checkpoint }) + .await + { Ok(out @ ExecOutput { checkpoint, done }) => { made_progress |= checkpoint.block_number != prev_checkpoint.unwrap_or_default().block_number; @@ -356,7 +365,7 @@ where "Stage committed progress" ); self.metrics.stage_checkpoint(stage_id, checkpoint, target); - tx.save_stage_checkpoint(stage_id, checkpoint)?; + provider_rw.save_stage_checkpoint(stage_id, checkpoint)?; self.listeners.notify(PipelineEvent::Ran { pipeline_position: stage_index + 1, @@ -366,7 +375,8 @@ where }); // TODO: Make the commit interval configurable - tx.commit()?; + provider_rw.commit()?; + provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; if done { let stage_progress = checkpoint.block_number; @@ -466,7 +476,7 @@ mod tests { use reth_interfaces::{ consensus, provider::ProviderError, test_utils::generators::random_header, }; - use reth_primitives::stage::StageCheckpoint; + use reth_primitives::{stage::StageCheckpoint, MAINNET}; use tokio_stream::StreamExt; #[test] @@ -511,7 +521,7 @@ mod tests { .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .with_max_block(10) - .build(db); + .build(db, MAINNET.clone()); let events = pipeline.events(); // Run pipeline @@ -573,7 +583,7 @@ mod tests { .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) })), ) .with_max_block(10) - .build(db); + .build(db, MAINNET.clone()); let events = pipeline.events(); // Run pipeline @@ -683,7 +693,7 @@ mod tests { .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .with_max_block(10) - .build(db); + .build(db, MAINNET.clone()); let events = pipeline.events(); // Run pipeline @@ -776,7 +786,7 @@ mod tests { .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .with_max_block(10) - .build(db); + .build(db, MAINNET.clone()); let events = pipeline.events(); // Run pipeline @@ -859,7 +869,7 @@ mod tests { .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .with_max_block(10) - .build(db); + .build(db, MAINNET.clone()); let result = pipeline.run().await; assert_matches!(result, Ok(())); @@ -869,7 +879,7 @@ mod tests { .add_stage(TestStage::new(StageId::Other("Fatal")).add_exec(Err( StageError::DatabaseIntegrity(ProviderError::BlockBodyIndicesNotFound(5)), ))) - .build(db); + .build(db, MAINNET.clone()); let result = pipeline.run().await; assert_matches!( result, diff --git a/crates/stages/src/sets.rs b/crates/stages/src/sets.rs index 15c927bf2790..ee4858eaa2d1 100644 --- a/crates/stages/src/sets.rs +++ b/crates/stages/src/sets.rs @@ -20,7 +20,7 @@ //! # let db = create_test_rw_db(); //! // Build a pipeline with all offline stages. //! # let pipeline = -//! Pipeline::builder().add_stages(OfflineStages::new(factory)).build(db); +//! Pipeline::builder().add_stages(OfflineStages::new(factory)).build(db, MAINNET.clone()); //! ``` //! //! ```ignore diff --git a/crates/stages/src/stage.rs b/crates/stages/src/stage.rs index 72b4da1fc518..a7de484994cd 100644 --- a/crates/stages/src/stage.rs +++ b/crates/stages/src/stage.rs @@ -5,7 +5,7 @@ use reth_primitives::{ stage::{StageCheckpoint, StageId}, BlockNumber, TxNumber, }; -use reth_provider::{ProviderError, Transaction}; +use reth_provider::{DatabaseProviderRW, ProviderError}; use std::{ cmp::{max, min}, ops::RangeInclusive, @@ -75,11 +75,12 @@ impl ExecInput { /// the number of transactions exceeds the threshold. pub fn next_block_range_with_transaction_threshold( &self, - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, DB>, tx_threshold: u64, ) -> Result<(RangeInclusive, RangeInclusive, bool), StageError> { let start_block = self.next_block(); - let start_block_body = tx + let start_block_body = provider + .tx_ref() .get::(start_block)? .ok_or(ProviderError::BlockBodyIndicesNotFound(start_block))?; @@ -88,7 +89,8 @@ impl ExecInput { let first_tx_number = start_block_body.first_tx_num(); let mut last_tx_number = start_block_body.last_tx_num(); let mut end_block_number = start_block; - let mut body_indices_cursor = tx.cursor_read::()?; + let mut body_indices_cursor = + provider.tx_ref().cursor_read::()?; for entry in body_indices_cursor.walk_range(start_block..=target_block)? { let (block, body) = entry?; last_tx_number = body.last_tx_num(); @@ -171,8 +173,7 @@ pub struct UnwindOutput { /// /// Stages are executed as part of a pipeline where they are executed serially. /// -/// Stages receive [`Transaction`] which manages the lifecycle of a transaction, -/// such as when to commit / reopen a new one etc. +/// Stages receive [`DatabaseProviderRW`]. #[async_trait] pub trait Stage: Send + Sync { /// Get the ID of the stage. @@ -183,14 +184,14 @@ pub trait Stage: Send + Sync { /// Execute the stage. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result; /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result; } diff --git a/crates/stages/src/stages/bodies.rs b/crates/stages/src/stages/bodies.rs index 7ce5504956cb..0108f782804d 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -13,8 +13,8 @@ use reth_interfaces::{ p2p::bodies::{downloader::BodyDownloader, response::BlockResponse}, }; use reth_primitives::stage::{EntitiesCheckpoint, StageCheckpoint, StageId}; -use reth_provider::Transaction; -use std::{ops::Deref, sync::Arc}; +use reth_provider::DatabaseProviderRW; +use std::sync::Arc; use tracing::*; // TODO(onbjerg): Metrics and events (gradual status for e.g. CLI) @@ -67,7 +67,7 @@ impl Stage for BodyStage { /// header, limited by the stage's batch size. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -80,6 +80,7 @@ impl Stage for BodyStage { let (from_block, to_block) = range.into_inner(); // Cursors used to write bodies, ommers and transactions + let tx = provider.tx_ref(); let mut block_indices_cursor = tx.cursor_write::()?; let mut tx_cursor = tx.cursor_write::()?; let mut tx_block_cursor = tx.cursor_write::()?; @@ -154,7 +155,7 @@ impl Stage for BodyStage { let done = highest_block == to_block; Ok(ExecOutput { checkpoint: StageCheckpoint::new(highest_block) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), done, }) } @@ -162,9 +163,10 @@ impl Stage for BodyStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { + let tx = provider.tx_ref(); // Cursors to unwind bodies, ommers let mut body_cursor = tx.cursor_write::()?; let mut transaction_cursor = tx.cursor_write::()?; @@ -210,7 +212,7 @@ impl Stage for BodyStage { Ok(UnwindOutput { checkpoint: StageCheckpoint::new(input.unwind_to) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), }) } } @@ -219,11 +221,11 @@ impl Stage for BodyStage { // beforehand how many bytes we need to download. So the good solution would be to measure the // progress in gas as a proxy to size. Execution stage uses a similar approach. fn stage_checkpoint( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, DB>, ) -> Result { Ok(EntitiesCheckpoint { - processed: tx.deref().entries::()? as u64, - total: tx.deref().entries::()? as u64, + processed: provider.tx_ref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }) } diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 0a9d08f9b6cd..f6b40ee05ad9 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -19,7 +19,8 @@ use reth_primitives::{ Block, BlockNumber, BlockWithSenders, Header, TransactionSigned, U256, }; use reth_provider::{ - post_state::PostState, BlockExecutor, ExecutorFactory, LatestStateProviderRef, Transaction, + post_state::PostState, BlockExecutor, BlockProvider, DatabaseProviderRW, ExecutorFactory, + HeaderProvider, LatestStateProviderRef, ProviderError, WithdrawalsProvider, }; use std::{ops::RangeInclusive, time::Instant}; use tracing::*; @@ -83,22 +84,26 @@ impl ExecutionStage { Self::new(executor_factory, ExecutionStageThresholds::default()) } - // TODO: This should be in the block provider trait once we consolidate - // SharedDatabase/Transaction + // TODO(joshie): This should be in the block provider trait once we consolidate fn read_block_with_senders( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, block_number: BlockNumber, ) -> Result<(BlockWithSenders, U256), StageError> { - let header = tx.get_header(block_number)?; - let td = tx.get_td(block_number)?; - let ommers = tx.get::(block_number)?.unwrap_or_default().ommers; - let withdrawals = tx.get::(block_number)?.map(|v| v.withdrawals); + let header = provider + .header_by_number(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + let td = provider + .header_td_by_number(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + let ommers = provider.ommers(block_number.into())?.unwrap_or_default(); + let withdrawals = provider.withdrawals_by_block(block_number.into(), header.timestamp)?; // Get the block body - let body = tx.get::(block_number)?.unwrap(); + let body = provider.block_body_indices(block_number)?; let tx_range = body.tx_num_range(); // Get the transactions in the body + let tx = provider.tx_ref(); let (transactions, senders) = if tx_range.is_empty() { (Vec::new(), Vec::new()) } else { @@ -135,7 +140,7 @@ impl ExecutionStage { /// Execute the stage. pub fn execute_inner( &self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -146,17 +151,18 @@ impl ExecutionStage { let max_block = input.target(); // Build executor - let mut executor = self.executor_factory.with_sp(LatestStateProviderRef::new(&**tx)); + let mut executor = + self.executor_factory.with_sp(LatestStateProviderRef::new(provider.tx_ref())); // Progress tracking let mut stage_progress = start_block; let mut stage_checkpoint = - execution_checkpoint(tx, start_block, max_block, input.checkpoint())?; + execution_checkpoint(provider, start_block, max_block, input.checkpoint())?; // Execute block range let mut state = PostState::default(); for block_number in start_block..=max_block { - let (block, td) = Self::read_block_with_senders(tx, block_number)?; + let (block, td) = Self::read_block_with_senders(provider, block_number)?; // Configure the executor to use the current state. trace!(target: "sync::stages::execution", number = block_number, txs = block.body.len(), "Executing block"); @@ -190,7 +196,7 @@ impl ExecutionStage { // Write remaining changes trace!(target: "sync::stages::execution", accounts = state.accounts().len(), "Writing updated state to database"); let start = Instant::now(); - state.write_to_db(&**tx)?; + state.write_to_db(provider.tx_ref())?; trace!(target: "sync::stages::execution", took = ?start.elapsed(), "Wrote state"); let done = stage_progress == max_block; @@ -203,7 +209,7 @@ impl ExecutionStage { } fn execution_checkpoint( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, start_block: BlockNumber, max_block: BlockNumber, checkpoint: StageCheckpoint, @@ -225,7 +231,7 @@ fn execution_checkpoint( block_range: CheckpointBlockRange { from: start_block, to: max_block }, progress: EntitiesCheckpoint { processed, - total: total + calculate_gas_used_from_headers(tx, start_block..=max_block)?, + total: total + calculate_gas_used_from_headers(provider, start_block..=max_block)?, }, }, // If checkpoint block range ends on the same block as our range, we take the previously @@ -242,7 +248,7 @@ fn execution_checkpoint( // to be processed not including the checkpoint range. Some(ExecutionCheckpoint { progress: EntitiesCheckpoint { processed, .. }, .. }) => { let after_checkpoint_block_number = - calculate_gas_used_from_headers(tx, checkpoint.block_number + 1..=max_block)?; + calculate_gas_used_from_headers(provider, checkpoint.block_number + 1..=max_block)?; ExecutionCheckpoint { block_range: CheckpointBlockRange { from: start_block, to: max_block }, @@ -255,14 +261,14 @@ fn execution_checkpoint( // Otherwise, we recalculate the whole stage checkpoint including the amount of gas // already processed, if there's any. _ => { - let processed = calculate_gas_used_from_headers(tx, 0..=start_block - 1)?; + let processed = calculate_gas_used_from_headers(provider, 0..=start_block - 1)?; ExecutionCheckpoint { block_range: CheckpointBlockRange { from: start_block, to: max_block }, progress: EntitiesCheckpoint { processed, total: processed + - calculate_gas_used_from_headers(tx, start_block..=max_block)?, + calculate_gas_used_from_headers(provider, start_block..=max_block)?, }, } } @@ -270,13 +276,13 @@ fn execution_checkpoint( } fn calculate_gas_used_from_headers( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, range: RangeInclusive, ) -> Result { let mut gas_total = 0; let start = Instant::now(); - for entry in tx.cursor_read::()?.walk_range(range.clone())? { + for entry in provider.tx_ref().cursor_read::()?.walk_range(range.clone())? { let (_, Header { gas_used, .. }) = entry?; gas_total += gas_used; } @@ -304,7 +310,7 @@ impl Stage for ExecutionStage { /// Execute the stage async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { // For Ethereum transactions that reaches the max call depth (1024) revm can use more stack @@ -321,7 +327,7 @@ impl Stage for ExecutionStage { .stack_size(BIG_STACK_SIZE) .spawn_scoped(scope, || { // execute and store output to results - self.execute_inner(tx, input) + self.execute_inner(provider, input) }) .expect("Expects that thread name is not null"); handle.join().expect("Expects for thread to not panic") @@ -331,9 +337,10 @@ impl Stage for ExecutionStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { + let tx = provider.tx_ref(); // Acquire changeset cursors let mut account_changeset = tx.cursor_dup_write::()?; let mut storage_changeset = tx.cursor_dup_write::()?; @@ -382,7 +389,7 @@ impl Stage for ExecutionStage { } // Discard unwinded changesets - tx.unwind_table_by_num::(unwind_to)?; + provider.unwind_table_by_num::(unwind_to)?; let mut rev_storage_changeset_walker = storage_changeset.walk_back(None)?; while let Some((key, _)) = rev_storage_changeset_walker.next().transpose()? { @@ -394,7 +401,7 @@ impl Stage for ExecutionStage { } // Look up the start index for the transaction range - let first_tx_num = tx.block_body_indices(*range.start())?.first_tx_num(); + let first_tx_num = provider.block_body_indices(*range.start())?.first_tx_num(); let mut stage_checkpoint = input.checkpoint.execution_stage_checkpoint(); @@ -461,15 +468,12 @@ mod tests { }; use reth_primitives::{ hex_literal::hex, keccak256, stage::StageUnitCheckpoint, Account, Bytecode, - ChainSpecBuilder, SealedBlock, StorageEntry, H160, H256, U256, + ChainSpecBuilder, SealedBlock, StorageEntry, H160, H256, MAINNET, U256, }; - use reth_provider::insert_canonical_block; + use reth_provider::{insert_canonical_block, ShareableDatabase}; use reth_revm::Factory; use reth_rlp::Decodable; - use std::{ - ops::{Deref, DerefMut}, - sync::Arc, - }; + use std::sync::Arc; fn stage() -> ExecutionStage { let factory = @@ -483,7 +487,8 @@ mod tests { #[test] fn execution_checkpoint_matches() { let state_db = create_test_db::(EnvKind::RW); - let tx = Transaction::new(state_db.as_ref()).unwrap(); + let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); + let tx = db.provider_rw().unwrap(); let previous_stage_checkpoint = ExecutionCheckpoint { block_range: CheckpointBlockRange { from: 0, to: 0 }, @@ -507,15 +512,16 @@ mod tests { #[test] fn execution_checkpoint_precedes() { let state_db = create_test_db::(EnvKind::RW); - let mut tx = Transaction::new(state_db.as_ref()).unwrap(); + let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = db.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(tx.deref_mut(), genesis, None).unwrap(); - insert_canonical_block(tx.deref_mut(), block.clone(), None).unwrap(); - tx.commit().unwrap(); + insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); + insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.commit().unwrap(); let previous_stage_checkpoint = ExecutionCheckpoint { block_range: CheckpointBlockRange { from: 0, to: 0 }, @@ -526,7 +532,8 @@ mod tests { stage_checkpoint: Some(StageUnitCheckpoint::Execution(previous_stage_checkpoint)), }; - let stage_checkpoint = execution_checkpoint(&tx, 1, 1, previous_checkpoint); + let provider = db.provider_rw().unwrap(); + let stage_checkpoint = execution_checkpoint(&provider, 1, 1, previous_checkpoint); assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint { block_range: CheckpointBlockRange { from: 1, to: 1 }, @@ -541,15 +548,16 @@ mod tests { #[test] fn execution_checkpoint_recalculate_full_previous_some() { let state_db = create_test_db::(EnvKind::RW); - let mut tx = Transaction::new(state_db.as_ref()).unwrap(); + let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = db.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(tx.deref_mut(), genesis, None).unwrap(); - insert_canonical_block(tx.deref_mut(), block.clone(), None).unwrap(); - tx.commit().unwrap(); + insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); + insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.commit().unwrap(); let previous_stage_checkpoint = ExecutionCheckpoint { block_range: CheckpointBlockRange { from: 0, to: 0 }, @@ -560,7 +568,8 @@ mod tests { stage_checkpoint: Some(StageUnitCheckpoint::Execution(previous_stage_checkpoint)), }; - let stage_checkpoint = execution_checkpoint(&tx, 1, 1, previous_checkpoint); + let provider = db.provider_rw().unwrap(); + let stage_checkpoint = execution_checkpoint(&provider, 1, 1, previous_checkpoint); assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint { block_range: CheckpointBlockRange { from: 1, to: 1 }, @@ -575,19 +584,21 @@ mod tests { #[test] fn execution_checkpoint_recalculate_full_previous_none() { let state_db = create_test_db::(EnvKind::RW); - let mut tx = Transaction::new(state_db.as_ref()).unwrap(); + let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = db.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(tx.deref_mut(), genesis, None).unwrap(); - insert_canonical_block(tx.deref_mut(), block.clone(), None).unwrap(); - tx.commit().unwrap(); + insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); + insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.commit().unwrap(); let previous_checkpoint = StageCheckpoint { block_number: 1, stage_checkpoint: None }; - let stage_checkpoint = execution_checkpoint(&tx, 1, 1, previous_checkpoint); + let provider = db.provider_rw().unwrap(); + let stage_checkpoint = execution_checkpoint(&provider, 1, 1, previous_checkpoint); assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint { block_range: CheckpointBlockRange { from: 1, to: 1 }, @@ -603,7 +614,8 @@ mod tests { // TODO cleanup the setup after https://github.com/paradigmxyz/reth/issues/332 // is merged as it has similar framework let state_db = create_test_db::(EnvKind::RW); - let mut tx = Transaction::new(state_db.as_ref()).unwrap(); + let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = db.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -613,12 +625,13 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(tx.deref_mut(), genesis, None).unwrap(); - insert_canonical_block(tx.deref_mut(), block.clone(), None).unwrap(); - tx.commit().unwrap(); + insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); + insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.commit().unwrap(); // insert pre state - let db_tx = tx.deref_mut(); + let mut provider = db.provider_rw().unwrap(); + let db_tx = provider.tx_mut(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); let code = hex!("5a465a905090036002900360015500"); @@ -637,11 +650,12 @@ mod tests { ) .unwrap(); db_tx.put::(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap(); - tx.commit().unwrap(); + provider.commit().unwrap(); + let mut provider = db.provider_rw().unwrap(); let mut execution_stage = stage(); - let output = execution_stage.execute(&mut tx, input).await.unwrap(); - tx.commit().unwrap(); + let output = execution_stage.execute(&mut provider, input).await.unwrap(); + provider.commit().unwrap(); assert_matches!(output, ExecOutput { checkpoint: StageCheckpoint { block_number: 1, @@ -658,7 +672,8 @@ mod tests { }, done: true } if processed == total && total == block.gas_used); - let tx = tx.deref_mut(); + let mut provider = db.provider_rw().unwrap(); + let tx = provider.tx_mut(); // check post state let account1 = H160(hex!("1000000000000000000000000000000000000000")); let account1_info = @@ -707,7 +722,8 @@ mod tests { // is merged as it has similar framework let state_db = create_test_db::(EnvKind::RW); - let mut tx = Transaction::new(state_db.as_ref()).unwrap(); + let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = db.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -717,16 +733,17 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(tx.deref_mut(), genesis, None).unwrap(); - insert_canonical_block(tx.deref_mut(), block.clone(), None).unwrap(); - tx.commit().unwrap(); + insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); + insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.commit().unwrap(); // variables let code = hex!("5a465a905090036002900360015500"); let balance = U256::from(0x3635c9adc5dea00000u128); let code_hash = keccak256(code); // pre state - let db_tx = tx.deref_mut(); + let mut provider = db.provider_rw().unwrap(); + let db_tx = provider.tx_mut(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc1_info = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }; let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); @@ -735,17 +752,19 @@ mod tests { db_tx.put::(acc1, acc1_info).unwrap(); db_tx.put::(acc2, acc2_info).unwrap(); db_tx.put::(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap(); - tx.commit().unwrap(); + provider.commit().unwrap(); // execute + let mut provider = db.provider_rw().unwrap(); let mut execution_stage = stage(); - let result = execution_stage.execute(&mut tx, input).await.unwrap(); - tx.commit().unwrap(); + let result = execution_stage.execute(&mut provider, input).await.unwrap(); + provider.commit().unwrap(); + let mut provider = db.provider_rw().unwrap(); let mut stage = stage(); let result = stage .unwind( - &mut tx, + &mut provider, UnwindInput { checkpoint: result.checkpoint, unwind_to: 0, bad_block: None }, ) .await @@ -768,7 +787,7 @@ mod tests { } if total == block.gas_used); // assert unwind stage - let db_tx = tx.deref(); + let db_tx = provider.tx_ref(); assert_eq!( db_tx.get::(acc1), Ok(Some(acc1_info)), @@ -793,7 +812,8 @@ mod tests { #[tokio::test] async fn test_selfdestruct() { let test_tx = TestTransaction::default(); - let mut tx = test_tx.inner(); + let factory = ShareableDatabase::new(test_tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -803,9 +823,9 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(tx.deref_mut(), genesis, None).unwrap(); - insert_canonical_block(tx.deref_mut(), block.clone(), None).unwrap(); - tx.commit().unwrap(); + insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); + insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.commit().unwrap(); // variables let caller_address = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); @@ -817,50 +837,60 @@ mod tests { let code_hash = keccak256(code); // pre state - let db_tx = tx.deref_mut(); let caller_info = Account { nonce: 0, balance, bytecode_hash: None }; let destroyed_info = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }; // set account - db_tx.put::(caller_address, caller_info).unwrap(); - db_tx.put::(destroyed_address, destroyed_info).unwrap(); - db_tx.put::(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap(); + let provider = factory.provider_rw().unwrap(); + provider.tx_ref().put::(caller_address, caller_info).unwrap(); + provider + .tx_ref() + .put::(destroyed_address, destroyed_info) + .unwrap(); + provider + .tx_ref() + .put::(code_hash, Bytecode::new_raw(code.to_vec().into())) + .unwrap(); // set storage to check when account gets destroyed. - db_tx + provider + .tx_ref() .put::( destroyed_address, StorageEntry { key: H256::zero(), value: U256::ZERO }, ) .unwrap(); - db_tx + provider + .tx_ref() .put::( destroyed_address, StorageEntry { key: H256::from_low_u64_be(1), value: U256::from(1u64) }, ) .unwrap(); - tx.commit().unwrap(); + provider.commit().unwrap(); // execute + let mut provider = factory.provider_rw().unwrap(); let mut execution_stage = stage(); - let _ = execution_stage.execute(&mut tx, input).await.unwrap(); - tx.commit().unwrap(); + let _ = execution_stage.execute(&mut provider, input).await.unwrap(); + provider.commit().unwrap(); // assert unwind stage + let provider = factory.provider_rw().unwrap(); assert_eq!( - tx.deref().get::(destroyed_address), + provider.tx_ref().get::(destroyed_address), Ok(None), "Account was destroyed" ); assert_eq!( - tx.deref().get::(destroyed_address), + provider.tx_ref().get::(destroyed_address), Ok(None), "There is storage for destroyed account" ); // drops tx so that it returns write privilege to test_tx - drop(tx); + drop(provider); let plain_accounts = test_tx.table::().unwrap(); let plain_storage = test_tx.table::().unwrap(); diff --git a/crates/stages/src/stages/finish.rs b/crates/stages/src/stages/finish.rs index e5f4f9218c6c..bae21c8c76b8 100644 --- a/crates/stages/src/stages/finish.rs +++ b/crates/stages/src/stages/finish.rs @@ -1,7 +1,7 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::database::Database; use reth_primitives::stage::{StageCheckpoint, StageId}; -use reth_provider::Transaction; +use reth_provider::DatabaseProviderRW; /// The finish stage. /// @@ -18,7 +18,7 @@ impl Stage for FinishStage { async fn execute( &mut self, - _tx: &mut Transaction<'_, DB>, + _provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()), done: true }) @@ -26,7 +26,7 @@ impl Stage for FinishStage { async fn unwind( &mut self, - _tx: &mut Transaction<'_, DB>, + _provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { Ok(UnwindOutput { checkpoint: StageCheckpoint::new(input.unwind_to) }) diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 0ac772502237..ab56a3398494 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -16,11 +16,11 @@ use reth_primitives::{ StageId, }, }; -use reth_provider::Transaction; +use reth_provider::{AccountExtProvider, DatabaseProviderRW}; use std::{ cmp::max, fmt::Debug, - ops::{Deref, Range, RangeInclusive}, + ops::{Range, RangeInclusive}, }; use tokio::sync::mpsc; use tracing::*; @@ -79,7 +79,7 @@ impl AccountHashingStage { /// Proceeds to go to the `BlockTransitionIndex` end, go back `transitions` and change the /// account state in the `AccountChangeSet` table. pub fn seed( - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, DB>, opts: SeedOpts, ) -> Result, StageError> { use reth_db::models::AccountBeforeTx; @@ -92,18 +92,20 @@ impl AccountHashingStage { let blocks = random_block_range(opts.blocks.clone(), H256::zero(), opts.txs); for block in blocks { - insert_canonical_block(&**tx, block, None).unwrap(); + insert_canonical_block(provider.tx_ref(), block, None).unwrap(); } let mut accounts = random_eoa_account_range(opts.accounts); { // Account State generator - let mut account_cursor = tx.cursor_write::()?; + let mut account_cursor = + provider.tx_ref().cursor_write::()?; accounts.sort_by(|a, b| a.0.cmp(&b.0)); for (addr, acc) in accounts.iter() { account_cursor.append(*addr, *acc)?; } - let mut acc_changeset_cursor = tx.cursor_write::()?; + let mut acc_changeset_cursor = + provider.tx_ref().cursor_write::()?; for (t, (addr, acc)) in (opts.blocks).zip(&accounts) { let Account { nonce, balance, .. } = acc; let prev_acc = Account { @@ -116,8 +118,6 @@ impl AccountHashingStage { } } - tx.commit()?; - Ok(accounts) } } @@ -132,7 +132,7 @@ impl Stage for AccountHashingStage { /// Execute the stage. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -146,6 +146,7 @@ impl Stage for AccountHashingStage { // AccountHashing table. Also, if we start from genesis, we need to hash from scratch, as // genesis accounts are not in changeset. if to_block - from_block > self.clean_threshold || from_block == 1 { + let tx = provider.tx_ref(); let stage_checkpoint = input .checkpoint .and_then(|checkpoint| checkpoint.account_hashing_stage_checkpoint()); @@ -231,7 +232,7 @@ impl Stage for AccountHashingStage { AccountHashingCheckpoint { address: Some(next_address.key().unwrap()), block_range: CheckpointBlockRange { from: from_block, to: to_block }, - progress: stage_checkpoint_progress(tx)?, + progress: stage_checkpoint_progress(provider)?, }, ); @@ -240,20 +241,20 @@ impl Stage for AccountHashingStage { } else { // Aggregate all transition changesets and make a list of accounts that have been // changed. - let lists = tx.get_addresses_of_changed_accounts(from_block..=to_block)?; + let lists = provider.changed_accounts_with_range(from_block..=to_block)?; // Iterate over plain state and get newest value. // Assumption we are okay to make is that plainstate represent // `previous_stage_progress` state. - let accounts = tx.get_plainstate_accounts(lists)?; + let accounts = provider.basic_accounts(lists)?; // Insert and hash accounts to hashing table - tx.insert_account_for_hashing(accounts.into_iter())?; + provider.insert_account_for_hashing(accounts.into_iter())?; } // We finished the hashing stage, no future iterations is expected for the same block range, // so no checkpoint is needed. let checkpoint = StageCheckpoint::new(input.target()) .with_account_hashing_stage_checkpoint(AccountHashingCheckpoint { - progress: stage_checkpoint_progress(tx)?, + progress: stage_checkpoint_progress(provider)?, ..Default::default() }); @@ -263,19 +264,19 @@ impl Stage for AccountHashingStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); // Aggregate all transition changesets and make a list of accounts that have been changed. - tx.unwind_account_hashing(range)?; + provider.unwind_account_hashing(range)?; let mut stage_checkpoint = input.checkpoint.account_hashing_stage_checkpoint().unwrap_or_default(); - stage_checkpoint.progress = stage_checkpoint_progress(tx)?; + stage_checkpoint.progress = stage_checkpoint_progress(provider)?; Ok(UnwindOutput { checkpoint: StageCheckpoint::new(unwind_progress) @@ -285,11 +286,11 @@ impl Stage for AccountHashingStage { } fn stage_checkpoint_progress( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, ) -> Result { Ok(EntitiesCheckpoint { - processed: tx.deref().entries::()? as u64, - total: tx.deref().entries::()? as u64, + processed: provider.tx_ref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }) } @@ -531,11 +532,14 @@ mod tests { type Seed = Vec<(Address, Account)>; fn seed_execution(&mut self, input: ExecInput) -> Result { - Ok(AccountHashingStage::seed( - &mut self.tx.inner(), + let mut provider = self.tx.inner(); + let res = Ok(AccountHashingStage::seed( + &mut provider, SeedOpts { blocks: 1..=input.target(), accounts: 0..10, txs: 0..3 }, ) - .unwrap()) + .unwrap()); + provider.commit().expect("failed to commit"); + res } fn validate_execution( diff --git a/crates/stages/src/stages/hashing_storage.rs b/crates/stages/src/stages/hashing_storage.rs index d8dfc0054196..acb109b0e9d6 100644 --- a/crates/stages/src/stages/hashing_storage.rs +++ b/crates/stages/src/stages/hashing_storage.rs @@ -16,8 +16,8 @@ use reth_primitives::{ }, StorageEntry, }; -use reth_provider::Transaction; -use std::{collections::BTreeMap, fmt::Debug, ops::Deref}; +use reth_provider::DatabaseProviderRW; +use std::{collections::BTreeMap, fmt::Debug}; use tracing::*; /// Storage hashing stage hashes plain storage. @@ -54,9 +54,10 @@ impl Stage for StorageHashingStage { /// Execute the stage. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { + let tx = provider.tx_ref(); if input.target_reached() { return Ok(ExecOutput::done(input.checkpoint())) } @@ -161,7 +162,7 @@ impl Stage for StorageHashingStage { address: current_key, storage: current_subkey, block_range: CheckpointBlockRange { from: from_block, to: to_block }, - progress: stage_checkpoint_progress(tx)?, + progress: stage_checkpoint_progress(provider)?, }, ); @@ -170,19 +171,20 @@ impl Stage for StorageHashingStage { } else { // Aggregate all changesets and and make list of storages that have been // changed. - let lists = tx.get_addresses_and_keys_of_changed_storages(from_block..=to_block)?; + let lists = + provider.get_addresses_and_keys_of_changed_storages(from_block..=to_block)?; // iterate over plain state and get newest storage value. // Assumption we are okay with is that plain state represent // `previous_stage_progress` state. - let storages = tx.get_plainstate_storages(lists)?; - tx.insert_storage_for_hashing(storages.into_iter())?; + let storages = provider.get_plainstate_storages(lists)?; + provider.insert_storage_for_hashing(storages.into_iter())?; } // We finished the hashing stage, no future iterations is expected for the same block range, // so no checkpoint is needed. let checkpoint = StageCheckpoint::new(input.target()) .with_storage_hashing_stage_checkpoint(StorageHashingCheckpoint { - progress: stage_checkpoint_progress(tx)?, + progress: stage_checkpoint_progress(provider)?, ..Default::default() }); @@ -192,18 +194,18 @@ impl Stage for StorageHashingStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); - tx.unwind_storage_hashing(BlockNumberAddress::range(range))?; + provider.unwind_storage_hashing(BlockNumberAddress::range(range))?; let mut stage_checkpoint = input.checkpoint.storage_hashing_stage_checkpoint().unwrap_or_default(); - stage_checkpoint.progress = stage_checkpoint_progress(tx)?; + stage_checkpoint.progress = stage_checkpoint_progress(provider)?; Ok(UnwindOutput { checkpoint: StageCheckpoint::new(unwind_progress) @@ -213,11 +215,11 @@ impl Stage for StorageHashingStage { } fn stage_checkpoint_progress( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, ) -> Result { Ok(EntitiesCheckpoint { - processed: tx.deref().entries::()? as u64, - total: tx.deref().entries::()? as u64, + processed: provider.tx_ref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }) } diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index faf8bdc5227c..ad857d63515b 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -19,7 +19,8 @@ use reth_primitives::{ }, BlockHashOrNumber, BlockNumber, SealedHeader, H256, }; -use reth_provider::Transaction; +use reth_provider::DatabaseProviderRW; +use std::ops::Deref; use tokio::sync::watch; use tracing::*; @@ -68,7 +69,7 @@ where fn is_stage_done( &self, - tx: &Transaction<'_, DB>, + tx: &>::TXMut, checkpoint: u64, ) -> Result { let mut header_cursor = tx.cursor_read::()?; @@ -84,12 +85,12 @@ where /// See also [SyncTarget] async fn get_sync_gap( &mut self, - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, checkpoint: u64, ) -> Result { // Create a cursor over canonical header hashes - let mut cursor = tx.cursor_read::()?; - let mut header_cursor = tx.cursor_read::()?; + let mut cursor = provider.tx_ref().cursor_read::()?; + let mut header_cursor = provider.tx_ref().cursor_read::()?; // Get head hash and reposition the cursor let (head_num, head_hash) = cursor @@ -149,7 +150,7 @@ where /// Note: this writes the headers with rising block numbers. fn write_headers( &self, - tx: &Transaction<'_, DB>, + tx: &>::TXMut, headers: Vec, ) -> Result, StageError> { trace!(target: "sync::stages::headers", len = headers.len(), "writing headers"); @@ -195,13 +196,14 @@ where /// starting from the tip of the chain async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { + let tx = provider.tx_ref(); let current_checkpoint = input.checkpoint(); // Lookup the head and tip of the sync range - let gap = self.get_sync_gap(tx, current_checkpoint.block_number).await?; + let gap = self.get_sync_gap(provider.deref(), current_checkpoint.block_number).await?; let local_head = gap.local_head.number; let tip = gap.target.tip(); @@ -301,7 +303,7 @@ where // Write the headers to db self.write_headers::(tx, downloaded_headers)?.unwrap_or_default(); - if self.is_stage_done(tx, current_checkpoint.block_number)? { + if self.is_stage_done::(tx, current_checkpoint.block_number)? { let checkpoint = current_checkpoint.block_number.max( tx.cursor_read::()? .last()? @@ -324,15 +326,15 @@ where /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { // TODO: handle bad block - tx.unwind_table_by_walker::( + provider.unwind_table_by_walker::( input.unwind_to + 1, )?; - tx.unwind_table_by_num::(input.unwind_to)?; - let unwound_headers = tx.unwind_table_by_num::(input.unwind_to)?; + provider.unwind_table_by_num::(input.unwind_to)?; + let unwound_headers = provider.unwind_table_by_num::(input.unwind_to)?; let stage_checkpoint = input.checkpoint.headers_stage_checkpoint().map(|stage_checkpoint| HeadersCheckpoint { @@ -380,13 +382,15 @@ impl SyncGap { #[cfg(test)] mod tests { + use super::*; use crate::test_utils::{ stage_test_suite, ExecuteStageTestRunner, StageTestRunner, UnwindStageTestRunner, }; use assert_matches::assert_matches; use reth_interfaces::test_utils::generators::random_header; - use reth_primitives::{stage::StageUnitCheckpoint, H256}; + use reth_primitives::{stage::StageUnitCheckpoint, H256, MAINNET}; + use reth_provider::ShareableDatabase; use test_runner::HeadersTestRunner; mod test_runner { @@ -598,7 +602,9 @@ mod tests { #[tokio::test] async fn head_and_tip_lookup() { let runner = HeadersTestRunner::default(); - let tx = runner.tx().inner(); + let factory = ShareableDatabase::new(runner.tx().tx.as_ref(), MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); + let tx = provider.tx_ref(); let mut stage = runner.stage(); let consensus_tip = H256::random(); @@ -612,7 +618,7 @@ mod tests { // Empty database assert_matches!( - stage.get_sync_gap(&tx, checkpoint).await, + stage.get_sync_gap(&provider, checkpoint).await, Err(StageError::DatabaseIntegrity(ProviderError::HeaderNotFound(block_number))) if block_number.as_number().unwrap() == checkpoint ); @@ -623,7 +629,7 @@ mod tests { tx.put::(head.number, head.clone().unseal()) .expect("failed to write header"); - let gap = stage.get_sync_gap(&tx, checkpoint).await.unwrap(); + let gap = stage.get_sync_gap(&provider, checkpoint).await.unwrap(); assert_eq!(gap.local_head, head); assert_eq!(gap.target.tip(), consensus_tip.into()); @@ -633,7 +639,7 @@ mod tests { tx.put::(gap_tip.number, gap_tip.clone().unseal()) .expect("failed to write header"); - let gap = stage.get_sync_gap(&tx, checkpoint).await.unwrap(); + let gap = stage.get_sync_gap(&provider, checkpoint).await.unwrap(); assert_eq!(gap.local_head, head); assert_eq!(gap.target.tip(), gap_tip.parent_hash.into()); @@ -644,7 +650,7 @@ mod tests { .expect("failed to write header"); assert_matches!( - stage.get_sync_gap(&tx, checkpoint).await, + stage.get_sync_gap(&provider, checkpoint).await, Err(StageError::StageCheckpoint(_checkpoint)) if _checkpoint == checkpoint ); } diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index 108481eb81e0..f965009092ad 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -6,11 +6,8 @@ use reth_primitives::{ }, BlockNumber, }; -use reth_provider::Transaction; -use std::{ - fmt::Debug, - ops::{Deref, RangeInclusive}, -}; +use reth_provider::DatabaseProviderRW; +use std::{fmt::Debug, ops::RangeInclusive}; /// Stage is indexing history the account changesets generated in /// [`ExecutionStage`][crate::stages::ExecutionStage]. For more information @@ -38,7 +35,7 @@ impl Stage for IndexAccountHistoryStage { /// Execute the stage. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -48,18 +45,18 @@ impl Stage for IndexAccountHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); let mut stage_checkpoint = stage_checkpoint( - tx, + provider, input.checkpoint(), // It is important to provide the full block range into the checkpoint, // not the one accounting for commit threshold, to get the correct range end. &input.next_block_range(), )?; - let indices = tx.get_account_transition_ids_from_changeset(range.clone())?; + let indices = provider.get_account_transition_ids_from_changeset(range.clone())?; let changesets = indices.values().map(|blocks| blocks.len() as u64).sum::(); // Insert changeset to history index - tx.insert_account_history_index(indices)?; + provider.insert_account_history_index(indices)?; stage_checkpoint.progress.processed += changesets; @@ -73,13 +70,13 @@ impl Stage for IndexAccountHistoryStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); - let changesets = tx.unwind_account_history_indices(range)?; + let changesets = provider.unwind_account_history_indices(range)?; let checkpoint = if let Some(mut stage_checkpoint) = input.checkpoint.index_history_stage_checkpoint() { @@ -105,7 +102,7 @@ impl Stage for IndexAccountHistoryStage { /// given block range and calculates the progress by counting the number of processed entries in the /// [tables::AccountChangeSet] table within the given block range. fn stage_checkpoint( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, checkpoint: StageCheckpoint, range: &RangeInclusive, ) -> Result { @@ -122,18 +119,19 @@ fn stage_checkpoint( block_range: CheckpointBlockRange::from(range), progress: EntitiesCheckpoint { processed: progress.processed, - total: tx.deref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }, } } _ => IndexHistoryCheckpoint { block_range: CheckpointBlockRange::from(range), progress: EntitiesCheckpoint { - processed: tx + processed: provider + .tx_ref() .cursor_read::()? .walk_range(0..=checkpoint.block_number)? .count() as u64, - total: tx.deref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }, }, }) @@ -142,6 +140,7 @@ fn stage_checkpoint( #[cfg(test)] mod tests { use assert_matches::assert_matches; + use reth_provider::ShareableDatabase; use std::collections::BTreeMap; use super::*; @@ -155,7 +154,7 @@ mod tests { transaction::DbTxMut, BlockNumberList, }; - use reth_primitives::{hex_literal::hex, H160}; + use reth_primitives::{hex_literal::hex, H160, MAINNET}; const ADDRESS: H160 = H160(hex!("0000000000000000000000000000000000000001")); @@ -211,8 +210,9 @@ mod tests { async fn run(tx: &TestTransaction, run_to: u64) { let input = ExecInput { target: Some(run_to), ..Default::default() }; let mut stage = IndexAccountHistoryStage::default(); - let mut tx = tx.inner(); - let out = stage.execute(&mut tx, input).await.unwrap(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); + let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( out, ExecOutput { @@ -225,7 +225,7 @@ mod tests { done: true } ); - tx.commit().unwrap(); + provider.commit().unwrap(); } async fn unwind(tx: &TestTransaction, unwind_from: u64, unwind_to: u64) { @@ -235,10 +235,11 @@ mod tests { ..Default::default() }; let mut stage = IndexAccountHistoryStage::default(); - let mut tx = tx.inner(); - let out = stage.unwind(&mut tx, input).await.unwrap(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); + let out = stage.unwind(&mut provider, input).await.unwrap(); assert_eq!(out, UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) }); - tx.commit().unwrap(); + provider.commit().unwrap(); } #[tokio::test] @@ -448,10 +449,11 @@ mod tests { // run { let mut stage = IndexAccountHistoryStage { commit_threshold: 4 }; // Two runs required - let mut tx = test_tx.inner(); + let factory = ShareableDatabase::new(&test_tx.tx, MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let mut input = ExecInput { target: Some(5), ..Default::default() }; - let out = stage.execute(&mut tx, input).await.unwrap(); + let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( out, ExecOutput { @@ -466,7 +468,7 @@ mod tests { ); input.checkpoint = Some(out.checkpoint); - let out = stage.execute(&mut tx, input).await.unwrap(); + let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( out, ExecOutput { @@ -480,7 +482,7 @@ mod tests { } ); - tx.commit().unwrap(); + provider.commit().unwrap(); } // verify @@ -536,8 +538,11 @@ mod tests { }) .unwrap(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); + assert_matches!( - stage_checkpoint(&tx.inner(), StageCheckpoint::new(1), &(1..=2)).unwrap(), + stage_checkpoint(&provider, StageCheckpoint::new(1), &(1..=2)).unwrap(), IndexHistoryCheckpoint { block_range: CheckpointBlockRange { from: 1, to: 2 }, progress: EntitiesCheckpoint { processed: 2, total: 4 } diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index bc2a426181fc..cc354a4daf97 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -9,11 +9,8 @@ use reth_primitives::{ }, BlockNumber, }; -use reth_provider::Transaction; -use std::{ - fmt::Debug, - ops::{Deref, RangeInclusive}, -}; +use reth_provider::DatabaseProviderRW; +use std::{fmt::Debug, ops::RangeInclusive}; /// Stage is indexing history the account changesets generated in /// [`ExecutionStage`][crate::stages::ExecutionStage]. For more information @@ -41,7 +38,7 @@ impl Stage for IndexStorageHistoryStage { /// Execute the stage. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -51,17 +48,17 @@ impl Stage for IndexStorageHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); let mut stage_checkpoint = stage_checkpoint( - tx, + provider, input.checkpoint(), // It is important to provide the full block range into the checkpoint, // not the one accounting for commit threshold, to get the correct range end. &input.next_block_range(), )?; - let indices = tx.get_storage_transition_ids_from_changeset(range.clone())?; + let indices = provider.get_storage_transition_ids_from_changeset(range.clone())?; let changesets = indices.values().map(|blocks| blocks.len() as u64).sum::(); - tx.insert_storage_history_index(indices)?; + provider.insert_storage_history_index(indices)?; stage_checkpoint.progress.processed += changesets; @@ -75,13 +72,14 @@ impl Stage for IndexStorageHistoryStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); - let changesets = tx.unwind_storage_history_indices(BlockNumberAddress::range(range))?; + let changesets = + provider.unwind_storage_history_indices(BlockNumberAddress::range(range))?; let checkpoint = if let Some(mut stage_checkpoint) = input.checkpoint.index_history_stage_checkpoint() { @@ -106,7 +104,7 @@ impl Stage for IndexStorageHistoryStage { /// given block range and calculates the progress by counting the number of processed entries in the /// [tables::StorageChangeSet] table within the given block range. fn stage_checkpoint( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, checkpoint: StageCheckpoint, range: &RangeInclusive, ) -> Result { @@ -123,18 +121,19 @@ fn stage_checkpoint( block_range: CheckpointBlockRange::from(range), progress: EntitiesCheckpoint { processed: progress.processed, - total: tx.deref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }, } } _ => IndexHistoryCheckpoint { block_range: CheckpointBlockRange::from(range), progress: EntitiesCheckpoint { - processed: tx + processed: provider + .tx_ref() .cursor_read::()? .walk_range(BlockNumberAddress::range(0..=checkpoint.block_number))? .count() as u64, - total: tx.deref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }, }, }) @@ -144,6 +143,7 @@ fn stage_checkpoint( mod tests { use assert_matches::assert_matches; + use reth_provider::ShareableDatabase; use std::collections::BTreeMap; use super::*; @@ -157,7 +157,7 @@ mod tests { transaction::DbTxMut, BlockNumberList, }; - use reth_primitives::{hex_literal::hex, StorageEntry, H160, H256, U256}; + use reth_primitives::{hex_literal::hex, StorageEntry, H160, H256, MAINNET, U256}; const ADDRESS: H160 = H160(hex!("0000000000000000000000000000000000000001")); const STORAGE_KEY: H256 = @@ -223,8 +223,9 @@ mod tests { async fn run(tx: &TestTransaction, run_to: u64) { let input = ExecInput { target: Some(run_to), ..Default::default() }; let mut stage = IndexStorageHistoryStage::default(); - let mut tx = tx.inner(); - let out = stage.execute(&mut tx, input).await.unwrap(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); + let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( out, ExecOutput { @@ -237,7 +238,7 @@ mod tests { done: true } ); - tx.commit().unwrap(); + provider.commit().unwrap(); } async fn unwind(tx: &TestTransaction, unwind_from: u64, unwind_to: u64) { @@ -247,10 +248,11 @@ mod tests { ..Default::default() }; let mut stage = IndexStorageHistoryStage::default(); - let mut tx = tx.inner(); - let out = stage.unwind(&mut tx, input).await.unwrap(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); + let out = stage.unwind(&mut provider, input).await.unwrap(); assert_eq!(out, UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) }); - tx.commit().unwrap(); + provider.commit().unwrap(); } #[tokio::test] @@ -463,10 +465,11 @@ mod tests { // run { let mut stage = IndexStorageHistoryStage { commit_threshold: 4 }; // Two runs required - let mut tx = test_tx.inner(); + let factory = ShareableDatabase::new(&test_tx.tx, MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let mut input = ExecInput { target: Some(5), ..Default::default() }; - let out = stage.execute(&mut tx, input).await.unwrap(); + let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( out, ExecOutput { @@ -481,7 +484,7 @@ mod tests { ); input.checkpoint = Some(out.checkpoint); - let out = stage.execute(&mut tx, input).await.unwrap(); + let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( out, ExecOutput { @@ -495,7 +498,7 @@ mod tests { } ); - tx.commit().unwrap(); + provider.commit().unwrap(); } // verify @@ -561,8 +564,11 @@ mod tests { }) .unwrap(); + let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); + assert_matches!( - stage_checkpoint(&tx.inner(), StageCheckpoint::new(1), &(1..=2)).unwrap(), + stage_checkpoint(&provider, StageCheckpoint::new(1), &(1..=2)).unwrap(), IndexHistoryCheckpoint { block_range: CheckpointBlockRange { from: 1, to: 2 }, progress: EntitiesCheckpoint { processed: 3, total: 6 } diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index 4eb9f5033c5a..9201815789c1 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -12,12 +12,9 @@ use reth_primitives::{ trie::StoredSubNode, BlockNumber, SealedHeader, H256, }; -use reth_provider::Transaction; +use reth_provider::{DatabaseProviderRW, HeaderProvider, ProviderError}; use reth_trie::{IntermediateStateRootState, StateRoot, StateRootProgress}; -use std::{ - fmt::Debug, - ops::{Deref, DerefMut}, -}; +use std::fmt::Debug; use tracing::*; /// The merkle hashing stage uses input from @@ -93,11 +90,10 @@ impl MerkleStage { /// Gets the hashing progress pub fn get_execution_checkpoint( &self, - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, ) -> Result, StageError> { - let buf = tx - .get::(StageId::MerkleExecute.to_string())? - .unwrap_or_default(); + let buf = + provider.get_stage_checkpoint_progress(StageId::MerkleExecute)?.unwrap_or_default(); if buf.is_empty() { return Ok(None) @@ -110,7 +106,7 @@ impl MerkleStage { /// Saves the hashing progress pub fn save_execution_checkpoint( &mut self, - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, checkpoint: Option, ) -> Result<(), StageError> { let mut buf = vec![]; @@ -123,8 +119,7 @@ impl MerkleStage { ); checkpoint.to_compact(&mut buf); } - tx.put::(StageId::MerkleExecute.to_string(), buf)?; - Ok(()) + Ok(provider.save_stage_checkpoint_progress(StageId::MerkleExecute, buf)?) } } @@ -143,7 +138,7 @@ impl Stage for MerkleStage { /// Execute the stage. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { let threshold = match self { @@ -160,10 +155,12 @@ impl Stage for MerkleStage { let (from_block, to_block) = range.clone().into_inner(); let current_block = input.target(); - let block = tx.get_header(current_block)?; + let block = provider + .header_by_number(current_block)? + .ok_or_else(|| ProviderError::HeaderNotFound(current_block.into()))?; let block_root = block.state_root; - let mut checkpoint = self.get_execution_checkpoint(tx)?; + let mut checkpoint = self.get_execution_checkpoint(provider)?; let (trie_root, entities_checkpoint) = if range.is_empty() { (block_root, input.checkpoint().entities_stage_checkpoint().unwrap_or_default()) @@ -192,25 +189,27 @@ impl Stage for MerkleStage { ); // Reset the checkpoint and clear trie tables checkpoint = None; - self.save_execution_checkpoint(tx, None)?; - tx.clear::()?; - tx.clear::()?; + self.save_execution_checkpoint(provider, None)?; + provider.tx_ref().clear::()?; + provider.tx_ref().clear::()?; None } .unwrap_or(EntitiesCheckpoint { processed: 0, - total: (tx.deref().entries::()? + - tx.deref().entries::()?) as u64, + total: (provider.tx_ref().entries::()? + + provider.tx_ref().entries::()?) + as u64, }); - let progress = StateRoot::new(tx.deref_mut()) + let tx = provider.tx_ref(); + let progress = StateRoot::new(tx) .with_intermediate_state(checkpoint.map(IntermediateStateRootState::from)) .root_with_progress() .map_err(|e| StageError::Fatal(Box::new(e)))?; match progress { StateRootProgress::Progress(state, hashed_entries_walked, updates) => { - updates.flush(tx.deref_mut())?; + updates.flush(tx)?; let checkpoint = MerkleCheckpoint::new( to_block, @@ -219,7 +218,7 @@ impl Stage for MerkleStage { state.walker_stack.into_iter().map(StoredSubNode::from).collect(), state.hash_builder.into(), ); - self.save_execution_checkpoint(tx, Some(checkpoint))?; + self.save_execution_checkpoint(provider, Some(checkpoint))?; entities_checkpoint.processed += hashed_entries_walked as u64; @@ -231,7 +230,7 @@ impl Stage for MerkleStage { }) } StateRootProgress::Complete(root, hashed_entries_walked, updates) => { - updates.flush(tx.deref_mut())?; + updates.flush(tx)?; entities_checkpoint.processed += hashed_entries_walked as u64; @@ -240,12 +239,13 @@ impl Stage for MerkleStage { } } else { debug!(target: "sync::stages::merkle::exec", current = ?current_block, target = ?to_block, "Updating trie"); - let (root, updates) = StateRoot::incremental_root_with_updates(tx.deref_mut(), range) - .map_err(|e| StageError::Fatal(Box::new(e)))?; - updates.flush(tx.deref_mut())?; + let (root, updates) = + StateRoot::incremental_root_with_updates(provider.tx_ref(), range) + .map_err(|e| StageError::Fatal(Box::new(e)))?; + updates.flush(provider.tx_ref())?; - let total_hashed_entries = (tx.deref().entries::()? + - tx.deref().entries::()?) + let total_hashed_entries = (provider.tx_ref().entries::()? + + provider.tx_ref().entries::()?) as u64; let entities_checkpoint = EntitiesCheckpoint { @@ -260,7 +260,7 @@ impl Stage for MerkleStage { }; // Reset the checkpoint - self.save_execution_checkpoint(tx, None)?; + self.save_execution_checkpoint(provider, None)?; self.validate_state_root(trie_root, block.seal_slow(), to_block)?; @@ -274,9 +274,10 @@ impl Stage for MerkleStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { + let tx = provider.tx_ref(); let range = input.unwind_block_range(); if matches!(self, MerkleStage::Execution { .. }) { info!(target: "sync::stages::merkle::unwind", "Stage is always skipped"); @@ -286,8 +287,8 @@ impl Stage for MerkleStage { let mut entities_checkpoint = input.checkpoint.entities_stage_checkpoint().unwrap_or(EntitiesCheckpoint { processed: 0, - total: (tx.deref().entries::()? + - tx.deref().entries::()?) as u64, + total: (tx.entries::()? + + tx.entries::()?) as u64, }); if input.unwind_to == 0 { @@ -304,16 +305,17 @@ impl Stage for MerkleStage { // Unwind trie only if there are transitions if !range.is_empty() { - let (block_root, updates) = - StateRoot::incremental_root_with_updates(tx.deref_mut(), range) - .map_err(|e| StageError::Fatal(Box::new(e)))?; + let (block_root, updates) = StateRoot::incremental_root_with_updates(tx, range) + .map_err(|e| StageError::Fatal(Box::new(e)))?; // Validate the calulated state root - let target = tx.get_header(input.unwind_to)?; + let target = provider + .header_by_number(input.unwind_to)? + .ok_or_else(|| ProviderError::HeaderNotFound(input.unwind_to.into()))?; self.validate_state_root(block_root, target.seal_slow(), input.unwind_to)?; // Validation passed, apply unwind changes to the database. - updates.flush(tx.deref_mut())?; + updates.flush(provider.tx_ref())?; // TODO(alexey): update entities checkpoint } else { diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index ab863a8d7255..a26cbad7a1fa 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -13,8 +13,8 @@ use reth_primitives::{ stage::{EntitiesCheckpoint, StageCheckpoint, StageId}, TransactionSignedNoHash, TxNumber, H160, }; -use reth_provider::{ProviderError, Transaction}; -use std::{fmt::Debug, ops::Deref}; +use reth_provider::{DatabaseProviderRW, HeaderProvider, ProviderError}; +use std::fmt::Debug; use thiserror::Error; use tokio::sync::mpsc; use tracing::*; @@ -56,7 +56,7 @@ impl Stage for SenderRecoveryStage { /// the [`TxSenders`][reth_db::tables::TxSenders] table. async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -64,7 +64,7 @@ impl Stage for SenderRecoveryStage { } let (tx_range, block_range, is_final_range) = - input.next_block_range_with_transaction_threshold(tx, self.commit_threshold)?; + input.next_block_range_with_transaction_threshold(provider, self.commit_threshold)?; let end_block = *block_range.end(); // No transactions to walk over @@ -72,11 +72,13 @@ impl Stage for SenderRecoveryStage { info!(target: "sync::stages::sender_recovery", ?tx_range, "Target transaction already reached"); return Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), done: is_final_range, }) } + let tx = provider.tx_ref(); + // Acquire the cursor for inserting elements let mut senders_cursor = tx.cursor_write::()?; @@ -133,7 +135,9 @@ impl Stage for SenderRecoveryStage { // fetch the sealed header so we can use it in the sender recovery // unwind - let sealed_header = tx.get_sealed_header(block_number)?; + let sealed_header = provider + .sealed_header(block_number)? + .ok_or(ProviderError::HeaderNotFound(block_number.into()))?; return Err(StageError::Validation { block: sealed_header, error: @@ -150,7 +154,7 @@ impl Stage for SenderRecoveryStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), done: is_final_range, }) } @@ -158,18 +162,18 @@ impl Stage for SenderRecoveryStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); // Lookup latest tx id that we should unwind to - let latest_tx_id = tx.block_body_indices(unwind_to)?.last_tx_num(); - tx.unwind_table_by_num::(latest_tx_id)?; + let latest_tx_id = provider.block_body_indices(unwind_to)?.last_tx_num(); + provider.unwind_table_by_num::(latest_tx_id)?; Ok(UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), }) } } @@ -194,11 +198,11 @@ fn recover_sender( } fn stage_checkpoint( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, ) -> Result { Ok(EntitiesCheckpoint { - processed: tx.deref().entries::()? as u64, - total: tx.deref().entries::()? as u64, + processed: provider.tx_ref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }) } diff --git a/crates/stages/src/stages/total_difficulty.rs b/crates/stages/src/stages/total_difficulty.rs index a9b0de762668..41afa821300e 100644 --- a/crates/stages/src/stages/total_difficulty.rs +++ b/crates/stages/src/stages/total_difficulty.rs @@ -11,8 +11,8 @@ use reth_primitives::{ stage::{EntitiesCheckpoint, StageCheckpoint, StageId}, U256, }; -use reth_provider::Transaction; -use std::{ops::Deref, sync::Arc}; +use reth_provider::DatabaseProviderRW; +use std::sync::Arc; use tracing::*; /// The total difficulty stage. @@ -51,9 +51,10 @@ impl Stage for TotalDifficultyStage { /// Write total difficulty entries async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { + let tx = provider.tx_ref(); if input.target_reached() { return Ok(ExecOutput::done(input.checkpoint())) } @@ -89,7 +90,7 @@ impl Stage for TotalDifficultyStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), done: is_final_range, }) } @@ -97,26 +98,26 @@ impl Stage for TotalDifficultyStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); - tx.unwind_table_by_num::(unwind_to)?; + provider.unwind_table_by_num::(unwind_to)?; Ok(UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), }) } } fn stage_checkpoint( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, DB>, ) -> Result { Ok(EntitiesCheckpoint { - processed: tx.deref().entries::()? as u64, - total: tx.deref().entries::()? as u64, + processed: provider.tx_ref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }) } diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index 56ea803abd61..f379598d951e 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -13,8 +13,7 @@ use reth_primitives::{ stage::{EntitiesCheckpoint, StageCheckpoint, StageId}, TransactionSignedNoHash, TxNumber, H256, }; -use reth_provider::Transaction; -use std::ops::Deref; +use reth_provider::DatabaseProviderRW; use tokio::sync::mpsc; use tracing::*; @@ -52,19 +51,19 @@ impl Stage for TransactionLookupStage { /// Write transaction hash -> id entries async fn execute( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { return Ok(ExecOutput::done(input.checkpoint())) } - let (tx_range, block_range, is_final_range) = - input.next_block_range_with_transaction_threshold(tx, self.commit_threshold)?; + input.next_block_range_with_transaction_threshold(provider, self.commit_threshold)?; let end_block = *block_range.end(); debug!(target: "sync::stages::transaction_lookup", ?tx_range, "Updating transaction lookup"); + let tx = provider.tx_ref(); let mut tx_cursor = tx.cursor_read::()?; let tx_walker = tx_cursor.walk_range(tx_range)?; @@ -138,7 +137,7 @@ impl Stage for TransactionLookupStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), done: is_final_range, }) } @@ -146,9 +145,10 @@ impl Stage for TransactionLookupStage { /// Unwind the stage. async fn unwind( &mut self, - tx: &mut Transaction<'_, DB>, + provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { + let tx = provider.tx_ref(); let (range, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); // Cursors to unwind tx hash to number @@ -174,17 +174,17 @@ impl Stage for TransactionLookupStage { Ok(UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) - .with_entities_stage_checkpoint(stage_checkpoint(tx)?), + .with_entities_stage_checkpoint(stage_checkpoint(provider)?), }) } } fn stage_checkpoint( - tx: &Transaction<'_, DB>, + provider: &DatabaseProviderRW<'_, &DB>, ) -> Result { Ok(EntitiesCheckpoint { - processed: tx.deref().entries::()? as u64, - total: tx.deref().entries::()? as u64, + processed: provider.tx_ref().entries::()? as u64, + total: provider.tx_ref().entries::()? as u64, }) } diff --git a/crates/stages/src/test_utils/runner.rs b/crates/stages/src/test_utils/runner.rs index de1b96fea5ac..7666e0755e8c 100644 --- a/crates/stages/src/test_utils/runner.rs +++ b/crates/stages/src/test_utils/runner.rs @@ -1,8 +1,9 @@ use super::TestTransaction; use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::mdbx::{Env, WriteMap}; -use reth_provider::Transaction; -use std::borrow::Borrow; +use reth_primitives::MAINNET; +use reth_provider::ShareableDatabase; +use std::{borrow::Borrow, sync::Arc}; use tokio::sync::oneshot; #[derive(thiserror::Error, Debug)] @@ -44,9 +45,11 @@ pub(crate) trait ExecuteStageTestRunner: StageTestRunner { let (tx, rx) = oneshot::channel(); let (db, mut stage) = (self.tx().inner_raw(), self.stage()); tokio::spawn(async move { - let mut db = Transaction::new(db.borrow()).expect("failed to create db container"); - let result = stage.execute(&mut db, input).await; - db.commit().expect("failed to commit"); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); + + let result = stage.execute(&mut provider, input).await; + provider.commit().expect("failed to commit"); tx.send(result).expect("failed to send message") }); rx @@ -68,9 +71,11 @@ pub(crate) trait UnwindStageTestRunner: StageTestRunner { let (tx, rx) = oneshot::channel(); let (db, mut stage) = (self.tx().inner_raw(), self.stage()); tokio::spawn(async move { - let mut db = Transaction::new(db.borrow()).expect("failed to create db container"); - let result = stage.unwind(&mut db, input).await; - db.commit().expect("failed to commit"); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); + + let result = stage.unwind(&mut provider, input).await; + provider.commit().expect("failed to commit"); tx.send(result).expect("failed to send result"); }); Box::pin(rx).await.unwrap() diff --git a/crates/stages/src/test_utils/stage.rs b/crates/stages/src/test_utils/stage.rs index 81056a5e1401..028b74218fcb 100644 --- a/crates/stages/src/test_utils/stage.rs +++ b/crates/stages/src/test_utils/stage.rs @@ -1,7 +1,7 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::database::Database; use reth_primitives::stage::StageId; -use reth_provider::Transaction; +use reth_provider::DatabaseProviderRW; use std::collections::VecDeque; #[derive(Debug)] @@ -48,7 +48,7 @@ impl Stage for TestStage { async fn execute( &mut self, - _: &mut Transaction<'_, DB>, + _: &mut DatabaseProviderRW<'_, &DB>, _input: ExecInput, ) -> Result { self.exec_outputs @@ -58,7 +58,7 @@ impl Stage for TestStage { async fn unwind( &mut self, - _: &mut Transaction<'_, DB>, + _: &mut DatabaseProviderRW<'_, &DB>, _input: UnwindInput, ) -> Result { self.unwind_outputs diff --git a/crates/stages/src/test_utils/test_db.rs b/crates/stages/src/test_utils/test_db.rs index 41a0f49569a1..9345441cb2d6 100644 --- a/crates/stages/src/test_utils/test_db.rs +++ b/crates/stages/src/test_utils/test_db.rs @@ -13,9 +13,10 @@ use reth_db::{ DatabaseError as DbError, }; use reth_primitives::{ - keccak256, Account, Address, BlockNumber, SealedBlock, SealedHeader, StorageEntry, H256, U256, + keccak256, Account, Address, BlockNumber, SealedBlock, SealedHeader, StorageEntry, H256, + MAINNET, U256, }; -use reth_provider::Transaction; +use reth_provider::{DatabaseProviderRW, ShareableDatabase}; use std::{ borrow::Borrow, collections::BTreeMap, @@ -36,26 +37,30 @@ pub struct TestTransaction { /// WriteMap DB pub tx: Arc>, pub path: Option, + factory: ShareableDatabase>>, } impl Default for TestTransaction { /// Create a new instance of [TestTransaction] fn default() -> Self { - Self { tx: create_test_db::(EnvKind::RW), path: None } + let tx = create_test_db::(EnvKind::RW); + Self { tx: tx.clone(), path: None, factory: ShareableDatabase::new(tx, MAINNET.clone()) } } } impl TestTransaction { pub fn new(path: &Path) -> Self { + let tx = create_test_db::(EnvKind::RW); Self { - tx: Arc::new(create_test_db_with_path::(EnvKind::RW, path)), + tx: tx.clone(), path: Some(path.to_path_buf()), + factory: ShareableDatabase::new(tx, MAINNET.clone()), } } - /// Return a database wrapped in [Transaction]. - pub fn inner(&self) -> Transaction<'_, Env> { - Transaction::new(self.tx.borrow()).expect("failed to create db container") + /// Return a database wrapped in [DatabaseProviderRW]. + pub fn inner(&self) -> DatabaseProviderRW<'_, Arc>> { + self.factory.provider_rw().expect("failed to create db container") } /// Get a pointer to an internal database. @@ -69,8 +74,8 @@ impl TestTransaction { F: FnOnce(&mut Tx<'_, RW, WriteMap>) -> Result<(), DbError>, { let mut tx = self.inner(); - f(&mut tx)?; - tx.commit()?; + f(tx.tx_mut())?; + tx.commit().expect("failed to commit"); Ok(()) } @@ -79,7 +84,7 @@ impl TestTransaction { where F: FnOnce(&Tx<'_, RW, WriteMap>) -> Result, { - f(&self.inner()) + f(self.inner().tx_ref()) } /// Check if the table is empty diff --git a/crates/storage/db/src/abstraction/database.rs b/crates/storage/db/src/abstraction/database.rs index a2071b80b7c9..421220d95f38 100644 --- a/crates/storage/db/src/abstraction/database.rs +++ b/crates/storage/db/src/abstraction/database.rs @@ -4,7 +4,7 @@ use crate::{ transaction::{DbTx, DbTxMut}, DatabaseError, }; -use std::sync::Arc; +use std::{fmt::Debug, sync::Arc}; /// Implements the GAT method from: /// . @@ -12,9 +12,9 @@ use std::sync::Arc; /// Sealed trait which cannot be implemented by 3rd parties, exposed only for implementers pub trait DatabaseGAT<'a, __ImplicitBounds: Sealed = Bounds<&'a Self>>: Send + Sync { /// RO database transaction - type TX: DbTx<'a> + Send + Sync; + type TX: DbTx<'a> + Send + Sync + Debug; /// RW database transaction - type TXMut: DbTxMut<'a> + DbTx<'a> + TableImporter<'a> + Send + Sync; + type TXMut: DbTxMut<'a> + DbTx<'a> + TableImporter<'a> + Send + Sync + Debug; } /// Main Database trait that spawns transactions to be executed. diff --git a/crates/storage/db/src/abstraction/mock.rs b/crates/storage/db/src/abstraction/mock.rs index 656bae654f26..c7b340925291 100644 --- a/crates/storage/db/src/abstraction/mock.rs +++ b/crates/storage/db/src/abstraction/mock.rs @@ -38,7 +38,7 @@ impl<'a> DatabaseGAT<'a> for DatabaseMock { } /// Mock read only tx -#[derive(Clone, Default)] +#[derive(Debug, Clone, Default)] pub struct TxMock { /// Table representation _table: BTreeMap, Vec>, diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index d4297db96048..b00e6646fc8a 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -11,11 +11,11 @@ /// Various provider traits. mod traits; pub use traits::{ - AccountProvider, BlockExecutor, BlockHashProvider, BlockIdProvider, BlockNumProvider, - BlockProvider, BlockProviderIdExt, BlockSource, BlockchainTreePendingStateProvider, - CanonChainTracker, CanonStateNotification, CanonStateNotificationSender, - CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, ExecutorFactory, - HeaderProvider, PostStateDataProvider, ReceiptProvider, ReceiptProviderIdExt, + AccountExtProvider, AccountProvider, BlockExecutor, BlockHashProvider, BlockIdProvider, + BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockSource, + BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotification, + CanonStateNotificationSender, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, + ExecutorFactory, HeaderProvider, PostStateDataProvider, ReceiptProvider, ReceiptProviderIdExt, StageCheckpointProvider, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, WithdrawalsProvider, }; @@ -23,8 +23,8 @@ pub use traits::{ /// Provider trait implementations. pub mod providers; pub use providers::{ - HistoricalStateProvider, HistoricalStateProviderRef, LatestStateProvider, - LatestStateProviderRef, ShareableDatabase, + DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW, HistoricalStateProvider, + HistoricalStateProviderRef, LatestStateProvider, LatestStateProviderRef, ShareableDatabase, }; /// Execution result @@ -33,7 +33,7 @@ pub use post_state::PostState; /// Helper types for interacting with the database mod transaction; -pub use transaction::{Transaction, TransactionError}; +pub use transaction::TransactionError; /// Common database utilities. mod utils; diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 61762de4db42..54c418336b91 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -18,7 +18,7 @@ use std::{ops::RangeBounds, sync::Arc}; use tracing::trace; mod provider; -use provider::{DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW}; +pub use provider::{DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW}; /// A common provider that fetches data from a database. /// @@ -34,16 +34,17 @@ pub struct ShareableDatabase { impl ShareableDatabase { /// Returns a provider with a created `DbTx` inside, which allows fetching data from the /// database using different types of providers. Example: [`HeaderProvider`] - /// [`BlockHashProvider`] + /// [`BlockHashProvider`]. This may fail if the inner read database transaction fails to open. pub fn provider(&self) -> Result> { Ok(DatabaseProvider::new(self.db.tx()?, self.chain_spec.clone())) } /// Returns a provider with a created `DbTxMut` inside, which allows fetching and updating /// data from the database using different types of providers. Example: [`HeaderProvider`] - /// [`BlockHashProvider`] + /// [`BlockHashProvider`]. This may fail if the inner read/write database transaction fails to + /// open. pub fn provider_rw(&self) -> Result> { - Ok(DatabaseProvider::new_rw(self.db.tx_mut()?, self.chain_spec.clone())) + Ok(DatabaseProviderRW(DatabaseProvider::new_rw(self.db.tx_mut()?, self.chain_spec.clone()))) } } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 64dd88c0ad18..a355a09e6394 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1,36 +1,87 @@ use crate::{ - traits::{BlockSource, ReceiptProvider}, - BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, HeaderProvider, - ProviderError, StageCheckpointProvider, TransactionsProvider, WithdrawalsProvider, + insert_canonical_block, + post_state::StorageChangeset, + traits::{AccountExtProvider, BlockSource, ReceiptProvider}, + AccountProvider, BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, + HeaderProvider, PostState, ProviderError, StageCheckpointProvider, TransactionError, + TransactionsProvider, WithdrawalsProvider, }; +use itertools::{izip, Itertools}; use reth_db::{ - cursor::DbCursorRO, - database::DatabaseGAT, - models::StoredBlockBodyIndices, + common::KeyValue, + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, + database::{Database, DatabaseGAT}, + models::{ + sharded_key, + storage_sharded_key::{self, StorageShardedKey}, + AccountBeforeTx, BlockNumberAddress, ShardedKey, StoredBlockBodyIndices, + }, + table::Table, tables, - transaction::{DbTx, DbTxMut}, + transaction::{DbTx, DbTxMut, DbTxMutGAT}, + BlockNumberList, DatabaseError, }; use reth_interfaces::Result; use reth_primitives::{ + keccak256, stage::{StageCheckpoint, StageId}, - Block, BlockHash, BlockHashOrNumber, BlockNumber, ChainInfo, ChainSpec, Head, Header, Receipt, - SealedBlock, SealedHeader, TransactionMeta, TransactionSigned, TxHash, TxNumber, Withdrawal, - H256, U256, + Account, Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, ChainInfo, ChainSpec, + Hardfork, Head, Header, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, + StorageEntry, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, TxHash, + TxNumber, Withdrawal, H256, U256, }; use reth_revm_primitives::{ config::revm_spec, env::{fill_block_env, fill_cfg_and_block_env, fill_cfg_env}, primitives::{BlockEnv, CfgEnv, SpecId}, }; -use std::{ops::RangeBounds, sync::Arc}; +use reth_trie::StateRoot; +use std::{ + collections::{btree_map::Entry, BTreeMap, BTreeSet}, + fmt::Debug, + ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive}, + sync::Arc, +}; + +use super::get_stage_checkpoint; /// A [`DatabaseProvider`] that holds a read-only database transaction. -pub(crate) type DatabaseProviderRO<'this, DB> = - DatabaseProvider<'this, >::TX>; +pub type DatabaseProviderRO<'this, DB> = DatabaseProvider<'this, >::TX>; /// A [`DatabaseProvider`] that holds a read-write database transaction. -pub(crate) type DatabaseProviderRW<'this, DB> = - DatabaseProvider<'this, >::TXMut>; +/// +/// Ideally this would be an alias type. However, there's some weird compiler error (), that forces us to wrap this in a struct instead. +/// Once that issue is solved, we can probably revert back to being an alias type. +#[derive(Debug)] +pub struct DatabaseProviderRW<'this, DB: Database>( + pub DatabaseProvider<'this, >::TXMut>, +); + +impl<'this, DB: Database> Deref for DatabaseProviderRW<'this, DB> { + type Target = DatabaseProvider<'this, >::TXMut>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'this, DB: Database> DerefMut for DatabaseProviderRW<'this, DB> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'this, DB: Database> DatabaseProviderRW<'this, DB> { + /// Commit database transaction + pub fn commit(self) -> Result { + self.0.commit() + } + + /// Consume `DbTx` or `DbTxMut`. + pub fn into_tx(self) -> >::TXMut { + self.0.into_tx() + } +} /// A provider struct that fetchs data from the database. /// Wrapper around [`DbTx`] and [`DbTxMut`]. Example: [`HeaderProvider`] [`BlockHashProvider`] @@ -43,7 +94,7 @@ where tx: TX, /// Chain spec chain_spec: Arc, - _phantom_data: std::marker::PhantomData<&'this ()>, + _phantom_data: std::marker::PhantomData<&'this TX>, } impl<'this, TX: DbTxMut<'this>> DatabaseProvider<'this, TX> { @@ -53,6 +104,78 @@ impl<'this, TX: DbTxMut<'this>> DatabaseProvider<'this, TX> { } } +/// Unwind all history shards. For boundary shard, remove it from database and +/// return last part of shard with still valid items. If all full shard were removed, return list +/// would be empty. +fn unwind_account_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( + cursor: &mut >::CursorMut, + address: Address, + block_number: BlockNumber, +) -> std::result::Result, TransactionError> { + let mut item = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; + + while let Some((sharded_key, list)) = item { + // there is no more shard for address + if sharded_key.key != address { + break + } + cursor.delete_current()?; + // check first item and if it is more and eq than `transition_id` delete current + // item. + let first = list.iter(0).next().expect("List can't empty"); + if first >= block_number as usize { + item = cursor.prev()?; + continue + } else if block_number <= sharded_key.highest_block_number { + // if first element is in scope whole list would be removed. + // so at least this first element is present. + return Ok(list.iter(0).take_while(|i| *i < block_number as usize).collect::>()) + } else { + let new_list = list.iter(0).collect::>(); + return Ok(new_list) + } + } + Ok(Vec::new()) +} + +/// Unwind all history shards. For boundary shard, remove it from database and +/// return last part of shard with still valid items. If all full shard were removed, return list +/// would be empty but this does not mean that there is none shard left but that there is no +/// split shards. +fn unwind_storage_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( + cursor: &mut >::CursorMut, + address: Address, + storage_key: H256, + block_number: BlockNumber, +) -> std::result::Result, TransactionError> { + let mut item = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; + + while let Some((storage_sharded_key, list)) = item { + // there is no more shard for address + if storage_sharded_key.address != address || + storage_sharded_key.sharded_key.key != storage_key + { + // there is no more shard for address and storage_key. + break + } + cursor.delete_current()?; + // check first item and if it is more and eq than `transition_id` delete current + // item. + let first = list.iter(0).next().expect("List can't empty"); + if first >= block_number as usize { + item = cursor.prev()?; + continue + } else if block_number <= storage_sharded_key.sharded_key.highest_block_number { + // if first element is in scope whole list would be removed. + // so at least this first element is present. + return Ok(list.iter(0).take_while(|i| *i < block_number as usize).collect::>()) + } else { + return Ok(list.iter(0).collect::>()) + } + } + Ok(Vec::new()) +} + impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { /// Creates a provider with an inner read-only transaction. pub fn new(tx: TX, chain_spec: Arc) -> Self { @@ -63,6 +186,162 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { pub fn into_tx(self) -> TX { self.tx } + + /// Pass `DbTx` or `DbTxMut` mutable reference. + pub fn tx_mut(&mut self) -> &mut TX { + &mut self.tx + } + + /// Pass `DbTx` or `DbTxMut` immutable reference. + pub fn tx_ref(&self) -> &TX { + &self.tx + } + + /// Return full table as Vec + pub fn table(&self) -> std::result::Result>, DatabaseError> + where + T::Key: Default + Ord, + { + self.tx + .cursor_read::()? + .walk(Some(T::Key::default()))? + .collect::, DatabaseError>>() + } + + // TODO(joshie) TEMPORARY should be moved to trait providers + + /// Iterate over account changesets and return all account address that were changed. + pub fn get_addresses_and_keys_of_changed_storages( + &self, + range: RangeInclusive, + ) -> std::result::Result>, TransactionError> { + Ok(self + .tx + .cursor_read::()? + .walk_range(BlockNumberAddress::range(range))? + .collect::, _>>()? + .into_iter() + // fold all storages and save its old state so we can remove it from HashedStorage + // it is needed as it is dup table. + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap>, + (BlockNumberAddress((_, address)), storage_entry)| { + accounts.entry(address).or_default().insert(storage_entry.key); + accounts + }, + )) + } + + /// Get plainstate storages + #[allow(clippy::type_complexity)] + pub fn get_plainstate_storages( + &self, + iter: impl IntoIterator)>, + ) -> std::result::Result)>, TransactionError> { + let mut plain_storage = self.tx.cursor_dup_read::()?; + + iter.into_iter() + .map(|(address, storage)| { + storage + .into_iter() + .map(|key| -> std::result::Result<_, TransactionError> { + let ret = plain_storage + .seek_by_key_subkey(address, key)? + .filter(|v| v.key == key) + .unwrap_or_default(); + Ok((key, ret.value)) + }) + .collect::, _>>() + .map(|storage| (address, storage)) + }) + .collect::, _>>() + } + + /// Get all transaction ids where account got changed. + /// + /// NOTE: Get inclusive range of blocks. + pub fn get_storage_transition_ids_from_changeset( + &self, + range: RangeInclusive, + ) -> std::result::Result>, TransactionError> { + let storage_changeset = self + .tx + .cursor_read::()? + .walk_range(BlockNumberAddress::range(range))? + .collect::, _>>()?; + + // fold all storages to one set of changes + let storage_changeset_lists = storage_changeset.into_iter().fold( + BTreeMap::new(), + |mut storages: BTreeMap<(Address, H256), Vec>, (index, storage)| { + storages + .entry((index.address(), storage.key)) + .or_default() + .push(index.block_number()); + storages + }, + ); + + Ok(storage_changeset_lists) + } + + /// Get all transaction ids where account got changed. + /// + /// NOTE: Get inclusive range of blocks. + pub fn get_account_transition_ids_from_changeset( + &self, + range: RangeInclusive, + ) -> std::result::Result>, TransactionError> { + let account_changesets = self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()?; + + let account_transtions = account_changesets + .into_iter() + // fold all account to one set of changed accounts + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap>, (index, account)| { + accounts.entry(account.address).or_default().push(index); + accounts + }, + ); + + Ok(account_transtions) + } + + /// Iterate over account changesets and return all account address that were changed. + pub fn get_addresses_of_changed_accounts( + &self, + range: RangeInclusive, + ) -> std::result::Result, TransactionError> { + Ok(self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()? + .into_iter() + // fold all account to one set of changed accounts + .fold(BTreeSet::new(), |mut accounts: BTreeSet
, (_, account_before)| { + accounts.insert(account_before.address); + accounts + })) + } + + /// Get plainstate account from iterator + pub fn get_plainstate_accounts( + &self, + iter: impl IntoIterator, + ) -> std::result::Result)>, TransactionError> { + let mut plain_accounts = self.tx.cursor_read::()?; + Ok(iter + .into_iter() + .map(|address| plain_accounts.seek_exact(address).map(|a| (address, a.map(|(_, v)| v)))) + .collect::, _>>()?) + } } impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { @@ -70,6 +349,1136 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { pub fn commit(self) -> Result { Ok(self.tx.commit()?) } + + // TODO(joshie) TEMPORARY should be moved to trait providers + + /// Get range of blocks and its execution result + pub fn get_block_and_execution_range( + &self, + chain_spec: &ChainSpec, + range: RangeInclusive, + ) -> std::result::Result, TransactionError> { + self.get_take_block_and_execution_range::(chain_spec, range) + } + + /// Take range of blocks and its execution result + pub fn take_block_and_execution_range( + &self, + chain_spec: &ChainSpec, + range: RangeInclusive, + ) -> std::result::Result, TransactionError> { + self.get_take_block_and_execution_range::(chain_spec, range) + } + + /// Unwind and clear account hashing + pub fn unwind_account_hashing( + &self, + range: RangeInclusive, + ) -> std::result::Result<(), TransactionError> { + let mut hashed_accounts = self.tx.cursor_write::()?; + + // Aggregate all transition changesets and make a list of accounts that have been changed. + self.tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()? + .into_iter() + .rev() + // fold all account to get the old balance/nonces and account that needs to be removed + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap>, (_, account_before)| { + accounts.insert(account_before.address, account_before.info); + accounts + }, + ) + .into_iter() + // hash addresses and collect it inside sorted BTreeMap. + // We are doing keccak only once per address. + .map(|(address, account)| (keccak256(address), account)) + .collect::>() + .into_iter() + // Apply values to HashedState (if Account is None remove it); + .try_for_each( + |(hashed_address, account)| -> std::result::Result<(), TransactionError> { + if let Some(account) = account { + hashed_accounts.upsert(hashed_address, account)?; + } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { + hashed_accounts.delete_current()?; + } + Ok(()) + }, + )?; + + Ok(()) + } + + /// Unwind and clear storage hashing + pub fn unwind_storage_hashing( + &self, + range: Range, + ) -> std::result::Result<(), TransactionError> { + let mut hashed_storage = self.tx.cursor_dup_write::()?; + + // Aggregate all transition changesets and make list of accounts that have been changed. + self.tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()? + .into_iter() + .rev() + // fold all account to get the old balance/nonces and account that needs to be removed + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap<(Address, H256), U256>, + (BlockNumberAddress((_, address)), storage_entry)| { + accounts.insert((address, storage_entry.key), storage_entry.value); + accounts + }, + ) + .into_iter() + // hash addresses and collect it inside sorted BTreeMap. + // We are doing keccak only once per address. + .map(|((address, key), value)| ((keccak256(address), keccak256(key)), value)) + .collect::>() + .into_iter() + // Apply values to HashedStorage (if Value is zero just remove it); + .try_for_each( + |((hashed_address, key), value)| -> std::result::Result<(), TransactionError> { + if hashed_storage + .seek_by_key_subkey(hashed_address, key)? + .filter(|entry| entry.key == key) + .is_some() + { + hashed_storage.delete_current()?; + } + + if value != U256::ZERO { + hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; + } + Ok(()) + }, + )?; + + Ok(()) + } + + /// Unwind and clear account history indices. + /// + /// Returns number of changesets walked. + pub fn unwind_account_history_indices( + &self, + range: RangeInclusive, + ) -> std::result::Result { + let account_changeset = self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()?; + let changesets = account_changeset.len(); + + let last_indices = account_changeset + .into_iter() + // reverse so we can get lowest transition id where we need to unwind account. + .rev() + // fold all account and get last transition index + .fold(BTreeMap::new(), |mut accounts: BTreeMap, (index, account)| { + // we just need address and lowest transition id. + accounts.insert(account.address, index); + accounts + }); + // try to unwind the index + let mut cursor = self.tx.cursor_write::()?; + for (address, rem_index) in last_indices { + let shard_part = unwind_account_history_shards::(&mut cursor, address, rem_index)?; + + // check last shard_part, if present, items needs to be reinserted. + if !shard_part.is_empty() { + // there are items in list + self.tx.put::( + ShardedKey::new(address, u64::MAX), + BlockNumberList::new(shard_part) + .expect("There is at least one element in list and it is sorted."), + )?; + } + } + + Ok(changesets) + } + + /// Unwind and clear storage history indices. + /// + /// Returns number of changesets walked. + pub fn unwind_storage_history_indices( + &self, + range: Range, + ) -> std::result::Result { + let storage_changesets = self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()?; + let changesets = storage_changesets.len(); + + let last_indices = storage_changesets + .into_iter() + // reverse so we can get lowest transition id where we need to unwind account. + .rev() + // fold all storages and get last transition index + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap<(Address, H256), u64>, (index, storage)| { + // we just need address and lowest transition id. + accounts.insert((index.address(), storage.key), index.block_number()); + accounts + }, + ); + + let mut cursor = self.tx.cursor_write::()?; + for ((address, storage_key), rem_index) in last_indices { + let shard_part = + unwind_storage_history_shards::(&mut cursor, address, storage_key, rem_index)?; + + // check last shard_part, if present, items needs to be reinserted. + if !shard_part.is_empty() { + // there are items in list + self.tx.put::( + StorageShardedKey::new(address, storage_key, u64::MAX), + BlockNumberList::new(shard_part) + .expect("There is at least one element in list and it is sorted."), + )?; + } + } + + Ok(changesets) + } + + /// Traverse over changesets and plain state and recreate the [`PostState`]s for the given range + /// of blocks. + /// + /// 1. Iterate over the [BlockBodyIndices][tables::BlockBodyIndices] table to get all + /// the transition indices. + /// 2. Iterate over the [StorageChangeSet][tables::StorageChangeSet] table + /// and the [AccountChangeSet][tables::AccountChangeSet] tables in reverse order to reconstruct + /// the changesets. + /// - In order to have both the old and new values in the changesets, we also access the + /// plain state tables. + /// 3. While iterating over the changeset tables, if we encounter a new account or storage slot, + /// we: + /// 1. Take the old value from the changeset + /// 2. Take the new value from the plain state + /// 3. Save the old value to the local state + /// 4. While iterating over the changeset tables, if we encounter an account/storage slot we + /// have seen before we: + /// 1. Take the old value from the changeset + /// 2. Take the new value from the local state + /// 3. Set the local state to the value in the changeset + /// + /// If `TAKE` is `true`, the local state will be written to the plain state tables. + /// 5. Get all receipts from table + fn get_take_block_execution_result_range( + &self, + range: RangeInclusive, + ) -> std::result::Result, TransactionError> { + if range.is_empty() { + return Ok(Vec::new()) + } + + // We are not removing block meta as it is used to get block transitions. + let block_bodies = self.get_or_take::(range.clone())?; + + // get transaction receipts + let from_transaction_num = + block_bodies.first().expect("already checked if there are blocks").1.first_tx_num(); + let to_transaction_num = + block_bodies.last().expect("already checked if there are blocks").1.last_tx_num(); + let receipts = + self.get_or_take::(from_transaction_num..=to_transaction_num)?; + + let storage_range = BlockNumberAddress::range(range.clone()); + + let storage_changeset = + self.get_or_take::(storage_range)?; + let account_changeset = self.get_or_take::(range)?; + + // iterate previous value and get plain state value to create changeset + // Double option around Account represent if Account state is know (first option) and + // account is removed (Second Option) + type LocalPlainState = BTreeMap>, BTreeMap)>; + + let mut local_plain_state: LocalPlainState = BTreeMap::new(); + + // iterate in reverse and get plain state. + + // Bundle execution changeset to its particular transaction and block + let mut block_states = + BTreeMap::from_iter(block_bodies.iter().map(|(num, _)| (*num, PostState::default()))); + + let mut plain_accounts_cursor = self.tx.cursor_write::()?; + let mut plain_storage_cursor = self.tx.cursor_dup_write::()?; + + // add account changeset changes + for (block_number, account_before) in account_changeset.into_iter().rev() { + let AccountBeforeTx { info: old_info, address } = account_before; + let new_info = match local_plain_state.entry(address) { + Entry::Vacant(entry) => { + let new_account = plain_accounts_cursor.seek_exact(address)?.map(|kv| kv.1); + entry.insert((Some(old_info), BTreeMap::new())); + new_account + } + Entry::Occupied(mut entry) => { + let new_account = std::mem::replace(&mut entry.get_mut().0, Some(old_info)); + new_account.expect("As we are stacking account first, account would always be Some(Some) or Some(None)") + } + }; + + let post_state = block_states.entry(block_number).or_default(); + match (old_info, new_info) { + (Some(old), Some(new)) => { + if new != old { + post_state.change_account(block_number, address, old, new); + } else { + unreachable!("Junk data in database: an account changeset did not represent any change"); + } + } + (None, Some(account)) => post_state.create_account(block_number, address, account), + (Some(old), None) => + post_state.destroy_account(block_number, address, old), + (None, None) => unreachable!("Junk data in database: an account changeset transitioned from no account to no account"), + }; + } + + // add storage changeset changes + let mut storage_changes: BTreeMap = BTreeMap::new(); + for (block_and_address, storage_entry) in storage_changeset.into_iter().rev() { + let BlockNumberAddress((_, address)) = block_and_address; + let new_storage = + match local_plain_state.entry(address).or_default().1.entry(storage_entry.key) { + Entry::Vacant(entry) => { + let new_storage = plain_storage_cursor + .seek_by_key_subkey(address, storage_entry.key)? + .filter(|storage| storage.key == storage_entry.key) + .unwrap_or_default(); + entry.insert(storage_entry.value); + new_storage.value + } + Entry::Occupied(mut entry) => { + std::mem::replace(entry.get_mut(), storage_entry.value) + } + }; + storage_changes.entry(block_and_address).or_default().insert( + U256::from_be_bytes(storage_entry.key.0), + (storage_entry.value, new_storage), + ); + } + + for (BlockNumberAddress((block_number, address)), storage_changeset) in + storage_changes.into_iter() + { + block_states.entry(block_number).or_default().change_storage( + block_number, + address, + storage_changeset, + ); + } + + if TAKE { + // iterate over local plain state remove all account and all storages. + for (address, (account, storage)) in local_plain_state.into_iter() { + // revert account + if let Some(account) = account { + let existing_entry = plain_accounts_cursor.seek_exact(address)?; + if let Some(account) = account { + plain_accounts_cursor.upsert(address, account)?; + } else if existing_entry.is_some() { + plain_accounts_cursor.delete_current()?; + } + } + + // revert storages + for (storage_key, storage_value) in storage.into_iter() { + let storage_entry = StorageEntry { key: storage_key, value: storage_value }; + // delete previous value + // TODO: This does not use dupsort features + if plain_storage_cursor + .seek_by_key_subkey(address, storage_key)? + .filter(|s| s.key == storage_key) + .is_some() + { + plain_storage_cursor.delete_current()? + } + + // TODO: This does not use dupsort features + // insert value if needed + if storage_value != U256::ZERO { + plain_storage_cursor.upsert(address, storage_entry)?; + } + } + } + } + + // iterate over block body and create ExecutionResult + let mut receipt_iter = receipts.into_iter(); + + // loop break if we are at the end of the blocks. + for (block_number, block_body) in block_bodies.into_iter() { + for _ in block_body.tx_num_range() { + if let Some((_, receipt)) = receipt_iter.next() { + block_states + .entry(block_number) + .or_default() + .add_receipt(block_number, receipt); + } + } + } + Ok(block_states.into_values().collect()) + } + + /// Return range of blocks and its execution result + pub fn get_take_block_and_execution_range( + &self, + chain_spec: &ChainSpec, + range: RangeInclusive, + ) -> std::result::Result, TransactionError> { + if TAKE { + let storage_range = BlockNumberAddress::range(range.clone()); + + self.unwind_account_hashing(range.clone())?; + self.unwind_account_history_indices(range.clone())?; + self.unwind_storage_hashing(storage_range.clone())?; + self.unwind_storage_history_indices(storage_range)?; + + // merkle tree + let (new_state_root, trie_updates) = + StateRoot::incremental_root_with_updates(&self.tx, range.clone())?; + + let parent_number = range.start().saturating_sub(1); + let parent_state_root = self + .tx + .get::(parent_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))? + .state_root; + + // state root should be always correct as we are reverting state. + // but for sake of double verification we will check it again. + if new_state_root != parent_state_root { + let parent_hash = self + .tx + .get::(parent_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))?; + return Err(TransactionError::UnwindStateRootMismatch { + got: new_state_root, + expected: parent_state_root, + block_number: parent_number, + block_hash: parent_hash, + }) + } + trie_updates.flush(&self.tx)?; + } + // get blocks + let blocks = self.get_take_block_range::(chain_spec, range.clone())?; + let unwind_to = blocks.first().map(|b| b.number.saturating_sub(1)); + // get execution res + let execution_res = self.get_take_block_execution_result_range::(range.clone())?; + // combine them + let blocks_with_exec_result: Vec<_> = + blocks.into_iter().zip(execution_res.into_iter()).collect(); + + // remove block bodies it is needed for both get block range and get block execution results + // that is why it is deleted afterwards. + if TAKE { + // rm block bodies + self.get_or_take::(range)?; + + // Update pipeline progress + if let Some(fork_number) = unwind_to { + self.update_pipeline_stages(fork_number, true)?; + } + } + + // return them + Ok(blocks_with_exec_result) + } + + /// Return list of entries from table + /// + /// If TAKE is true, opened cursor would be write and it would delete all values from db. + #[inline] + pub fn get_or_take( + &self, + range: impl RangeBounds, + ) -> std::result::Result>, DatabaseError> { + if TAKE { + let mut cursor_write = self.tx.cursor_write::()?; + let mut walker = cursor_write.walk_range(range)?; + let mut items = Vec::new(); + while let Some(i) = walker.next().transpose()? { + walker.delete_current()?; + items.push(i) + } + Ok(items) + } else { + self.tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>() + } + } + + /// Get requested blocks transaction with signer + fn get_take_block_transaction_range( + &self, + range: impl RangeBounds + Clone, + ) -> std::result::Result)>, TransactionError> + { + // Raad range of block bodies to get all transactions id's of this range. + let block_bodies = self.get_or_take::(range)?; + + if block_bodies.is_empty() { + return Ok(Vec::new()) + } + + // Compute the first and last tx ID in the range + let first_transaction = block_bodies.first().expect("If we have headers").1.first_tx_num(); + let last_transaction = block_bodies.last().expect("Not empty").1.last_tx_num(); + + // If this is the case then all of the blocks in the range are empty + if last_transaction < first_transaction { + return Ok(block_bodies.into_iter().map(|(n, _)| (n, Vec::new())).collect()) + } + + // Get transactions and senders + let transactions = self + .get_or_take::(first_transaction..=last_transaction)? + .into_iter() + .map(|(id, tx)| (id, tx.into())) + .collect::>(); + + let senders = + self.get_or_take::(first_transaction..=last_transaction)?; + + if TAKE { + // Remove TxHashNumber + let mut tx_hash_cursor = self.tx.cursor_write::()?; + for (_, tx) in transactions.iter() { + if tx_hash_cursor.seek_exact(tx.hash())?.is_some() { + tx_hash_cursor.delete_current()?; + } + } + + // Remove TransactionBlock index if there are transaction present + if !transactions.is_empty() { + let tx_id_range = transactions.first().unwrap().0..=transactions.last().unwrap().0; + self.get_or_take::(tx_id_range)?; + } + } + + // Merge transaction into blocks + let mut block_tx = Vec::with_capacity(block_bodies.len()); + let mut senders = senders.into_iter(); + let mut transactions = transactions.into_iter(); + for (block_number, block_body) in block_bodies { + let mut one_block_tx = Vec::with_capacity(block_body.tx_count as usize); + for _ in block_body.tx_num_range() { + let tx = transactions.next(); + let sender = senders.next(); + + let recovered = match (tx, sender) { + (Some((tx_id, tx)), Some((sender_tx_id, sender))) => { + if tx_id != sender_tx_id { + Err(ProviderError::MismatchOfTransactionAndSenderId { tx_id }) + } else { + Ok(TransactionSignedEcRecovered::from_signed_transaction(tx, sender)) + } + } + (Some((tx_id, _)), _) | (_, Some((tx_id, _))) => { + Err(ProviderError::MismatchOfTransactionAndSenderId { tx_id }) + } + (None, None) => Err(ProviderError::BlockBodyTransactionCount), + }?; + one_block_tx.push(recovered) + } + block_tx.push((block_number, one_block_tx)); + } + + Ok(block_tx) + } + + /// Return range of blocks and its execution result + fn get_take_block_range( + &self, + chain_spec: &ChainSpec, + range: impl RangeBounds + Clone, + ) -> std::result::Result, TransactionError> { + // For block we need Headers, Bodies, Uncles, withdrawals, Transactions, Signers + + let block_headers = self.get_or_take::(range.clone())?; + if block_headers.is_empty() { + return Ok(Vec::new()) + } + + let block_header_hashes = + self.get_or_take::(range.clone())?; + let block_ommers = self.get_or_take::(range.clone())?; + let block_withdrawals = + self.get_or_take::(range.clone())?; + + let block_tx = self.get_take_block_transaction_range::(range.clone())?; + + if TAKE { + // rm HeaderTD + self.get_or_take::(range)?; + // rm HeaderNumbers + let mut header_number_cursor = self.tx.cursor_write::()?; + for (_, hash) in block_header_hashes.iter() { + if header_number_cursor.seek_exact(*hash)?.is_some() { + header_number_cursor.delete_current()?; + } + } + } + + // merge all into block + let block_header_iter = block_headers.into_iter(); + let block_header_hashes_iter = block_header_hashes.into_iter(); + let block_tx_iter = block_tx.into_iter(); + + // Ommers can be empty for some blocks + let mut block_ommers_iter = block_ommers.into_iter(); + let mut block_withdrawals_iter = block_withdrawals.into_iter(); + let mut block_ommers = block_ommers_iter.next(); + let mut block_withdrawals = block_withdrawals_iter.next(); + + let mut blocks = Vec::new(); + for ((main_block_number, header), (_, header_hash), (_, tx)) in izip!( + block_header_iter.into_iter(), + block_header_hashes_iter.into_iter(), + block_tx_iter.into_iter() + ) { + let header = header.seal(header_hash); + + let (body, senders) = tx.into_iter().map(|tx| tx.to_components()).unzip(); + + // Ommers can be missing + let mut ommers = Vec::new(); + if let Some((block_number, _)) = block_ommers.as_ref() { + if *block_number == main_block_number { + ommers = block_ommers.take().unwrap().1.ommers; + block_ommers = block_ommers_iter.next(); + } + }; + + // withdrawal can be missing + let shanghai_is_active = + chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp); + let mut withdrawals = Some(Vec::new()); + if shanghai_is_active { + if let Some((block_number, _)) = block_withdrawals.as_ref() { + if *block_number == main_block_number { + withdrawals = Some(block_withdrawals.take().unwrap().1.withdrawals); + block_withdrawals = block_withdrawals_iter.next(); + } + } + } else { + withdrawals = None + } + + blocks.push(SealedBlockWithSenders { + block: SealedBlock { header, body, ommers, withdrawals }, + senders, + }) + } + + Ok(blocks) + } + + /// Update all pipeline sync stage progress. + pub fn update_pipeline_stages( + &self, + block_number: BlockNumber, + drop_stage_checkpoint: bool, + ) -> std::result::Result<(), TransactionError> { + // iterate over all existing stages in the table and update its progress. + let mut cursor = self.tx.cursor_write::()?; + while let Some((stage_name, checkpoint)) = cursor.next()? { + cursor.upsert( + stage_name, + StageCheckpoint { + block_number, + ..if drop_stage_checkpoint { Default::default() } else { checkpoint } + }, + )? + } + + Ok(()) + } + + /// Insert storage change index to database. Used inside StorageHistoryIndex stage + pub fn insert_storage_history_index( + &self, + storage_transitions: BTreeMap<(Address, H256), Vec>, + ) -> std::result::Result<(), TransactionError> { + for ((address, storage_key), mut indices) in storage_transitions { + let mut last_shard = self.take_last_storage_shard(address, storage_key)?; + last_shard.append(&mut indices); + + // chunk indices and insert them in shards of N size. + let mut chunks = last_shard + .iter() + .chunks(storage_sharded_key::NUM_OF_INDICES_IN_SHARD) + .into_iter() + .map(|chunks| chunks.map(|i| *i as usize).collect::>()) + .collect::>(); + let last_chunk = chunks.pop(); + + // chunk indices and insert them in shards of N size. + chunks.into_iter().try_for_each(|list| { + self.tx.put::( + StorageShardedKey::new( + address, + storage_key, + *list.last().expect("Chuck does not return empty list") as BlockNumber, + ), + BlockNumberList::new(list).expect("Indices are presorted and not empty"), + ) + })?; + // Insert last list with u64::MAX + if let Some(last_list) = last_chunk { + self.tx.put::( + StorageShardedKey::new(address, storage_key, u64::MAX), + BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), + )?; + } + } + Ok(()) + } + + /// Insert account change index to database. Used inside AccountHistoryIndex stage + pub fn insert_account_history_index( + &self, + account_transitions: BTreeMap>, + ) -> std::result::Result<(), TransactionError> { + // insert indexes to AccountHistory. + for (address, mut indices) in account_transitions { + let mut last_shard = self.take_last_account_shard(address)?; + last_shard.append(&mut indices); + // chunk indices and insert them in shards of N size. + let mut chunks = last_shard + .iter() + .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) + .into_iter() + .map(|chunks| chunks.map(|i| *i as usize).collect::>()) + .collect::>(); + let last_chunk = chunks.pop(); + + chunks.into_iter().try_for_each(|list| { + self.tx.put::( + ShardedKey::new( + address, + *list.last().expect("Chuck does not return empty list") as BlockNumber, + ), + BlockNumberList::new(list).expect("Indices are presorted and not empty"), + ) + })?; + // Insert last list with u64::MAX + if let Some(last_list) = last_chunk { + self.tx.put::( + ShardedKey::new(address, u64::MAX), + BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), + )? + } + } + Ok(()) + } + + /// Get the stage checkpoint. + pub fn get_stage_checkpoint( + &self, + id: StageId, + ) -> std::result::Result, DatabaseError> { + get_stage_checkpoint(&self.tx, id) + } + + /// Save stage checkpoint. + pub fn save_stage_checkpoint( + &self, + id: StageId, + checkpoint: StageCheckpoint, + ) -> std::result::Result<(), DatabaseError> { + self.tx.put::(id.to_string(), checkpoint)?; + Ok(()) + } + + /// Get stage checkpoint progress. + pub fn get_stage_checkpoint_progress( + &self, + id: StageId, + ) -> std::result::Result>, DatabaseError> { + self.tx.get::(id.to_string()) + } + + /// Save stage checkpoint progress. + pub fn save_stage_checkpoint_progress( + &self, + id: StageId, + checkpoint: Vec, + ) -> std::result::Result<(), DatabaseError> { + self.tx.put::(id.to_string(), checkpoint)?; + Ok(()) + } + + /// Get lastest block number. + pub fn tip_number(&self) -> std::result::Result { + Ok(self.tx.cursor_read::()?.last()?.unwrap_or_default().0) + } + + /// Query [tables::CanonicalHeaders] table for block hash by block number + pub fn get_block_hash( + &self, + block_number: BlockNumber, + ) -> std::result::Result { + let hash = self + .tx + .get::(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + Ok(hash) + } + + /// Query the block body by number. + pub fn block_body_indices( + &self, + number: BlockNumber, + ) -> std::result::Result { + let body = self + .tx + .get::(number)? + .ok_or(ProviderError::BlockBodyIndicesNotFound(number))?; + Ok(body) + } + + /// Query the block header by number + pub fn get_header(&self, number: BlockNumber) -> std::result::Result { + let header = self + .tx + .get::(number)? + .ok_or_else(|| ProviderError::HeaderNotFound(number.into()))?; + Ok(header) + } + + /// Get the total difficulty for a block. + pub fn get_td(&self, block: BlockNumber) -> std::result::Result { + let td = self + .tx + .get::(block)? + .ok_or(ProviderError::TotalDifficultyNotFound { number: block })?; + Ok(td.into()) + } + + /// Unwind table by some number key. + /// Returns number of rows unwound. + /// + /// Note: Key is not inclusive and specified key would stay in db. + #[inline] + pub fn unwind_table_by_num(&self, num: u64) -> std::result::Result + where + T: Table, + { + self.unwind_table::(num, |key| key) + } + + /// Unwind the table to a provided number key. + /// Returns number of rows unwound. + /// + /// Note: Key is not inclusive and specified key would stay in db. + pub(crate) fn unwind_table( + &self, + key: u64, + mut selector: F, + ) -> std::result::Result + where + T: Table, + F: FnMut(T::Key) -> u64, + { + let mut cursor = self.tx.cursor_write::()?; + let mut reverse_walker = cursor.walk_back(None)?; + let mut deleted = 0; + + while let Some(Ok((entry_key, _))) = reverse_walker.next() { + if selector(entry_key.clone()) <= key { + break + } + reverse_walker.delete_current()?; + deleted += 1; + } + + Ok(deleted) + } + + /// Unwind a table forward by a [Walker][reth_db::abstraction::cursor::Walker] on another table + pub fn unwind_table_by_walker( + &self, + start_at: T1::Key, + ) -> std::result::Result<(), DatabaseError> + where + T1: Table, + T2: Table, + { + let mut cursor = self.tx.cursor_write::()?; + let mut walker = cursor.walk(Some(start_at))?; + while let Some((_, value)) = walker.next().transpose()? { + self.tx.delete::(value, None)?; + } + Ok(()) + } + + /// Load last shard and check if it is full and remove if it is not. If list is empty, last + /// shard was full or there is no shards at all. + fn take_last_account_shard( + &self, + address: Address, + ) -> std::result::Result, TransactionError> { + let mut cursor = self.tx.cursor_read::()?; + let last = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; + if let Some((shard_key, list)) = last { + // delete old shard so new one can be inserted. + self.tx.delete::(shard_key, None)?; + let list = list.iter(0).map(|i| i as u64).collect::>(); + return Ok(list) + } + Ok(Vec::new()) + } + + /// Load last shard and check if it is full and remove if it is not. If list is empty, last + /// shard was full or there is no shards at all. + pub fn take_last_storage_shard( + &self, + address: Address, + storage_key: H256, + ) -> std::result::Result, TransactionError> { + let mut cursor = self.tx.cursor_read::()?; + let last = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; + if let Some((storage_shard_key, list)) = last { + // delete old shard so new one can be inserted. + self.tx.delete::(storage_shard_key, None)?; + let list = list.iter(0).map(|i| i as u64).collect::>(); + return Ok(list) + } + Ok(Vec::new()) + } + /// iterate over storages and insert them to hashing table + pub fn insert_storage_for_hashing( + &self, + storages: impl IntoIterator)>, + ) -> std::result::Result<(), TransactionError> { + // hash values + let hashed = storages.into_iter().fold(BTreeMap::new(), |mut map, (address, storage)| { + let storage = storage.into_iter().fold(BTreeMap::new(), |mut map, (key, value)| { + map.insert(keccak256(key), value); + map + }); + map.insert(keccak256(address), storage); + map + }); + + let mut hashed_storage = self.tx.cursor_dup_write::()?; + // Hash the address and key and apply them to HashedStorage (if Storage is None + // just remove it); + hashed.into_iter().try_for_each(|(hashed_address, storage)| { + storage.into_iter().try_for_each( + |(key, value)| -> std::result::Result<(), TransactionError> { + if hashed_storage + .seek_by_key_subkey(hashed_address, key)? + .filter(|entry| entry.key == key) + .is_some() + { + hashed_storage.delete_current()?; + } + + if value != U256::ZERO { + hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; + } + Ok(()) + }, + ) + })?; + Ok(()) + } + + /// iterate over accounts and insert them to hashing table + pub fn insert_account_for_hashing( + &self, + accounts: impl IntoIterator)>, + ) -> std::result::Result<(), TransactionError> { + let mut hashed_accounts = self.tx.cursor_write::()?; + + let hashes_accounts = accounts.into_iter().fold( + BTreeMap::new(), + |mut map: BTreeMap>, (address, account)| { + map.insert(keccak256(address), account); + map + }, + ); + + hashes_accounts.into_iter().try_for_each( + |(hashed_address, account)| -> std::result::Result<(), TransactionError> { + if let Some(account) = account { + hashed_accounts.upsert(hashed_address, account)? + } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { + hashed_accounts.delete_current()?; + } + Ok(()) + }, + )?; + Ok(()) + } + + /// Append blocks and insert its post state. + /// This will insert block data to all related tables and will update pipeline progress. + pub fn append_blocks_with_post_state( + &mut self, + blocks: Vec, + state: PostState, + ) -> std::result::Result<(), TransactionError> { + if blocks.is_empty() { + return Ok(()) + } + let new_tip = blocks.last().unwrap(); + let new_tip_number = new_tip.number; + + let first_number = blocks.first().unwrap().number; + + let last = blocks.last().unwrap(); + let last_block_number = last.number; + let last_block_hash = last.hash(); + let expected_state_root = last.state_root; + + // Insert the blocks + for block in blocks { + let (block, senders) = block.into_components(); + insert_canonical_block(self.tx_mut(), block, Some(senders))?; + } + + // Write state and changesets to the database. + // Must be written after blocks because of the receipt lookup. + state.write_to_db(self.tx_mut())?; + + self.insert_hashes(first_number..=last_block_number, last_block_hash, expected_state_root)?; + + self.calculate_history_indices(first_number..=last_block_number)?; + + // Update pipeline progress + self.update_pipeline_stages(new_tip_number, false)?; + + Ok(()) + } + + /// Insert full block and make it canonical. + pub fn insert_block( + &mut self, + block: SealedBlock, + senders: Option>, + ) -> std::result::Result<(), TransactionError> { + insert_canonical_block(self.tx_mut(), block, senders)?; + Ok(()) + } + + /// Read account/storage changesets and update account/storage history indices. + pub fn calculate_history_indices( + &mut self, + range: RangeInclusive, + ) -> std::result::Result<(), TransactionError> { + // account history stage + { + let indices = self.get_account_transition_ids_from_changeset(range.clone())?; + self.insert_account_history_index(indices)?; + } + + // storage history stage + { + let indices = self.get_storage_transition_ids_from_changeset(range)?; + self.insert_storage_history_index(indices)?; + } + + Ok(()) + } + + /// Calculate the hashes of all changed accounts and storages, and finally calculate the state + /// root. + /// + /// The chain goes from `fork_block_number + 1` to `current_block_number`, and hashes are + /// calculated from `from_transition_id` to `to_transition_id`. + /// + /// The resulting state root is compared with `expected_state_root`. + pub fn insert_hashes( + &mut self, + range: RangeInclusive, + end_block_hash: H256, + expected_state_root: H256, + ) -> std::result::Result<(), TransactionError> { + // storage hashing stage + { + let lists = self.get_addresses_and_keys_of_changed_storages(range.clone())?; + let storages = self.get_plainstate_storages(lists.into_iter())?; + self.insert_storage_for_hashing(storages.into_iter())?; + } + + // account hashing stage + { + let lists = self.get_addresses_of_changed_accounts(range.clone())?; + let accounts = self.get_plainstate_accounts(lists.into_iter())?; + self.insert_account_for_hashing(accounts.into_iter())?; + } + + // merkle tree + { + let (state_root, trie_updates) = + StateRoot::incremental_root_with_updates(&self.tx, range.clone())?; + if state_root != expected_state_root { + return Err(TransactionError::StateRootMismatch { + got: state_root, + expected: expected_state_root, + block_number: *range.end(), + block_hash: end_block_hash, + }) + } + trie_updates.flush(&self.tx)?; + } + Ok(()) + } +} + +impl<'this, TX: DbTx<'this>> AccountProvider for DatabaseProvider<'this, TX> { + fn basic_account(&self, address: Address) -> Result> { + Ok(self.tx.get::(address)?) + } +} + +impl<'this, TX: DbTx<'this>> AccountExtProvider for DatabaseProvider<'this, TX> { + fn changed_accounts_with_range( + &self, + range: impl RangeBounds, + ) -> Result> { + Ok(self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()? + .into_iter() + // fold all account to one set of changed accounts + .fold(BTreeSet::new(), |mut accounts: BTreeSet
, (_, account_before)| { + accounts.insert(account_before.address); + accounts + })) + } + fn basic_accounts( + &self, + iter: impl IntoIterator, + ) -> Result)>> { + let mut plain_accounts = self.tx.cursor_read::()?; + Ok(iter + .into_iter() + .map(|address| plain_accounts.seek_exact(address).map(|a| (address, a.map(|(_, v)| v)))) + .collect::, _>>()?) + } } impl<'this, TX: DbTx<'this>> HeaderProvider for DatabaseProvider<'this, TX> { diff --git a/crates/storage/provider/src/test_utils/blocks.rs b/crates/storage/provider/src/test_utils/blocks.rs index a9349698c22c..7df40d45e00c 100644 --- a/crates/storage/provider/src/test_utils/blocks.rs +++ b/crates/storage/provider/src/test_utils/blocks.rs @@ -1,6 +1,6 @@ //! Dummy blocks and data for tests -use crate::{post_state::PostState, Transaction}; +use crate::{post_state::PostState, DatabaseProviderRW}; use reth_db::{database::Database, models::StoredBlockBodyIndices, tables}; use reth_primitives::{ hex_literal::hex, Account, BlockNumber, Bytes, Header, Log, Receipt, SealedBlock, @@ -10,9 +10,11 @@ use reth_rlp::Decodable; use std::collections::BTreeMap; /// Assert genesis block -pub fn assert_genesis_block(tx: &Transaction<'_, DB>, g: SealedBlock) { +pub fn assert_genesis_block(provider: &DatabaseProviderRW<'_, DB>, g: SealedBlock) { let n = g.number; let h = H256::zero(); + let tx = provider; + // check if all tables are empty assert_eq!(tx.table::().unwrap(), vec![(g.number, g.header.clone().unseal())]); diff --git a/crates/storage/provider/src/traits/account.rs b/crates/storage/provider/src/traits/account.rs index 5660ebb07959..6a48104f55ff 100644 --- a/crates/storage/provider/src/traits/account.rs +++ b/crates/storage/provider/src/traits/account.rs @@ -1,6 +1,7 @@ use auto_impl::auto_impl; use reth_interfaces::Result; -use reth_primitives::{Account, Address}; +use reth_primitives::{Account, Address, BlockNumber}; +use std::{collections::BTreeSet, ops::RangeBounds}; /// Account provider #[auto_impl(&, Arc, Box)] @@ -10,3 +11,22 @@ pub trait AccountProvider: Send + Sync { /// Returns `None` if the account doesn't exist. fn basic_account(&self, address: Address) -> Result>; } + +/// Account provider +#[auto_impl(&, Arc, Box)] +pub trait AccountExtProvider: Send + Sync { + /// Iterate over account changesets and return all account address that were changed. + fn changed_accounts_with_range( + &self, + _range: impl RangeBounds, + ) -> Result>; + + /// Get basic account information for multiple accounts. A more efficient version than calling + /// [`AccountProvider::basic_account`] repeatedly. + /// + /// Returns `None` if the account doesn't exist. + fn basic_accounts( + &self, + _iter: impl IntoIterator, + ) -> Result)>>; +} diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index 11439a87f150..1c7c5680714b 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -1,7 +1,7 @@ //! Collection of common provider traits. mod account; -pub use account::AccountProvider; +pub use account::{AccountExtProvider, AccountProvider}; mod block; pub use block::{BlockProvider, BlockProviderIdExt, BlockSource}; diff --git a/crates/storage/provider/src/transaction.rs b/crates/storage/provider/src/transaction.rs index 954e615a5051..24a470ebdfd1 100644 --- a/crates/storage/provider/src/transaction.rs +++ b/crates/storage/provider/src/transaction.rs @@ -1,1412 +1,7 @@ -use crate::{ - insert_canonical_block, - post_state::{PostState, StorageChangeset}, - providers::get_stage_checkpoint, -}; -use itertools::{izip, Itertools}; -use reth_db::{ - common::KeyValue, - cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, - database::{Database, DatabaseGAT}, - models::{ - sharded_key, - storage_sharded_key::{self, StorageShardedKey}, - AccountBeforeTx, BlockNumberAddress, ShardedKey, StoredBlockBodyIndices, - }, - table::Table, - tables, - transaction::{DbTx, DbTxMut, DbTxMutGAT}, - BlockNumberList, -}; use reth_interfaces::{db::DatabaseError as DbError, provider::ProviderError}; -use reth_primitives::{ - keccak256, - stage::{StageCheckpoint, StageId}, - Account, Address, BlockHash, BlockNumber, ChainSpec, Hardfork, Header, SealedBlock, - SealedBlockWithSenders, SealedHeader, StorageEntry, TransactionSigned, - TransactionSignedEcRecovered, H256, U256, -}; -use reth_trie::{StateRoot, StateRootError}; -use std::{ - collections::{btree_map::Entry, BTreeMap, BTreeSet}, - fmt::Debug, - ops::{Deref, DerefMut, Range, RangeBounds, RangeInclusive}, -}; - -/// A container for any DB transaction that will open a new inner transaction when the current -/// one is committed. -// NOTE: This container is needed since `Transaction::commit` takes `mut self`, so methods in -// the pipeline that just take a reference will not be able to commit their transaction and let -// the pipeline continue. Is there a better way to do this? -// -// TODO: Re-evaluate if this is actually needed, this was introduced as a way to manage the -// lifetime of the `TXMut` and having a nice API for re-opening a new transaction after `commit` -pub struct Transaction<'this, DB: Database> { - /// A handle to the DB. - pub(crate) db: &'this DB, - tx: Option<>::TXMut>, -} - -impl<'a, DB: Database> Debug for Transaction<'a, DB> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Transaction").finish() - } -} - -impl<'a, DB: Database> Deref for Transaction<'a, DB> { - type Target = >::TXMut; - - /// Dereference as the inner transaction. - /// - /// # Panics - /// - /// Panics if an inner transaction does not exist. This should never be the case unless - /// [Transaction::close] was called without following up with a call to [Transaction::open]. - fn deref(&self) -> &Self::Target { - self.tx.as_ref().expect("Tried getting a reference to a non-existent transaction") - } -} - -impl<'a, DB: Database> DerefMut for Transaction<'a, DB> { - /// Dereference as a mutable reference to the inner transaction. - /// - /// # Panics - /// - /// Panics if an inner transaction does not exist. This should never be the case unless - /// [Transaction::close] was called without following up with a call to [Transaction::open]. - fn deref_mut(&mut self) -> &mut Self::Target { - self.tx.as_mut().expect("Tried getting a mutable reference to a non-existent transaction") - } -} - -// === Core impl === - -impl<'this, DB> Transaction<'this, DB> -where - DB: Database, -{ - /// Create a new container with the given database handle. - /// - /// A new inner transaction will be opened. - pub fn new(db: &'this DB) -> Result { - Ok(Self { db, tx: Some(db.tx_mut()?) }) - } - - /// Creates a new container with given database and transaction handles. - pub fn new_raw(db: &'this DB, tx: >::TXMut) -> Self { - Self { db, tx: Some(tx) } - } - - /// Accessor to the internal Database - pub fn inner(&self) -> &'this DB { - self.db - } - - /// Drops the current inner transaction and open a new one. - pub fn drop(&mut self) -> Result<(), DbError> { - if let Some(tx) = self.tx.take() { - drop(tx); - } - - self.tx = Some(self.db.tx_mut()?); - - Ok(()) - } - - /// Open a new inner transaction. - pub fn open(&mut self) -> Result<(), DbError> { - self.tx = Some(self.db.tx_mut()?); - Ok(()) - } - - /// Close the current inner transaction. - pub fn close(&mut self) { - self.tx.take(); - } - - /// Commit the current inner transaction and open a new one. - /// - /// # Panics - /// - /// Panics if an inner transaction does not exist. This should never be the case unless - /// [Transaction::close] was called without following up with a call to [Transaction::open]. - pub fn commit(&mut self) -> Result { - let success = if let Some(tx) = self.tx.take() { tx.commit()? } else { false }; - self.tx = Some(self.db.tx_mut()?); - Ok(success) - } -} - -// === Misc helpers === - -impl<'this, DB> Transaction<'this, DB> -where - DB: Database, -{ - /// Get lastest block number. - pub fn tip_number(&self) -> Result { - Ok(self.cursor_read::()?.last()?.unwrap_or_default().0) - } - - /// Query [tables::CanonicalHeaders] table for block hash by block number - pub fn get_block_hash(&self, block_number: BlockNumber) -> Result { - let hash = self - .get::(block_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; - Ok(hash) - } - - /// Query the block body by number. - pub fn block_body_indices( - &self, - number: BlockNumber, - ) -> Result { - let body = self - .get::(number)? - .ok_or(ProviderError::BlockBodyIndicesNotFound(number))?; - Ok(body) - } - - /// Query the block header by number - pub fn get_header(&self, number: BlockNumber) -> Result { - let header = self - .get::(number)? - .ok_or_else(|| ProviderError::HeaderNotFound(number.into()))?; - Ok(header) - } - - /// Get the total difficulty for a block. - pub fn get_td(&self, block: BlockNumber) -> Result { - let td = self - .get::(block)? - .ok_or(ProviderError::TotalDifficultyNotFound { number: block })?; - Ok(td.into()) - } - - /// Query the sealed header by number - pub fn get_sealed_header(&self, number: BlockNumber) -> Result { - let header = self.get_header(number)?; - let block_hash = self.get_block_hash(number)?; - Ok(header.seal(block_hash)) - } - - /// Unwind table by some number key. - /// Returns number of rows unwound. - /// - /// Note: Key is not inclusive and specified key would stay in db. - #[inline] - pub fn unwind_table_by_num(&self, num: u64) -> Result - where - DB: Database, - T: Table, - { - self.unwind_table::(num, |key| key) - } - - /// Unwind the table to a provided number key. - /// Returns number of rows unwound. - /// - /// Note: Key is not inclusive and specified key would stay in db. - pub(crate) fn unwind_table(&self, key: u64, mut selector: F) -> Result - where - DB: Database, - T: Table, - F: FnMut(T::Key) -> u64, - { - let mut cursor = self.cursor_write::()?; - let mut reverse_walker = cursor.walk_back(None)?; - let mut deleted = 0; - - while let Some(Ok((entry_key, _))) = reverse_walker.next() { - if selector(entry_key.clone()) <= key { - break - } - reverse_walker.delete_current()?; - deleted += 1; - } - - Ok(deleted) - } - - /// Unwind a table forward by a [Walker][reth_db::abstraction::cursor::Walker] on another table - pub fn unwind_table_by_walker(&self, start_at: T1::Key) -> Result<(), DbError> - where - DB: Database, - T1: Table, - T2: Table, - { - let mut cursor = self.cursor_write::()?; - let mut walker = cursor.walk(Some(start_at))?; - while let Some((_, value)) = walker.next().transpose()? { - self.delete::(value, None)?; - } - Ok(()) - } - - /// Load last shard and check if it is full and remove if it is not. If list is empty, last - /// shard was full or there is no shards at all. - fn take_last_account_shard(&self, address: Address) -> Result, TransactionError> { - let mut cursor = self.cursor_read::()?; - let last = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; - if let Some((shard_key, list)) = last { - // delete old shard so new one can be inserted. - self.delete::(shard_key, None)?; - let list = list.iter(0).map(|i| i as u64).collect::>(); - return Ok(list) - } - Ok(Vec::new()) - } - - /// Load last shard and check if it is full and remove if it is not. If list is empty, last - /// shard was full or there is no shards at all. - pub fn take_last_storage_shard( - &self, - address: Address, - storage_key: H256, - ) -> Result, TransactionError> { - let mut cursor = self.cursor_read::()?; - let last = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; - if let Some((storage_shard_key, list)) = last { - // delete old shard so new one can be inserted. - self.delete::(storage_shard_key, None)?; - let list = list.iter(0).map(|i| i as u64).collect::>(); - return Ok(list) - } - Ok(Vec::new()) - } -} - -// === Stages impl === - -impl<'this, DB> Transaction<'this, DB> -where - DB: Database, -{ - /// Get range of blocks and its execution result - pub fn get_block_and_execution_range( - &self, - chain_spec: &ChainSpec, - range: RangeInclusive, - ) -> Result, TransactionError> { - self.get_take_block_and_execution_range::(chain_spec, range) - } - - /// Take range of blocks and its execution result - pub fn take_block_and_execution_range( - &self, - chain_spec: &ChainSpec, - range: RangeInclusive, - ) -> Result, TransactionError> { - self.get_take_block_and_execution_range::(chain_spec, range) - } - - /// Unwind and clear account hashing. - pub fn unwind_account_hashing( - &self, - range: RangeInclusive, - ) -> Result<(), TransactionError> { - let mut hashed_accounts = self.cursor_write::()?; - - // Aggregate all transition changesets and make a list of accounts that have been changed. - self.cursor_read::()? - .walk_range(range)? - .collect::, _>>()? - .into_iter() - .rev() - // fold all account to get the old balance/nonces and account that needs to be removed - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, (_, account_before)| { - accounts.insert(account_before.address, account_before.info); - accounts - }, - ) - .into_iter() - // hash addresses and collect it inside sorted BTreeMap. - // We are doing keccak only once per address. - .map(|(address, account)| (keccak256(address), account)) - .collect::>() - .into_iter() - // Apply values to HashedState (if Account is None remove it); - .try_for_each(|(hashed_address, account)| -> Result<(), TransactionError> { - if let Some(account) = account { - hashed_accounts.upsert(hashed_address, account)?; - } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { - hashed_accounts.delete_current()?; - } - Ok(()) - })?; - - Ok(()) - } - - /// Unwind and clear storage hashing. - pub fn unwind_storage_hashing( - &self, - range: Range, - ) -> Result<(), TransactionError> { - let mut hashed_storage = self.cursor_dup_write::()?; - - // Aggregate all transition changesets and make list of accounts that have been changed. - self.cursor_read::()? - .walk_range(range)? - .collect::, _>>()? - .into_iter() - .rev() - // fold all account to get the old balance/nonces and account that needs to be removed - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap<(Address, H256), U256>, - (BlockNumberAddress((_, address)), storage_entry)| { - accounts.insert((address, storage_entry.key), storage_entry.value); - accounts - }, - ) - .into_iter() - // hash addresses and collect it inside sorted BTreeMap. - // We are doing keccak only once per address. - .map(|((address, key), value)| ((keccak256(address), keccak256(key)), value)) - .collect::>() - .into_iter() - // Apply values to HashedStorage (if Value is zero just remove it); - .try_for_each(|((hashed_address, key), value)| -> Result<(), TransactionError> { - if hashed_storage - .seek_by_key_subkey(hashed_address, key)? - .filter(|entry| entry.key == key) - .is_some() - { - hashed_storage.delete_current()?; - } - - if value != U256::ZERO { - hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; - } - Ok(()) - })?; - - Ok(()) - } - - /// Unwind and clear account history indices. - /// - /// Returns number of changesets walked. - pub fn unwind_account_history_indices( - &self, - range: RangeInclusive, - ) -> Result { - let account_changeset = self - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; - let changesets = account_changeset.len(); - - let last_indices = account_changeset - .into_iter() - // reverse so we can get lowest transition id where we need to unwind account. - .rev() - // fold all account and get last transition index - .fold(BTreeMap::new(), |mut accounts: BTreeMap, (index, account)| { - // we just need address and lowest transition id. - accounts.insert(account.address, index); - accounts - }); - - // try to unwind the index - let mut cursor = self.cursor_write::()?; - for (address, rem_index) in last_indices { - let shard_part = unwind_account_history_shards::(&mut cursor, address, rem_index)?; - - // check last shard_part, if present, items needs to be reinserted. - if !shard_part.is_empty() { - // there are items in list - self.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(shard_part) - .expect("There is at least one element in list and it is sorted."), - )?; - } - } - - Ok(changesets) - } - - /// Unwind and clear storage history indices. - /// - /// Returns number of changesets walked. - pub fn unwind_storage_history_indices( - &self, - range: Range, - ) -> Result { - let storage_changesets = self - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; - let changesets = storage_changesets.len(); - - let last_indices = storage_changesets - .into_iter() - // reverse so we can get lowest transition id where we need to unwind account. - .rev() - // fold all storages and get last transition index - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap<(Address, H256), u64>, (index, storage)| { - // we just need address and lowest transition id. - accounts.insert((index.address(), storage.key), index.block_number()); - accounts - }, - ); - - let mut cursor = self.cursor_write::()?; - for ((address, storage_key), rem_index) in last_indices { - let shard_part = - unwind_storage_history_shards::(&mut cursor, address, storage_key, rem_index)?; - - // check last shard_part, if present, items needs to be reinserted. - if !shard_part.is_empty() { - // there are items in list - self.put::( - StorageShardedKey::new(address, storage_key, u64::MAX), - BlockNumberList::new(shard_part) - .expect("There is at least one element in list and it is sorted."), - )?; - } - } - - Ok(changesets) - } - - /// Append blocks and insert its post state. - /// This will insert block data to all related tables and will update pipeline progress. - pub fn append_blocks_with_post_state( - &mut self, - blocks: Vec, - state: PostState, - ) -> Result<(), TransactionError> { - if blocks.is_empty() { - return Ok(()) - } - let new_tip = blocks.last().unwrap(); - let new_tip_number = new_tip.number; - - let first_number = blocks.first().unwrap().number; - - let last = blocks.last().unwrap(); - let last_block_number = last.number; - let last_block_hash = last.hash(); - let expected_state_root = last.state_root; - - // Insert the blocks - for block in blocks { - let (block, senders) = block.into_components(); - insert_canonical_block(self.deref_mut(), block, Some(senders))?; - } - - // Write state and changesets to the database. - // Must be written after blocks because of the receipt lookup. - state.write_to_db(self.deref_mut())?; - - self.insert_hashes(first_number..=last_block_number, last_block_hash, expected_state_root)?; - - self.calculate_history_indices(first_number..=last_block_number)?; - - // Update pipeline progress - self.update_pipeline_stages(new_tip_number, false)?; - - Ok(()) - } - - /// Insert full block and make it canonical. - pub fn insert_block( - &mut self, - block: SealedBlock, - senders: Option>, - ) -> Result<(), TransactionError> { - insert_canonical_block(self.deref_mut(), block, senders)?; - Ok(()) - } - - /// Read account/storage changesets and update account/storage history indices. - pub fn calculate_history_indices( - &mut self, - range: RangeInclusive, - ) -> Result<(), TransactionError> { - // account history stage - { - let indices = self.get_account_transition_ids_from_changeset(range.clone())?; - self.insert_account_history_index(indices)?; - } - - // storage history stage - { - let indices = self.get_storage_transition_ids_from_changeset(range)?; - self.insert_storage_history_index(indices)?; - } - - Ok(()) - } - - /// Calculate the hashes of all changed accounts and storages, and finally calculate the state - /// root. - /// - /// The chain goes from `fork_block_number + 1` to `current_block_number`, and hashes are - /// calculated from `from_transition_id` to `to_transition_id`. - /// - /// The resulting state root is compared with `expected_state_root`. - pub fn insert_hashes( - &mut self, - range: RangeInclusive, - end_block_hash: H256, - expected_state_root: H256, - ) -> Result<(), TransactionError> { - // storage hashing stage - { - let lists = self.get_addresses_and_keys_of_changed_storages(range.clone())?; - let storages = self.get_plainstate_storages(lists.into_iter())?; - self.insert_storage_for_hashing(storages.into_iter())?; - } - - // account hashing stage - { - let lists = self.get_addresses_of_changed_accounts(range.clone())?; - let accounts = self.get_plainstate_accounts(lists.into_iter())?; - self.insert_account_for_hashing(accounts.into_iter())?; - } - - // merkle tree - { - let (state_root, trie_updates) = - StateRoot::incremental_root_with_updates(self.deref_mut(), range.clone())?; - if state_root != expected_state_root { - return Err(TransactionError::StateRootMismatch { - got: state_root, - expected: expected_state_root, - block_number: *range.end(), - block_hash: end_block_hash, - }) - } - trie_updates.flush(self.deref_mut())?; - } - Ok(()) - } - - /// Return list of entries from table - /// - /// If TAKE is true, opened cursor would be write and it would delete all values from db. - #[inline] - pub fn get_or_take( - &self, - range: impl RangeBounds, - ) -> Result>, DbError> { - if TAKE { - let mut cursor_write = self.cursor_write::()?; - let mut walker = cursor_write.walk_range(range)?; - let mut items = Vec::new(); - while let Some(i) = walker.next().transpose()? { - walker.delete_current()?; - items.push(i) - } - Ok(items) - } else { - self.cursor_read::()?.walk_range(range)?.collect::, _>>() - } - } - - /// Get requested blocks transaction with signer - fn get_take_block_transaction_range( - &self, - range: impl RangeBounds + Clone, - ) -> Result)>, TransactionError> { - // Raad range of block bodies to get all transactions id's of this range. - let block_bodies = self.get_or_take::(range)?; - - if block_bodies.is_empty() { - return Ok(Vec::new()) - } - - // Compute the first and last tx ID in the range - let first_transaction = block_bodies.first().expect("If we have headers").1.first_tx_num(); - let last_transaction = block_bodies.last().expect("Not empty").1.last_tx_num(); - - // If this is the case then all of the blocks in the range are empty - if last_transaction < first_transaction { - return Ok(block_bodies.into_iter().map(|(n, _)| (n, Vec::new())).collect()) - } - - // Get transactions and senders - let transactions = self - .get_or_take::(first_transaction..=last_transaction)? - .into_iter() - .map(|(id, tx)| (id, tx.into())) - .collect::>(); - - let senders = - self.get_or_take::(first_transaction..=last_transaction)?; - - if TAKE { - // Remove TxHashNumber - let mut tx_hash_cursor = self.cursor_write::()?; - for (_, tx) in transactions.iter() { - if tx_hash_cursor.seek_exact(tx.hash())?.is_some() { - tx_hash_cursor.delete_current()?; - } - } - - // Remove TransactionBlock index if there are transaction present - if !transactions.is_empty() { - let tx_id_range = transactions.first().unwrap().0..=transactions.last().unwrap().0; - self.get_or_take::(tx_id_range)?; - } - } - - // Merge transaction into blocks - let mut block_tx = Vec::with_capacity(block_bodies.len()); - let mut senders = senders.into_iter(); - let mut transactions = transactions.into_iter(); - for (block_number, block_body) in block_bodies { - let mut one_block_tx = Vec::with_capacity(block_body.tx_count as usize); - for _ in block_body.tx_num_range() { - let tx = transactions.next(); - let sender = senders.next(); - - let recovered = match (tx, sender) { - (Some((tx_id, tx)), Some((sender_tx_id, sender))) => { - if tx_id != sender_tx_id { - Err(ProviderError::MismatchOfTransactionAndSenderId { tx_id }) - } else { - Ok(TransactionSignedEcRecovered::from_signed_transaction(tx, sender)) - } - } - (Some((tx_id, _)), _) | (_, Some((tx_id, _))) => { - Err(ProviderError::MismatchOfTransactionAndSenderId { tx_id }) - } - (None, None) => Err(ProviderError::BlockBodyTransactionCount), - }?; - one_block_tx.push(recovered) - } - block_tx.push((block_number, one_block_tx)); - } - - Ok(block_tx) - } - - /// Return range of blocks and its execution result - fn get_take_block_range( - &self, - chain_spec: &ChainSpec, - range: impl RangeBounds + Clone, - ) -> Result, TransactionError> { - // For block we need Headers, Bodies, Uncles, withdrawals, Transactions, Signers - - let block_headers = self.get_or_take::(range.clone())?; - if block_headers.is_empty() { - return Ok(Vec::new()) - } - - let block_header_hashes = - self.get_or_take::(range.clone())?; - let block_ommers = self.get_or_take::(range.clone())?; - let block_withdrawals = - self.get_or_take::(range.clone())?; - - let block_tx = self.get_take_block_transaction_range::(range.clone())?; - - if TAKE { - // rm HeaderTD - self.get_or_take::(range)?; - // rm HeaderNumbers - let mut header_number_cursor = self.cursor_write::()?; - for (_, hash) in block_header_hashes.iter() { - if header_number_cursor.seek_exact(*hash)?.is_some() { - header_number_cursor.delete_current()?; - } - } - } - - // merge all into block - let block_header_iter = block_headers.into_iter(); - let block_header_hashes_iter = block_header_hashes.into_iter(); - let block_tx_iter = block_tx.into_iter(); - - // Ommers can be empty for some blocks - let mut block_ommers_iter = block_ommers.into_iter(); - let mut block_withdrawals_iter = block_withdrawals.into_iter(); - let mut block_ommers = block_ommers_iter.next(); - let mut block_withdrawals = block_withdrawals_iter.next(); - - let mut blocks = Vec::new(); - for ((main_block_number, header), (_, header_hash), (_, tx)) in izip!( - block_header_iter.into_iter(), - block_header_hashes_iter.into_iter(), - block_tx_iter.into_iter() - ) { - let header = header.seal(header_hash); - - let (body, senders) = tx.into_iter().map(|tx| tx.to_components()).unzip(); - - // Ommers can be missing - let mut ommers = Vec::new(); - if let Some((block_number, _)) = block_ommers.as_ref() { - if *block_number == main_block_number { - ommers = block_ommers.take().unwrap().1.ommers; - block_ommers = block_ommers_iter.next(); - } - }; - - // withdrawal can be missing - let shanghai_is_active = - chain_spec.fork(Hardfork::Shanghai).active_at_timestamp(header.timestamp); - let mut withdrawals = Some(Vec::new()); - if shanghai_is_active { - if let Some((block_number, _)) = block_withdrawals.as_ref() { - if *block_number == main_block_number { - withdrawals = Some(block_withdrawals.take().unwrap().1.withdrawals); - block_withdrawals = block_withdrawals_iter.next(); - } - } - } else { - withdrawals = None - } - - blocks.push(SealedBlockWithSenders { - block: SealedBlock { header, body, ommers, withdrawals }, - senders, - }) - } - - Ok(blocks) - } - - /// Traverse over changesets and plain state and recreate the [`PostState`]s for the given range - /// of blocks. - /// - /// 1. Iterate over the [BlockBodyIndices][tables::BlockBodyIndices] table to get all - /// the transition indices. - /// 2. Iterate over the [StorageChangeSet][tables::StorageChangeSet] table - /// and the [AccountChangeSet][tables::AccountChangeSet] tables in reverse order to reconstruct - /// the changesets. - /// - In order to have both the old and new values in the changesets, we also access the - /// plain state tables. - /// 3. While iterating over the changeset tables, if we encounter a new account or storage slot, - /// we: - /// 1. Take the old value from the changeset - /// 2. Take the new value from the plain state - /// 3. Save the old value to the local state - /// 4. While iterating over the changeset tables, if we encounter an account/storage slot we - /// have seen before we: - /// 1. Take the old value from the changeset - /// 2. Take the new value from the local state - /// 3. Set the local state to the value in the changeset - /// - /// If `TAKE` is `true`, the local state will be written to the plain state tables. - /// 5. Get all receipts from table - fn get_take_block_execution_result_range( - &self, - range: RangeInclusive, - ) -> Result, TransactionError> { - if range.is_empty() { - return Ok(Vec::new()) - } - - // We are not removing block meta as it is used to get block transitions. - let block_bodies = self.get_or_take::(range.clone())?; - - // get transaction receipts - let from_transaction_num = - block_bodies.first().expect("already checked if there are blocks").1.first_tx_num(); - let to_transaction_num = - block_bodies.last().expect("already checked if there are blocks").1.last_tx_num(); - let receipts = - self.get_or_take::(from_transaction_num..=to_transaction_num)?; - - let storage_range = BlockNumberAddress::range(range.clone()); - - let storage_changeset = - self.get_or_take::(storage_range)?; - let account_changeset = self.get_or_take::(range)?; - - // iterate previous value and get plain state value to create changeset - // Double option around Account represent if Account state is know (first option) and - // account is removed (Second Option) - type LocalPlainState = BTreeMap>, BTreeMap)>; - - let mut local_plain_state: LocalPlainState = BTreeMap::new(); - - // iterate in reverse and get plain state. - - // Bundle execution changeset to its particular transaction and block - let mut block_states = - BTreeMap::from_iter(block_bodies.iter().map(|(num, _)| (*num, PostState::default()))); - - let mut plain_accounts_cursor = self.cursor_write::()?; - let mut plain_storage_cursor = self.cursor_dup_write::()?; - - // add account changeset changes - for (block_number, account_before) in account_changeset.into_iter().rev() { - let AccountBeforeTx { info: old_info, address } = account_before; - let new_info = match local_plain_state.entry(address) { - Entry::Vacant(entry) => { - let new_account = plain_accounts_cursor.seek_exact(address)?.map(|kv| kv.1); - entry.insert((Some(old_info), BTreeMap::new())); - new_account - } - Entry::Occupied(mut entry) => { - let new_account = std::mem::replace(&mut entry.get_mut().0, Some(old_info)); - new_account.expect("As we are stacking account first, account would always be Some(Some) or Some(None)") - } - }; - - let post_state = block_states.entry(block_number).or_default(); - match (old_info, new_info) { - (Some(old), Some(new)) => { - if new != old { - post_state.change_account(block_number, address, old, new); - } else { - unreachable!("Junk data in database: an account changeset did not represent any change"); - } - } - (None, Some(account)) => post_state.create_account(block_number, address, account), - (Some(old), None) => - post_state.destroy_account(block_number, address, old), - (None, None) => unreachable!("Junk data in database: an account changeset transitioned from no account to no account"), - }; - } - - // add storage changeset changes - let mut storage_changes: BTreeMap = BTreeMap::new(); - for (block_and_address, storage_entry) in storage_changeset.into_iter().rev() { - let BlockNumberAddress((_, address)) = block_and_address; - let new_storage = - match local_plain_state.entry(address).or_default().1.entry(storage_entry.key) { - Entry::Vacant(entry) => { - let new_storage = plain_storage_cursor - .seek_by_key_subkey(address, storage_entry.key)? - .filter(|storage| storage.key == storage_entry.key) - .unwrap_or_default(); - entry.insert(storage_entry.value); - new_storage.value - } - Entry::Occupied(mut entry) => { - std::mem::replace(entry.get_mut(), storage_entry.value) - } - }; - storage_changes.entry(block_and_address).or_default().insert( - U256::from_be_bytes(storage_entry.key.0), - (storage_entry.value, new_storage), - ); - } - - for (BlockNumberAddress((block_number, address)), storage_changeset) in - storage_changes.into_iter() - { - block_states.entry(block_number).or_default().change_storage( - block_number, - address, - storage_changeset, - ); - } - - if TAKE { - // iterate over local plain state remove all account and all storages. - for (address, (account, storage)) in local_plain_state.into_iter() { - // revert account - if let Some(account) = account { - let existing_entry = plain_accounts_cursor.seek_exact(address)?; - if let Some(account) = account { - plain_accounts_cursor.upsert(address, account)?; - } else if existing_entry.is_some() { - plain_accounts_cursor.delete_current()?; - } - } - - // revert storages - for (storage_key, storage_value) in storage.into_iter() { - let storage_entry = StorageEntry { key: storage_key, value: storage_value }; - // delete previous value - // TODO: This does not use dupsort features - if plain_storage_cursor - .seek_by_key_subkey(address, storage_key)? - .filter(|s| s.key == storage_key) - .is_some() - { - plain_storage_cursor.delete_current()? - } - - // TODO: This does not use dupsort features - // insert value if needed - if storage_value != U256::ZERO { - plain_storage_cursor.upsert(address, storage_entry)?; - } - } - } - } - - // iterate over block body and create ExecutionResult - let mut receipt_iter = receipts.into_iter(); - - // loop break if we are at the end of the blocks. - for (block_number, block_body) in block_bodies.into_iter() { - for _ in block_body.tx_num_range() { - if let Some((_, receipt)) = receipt_iter.next() { - block_states - .entry(block_number) - .or_default() - .add_receipt(block_number, receipt); - } - } - } - Ok(block_states.into_values().collect()) - } - - /// Return range of blocks and its execution result - pub fn get_take_block_and_execution_range( - &self, - chain_spec: &ChainSpec, - range: RangeInclusive, - ) -> Result, TransactionError> { - if TAKE { - let storage_range = BlockNumberAddress::range(range.clone()); - - self.unwind_account_hashing(range.clone())?; - self.unwind_account_history_indices(range.clone())?; - self.unwind_storage_hashing(storage_range.clone())?; - self.unwind_storage_history_indices(storage_range)?; - - // merkle tree - let (new_state_root, trie_updates) = - StateRoot::incremental_root_with_updates(self.deref(), range.clone())?; - - let parent_number = range.start().saturating_sub(1); - let parent_state_root = self.get_header(parent_number)?.state_root; - - // state root should be always correct as we are reverting state. - // but for sake of double verification we will check it again. - if new_state_root != parent_state_root { - let parent_hash = self.get_block_hash(parent_number)?; - return Err(TransactionError::UnwindStateRootMismatch { - got: new_state_root, - expected: parent_state_root, - block_number: parent_number, - block_hash: parent_hash, - }) - } - trie_updates.flush(self.deref())?; - } - // get blocks - let blocks = self.get_take_block_range::(chain_spec, range.clone())?; - let unwind_to = blocks.first().map(|b| b.number.saturating_sub(1)); - // get execution res - let execution_res = self.get_take_block_execution_result_range::(range.clone())?; - // combine them - let blocks_with_exec_result: Vec<_> = - blocks.into_iter().zip(execution_res.into_iter()).collect(); - - // remove block bodies it is needed for both get block range and get block execution results - // that is why it is deleted afterwards. - if TAKE { - // rm block bodies - self.get_or_take::(range)?; - - // Update pipeline progress - if let Some(fork_number) = unwind_to { - self.update_pipeline_stages(fork_number, true)?; - } - } - - // return them - Ok(blocks_with_exec_result) - } - - /// Update all pipeline sync stage progress. - pub fn update_pipeline_stages( - &self, - block_number: BlockNumber, - drop_stage_checkpoint: bool, - ) -> Result<(), TransactionError> { - // iterate over all existing stages in the table and update its progress. - let mut cursor = self.cursor_write::()?; - while let Some((stage_name, checkpoint)) = cursor.next()? { - cursor.upsert( - stage_name, - StageCheckpoint { - block_number, - ..if drop_stage_checkpoint { Default::default() } else { checkpoint } - }, - )? - } - - Ok(()) - } - - /// Iterate over account changesets and return all account address that were changed. - pub fn get_addresses_and_keys_of_changed_storages( - &self, - range: RangeInclusive, - ) -> Result>, TransactionError> { - Ok(self - .cursor_read::()? - .walk_range(BlockNumberAddress::range(range))? - .collect::, _>>()? - .into_iter() - // fold all storages and save its old state so we can remove it from HashedStorage - // it is needed as it is dup table. - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, - (BlockNumberAddress((_, address)), storage_entry)| { - accounts.entry(address).or_default().insert(storage_entry.key); - accounts - }, - )) - } - - /// Get plainstate storages - #[allow(clippy::type_complexity)] - pub fn get_plainstate_storages( - &self, - iter: impl IntoIterator)>, - ) -> Result)>, TransactionError> { - let mut plain_storage = self.cursor_dup_read::()?; - - iter.into_iter() - .map(|(address, storage)| { - storage - .into_iter() - .map(|key| -> Result<_, TransactionError> { - let ret = plain_storage - .seek_by_key_subkey(address, key)? - .filter(|v| v.key == key) - .unwrap_or_default(); - Ok((key, ret.value)) - }) - .collect::, _>>() - .map(|storage| (address, storage)) - }) - .collect::, _>>() - } - - /// iterate over storages and insert them to hashing table - pub fn insert_storage_for_hashing( - &self, - storages: impl IntoIterator)>, - ) -> Result<(), TransactionError> { - // hash values - let hashed = storages.into_iter().fold(BTreeMap::new(), |mut map, (address, storage)| { - let storage = storage.into_iter().fold(BTreeMap::new(), |mut map, (key, value)| { - map.insert(keccak256(key), value); - map - }); - map.insert(keccak256(address), storage); - map - }); - - let mut hashed_storage = self.cursor_dup_write::()?; - // Hash the address and key and apply them to HashedStorage (if Storage is None - // just remove it); - hashed.into_iter().try_for_each(|(hashed_address, storage)| { - storage.into_iter().try_for_each(|(key, value)| -> Result<(), TransactionError> { - if hashed_storage - .seek_by_key_subkey(hashed_address, key)? - .filter(|entry| entry.key == key) - .is_some() - { - hashed_storage.delete_current()?; - } - - if value != U256::ZERO { - hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; - } - Ok(()) - }) - })?; - Ok(()) - } - - /// Iterate over account changesets and return all account address that were changed. - pub fn get_addresses_of_changed_accounts( - &self, - range: RangeInclusive, - ) -> Result, TransactionError> { - Ok(self - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()? - .into_iter() - // fold all account to one set of changed accounts - .fold(BTreeSet::new(), |mut accounts: BTreeSet
, (_, account_before)| { - accounts.insert(account_before.address); - accounts - })) - } - - /// Get plainstate account from iterator - pub fn get_plainstate_accounts( - &self, - iter: impl IntoIterator, - ) -> Result)>, TransactionError> { - let mut plain_accounts = self.cursor_read::()?; - Ok(iter - .into_iter() - .map(|address| plain_accounts.seek_exact(address).map(|a| (address, a.map(|(_, v)| v)))) - .collect::, _>>()?) - } - - /// iterate over accounts and insert them to hashing table - pub fn insert_account_for_hashing( - &self, - accounts: impl IntoIterator)>, - ) -> Result<(), TransactionError> { - let mut hashed_accounts = self.cursor_write::()?; - - let hashes_accounts = accounts.into_iter().fold( - BTreeMap::new(), - |mut map: BTreeMap>, (address, account)| { - map.insert(keccak256(address), account); - map - }, - ); - - hashes_accounts.into_iter().try_for_each( - |(hashed_address, account)| -> Result<(), TransactionError> { - if let Some(account) = account { - hashed_accounts.upsert(hashed_address, account)? - } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { - hashed_accounts.delete_current()?; - } - Ok(()) - }, - )?; - Ok(()) - } - - /// Get all transaction ids where account got changed. - /// - /// NOTE: Get inclusive range of blocks. - pub fn get_storage_transition_ids_from_changeset( - &self, - range: RangeInclusive, - ) -> Result>, TransactionError> { - let storage_changeset = self - .cursor_read::()? - .walk_range(BlockNumberAddress::range(range))? - .collect::, _>>()?; - - // fold all storages to one set of changes - let storage_changeset_lists = storage_changeset.into_iter().fold( - BTreeMap::new(), - |mut storages: BTreeMap<(Address, H256), Vec>, (index, storage)| { - storages - .entry((index.address(), storage.key)) - .or_default() - .push(index.block_number()); - storages - }, - ); - Ok(storage_changeset_lists) - } - - /// Get all transaction ids where account got changed. - /// - /// NOTE: Get inclusive range of blocks. - pub fn get_account_transition_ids_from_changeset( - &self, - range: RangeInclusive, - ) -> Result>, TransactionError> { - let account_changesets = self - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; - - let account_transitions = account_changesets - .into_iter() - // fold all account to one set of changed accounts - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, (index, account)| { - accounts.entry(account.address).or_default().push(index); - accounts - }, - ); - Ok(account_transitions) - } - - /// Insert storage change index to database. Used inside StorageHistoryIndex stage - pub fn insert_storage_history_index( - &self, - storage_transitions: BTreeMap<(Address, H256), Vec>, - ) -> Result<(), TransactionError> { - for ((address, storage_key), mut indices) in storage_transitions { - let mut last_shard = self.take_last_storage_shard(address, storage_key)?; - last_shard.append(&mut indices); - - // chunk indices and insert them in shards of N size. - let mut chunks = last_shard - .iter() - .chunks(storage_sharded_key::NUM_OF_INDICES_IN_SHARD) - .into_iter() - .map(|chunks| chunks.map(|i| *i as usize).collect::>()) - .collect::>(); - let last_chunk = chunks.pop(); - - // chunk indices and insert them in shards of N size. - chunks.into_iter().try_for_each(|list| { - self.put::( - StorageShardedKey::new( - address, - storage_key, - *list.last().expect("Chuck does not return empty list") as BlockNumber, - ), - BlockNumberList::new(list).expect("Indices are presorted and not empty"), - ) - })?; - // Insert last list with u64::MAX - if let Some(last_list) = last_chunk { - self.put::( - StorageShardedKey::new(address, storage_key, u64::MAX), - BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), - )?; - } - } - Ok(()) - } - - /// Insert account change index to database. Used inside AccountHistoryIndex stage - pub fn insert_account_history_index( - &self, - account_transitions: BTreeMap>, - ) -> Result<(), TransactionError> { - // insert indexes to AccountHistory. - for (address, mut indices) in account_transitions { - let mut last_shard = self.take_last_account_shard(address)?; - last_shard.append(&mut indices); - // chunk indices and insert them in shards of N size. - let mut chunks = last_shard - .iter() - .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) - .into_iter() - .map(|chunks| chunks.map(|i| *i as usize).collect::>()) - .collect::>(); - let last_chunk = chunks.pop(); - - chunks.into_iter().try_for_each(|list| { - self.put::( - ShardedKey::new( - address, - *list.last().expect("Chuck does not return empty list") as BlockNumber, - ), - BlockNumberList::new(list).expect("Indices are presorted and not empty"), - ) - })?; - // Insert last list with u64::MAX - if let Some(last_list) = last_chunk { - self.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), - )? - } - } - Ok(()) - } - - /// Get the stage checkpoint. - pub fn get_stage_checkpoint(&self, id: StageId) -> Result, DbError> { - get_stage_checkpoint(self.deref(), id) - } - - /// Save stage checkpoint. - pub fn save_stage_checkpoint( - &self, - id: StageId, - checkpoint: StageCheckpoint, - ) -> Result<(), DbError> { - self.put::(id.to_string(), checkpoint)?; - Ok(()) - } - - /// Return full table as Vec - pub fn table(&self) -> Result>, DbError> - where - T::Key: Default + Ord, - { - self.cursor_read::()?.walk(Some(T::Key::default()))?.collect::, DbError>>() - } -} - -/// Unwind all history shards. For boundary shard, remove it from database and -/// return last part of shard with still valid items. If all full shard were removed, return list -/// would be empty. -fn unwind_account_history_shards( - cursor: &mut <>::TXMut as DbTxMutGAT<'_>>::CursorMut< - tables::AccountHistory, - >, - address: Address, - block_number: BlockNumber, -) -> Result, TransactionError> { - let mut item = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; - - while let Some((sharded_key, list)) = item { - // there is no more shard for address - if sharded_key.key != address { - break - } - cursor.delete_current()?; - // check first item and if it is more and eq than `transition_id` delete current - // item. - let first = list.iter(0).next().expect("List can't empty"); - if first >= block_number as usize { - item = cursor.prev()?; - continue - } else if block_number <= sharded_key.highest_block_number { - // if first element is in scope whole list would be removed. - // so at least this first element is present. - return Ok(list.iter(0).take_while(|i| *i < block_number as usize).collect::>()) - } else { - let new_list = list.iter(0).collect::>(); - return Ok(new_list) - } - } - Ok(Vec::new()) -} - -/// Unwind all history shards. For boundary shard, remove it from database and -/// return last part of shard with still valid items. If all full shard were removed, return list -/// would be empty but this does not mean that there is none shard left but that there is no -/// split shards. -fn unwind_storage_history_shards( - cursor: &mut <>::TXMut as DbTxMutGAT<'_>>::CursorMut< - tables::StorageHistory, - >, - address: Address, - storage_key: H256, - block_number: BlockNumber, -) -> Result, TransactionError> { - let mut item = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; - - while let Some((storage_sharded_key, list)) = item { - // there is no more shard for address - if storage_sharded_key.address != address || - storage_sharded_key.sharded_key.key != storage_key - { - // there is no more shard for address and storage_key. - break - } - cursor.delete_current()?; - // check first item and if it is more and eq than `transition_id` delete current - // item. - let first = list.iter(0).next().expect("List can't empty"); - if first >= block_number as usize { - item = cursor.prev()?; - continue - } else if block_number <= storage_sharded_key.sharded_key.highest_block_number { - // if first element is in scope whole list would be removed. - // so at least this first element is present. - return Ok(list.iter(0).take_while(|i| *i < block_number as usize).collect::>()) - } else { - return Ok(list.iter(0).collect::>()) - } - } - Ok(Vec::new()) -} +use reth_primitives::{BlockHash, BlockNumber, H256}; +use reth_trie::StateRootError; +use std::fmt::Debug; /// An error that can occur when using the transaction container #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] @@ -1449,8 +44,7 @@ pub enum TransactionError { #[cfg(test)] mod test { use crate::{ - insert_canonical_block, test_utils::blocks::*, ShareableDatabase, Transaction, - TransactionsProvider, + insert_canonical_block, test_utils::blocks::*, ShareableDatabase, TransactionsProvider, }; use reth_db::{ mdbx::test_utils::create_test_rw_db, @@ -1458,20 +52,22 @@ mod test { tables, }; use reth_primitives::{ChainSpecBuilder, IntegerList, H160, MAINNET, U256}; - use std::ops::DerefMut; + use std::sync::Arc; #[test] fn insert_block_and_hashes_get_take() { let db = create_test_rw_db(); // setup - let mut tx = Transaction::new(db.as_ref()).unwrap(); let chain_spec = ChainSpecBuilder::default() .chain(MAINNET.chain) .genesis(MAINNET.genesis.clone()) .shanghai_activated() .build(); + let factory = ShareableDatabase::new(db.as_ref(), Arc::new(chain_spec.clone())); + let mut provider = factory.provider_rw().unwrap(); + let data = BlockChainTestData::default(); let genesis = data.genesis.clone(); let (block1, exec_res1) = data.blocks[0].clone(); @@ -1482,60 +78,60 @@ mod test { let storage1_shard_key = StorageShardedKey::new(H160([0x60; 20]), U256::from(5).into(), u64::MAX); - insert_canonical_block(tx.deref_mut(), data.genesis.clone(), None).unwrap(); + insert_canonical_block(provider.tx_ref(), data.genesis.clone(), None).unwrap(); - assert_genesis_block(&tx, data.genesis); + assert_genesis_block(&provider, data.genesis); - tx.append_blocks_with_post_state(vec![block1.clone()], exec_res1.clone()).unwrap(); + provider.append_blocks_with_post_state(vec![block1.clone()], exec_res1.clone()).unwrap(); assert_eq!( - tx.table::().unwrap(), + provider.table::().unwrap(), vec![ (acc1_shard_key.clone(), IntegerList::new(vec![1]).unwrap()), (acc2_shard_key.clone(), IntegerList::new(vec![1]).unwrap()) ] ); assert_eq!( - tx.table::().unwrap(), + provider.table::().unwrap(), vec![(storage1_shard_key.clone(), IntegerList::new(vec![1]).unwrap())] ); // get one block - let get = tx.get_block_and_execution_range(&chain_spec, 1..=1).unwrap(); + let get = provider.get_block_and_execution_range(&chain_spec, 1..=1).unwrap(); let get_block = get[0].0.clone(); let get_state = get[0].1.clone(); assert_eq!(get_block, block1); assert_eq!(get_state, exec_res1); // take one block - let take = tx.take_block_and_execution_range(&chain_spec, 1..=1).unwrap(); + let take = provider.take_block_and_execution_range(&chain_spec, 1..=1).unwrap(); assert_eq!(take, vec![(block1.clone(), exec_res1.clone())]); - assert_genesis_block(&tx, genesis.clone()); + assert_genesis_block(&provider, genesis.clone()); // check if history is empty. - assert_eq!(tx.table::().unwrap(), vec![]); - assert_eq!(tx.table::().unwrap(), vec![]); + assert_eq!(provider.table::().unwrap(), vec![]); + assert_eq!(provider.table::().unwrap(), vec![]); - tx.append_blocks_with_post_state(vec![block1.clone()], exec_res1.clone()).unwrap(); - tx.append_blocks_with_post_state(vec![block2.clone()], exec_res2.clone()).unwrap(); + provider.append_blocks_with_post_state(vec![block1.clone()], exec_res1.clone()).unwrap(); + provider.append_blocks_with_post_state(vec![block2.clone()], exec_res2.clone()).unwrap(); // check history of two blocks assert_eq!( - tx.table::().unwrap(), + provider.table::().unwrap(), vec![ (acc1_shard_key, IntegerList::new(vec![1, 2]).unwrap()), (acc2_shard_key, IntegerList::new(vec![1]).unwrap()) ] ); assert_eq!( - tx.table::().unwrap(), + provider.table::().unwrap(), vec![(storage1_shard_key, IntegerList::new(vec![1, 2]).unwrap())] ); - tx.commit().unwrap(); + provider.commit().unwrap(); // Check that transactions map onto blocks correctly. { - let provider = ShareableDatabase::new(tx.db, MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); assert_eq!( provider.transaction_block(0).unwrap(), Some(1), @@ -1553,23 +149,24 @@ mod test { ); } + let provider = factory.provider_rw().unwrap(); // get second block - let get = tx.get_block_and_execution_range(&chain_spec, 2..=2).unwrap(); + let get = provider.get_block_and_execution_range(&chain_spec, 2..=2).unwrap(); assert_eq!(get, vec![(block2.clone(), exec_res2.clone())]); // get two blocks - let get = tx.get_block_and_execution_range(&chain_spec, 1..=2).unwrap(); + let get = provider.get_block_and_execution_range(&chain_spec, 1..=2).unwrap(); assert_eq!(get[0].0, block1); assert_eq!(get[1].0, block2); assert_eq!(get[0].1, exec_res1); assert_eq!(get[1].1, exec_res2); // take two blocks - let get = tx.take_block_and_execution_range(&chain_spec, 1..=2).unwrap(); + let get = provider.take_block_and_execution_range(&chain_spec, 1..=2).unwrap(); assert_eq!(get, vec![(block1, exec_res1), (block2, exec_res2)]); // assert genesis state - assert_genesis_block(&tx, genesis); + assert_genesis_block(&provider, genesis); } #[test] @@ -1577,58 +174,64 @@ mod test { let db = create_test_rw_db(); // setup - let mut tx = Transaction::new(db.as_ref()).unwrap(); - let chain_spec = ChainSpecBuilder::default() - .chain(MAINNET.chain) - .genesis(MAINNET.genesis.clone()) - .shanghai_activated() - .build(); + + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(MAINNET.genesis.clone()) + .shanghai_activated() + .build(), + ); + + let factory = ShareableDatabase::new(db.as_ref(), chain_spec.clone()); + let mut provider = factory.provider_rw().unwrap(); let data = BlockChainTestData::default(); let genesis = data.genesis.clone(); let (block1, exec_res1) = data.blocks[0].clone(); let (block2, exec_res2) = data.blocks[1].clone(); - insert_canonical_block(tx.deref_mut(), data.genesis.clone(), None).unwrap(); + insert_canonical_block(provider.tx_mut(), data.genesis.clone(), None).unwrap(); - assert_genesis_block(&tx, data.genesis); + assert_genesis_block(&provider, data.genesis); - tx.append_blocks_with_post_state(vec![block1.clone()], exec_res1.clone()).unwrap(); + provider.append_blocks_with_post_state(vec![block1.clone()], exec_res1.clone()).unwrap(); // get one block - let get = tx.get_block_and_execution_range(&chain_spec, 1..=1).unwrap(); + let get = provider.get_block_and_execution_range(&chain_spec, 1..=1).unwrap(); assert_eq!(get, vec![(block1.clone(), exec_res1.clone())]); // take one block - let take = tx.take_block_and_execution_range(&chain_spec, 1..=1).unwrap(); + let take = provider.take_block_and_execution_range(&chain_spec, 1..=1).unwrap(); assert_eq!(take, vec![(block1.clone(), exec_res1.clone())]); - assert_genesis_block(&tx, genesis.clone()); + assert_genesis_block(&provider, genesis.clone()); // insert two blocks let mut merged_state = exec_res1.clone(); merged_state.extend(exec_res2.clone()); - tx.append_blocks_with_post_state( - vec![block1.clone(), block2.clone()], - merged_state.clone(), - ) - .unwrap(); + provider + .append_blocks_with_post_state( + vec![block1.clone(), block2.clone()], + merged_state.clone(), + ) + .unwrap(); // get second block - let get = tx.get_block_and_execution_range(&chain_spec, 2..=2).unwrap(); + let get = provider.get_block_and_execution_range(&chain_spec, 2..=2).unwrap(); assert_eq!(get, vec![(block2.clone(), exec_res2.clone())]); // get two blocks - let get = tx.get_block_and_execution_range(&chain_spec, 1..=2).unwrap(); + let get = provider.get_block_and_execution_range(&chain_spec, 1..=2).unwrap(); assert_eq!( get, vec![(block1.clone(), exec_res1.clone()), (block2.clone(), exec_res2.clone())] ); // take two blocks - let get = tx.take_block_and_execution_range(&chain_spec, 1..=2).unwrap(); + let get = provider.take_block_and_execution_range(&chain_spec, 1..=2).unwrap(); assert_eq!(get, vec![(block1, exec_res1), (block2, exec_res2)]); // assert genesis state - assert_genesis_block(&tx, genesis); + assert_genesis_block(&provider, genesis); } } diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index 92c82f88193e..0c9b1b4537a9 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -523,14 +523,10 @@ mod tests { keccak256, proofs::KeccakHasher, trie::{BranchNodeCompact, TrieMask}, - Account, Address, H256, U256, - }; - use reth_provider::Transaction; - use std::{ - collections::BTreeMap, - ops::{Deref, DerefMut, Mul}, - str::FromStr, + Account, Address, H256, MAINNET, U256, }; + use reth_provider::{DatabaseProviderRW, ShareableDatabase}; + use std::{collections::BTreeMap, ops::Mul, str::FromStr}; fn insert_account<'a, TX: DbTxMut<'a>>( tx: &mut TX, @@ -559,10 +555,12 @@ mod tests { fn incremental_vs_full_root(inputs: &[&str], modified: &str) { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); let hashed_address = H256::from_low_u64_be(1); - let mut hashed_storage_cursor = tx.cursor_dup_write::().unwrap(); + let mut hashed_storage_cursor = + tx.tx_ref().cursor_dup_write::().unwrap(); let data = inputs.iter().map(|x| H256::from_str(x).unwrap()); let value = U256::from(0); for key in data { @@ -571,7 +569,7 @@ mod tests { // Generate the intermediate nodes on the receiving end of the channel let (_, _, trie_updates) = - StorageRoot::new_hashed(tx.deref(), hashed_address).root_with_updates().unwrap(); + StorageRoot::new_hashed(tx.tx_ref(), hashed_address).root_with_updates().unwrap(); // 1. Some state transition happens, update the hashed storage to the new value let modified_key = H256::from_str(modified).unwrap(); @@ -585,16 +583,16 @@ mod tests { .unwrap(); // 2. Calculate full merkle root - let loader = StorageRoot::new_hashed(tx.deref(), hashed_address); + let loader = StorageRoot::new_hashed(tx.tx_ref(), hashed_address); let modified_root = loader.root().unwrap(); // Update the intermediate roots table so that we can run the incremental verification - trie_updates.flush(tx.deref()).unwrap(); + trie_updates.flush(tx.tx_ref()).unwrap(); // 3. Calculate the incremental root let mut storage_changes = PrefixSet::default(); storage_changes.insert(Nibbles::unpack(modified_key)); - let loader = StorageRoot::new_hashed(tx.deref_mut(), hashed_address) + let loader = StorageRoot::new_hashed(tx.tx_mut(), hashed_address) .with_changed_prefixes(storage_changes); let incremental_root = loader.root().unwrap(); @@ -624,9 +622,10 @@ mod tests { let hashed_address = keccak256(address); let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let tx = factory.provider_rw().unwrap(); for (key, value) in &storage { - tx.put::( + tx.tx_ref().put::( hashed_address, StorageEntry { key: keccak256(key), value: *value }, ) @@ -634,7 +633,8 @@ mod tests { } tx.commit().unwrap(); - let got = StorageRoot::new(tx.deref_mut(), address).root().unwrap(); + let mut tx = factory.provider_rw().unwrap(); + let got = StorageRoot::new(tx.tx_mut(), address).root().unwrap(); let expected = storage_root(storage.into_iter()); assert_eq!(expected, got); }); @@ -680,7 +680,8 @@ mod tests { // This ensures we return an empty root when there are no storage entries fn test_empty_storage_root() { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); let address = Address::random(); let code = "el buen fla"; @@ -689,10 +690,11 @@ mod tests { balance: U256::from(414241124u32), bytecode_hash: Some(keccak256(code)), }; - insert_account(&mut *tx, address, account, &Default::default()); + insert_account(tx.tx_mut(), address, account, &Default::default()); tx.commit().unwrap(); - let got = StorageRoot::new(tx.deref_mut(), address).root().unwrap(); + let mut tx = factory.provider_rw().unwrap(); + let got = StorageRoot::new(tx.tx_mut(), address).root().unwrap(); assert_eq!(got, EMPTY_ROOT); } @@ -700,7 +702,8 @@ mod tests { // This ensures that the walker goes over all the storage slots fn test_storage_root() { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); let address = Address::random(); let storage = BTreeMap::from([ @@ -715,10 +718,11 @@ mod tests { bytecode_hash: Some(keccak256(code)), }; - insert_account(&mut *tx, address, account, &storage); + insert_account(tx.tx_mut(), address, account, &storage); tx.commit().unwrap(); - let got = StorageRoot::new(tx.deref_mut(), address).root().unwrap(); + let mut tx = factory.provider_rw().unwrap(); + let got = StorageRoot::new(tx.tx_mut(), address).root().unwrap(); assert_eq!(storage_root(storage.into_iter()), got); } @@ -742,12 +746,15 @@ mod tests { state.values().map(|(_, slots)| slots.len()).sum::(); let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); for (address, (account, storage)) in &state { - insert_account(&mut *tx, *address, *account, storage) + insert_account(tx.tx_mut(), *address, *account, storage) } tx.commit().unwrap(); + let mut tx = factory.provider_rw().unwrap(); + let expected = state_root(state.into_iter()); let threshold = 10; @@ -756,7 +763,7 @@ mod tests { let mut intermediate_state: Option> = None; while got.is_none() { - let calculator = StateRoot::new(tx.deref_mut()) + let calculator = StateRoot::new(tx.tx_mut()) .with_threshold(threshold) .with_intermediate_state(intermediate_state.take().map(|state| *state)); match calculator.root_with_progress().unwrap() { @@ -778,15 +785,17 @@ mod tests { fn test_state_root_with_state(state: State) { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); for (address, (account, storage)) in &state { - insert_account(&mut *tx, *address, *account, storage) + insert_account(tx.tx_mut(), *address, *account, storage) } tx.commit().unwrap(); let expected = state_root(state.into_iter()); - let got = StateRoot::new(tx.deref_mut()).root().unwrap(); + let mut tx = factory.provider_rw().unwrap(); + let got = StateRoot::new(tx.tx_mut()).root().unwrap(); assert_eq!(expected, got); } @@ -803,7 +812,8 @@ mod tests { #[test] fn storage_root_regression() { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let tx = factory.provider_rw().unwrap(); // Some address whose hash starts with 0xB041 let address3 = Address::from_str("16b07afd1c635f77172e842a000ead9a2a222459").unwrap(); let key3 = keccak256(address3); @@ -820,13 +830,15 @@ mod tests { .map(|(slot, val)| (H256::from_str(slot).unwrap(), U256::from(val))), ); - let mut hashed_storage_cursor = tx.cursor_dup_write::().unwrap(); + let mut hashed_storage_cursor = + tx.tx_ref().cursor_dup_write::().unwrap(); for (hashed_slot, value) in storage.clone() { hashed_storage_cursor.upsert(key3, StorageEntry { key: hashed_slot, value }).unwrap(); } tx.commit().unwrap(); + let mut tx = factory.provider_rw().unwrap(); - let account3_storage_root = StorageRoot::new(tx.deref_mut(), address3).root().unwrap(); + let account3_storage_root = StorageRoot::new(tx.tx_mut(), address3).root().unwrap(); let expected_root = storage_root_prehashed(storage.into_iter()); assert_eq!(expected_root, account3_storage_root); } @@ -845,10 +857,13 @@ mod tests { ); let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); - let mut hashed_account_cursor = tx.cursor_write::().unwrap(); - let mut hashed_storage_cursor = tx.cursor_dup_write::().unwrap(); + let mut hashed_account_cursor = + tx.tx_ref().cursor_write::().unwrap(); + let mut hashed_storage_cursor = + tx.tx_ref().cursor_dup_write::().unwrap(); let mut hash_builder = HashBuilder::default(); @@ -891,7 +906,7 @@ mod tests { } hashed_storage_cursor.upsert(key3, StorageEntry { key: hashed_slot, value }).unwrap(); } - let account3_storage_root = StorageRoot::new(tx.deref_mut(), address3).root().unwrap(); + let account3_storage_root = StorageRoot::new(tx.tx_mut(), address3).root().unwrap(); hash_builder.add_leaf( Nibbles::unpack(key3), &encode_account(account3, Some(account3_storage_root)), @@ -940,7 +955,7 @@ mod tests { assert_eq!(hash_builder.root(), computed_expected_root); // Check state root calculation from scratch - let (root, trie_updates) = StateRoot::new(tx.deref()).root_with_updates().unwrap(); + let (root, trie_updates) = StateRoot::new(tx.tx_ref()).root_with_updates().unwrap(); assert_eq!(root, computed_expected_root); // Check account trie @@ -1005,7 +1020,7 @@ mod tests { H256::from_str("8e263cd4eefb0c3cbbb14e5541a66a755cad25bcfab1e10dd9d706263e811b28") .unwrap(); - let (root, trie_updates) = StateRoot::new(tx.deref()) + let (root, trie_updates) = StateRoot::new(tx.tx_ref()) .with_changed_account_prefixes(prefix_set) .root_with_updates() .unwrap(); @@ -1035,9 +1050,11 @@ mod tests { assert_eq!(nibbles2b.inner[..], [0xB, 0x0]); assert_eq!(node2a, node2b); tx.commit().unwrap(); + let tx = factory.provider_rw().unwrap(); { - let mut hashed_account_cursor = tx.cursor_write::().unwrap(); + let mut hashed_account_cursor = + tx.tx_ref().cursor_write::().unwrap(); let account = hashed_account_cursor.seek_exact(key2).unwrap().unwrap(); hashed_account_cursor.delete_current().unwrap(); @@ -1055,7 +1072,7 @@ mod tests { (key6, encode_account(account6, None)), ]); - let (root, trie_updates) = StateRoot::new(tx.deref()) + let (root, trie_updates) = StateRoot::new(tx.tx_ref()) .with_changed_account_prefixes(account_prefix_set) .root_with_updates() .unwrap(); @@ -1085,11 +1102,13 @@ mod tests { assert_ne!(node1c.hashes[0], node1b.hashes[0]); assert_eq!(node1c.hashes[1], node1b.hashes[1]); assert_eq!(node1c.hashes[2], node1b.hashes[2]); - tx.drop().unwrap(); + drop(tx); } + let mut tx = factory.provider_rw().unwrap(); { - let mut hashed_account_cursor = tx.cursor_write::().unwrap(); + let mut hashed_account_cursor = + tx.tx_ref().cursor_write::().unwrap(); let account2 = hashed_account_cursor.seek_exact(key2).unwrap().unwrap(); hashed_account_cursor.delete_current().unwrap(); @@ -1110,7 +1129,7 @@ mod tests { (key6, encode_account(account6, None)), ]); - let (root, trie_updates) = StateRoot::new(tx.deref_mut()) + let (root, trie_updates) = StateRoot::new(tx.tx_mut()) .with_changed_account_prefixes(account_prefix_set) .root_with_updates() .unwrap(); @@ -1145,11 +1164,12 @@ mod tests { #[test] fn account_trie_around_extension_node() { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); let expected = extension_node_trie(&mut tx); - let (got, updates) = StateRoot::new(tx.deref_mut()).root_with_updates().unwrap(); + let (got, updates) = StateRoot::new(tx.tx_mut()).root_with_updates().unwrap(); assert_eq!(expected, got); // Check account trie @@ -1170,16 +1190,17 @@ mod tests { fn account_trie_around_extension_node_with_dbtrie() { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); let expected = extension_node_trie(&mut tx); - let (got, updates) = StateRoot::new(tx.deref_mut()).root_with_updates().unwrap(); + let (got, updates) = StateRoot::new(tx.tx_mut()).root_with_updates().unwrap(); assert_eq!(expected, got); - updates.flush(tx.deref_mut()).unwrap(); + updates.flush(tx.tx_mut()).unwrap(); // read the account updates from the db - let mut accounts_trie = tx.cursor_read::().unwrap(); + let mut accounts_trie = tx.tx_ref().cursor_read::().unwrap(); let walker = accounts_trie.walk(None).unwrap(); let mut account_updates = HashMap::new(); for item in walker { @@ -1197,8 +1218,9 @@ mod tests { tokio::runtime::Runtime::new().unwrap().block_on(async { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); - let mut hashed_account_cursor = tx.cursor_write::().unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); + let mut hashed_account_cursor = tx.tx_ref().cursor_write::().unwrap(); let mut state = BTreeMap::default(); for accounts in account_changes { @@ -1211,7 +1233,7 @@ mod tests { } } - let (state_root, trie_updates) = StateRoot::new(tx.deref_mut()) + let (state_root, trie_updates) = StateRoot::new(tx.tx_mut()) .with_changed_account_prefixes(changes) .root_with_updates() .unwrap(); @@ -1221,7 +1243,7 @@ mod tests { state.clone().into_iter().map(|(key, balance)| (key, (Account { balance, ..Default::default() }, std::iter::empty()))) ); assert_eq!(expected_root, state_root); - trie_updates.flush(tx.deref_mut()).unwrap(); + trie_updates.flush(tx.tx_mut()).unwrap(); } }); } @@ -1230,14 +1252,15 @@ mod tests { #[test] fn storage_trie_around_extension_node() { let db = create_test_rw_db(); - let mut tx = Transaction::new(db.as_ref()).unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let mut tx = factory.provider_rw().unwrap(); let hashed_address = H256::random(); let (expected_root, expected_updates) = extension_node_storage_trie(&mut tx, hashed_address); let (got, _, updates) = - StorageRoot::new_hashed(tx.deref_mut(), hashed_address).root_with_updates().unwrap(); + StorageRoot::new_hashed(tx.tx_mut(), hashed_address).root_with_updates().unwrap(); assert_eq!(expected_root, got); // Check account trie @@ -1256,12 +1279,12 @@ mod tests { } fn extension_node_storage_trie( - tx: &mut Transaction<'_, Env>, + tx: &mut DatabaseProviderRW<'_, &Env>, hashed_address: H256, ) -> (H256, HashMap) { let value = U256::from(1); - let mut hashed_storage = tx.cursor_write::().unwrap(); + let mut hashed_storage = tx.tx_ref().cursor_write::().unwrap(); let mut hb = HashBuilder::default().with_updates(true); @@ -1282,12 +1305,12 @@ mod tests { (root, updates) } - fn extension_node_trie(tx: &mut Transaction<'_, Env>) -> H256 { + fn extension_node_trie(tx: &mut DatabaseProviderRW<'_, &Env>) -> H256 { let a = Account { nonce: 0, balance: U256::from(1u64), bytecode_hash: Some(H256::random()) }; let val = encode_account(a, None); - let mut hashed_accounts = tx.cursor_write::().unwrap(); + let mut hashed_accounts = tx.tx_ref().cursor_write::().unwrap(); let mut hb = HashBuilder::default(); for key in [ diff --git a/crates/trie/src/trie_cursor/account_cursor.rs b/crates/trie/src/trie_cursor/account_cursor.rs index cb2db71d8d52..a74789e7c4bb 100644 --- a/crates/trie/src/trie_cursor/account_cursor.rs +++ b/crates/trie/src/trie_cursor/account_cursor.rs @@ -38,6 +38,7 @@ where #[cfg(test)] mod tests { + use super::*; use reth_db::{ cursor::{DbCursorRO, DbCursorRW}, @@ -45,14 +46,15 @@ mod tests { tables, transaction::DbTxMut, }; - use reth_primitives::hex_literal::hex; - use reth_provider::Transaction; + use reth_primitives::{hex_literal::hex, MAINNET}; + use reth_provider::ShareableDatabase; #[test] fn test_account_trie_order() { let db = create_test_rw_db(); - let tx = Transaction::new(db.as_ref()).unwrap(); - let mut cursor = tx.cursor_write::().unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); + let mut cursor = provider.tx_ref().cursor_write::().unwrap(); let data = vec![ hex!("0303040e").to_vec(), diff --git a/crates/trie/src/trie_cursor/storage_cursor.rs b/crates/trie/src/trie_cursor/storage_cursor.rs index 317bf690dd9c..677cae49796e 100644 --- a/crates/trie/src/trie_cursor/storage_cursor.rs +++ b/crates/trie/src/trie_cursor/storage_cursor.rs @@ -55,19 +55,24 @@ where #[cfg(test)] mod tests { + use super::*; use reth_db::{ cursor::DbCursorRW, mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut, }; - use reth_primitives::trie::{BranchNodeCompact, StorageTrieEntry}; - use reth_provider::Transaction; + use reth_primitives::{ + trie::{BranchNodeCompact, StorageTrieEntry}, + MAINNET, + }; + use reth_provider::ShareableDatabase; // tests that upsert and seek match on the storagetrie cursor #[test] fn test_storage_cursor_abstraction() { let db = create_test_rw_db(); - let tx = Transaction::new(db.as_ref()).unwrap(); - let mut cursor = tx.cursor_dup_write::().unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); + let mut cursor = provider.tx_ref().cursor_dup_write::().unwrap(); let hashed_address = H256::random(); let key = vec![0x2, 0x3]; diff --git a/crates/trie/src/walker.rs b/crates/trie/src/walker.rs index 3f6c8619674f..f91f25843bc1 100644 --- a/crates/trie/src/walker.rs +++ b/crates/trie/src/walker.rs @@ -256,13 +256,14 @@ impl<'a, K: Key + From>, C: TrieCursor> TrieWalker<'a, K, C> { #[cfg(test)] mod tests { + use super::*; use crate::trie_cursor::{AccountTrieCursor, StorageTrieCursor}; use reth_db::{ cursor::DbCursorRW, mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut, }; - use reth_primitives::trie::StorageTrieEntry; - use reth_provider::Transaction; + use reth_primitives::{trie::StorageTrieEntry, MAINNET}; + use reth_provider::ShareableDatabase; #[test] fn walk_nodes_with_common_prefix() { @@ -288,8 +289,11 @@ mod tests { ]; let db = create_test_rw_db(); - let tx = Transaction::new(db.as_ref()).unwrap(); - let mut account_cursor = tx.cursor_write::().unwrap(); + + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let tx = factory.provider_rw().unwrap(); + + let mut account_cursor = tx.tx_ref().cursor_write::().unwrap(); for (k, v) in &inputs { account_cursor.upsert(k.clone().into(), v.clone()).unwrap(); } @@ -297,7 +301,7 @@ mod tests { test_cursor(account_trie, &expected); let hashed_address = H256::random(); - let mut storage_cursor = tx.cursor_dup_write::().unwrap(); + let mut storage_cursor = tx.tx_ref().cursor_dup_write::().unwrap(); for (k, v) in &inputs { storage_cursor .upsert( @@ -332,8 +336,9 @@ mod tests { #[test] fn cursor_rootnode_with_changesets() { let db = create_test_rw_db(); - let tx = Transaction::new(db.as_ref()).unwrap(); - let mut cursor = tx.cursor_dup_write::().unwrap(); + let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let tx = factory.provider_rw().unwrap(); + let mut cursor = tx.tx_ref().cursor_dup_write::().unwrap(); let nodes = vec![ ( diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index ec295bd03476..e57ebfc3e877 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -6,9 +6,9 @@ use crate::{ }; use reth_db::mdbx::test_utils::create_test_rw_db; use reth_primitives::{BlockBody, SealedBlock}; -use reth_provider::Transaction; +use reth_provider::ShareableDatabase; use reth_stages::{stages::ExecutionStage, ExecInput, Stage}; -use std::{collections::BTreeMap, ffi::OsStr, fs, ops::Deref, path::Path, sync::Arc}; +use std::{collections::BTreeMap, ffi::OsStr, fs, path::Path, sync::Arc}; /// A handler for the blockchain test suite. #[derive(Debug)] @@ -75,19 +75,21 @@ impl Case for BlockchainTestCase { // Create the database let db = create_test_rw_db(); - let mut transaction = Transaction::new(db.as_ref())?; + let factory = + ShareableDatabase::new(db.as_ref(), Arc::new(case.network.clone().into())); + let mut provider = factory.provider_rw().unwrap(); // Insert test state reth_provider::insert_canonical_block( - transaction.deref(), + provider.tx_ref(), SealedBlock::new(case.genesis_block_header.clone().into(), BlockBody::default()), None, )?; - case.pre.write_to_db(transaction.deref())?; + case.pre.write_to_db(provider.tx_ref())?; let mut last_block = None; for block in case.blocks.iter() { - last_block = Some(block.write_to_db(transaction.deref())?); + last_block = Some(block.write_to_db(provider.tx_ref())?); } // Call execution stage @@ -103,7 +105,7 @@ impl Case for BlockchainTestCase { // ignore error let _ = stage .execute( - &mut transaction, + &mut provider, ExecInput { target: last_block, checkpoint: None }, ) .await; @@ -118,13 +120,13 @@ impl Case for BlockchainTestCase { } Some(RootOrState::State(state)) => { for (&address, account) in state.iter() { - account.assert_db(address, transaction.deref())?; + account.assert_db(address, provider.tx_ref())?; } } None => println!("No post-state"), } - transaction.close(); + drop(provider); } Ok(()) } From e43455c2c3230f28445ab1543beeaf3b42effec9 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 13 Jun 2023 03:27:02 -0400 Subject: [PATCH 005/216] fix: re-enable the geth genesis format (#3116) --- bin/reth/src/args/utils.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bin/reth/src/args/utils.rs b/bin/reth/src/args/utils.rs index 56e02780d76f..031b457d2ef4 100644 --- a/bin/reth/src/args/utils.rs +++ b/bin/reth/src/args/utils.rs @@ -40,10 +40,7 @@ pub fn genesis_value_parser(s: &str) -> eyre::Result, eyre::Error _ => { let raw = std::fs::read_to_string(PathBuf::from(shellexpand::full(s)?.into_owned()))?; let genesis: AllGenesisFormats = serde_json::from_str(&raw)?; - match genesis { - AllGenesisFormats::Reth(chain_spec) => Arc::new(chain_spec), - _ => return Err(eyre::eyre!("Unexpected genesis format")), - } + Arc::new(genesis.into()) } }) } From 0561675bb978e4023241a66806adaff4a951fd66 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Tue, 13 Jun 2023 13:49:05 +0200 Subject: [PATCH 006/216] feat(trie): convert `vec` to `Bytes` in `Nibbles` (#3120) --- .../primitives/src/trie/hash_builder/mod.rs | 6 ++-- crates/primitives/src/trie/nibbles.rs | 29 +++++++++++++------ crates/primitives/src/trie/nodes/leaf.rs | 4 +-- crates/stages/src/stages/merkle.rs | 2 +- crates/trie/src/progress.rs | 2 +- crates/trie/src/trie.rs | 4 +-- crates/trie/src/trie_cursor/subnode.rs | 4 +-- crates/trie/src/updates.rs | 9 ++++-- crates/trie/src/walker.rs | 16 +++++----- 9 files changed, 46 insertions(+), 30 deletions(-) diff --git a/crates/primitives/src/trie/hash_builder/mod.rs b/crates/primitives/src/trie/hash_builder/mod.rs index 34b3b702fd90..90c9210e0d4f 100644 --- a/crates/primitives/src/trie/hash_builder/mod.rs +++ b/crates/primitives/src/trie/hash_builder/mod.rs @@ -54,7 +54,7 @@ pub struct HashBuilder { impl From for HashBuilder { fn from(state: HashBuilderState) -> Self { Self { - key: Nibbles::from(state.key), + key: Nibbles::from_hex(state.key), stack: state.stack, value: state.value, groups: state.groups, @@ -70,7 +70,7 @@ impl From for HashBuilder { impl From for HashBuilderState { fn from(state: HashBuilder) -> Self { Self { - key: state.key.hex_data, + key: state.key.hex_data.to_vec(), stack: state.stack, value: state.value, groups: state.groups, @@ -153,7 +153,7 @@ impl HashBuilder { // Clears the internal state if !self.key.is_empty() { self.update(&Nibbles::default()); - self.key.clear(); + self.key.hex_data.0.clear(); self.value = HashBuilderValue::Bytes(vec![]); } self.current_root() diff --git a/crates/primitives/src/trie/nibbles.rs b/crates/primitives/src/trie/nibbles.rs index 9842b7f98038..3bbf74a8b762 100644 --- a/crates/primitives/src/trie/nibbles.rs +++ b/crates/primitives/src/trie/nibbles.rs @@ -78,7 +78,7 @@ impl Compact for StoredNibblesSubKey { )] pub struct Nibbles { /// The inner representation of the nibble sequence. - pub hex_data: Vec, + pub hex_data: Bytes, } impl From<&[u8]> for Nibbles { @@ -102,13 +102,20 @@ impl std::fmt::Debug for Nibbles { impl Nibbles { /// Creates a new [Nibbles] instance from bytes. pub fn from_hex(hex: Vec) -> Self { - Nibbles { hex_data: hex } + Nibbles { hex_data: Bytes::from(hex) } } /// Take a byte array (slice or vector) as input and convert it into a [Nibbles] struct /// containing the nibbles (half-bytes or 4 bits) that make up the input byte data. pub fn unpack>(data: T) -> Self { - Nibbles { hex_data: data.as_ref().iter().flat_map(|item| [item / 16, item % 16]).collect() } + Nibbles { + hex_data: Bytes::from( + data.as_ref() + .iter() + .flat_map(|item| vec![item / 16, item % 16]) + .collect::>(), + ), + } } /// Packs the nibbles stored in the struct into a byte vector. @@ -202,13 +209,13 @@ impl Nibbles { /// Increments the nibble sequence by one. pub fn increment(&self) -> Option { - let mut incremented = self.hex_data.clone(); + let mut incremented = self.hex_data.to_vec(); for nibble in incremented.iter_mut().rev() { assert!(*nibble < 0x10); if *nibble < 0xf { *nibble += 1; - return Some(Nibbles::from(incremented)) + return Some(Nibbles::from_hex(incremented)) } else { *nibble = 0; } @@ -269,12 +276,16 @@ impl Nibbles { /// Extend the current nibbles with another nibbles. pub fn extend(&mut self, b: impl AsRef<[u8]>) { - self.hex_data.extend_from_slice(b.as_ref()); + // self.hex_data.extend_from_slice(b.as_ref()); + + let mut bytes = self.hex_data.to_vec(); + bytes.extend_from_slice(b.as_ref()); + self.hex_data = bytes.into(); } /// Truncate the current nibbles to the given length. pub fn truncate(&mut self, len: usize) { - self.hex_data.truncate(len) + self.hex_data.0.truncate(len) } } @@ -286,7 +297,7 @@ mod tests { #[test] fn hashed_regression() { let nibbles = hex::decode("05010406040a040203030f010805020b050c04070003070e0909070f010b0a0805020301070c0a0902040b0f000f0006040a04050f020b090701000a0a040b").unwrap(); - let nibbles = Nibbles::from(nibbles); + let nibbles = Nibbles::from_hex(nibbles); let path = nibbles.encode_path_leaf(true); let expected = hex::decode("351464a4233f1852b5c47037e997f1ba852317ca924bf0f064a45f2b9710aa4b") @@ -304,7 +315,7 @@ mod tests { (vec![0xa, 0xb, 0x2, 0x0], vec![0xab, 0x20]), (vec![0xa, 0xb, 0x2, 0x7], vec![0xab, 0x27]), ] { - let nibbles = Nibbles::from(input); + let nibbles = Nibbles::from_hex(input); let encoded = nibbles.pack(); assert_eq!(encoded, expected); } diff --git a/crates/primitives/src/trie/nodes/leaf.rs b/crates/primitives/src/trie/nodes/leaf.rs index cbf3ecd551f1..5ccbcfb0c3bb 100644 --- a/crates/primitives/src/trie/nodes/leaf.rs +++ b/crates/primitives/src/trie/nodes/leaf.rs @@ -59,7 +59,7 @@ mod tests { // From manual regression test #[test] fn encode_leaf_node_nibble() { - let nibble = Nibbles { hex_data: hex!("0604060f").to_vec() }; + let nibble = Nibbles { hex_data: hex!("0604060f").into() }; let encoded = nibble.encode_path_leaf(true); let expected = hex!("20646f").to_vec(); assert_eq!(encoded, expected); @@ -67,7 +67,7 @@ mod tests { #[test] fn rlp_leaf_node_roundtrip() { - let nibble = Nibbles { hex_data: hex!("0604060f").to_vec() }; + let nibble = Nibbles { hex_data: hex!("0604060f").into() }; let val = hex!("76657262").to_vec(); let leaf = LeafNode::new(&nibble, &val); let rlp = leaf.rlp(&mut vec![]); diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index 9201815789c1..bfb0344e4360 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -214,7 +214,7 @@ impl Stage for MerkleStage { let checkpoint = MerkleCheckpoint::new( to_block, state.last_account_key, - state.last_walker_key.hex_data, + state.last_walker_key.hex_data.to_vec(), state.walker_stack.into_iter().map(StoredSubNode::from).collect(), state.hash_builder.into(), ); diff --git a/crates/trie/src/progress.rs b/crates/trie/src/progress.rs index 128fa3f50cbb..36b96643dc71 100644 --- a/crates/trie/src/progress.rs +++ b/crates/trie/src/progress.rs @@ -34,7 +34,7 @@ impl From for IntermediateStateRootState { hash_builder: HashBuilder::from(value.state), walker_stack: value.walker_stack.into_iter().map(CursorSubNode::from).collect(), last_account_key: value.last_account_key, - last_walker_key: Nibbles::from(value.last_walker_key), + last_walker_key: Nibbles::from_hex(value.last_walker_key), } } } diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index 0c9b1b4537a9..1ad0baa6ea60 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -1331,11 +1331,11 @@ mod tests { fn assert_trie_updates(account_updates: &HashMap) { assert_eq!(account_updates.len(), 2); - let node = account_updates.get(&vec![0x3].into()).unwrap(); + let node = account_updates.get(&vec![0x3].as_slice().into()).unwrap(); let expected = BranchNodeCompact::new(0b0011, 0b0001, 0b0000, vec![], None); assert_eq!(node, &expected); - let node = account_updates.get(&vec![0x3, 0x0, 0xA, 0xF].into()).unwrap(); + let node = account_updates.get(&vec![0x3, 0x0, 0xA, 0xF].as_slice().into()).unwrap(); assert_eq!(node.state_mask, TrieMask::new(0b101100000)); assert_eq!(node.tree_mask, TrieMask::new(0b000000000)); assert_eq!(node.hash_mask, TrieMask::new(0b001000000)); diff --git a/crates/trie/src/trie_cursor/subnode.rs b/crates/trie/src/trie_cursor/subnode.rs index 368af228658d..fb82dc5333c3 100644 --- a/crates/trie/src/trie_cursor/subnode.rs +++ b/crates/trie/src/trie_cursor/subnode.rs @@ -39,14 +39,14 @@ impl From for CursorSubNode { Some(n) => n as i8, None => -1, }; - Self { key: Nibbles::from(value.key), nibble, node: value.node } + Self { key: Nibbles::from_hex(value.key), nibble, node: value.node } } } impl From for StoredSubNode { fn from(value: CursorSubNode) -> Self { let nibble = if value.nibble >= 0 { Some(value.nibble as u8) } else { None }; - Self { key: value.key.hex_data, nibble, node: value.node } + Self { key: value.key.hex_data.to_vec(), nibble, node: value.node } } } diff --git a/crates/trie/src/updates.rs b/crates/trie/src/updates.rs index e3fe3e311d93..49ab105f007f 100644 --- a/crates/trie/src/updates.rs +++ b/crates/trie/src/updates.rs @@ -77,20 +77,25 @@ impl TrieUpdates { } /// Extend the updates with account trie updates. + #[allow(clippy::mutable_key_type)] pub fn extend_with_account_updates(&mut self, updates: HashMap) { self.extend(updates.into_iter().map(|(nibbles, node)| { - (TrieKey::AccountNode(nibbles.hex_data.into()), TrieOp::Update(node)) + (TrieKey::AccountNode(nibbles.hex_data.to_vec().into()), TrieOp::Update(node)) })); } /// Extend the updates with storage trie updates. + #[allow(clippy::mutable_key_type)] pub fn extend_with_storage_updates( &mut self, hashed_address: H256, updates: HashMap, ) { self.extend(updates.into_iter().map(|(nibbles, node)| { - (TrieKey::StorageNode(hashed_address, nibbles.hex_data.into()), TrieOp::Update(node)) + ( + TrieKey::StorageNode(hashed_address, nibbles.hex_data.to_vec().into()), + TrieOp::Update(node), + ) })); } diff --git a/crates/trie/src/walker.rs b/crates/trie/src/walker.rs index f91f25843bc1..284ad4f2b240 100644 --- a/crates/trie/src/walker.rs +++ b/crates/trie/src/walker.rs @@ -129,16 +129,16 @@ impl<'a, K: Key + From>, C: TrieCursor> TrieWalker<'a, K, C> { fn node(&mut self, exact: bool) -> Result, DatabaseError> { let key = self.key().expect("key must exist"); let entry = if exact { - self.cursor.seek_exact(key.hex_data.into())? + self.cursor.seek_exact(key.hex_data.to_vec().into())? } else { - self.cursor.seek(key.hex_data.into())? + self.cursor.seek(key.hex_data.to_vec().into())? }; if let Some((_, node)) = &entry { assert!(!node.state_mask.is_empty()); } - Ok(entry.map(|(k, v)| (Nibbles::from(k), v))) + Ok(entry.map(|(k, v)| (Nibbles::from_hex(k), v))) } /// Consumes the next node in the trie, updating the stack. @@ -374,7 +374,7 @@ mod tests { // No changes let mut cursor = TrieWalker::new(&mut trie, Default::default()); - assert_eq!(cursor.key(), Some(Nibbles::from(vec![]))); // root + assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![]))); // root assert!(cursor.can_skip_current_node); // due to root_hash cursor.advance().unwrap(); // skips to the end of trie assert_eq!(cursor.key(), None); @@ -385,15 +385,15 @@ mod tests { let mut cursor = TrieWalker::new(&mut trie, changed); // Root node - assert_eq!(cursor.key(), Some(Nibbles::from(vec![]))); + assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![]))); // Should not be able to skip state due to the changed values assert!(!cursor.can_skip_current_node); cursor.advance().unwrap(); - assert_eq!(cursor.key(), Some(Nibbles::from(vec![0x2]))); + assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![0x2]))); cursor.advance().unwrap(); - assert_eq!(cursor.key(), Some(Nibbles::from(vec![0x2, 0x1]))); + assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![0x2, 0x1]))); cursor.advance().unwrap(); - assert_eq!(cursor.key(), Some(Nibbles::from(vec![0x4]))); + assert_eq!(cursor.key(), Some(Nibbles::from_hex(vec![0x4]))); cursor.advance().unwrap(); assert_eq!(cursor.key(), None); // the end of trie From 6752d624a11790fc0ca4cfbf432d30c9b327ee2e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 13 Jun 2023 16:02:48 +0400 Subject: [PATCH 007/216] refactor(stages): input target reached & output done checks (#3119) --- bin/reth/src/debug_cmd/merkle.rs | 34 ++--- bin/reth/src/node/events.rs | 3 +- bin/reth/src/stage/dump/hashing_account.rs | 15 +- bin/reth/src/stage/dump/hashing_storage.rs | 15 +- bin/reth/src/stage/dump/merkle.rs | 19 +-- bin/reth/src/stage/run.rs | 13 +- crates/consensus/beacon/src/engine/mod.rs | 87 ++++++----- crates/consensus/beacon/src/engine/sync.rs | 6 - crates/stages/src/pipeline/event.rs | 4 + crates/stages/src/pipeline/mod.rs | 138 +++++++++++------- crates/stages/src/stage.rs | 54 ++++--- crates/stages/src/stages/bodies.rs | 23 +-- crates/stages/src/stages/execution.rs | 11 +- crates/stages/src/stages/finish.rs | 10 +- crates/stages/src/stages/hashing_account.rs | 25 +--- crates/stages/src/stages/hashing_storage.rs | 30 ++-- crates/stages/src/stages/headers.rs | 10 +- .../src/stages/index_account_history.rs | 19 +-- .../src/stages/index_storage_history.rs | 21 +-- crates/stages/src/stages/merkle.rs | 16 +- crates/stages/src/stages/sender_recovery.rs | 29 ++-- crates/stages/src/stages/total_difficulty.rs | 19 +-- crates/stages/src/stages/tx_lookup.rs | 28 ++-- crates/stages/src/test_utils/macros.rs | 53 +------ crates/stages/src/test_utils/stage.rs | 36 ++++- .../src/providers/database/provider.rs | 6 + 26 files changed, 332 insertions(+), 392 deletions(-) diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index c05fc6c54b94..909203e02b79 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -120,30 +120,22 @@ impl Command { let mut account_hashing_done = false; while !account_hashing_done { - let output = account_hashing_stage - .execute( - &mut provider_rw, - ExecInput { - target: Some(block), - checkpoint: progress.map(StageCheckpoint::new), - }, - ) - .await?; - account_hashing_done = output.done; + let input = ExecInput { + target: Some(block), + checkpoint: progress.map(StageCheckpoint::new), + }; + let output = account_hashing_stage.execute(&mut provider_rw, input).await?; + account_hashing_done = output.is_done(input); } let mut storage_hashing_done = false; while !storage_hashing_done { - let output = storage_hashing_stage - .execute( - &mut provider_rw, - ExecInput { - target: Some(block), - checkpoint: progress.map(StageCheckpoint::new), - }, - ) - .await?; - storage_hashing_done = output.done; + let input = ExecInput { + target: Some(block), + checkpoint: progress.map(StageCheckpoint::new), + }; + let output = storage_hashing_stage.execute(&mut provider_rw, input).await?; + storage_hashing_done = output.is_done(input); } let incremental_result = merkle_stage @@ -173,7 +165,7 @@ impl Command { loop { let clean_result = merkle_stage.execute(&mut provider_rw, clean_input).await; assert!(clean_result.is_ok(), "Clean state root calculation failed"); - if clean_result.unwrap().done { + if clean_result.unwrap().is_done(clean_input) { break } } diff --git a/bin/reth/src/node/events.rs b/bin/reth/src/node/events.rs index 04b36b59cc65..fdc56ebd86bd 100644 --- a/bin/reth/src/node/events.rs +++ b/bin/reth/src/node/events.rs @@ -72,7 +72,8 @@ impl NodeState { pipeline_position, pipeline_total, stage_id, - result: ExecOutput { checkpoint, done }, + result: ExecOutput { checkpoint }, + done, } => { self.current_checkpoint = checkpoint; diff --git a/bin/reth/src/stage/dump/hashing_account.rs b/bin/reth/src/stage/dump/hashing_account.rs index d63a14cc82c1..690c1f40e634 100644 --- a/bin/reth/src/stage/dump/hashing_account.rs +++ b/bin/reth/src/stage/dump/hashing_account.rs @@ -77,16 +77,11 @@ async fn dry_run( let mut exec_output = false; while !exec_output { - exec_output = exec_stage - .execute( - &mut provider, - reth_stages::ExecInput { - target: Some(to), - checkpoint: Some(StageCheckpoint::new(from)), - }, - ) - .await? - .done; + let exec_input = reth_stages::ExecInput { + target: Some(to), + checkpoint: Some(StageCheckpoint::new(from)), + }; + exec_output = exec_stage.execute(&mut provider, exec_input).await?.is_done(exec_input); } info!(target: "reth::cli", "Success."); diff --git a/bin/reth/src/stage/dump/hashing_storage.rs b/bin/reth/src/stage/dump/hashing_storage.rs index 6e717544c7ad..a022ef30dda1 100644 --- a/bin/reth/src/stage/dump/hashing_storage.rs +++ b/bin/reth/src/stage/dump/hashing_storage.rs @@ -76,16 +76,11 @@ async fn dry_run( let mut exec_output = false; while !exec_output { - exec_output = exec_stage - .execute( - &mut provider, - reth_stages::ExecInput { - target: Some(to), - checkpoint: Some(StageCheckpoint::new(from)), - }, - ) - .await? - .done; + let exec_input = reth_stages::ExecInput { + target: Some(to), + checkpoint: Some(StageCheckpoint::new(from)), + }; + exec_output = exec_stage.execute(&mut provider, exec_input).await?.is_done(exec_input); } info!(target: "reth::cli", "Success."); diff --git a/bin/reth/src/stage/dump/merkle.rs b/bin/reth/src/stage/dump/merkle.rs index 3eb38283be12..1d2e05005fff 100644 --- a/bin/reth/src/stage/dump/merkle.rs +++ b/bin/reth/src/stage/dump/merkle.rs @@ -119,20 +119,17 @@ async fn dry_run( let mut provider = shareable_db.provider_rw()?; let mut exec_output = false; while !exec_output { + let exec_input = reth_stages::ExecInput { + target: Some(to), + checkpoint: Some(StageCheckpoint::new(from)), + }; exec_output = MerkleStage::Execution { - clean_threshold: u64::MAX, /* Forces updating the root instead of calculating - * from - * scratch */ + // Forces updating the root instead of calculating from scratch + clean_threshold: u64::MAX, } - .execute( - &mut provider, - reth_stages::ExecInput { - target: Some(to), - checkpoint: Some(StageCheckpoint::new(from)), - }, - ) + .execute(&mut provider, exec_input) .await? - .done; + .is_done(exec_input); } info!(target: "reth::cli", "Success."); diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 4bfcea361a2a..a47c467ba79f 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -20,7 +20,7 @@ use reth_stages::{ IndexAccountHistoryStage, IndexStorageHistoryStage, MerkleStage, SenderRecoveryStage, StorageHashingStage, TransactionLookupStage, }, - ExecInput, ExecOutput, PipelineError, Stage, UnwindInput, + ExecInput, PipelineError, Stage, UnwindInput, }; use std::{any::Any, net::SocketAddr, path::PathBuf, sync::Arc}; use tracing::*; @@ -238,10 +238,13 @@ impl Command { checkpoint: Some(checkpoint.with_block_number(self.from)), }; - while let ExecOutput { checkpoint: stage_progress, done: false } = - exec_stage.execute(&mut provider_rw, input).await? - { - input.checkpoint = Some(stage_progress); + loop { + let result = exec_stage.execute(&mut provider_rw, input).await?; + if result.is_done(input) { + break + } + + input.checkpoint = Some(result.checkpoint); if self.commit { provider_rw.commit()?; diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 70f555555256..0875c3f7fb4e 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1369,6 +1369,7 @@ mod tests { chain_spec: Arc, pipeline_exec_outputs: VecDeque>, executor_results: Vec, + max_block: Option, ) -> (TestBeaconConsensusEngine, TestEnv>>) { reth_tracing::init_test_tracing(); let db = create_test_rw_db(); @@ -1380,10 +1381,13 @@ mod tests { // Setup pipeline let (tip_tx, tip_rx) = watch::channel(H256::default()); - let pipeline = Pipeline::builder() + let mut pipeline_builder = Pipeline::builder() .add_stages(TestStages::new(pipeline_exec_outputs, Default::default())) - .with_tip_sender(tip_tx) - .build(db.clone(), chain_spec.clone()); + .with_tip_sender(tip_tx); + if let Some(max_block) = max_block { + pipeline_builder = pipeline_builder.with_max_block(max_block); + } + let pipeline = pipeline_builder.build(db.clone(), chain_spec.clone()); // Setup blockchain tree let externals = @@ -1403,7 +1407,7 @@ mod tests { blockchain_provider, Box::::default(), Box::::default(), - None, + max_block, false, payload_builder, None, @@ -1438,6 +1442,7 @@ mod tests { chain_spec.clone(), VecDeque::from([Err(StageError::ChannelClosed)]), Vec::default(), + Some(1), ); let res = spawn_consensus_engine(consensus_engine); @@ -1467,6 +1472,7 @@ mod tests { chain_spec.clone(), VecDeque::from([Err(StageError::ChannelClosed)]), Vec::default(), + Some(1), ); let mut rx = spawn_consensus_engine(consensus_engine); @@ -1506,10 +1512,11 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { checkpoint: StageCheckpoint::new(1), done: true }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(1) }), Err(StageError::ChannelClosed), ]), Vec::default(), + Some(2), ); let rx = spawn_consensus_engine(consensus_engine); @@ -1522,7 +1529,9 @@ mod tests { assert_matches!( rx.await, - Ok(Err(BeaconConsensusEngineError::Pipeline(n))) if matches!(*n.as_ref(),PipelineError::Stage(StageError::ChannelClosed)) + Ok( + Err(BeaconConsensusEngineError::Pipeline(n)) + ) if matches!(*n.as_ref(),PipelineError::Stage(StageError::ChannelClosed)) ); } @@ -1536,15 +1545,12 @@ mod tests { .paris_activated() .build(), ); - let (mut consensus_engine, env) = setup_consensus_engine( + let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - checkpoint: StageCheckpoint::new(max_block), - done: true, - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(max_block) })]), Vec::default(), + Some(max_block), ); - consensus_engine.sync.set_max_block(max_block); let rx = spawn_consensus_engine(consensus_engine); let _ = env @@ -1584,11 +1590,9 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, - checkpoint: StageCheckpoint::new(0), - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), Vec::default(), + None, ); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1615,11 +1619,9 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, - checkpoint: StageCheckpoint::new(0), - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), Vec::default(), + None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1664,10 +1666,11 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), ]), Vec::default(), + None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1712,11 +1715,9 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, - checkpoint: StageCheckpoint::new(0), - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), Vec::default(), + None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1750,10 +1751,11 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), ]), Vec::default(), + None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1803,10 +1805,11 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), ]), Vec::default(), + None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1849,11 +1852,9 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, - checkpoint: StageCheckpoint::new(0), - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), Vec::default(), + None, ); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1882,11 +1883,9 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, - checkpoint: StageCheckpoint::new(0), - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), Vec::default(), + None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1932,11 +1931,9 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, - checkpoint: StageCheckpoint::new(0), - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), Vec::default(), + None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1989,11 +1986,9 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, - checkpoint: StageCheckpoint::new(0), - })]), + VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), Vec::from([exec_result2]), + None, ); insert_blocks( diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index a093b57bae8c..a5097c4c939b 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -83,12 +83,6 @@ where self.metrics.active_block_downloads.set(self.inflight_full_block_requests.len() as f64); } - /// Sets the max block value for testing - #[cfg(test)] - pub(crate) fn set_max_block(&mut self, block: BlockNumber) { - self.max_block = Some(block); - } - /// Cancels all full block requests that are in progress. pub(crate) fn clear_full_block_requests(&mut self) { self.inflight_full_block_requests.clear(); diff --git a/crates/stages/src/pipeline/event.rs b/crates/stages/src/pipeline/event.rs index 2230c4075e04..6133d89fa172 100644 --- a/crates/stages/src/pipeline/event.rs +++ b/crates/stages/src/pipeline/event.rs @@ -31,6 +31,8 @@ pub enum PipelineEvent { stage_id: StageId, /// The result of executing the stage. result: ExecOutput, + /// Stage completed executing the whole block range + done: bool, }, /// Emitted when a stage is about to be unwound. Unwinding { @@ -45,6 +47,8 @@ pub enum PipelineEvent { stage_id: StageId, /// The result of unwinding the stage. result: UnwindOutput, + /// Stage completed unwinding the whole block range + done: bool, }, /// Emitted when a stage encounters an error either during execution or unwinding. Error { diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index 6586365e84fa..a4b6c681e06d 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -262,8 +262,10 @@ where continue } + let mut done = UnwindInput { checkpoint, unwind_to: to, bad_block }.target_reached(); + debug!(target: "sync::pipeline", from = %checkpoint, %to, ?bad_block, "Starting unwind"); - while checkpoint.block_number > to { + while !done { let input = UnwindInput { checkpoint, unwind_to: to, bad_block }; self.listeners.notify(PipelineEvent::Unwinding { stage_id, input }); @@ -271,6 +273,7 @@ where match output { Ok(unwind_output) => { checkpoint = unwind_output.checkpoint; + done = unwind_output.is_done(input); info!( target: "sync::pipeline", stage = %stage_id, @@ -287,8 +290,11 @@ where ); provider_rw.save_stage_checkpoint(stage_id, checkpoint)?; - self.listeners - .notify(PipelineEvent::Unwound { stage_id, result: unwind_output }); + self.listeners.notify(PipelineEvent::Unwound { + stage_id, + result: unwind_output, + done, + }); provider_rw.commit()?; provider_rw = @@ -349,11 +355,18 @@ where checkpoint: prev_checkpoint, }); - match stage - .execute(&mut provider_rw, ExecInput { target, checkpoint: prev_checkpoint }) - .await - { - Ok(out @ ExecOutput { checkpoint, done }) => { + let input = ExecInput { target, checkpoint: prev_checkpoint }; + let result = if input.target_reached() { + Ok(ExecOutput { checkpoint: input.checkpoint() }) + } else { + stage + .execute(&mut provider_rw, ExecInput { target, checkpoint: prev_checkpoint }) + .await + }; + + match result { + Ok(out @ ExecOutput { checkpoint }) => { + let done = out.is_done(input); made_progress |= checkpoint.block_number != prev_checkpoint.unwrap_or_default().block_number; info!( @@ -372,6 +385,7 @@ where pipeline_total: total_stages, stage_id, result: out.clone(), + done, }); // TODO: Make the commit interval configurable @@ -470,7 +484,10 @@ impl std::fmt::Debug for Pipeline { #[cfg(test)] mod tests { use super::*; - use crate::{test_utils::TestStage, UnwindOutput}; + use crate::{ + test_utils::{TestStage, TestTransaction}, + UnwindOutput, + }; use assert_matches::assert_matches; use reth_db::mdbx::{self, test_utils, EnvKind}; use reth_interfaces::{ @@ -509,19 +526,23 @@ mod tests { /// Runs a simple pipeline. #[tokio::test] async fn run_pipeline() { - let db = test_utils::create_test_db::(EnvKind::RW); + let tx = TestTransaction::default(); let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20) })), ) .add_stage( TestStage::new(StageId::Other("B")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), + .with_checkpoint(Some(StageCheckpoint::new(10)), tx.inner()), + ) + .add_stage( + TestStage::new(StageId::Other("C")) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), ) .with_max_block(10) - .build(db, MAINNET.clone()); + .build(tx.inner_raw(), MAINNET.clone()); let events = pipeline.events(); // Run pipeline @@ -535,27 +556,30 @@ mod tests { vec![ PipelineEvent::Running { pipeline_position: 1, - pipeline_total: 2, + pipeline_total: 3, stage_id: StageId::Other("A"), checkpoint: None }, PipelineEvent::Ran { pipeline_position: 1, - pipeline_total: 2, + pipeline_total: 3, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(20) }, + done: true, }, + PipelineEvent::Skipped { stage_id: StageId::Other("B") }, PipelineEvent::Running { - pipeline_position: 2, - pipeline_total: 2, - stage_id: StageId::Other("B"), + pipeline_position: 3, + pipeline_total: 3, + stage_id: StageId::Other("C"), checkpoint: None }, PipelineEvent::Ran { - pipeline_position: 2, - pipeline_total: 2, - stage_id: StageId::Other("B"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, + pipeline_position: 3, + pipeline_total: 3, + stage_id: StageId::Other("C"), + result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, + done: true, }, ] ); @@ -569,17 +593,17 @@ mod tests { let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100) })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) })), ) .add_stage( TestStage::new(StageId::Other("B")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) })), ) .add_stage( TestStage::new(StageId::Other("C")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20) })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) })), ) .with_max_block(10) @@ -610,7 +634,8 @@ mod tests { pipeline_position: 1, pipeline_total: 3, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(100) }, + done: true }, PipelineEvent::Running { pipeline_position: 2, @@ -622,7 +647,8 @@ mod tests { pipeline_position: 2, pipeline_total: 3, stage_id: StageId::Other("B"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, + done: true }, PipelineEvent::Running { pipeline_position: 3, @@ -634,7 +660,8 @@ mod tests { pipeline_position: 3, pipeline_total: 3, stage_id: StageId::Other("C"), - result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(20) }, + done: true }, // Unwinding PipelineEvent::Unwinding { @@ -648,6 +675,7 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("C"), result: UnwindOutput { checkpoint: StageCheckpoint::new(1) }, + done: true }, PipelineEvent::Unwinding { stage_id: StageId::Other("B"), @@ -660,6 +688,7 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("B"), result: UnwindOutput { checkpoint: StageCheckpoint::new(1) }, + done: true }, PipelineEvent::Unwinding { stage_id: StageId::Other("A"), @@ -672,6 +701,7 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("A"), result: UnwindOutput { checkpoint: StageCheckpoint::new(1) }, + done: true }, ] ); @@ -685,12 +715,12 @@ mod tests { let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100) })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(50) })), ) .add_stage( TestStage::new(StageId::Other("B")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), ) .with_max_block(10) .build(db, MAINNET.clone()); @@ -720,7 +750,8 @@ mod tests { pipeline_position: 1, pipeline_total: 2, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(100) }, + done: true }, PipelineEvent::Running { pipeline_position: 2, @@ -732,7 +763,8 @@ mod tests { pipeline_position: 2, pipeline_total: 2, stage_id: StageId::Other("B"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, + done: true }, // Unwinding // Nothing to unwind in stage "B" @@ -748,6 +780,7 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("A"), result: UnwindOutput { checkpoint: StageCheckpoint::new(50) }, + done: true }, ] ); @@ -772,9 +805,9 @@ mod tests { let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) })) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), ) .add_stage( TestStage::new(StageId::Other("B")) @@ -783,7 +816,7 @@ mod tests { error: consensus::ConsensusError::BaseFeeMissing, })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) })) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), ) .with_max_block(10) .build(db, MAINNET.clone()); @@ -808,7 +841,8 @@ mod tests { pipeline_position: 1, pipeline_total: 2, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, + done: true }, PipelineEvent::Running { pipeline_position: 2, @@ -828,6 +862,7 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("A"), result: UnwindOutput { checkpoint: StageCheckpoint::new(0) }, + done: true }, PipelineEvent::Running { pipeline_position: 1, @@ -839,7 +874,8 @@ mod tests { pipeline_position: 1, pipeline_total: 2, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, + done: true }, PipelineEvent::Running { pipeline_position: 2, @@ -851,7 +887,8 @@ mod tests { pipeline_position: 2, pipeline_total: 2, stage_id: StageId::Other("B"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, + result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, + done: true }, ] ); @@ -861,17 +898,17 @@ mod tests { #[tokio::test] async fn pipeline_error_handling() { // Non-fatal - let db = test_utils::create_test_db::(EnvKind::RW); - let mut pipeline = Pipeline::builder() - .add_stage( - TestStage::new(StageId::Other("NonFatal")) - .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error)))) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), - ) - .with_max_block(10) - .build(db, MAINNET.clone()); - let result = pipeline.run().await; - assert_matches!(result, Ok(())); + // let db = test_utils::create_test_db::(EnvKind::RW); + // let mut pipeline = Pipeline::builder() + // .add_stage( + // TestStage::new(StageId::Other("NonFatal")) + // .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error)))) + // .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), + // ) + // .with_max_block(10) + // .build(db, MAINNET.clone()); + // let result = pipeline.run().await; + // assert_matches!(result, Ok(())); // Fatal let db = test_utils::create_test_db::(EnvKind::RW); @@ -879,6 +916,7 @@ mod tests { .add_stage(TestStage::new(StageId::Other("Fatal")).add_exec(Err( StageError::DatabaseIntegrity(ProviderError::BlockBodyIndicesNotFound(5)), ))) + .with_max_block(1) .build(db, MAINNET.clone()); let result = pipeline.run().await; assert_matches!( diff --git a/crates/stages/src/stage.rs b/crates/stages/src/stage.rs index a7de484994cd..b5f1311baf5a 100644 --- a/crates/stages/src/stage.rs +++ b/crates/stages/src/stage.rs @@ -10,6 +10,7 @@ use std::{ cmp::{max, min}, ops::RangeInclusive, }; +use tracing::warn; /// Stage execution input, see [Stage::execute]. #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] @@ -35,7 +36,7 @@ impl ExecInput { /// Returns `true` if the target block number has already been reached. pub fn target_reached(&self) -> bool { - self.checkpoint().block_number >= self.target() + ExecOutput { checkpoint: self.checkpoint.unwrap_or_default() }.is_done(*self) } /// Return the target block number or default. @@ -45,8 +46,7 @@ impl ExecInput { /// Return next block range that needs to be executed. pub fn next_block_range(&self) -> RangeInclusive { - let (range, _) = self.next_block_range_with_threshold(u64::MAX); - range + self.next_block_range_with_threshold(u64::MAX) } /// Return true if this is the first block range to execute. @@ -55,19 +55,15 @@ impl ExecInput { } /// Return the next block range to execute. - /// Return pair of the block range and if this is final block range. - pub fn next_block_range_with_threshold( - &self, - threshold: u64, - ) -> (RangeInclusive, bool) { + /// Return pair of the block range. + pub fn next_block_range_with_threshold(&self, threshold: u64) -> RangeInclusive { let current_block = self.checkpoint(); let start = current_block.block_number + 1; let target = self.target(); let end = min(target, current_block.block_number.saturating_add(threshold)); - let is_final_range = end == target; - (start..=end, is_final_range) + start..=end } /// Return the next block range determined the number of transactions within it. @@ -77,7 +73,7 @@ impl ExecInput { &self, provider: &DatabaseProviderRW<'_, DB>, tx_threshold: u64, - ) -> Result<(RangeInclusive, RangeInclusive, bool), StageError> { + ) -> Result<(RangeInclusive, RangeInclusive), StageError> { let start_block = self.next_block(); let start_block_body = provider .tx_ref() @@ -100,8 +96,7 @@ impl ExecInput { break } } - let is_final_range = end_block_number >= target_block; - Ok((first_tx_number..=last_tx_number, start_block..=end_block_number, is_final_range)) + Ok((first_tx_number..=last_tx_number, start_block..=end_block_number)) } } @@ -117,6 +112,11 @@ pub struct UnwindInput { } impl UnwindInput { + /// Returns `true` if the target block number has already been reached. + pub fn target_reached(&self) -> bool { + UnwindOutput { checkpoint: self.checkpoint }.is_done(*self) + } + /// Return next block range that needs to be unwound. pub fn unwind_block_range(&self) -> RangeInclusive { self.unwind_block_range_with_threshold(u64::MAX).0 @@ -126,7 +126,7 @@ impl UnwindInput { pub fn unwind_block_range_with_threshold( &self, threshold: u64, - ) -> (RangeInclusive, BlockNumber, bool) { + ) -> (RangeInclusive, BlockNumber) { // +1 is to skip the block we're unwinding to let mut start = self.unwind_to + 1; let end = self.checkpoint; @@ -135,8 +135,7 @@ impl UnwindInput { let unwind_to = start - 1; - let is_final_range = unwind_to == self.unwind_to; - (start..=end.block_number, unwind_to, is_final_range) + (start..=end.block_number, unwind_to) } } @@ -145,14 +144,16 @@ impl UnwindInput { pub struct ExecOutput { /// How far the stage got. pub checkpoint: StageCheckpoint, - /// Whether or not the stage is done. - pub done: bool, } impl ExecOutput { - /// Mark the stage as done, checkpointing at the given place. - pub fn done(checkpoint: StageCheckpoint) -> Self { - Self { checkpoint, done: true } + /// Returns `true` if the target block number has already been reached, + /// i.e. `checkpoint.block_number >= target`. + pub fn is_done(&self, input: ExecInput) -> bool { + if self.checkpoint.block_number > input.target() { + warn!(target: "sync::pipeline", ?input, output = ?self, "Checkpoint is beyond the execution target"); + } + self.checkpoint.block_number >= input.target() } } @@ -163,6 +164,17 @@ pub struct UnwindOutput { pub checkpoint: StageCheckpoint, } +impl UnwindOutput { + /// Returns `true` if the target block number has already been reached, + /// i.e. `checkpoint.block_number <= unwind_to`. + pub fn is_done(&self, input: UnwindInput) -> bool { + if self.checkpoint.block_number < input.unwind_to { + warn!(target: "sync::pipeline", ?input, output = ?self, "Checkpoint is beyond the unwind target"); + } + self.checkpoint.block_number <= input.unwind_to + } +} + /// A stage is a segmented part of the syncing process of the node. /// /// Each stage takes care of a well-defined task, such as downloading headers or executing diff --git a/crates/stages/src/stages/bodies.rs b/crates/stages/src/stages/bodies.rs index 0108f782804d..cdeca70e0bd1 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -70,10 +70,6 @@ impl Stage for BodyStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - let range = input.next_block_range(); // Update the header range on the downloader self.downloader.set_download_range(range.clone())?; @@ -152,11 +148,9 @@ impl Stage for BodyStage { // The stage is "done" if: // - We got fewer blocks than our target // - We reached our target and the target was not limited by the batch size of the stage - let done = highest_block == to_block; Ok(ExecOutput { checkpoint: StageCheckpoint::new(highest_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), - done, }) } @@ -232,15 +226,11 @@ fn stage_checkpoint( #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, UnwindStageTestRunner, - }; + use crate::test_utils::{ExecuteStageTestRunner, StageTestRunner, UnwindStageTestRunner}; use assert_matches::assert_matches; use reth_primitives::stage::StageUnitCheckpoint; use test_utils::*; - stage_test_suite_ext!(BodyTestRunner, body); - /// Checks that the stage downloads at most `batch_size` blocks. #[tokio::test] async fn partial_body_download() { @@ -273,7 +263,7 @@ mod tests { processed, // 1 seeded block body + batch size total // seeded headers })) - }, done: false }) if block_number < 200 && + }}) if block_number < 200 && processed == 1 + batch_size && total == previous_stage ); assert!(runner.validate_execution(input, output.ok()).is_ok(), "execution validation"); @@ -310,8 +300,7 @@ mod tests { processed, total })) - }, - done: true + } }) if processed == total && total == previous_stage ); assert!(runner.validate_execution(input, output.ok()).is_ok(), "execution validation"); @@ -346,7 +335,7 @@ mod tests { processed, total })) - }, done: false }) if block_number >= 10 && + }}) if block_number >= 10 && processed == 1 + batch_size && total == previous_stage ); let first_run_checkpoint = first_run.unwrap().checkpoint; @@ -366,7 +355,7 @@ mod tests { processed, total })) - }, done: true }) if block_number > first_run_checkpoint.block_number && + }}) if block_number > first_run_checkpoint.block_number && processed == total && total == previous_stage ); assert_matches!( @@ -406,7 +395,7 @@ mod tests { processed, total })) - }, done: true }) if block_number == previous_stage && + }}) if block_number == previous_stage && processed == total && total == previous_stage ); let checkpoint = output.unwrap().checkpoint; diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index f6b40ee05ad9..b58dd568873d 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -143,10 +143,6 @@ impl ExecutionStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - let start_block = input.next_block(); let max_block = input.target(); @@ -199,11 +195,9 @@ impl ExecutionStage { state.write_to_db(provider.tx_ref())?; trace!(target: "sync::stages::execution", took = ?start.elapsed(), "Wrote state"); - let done = stage_progress == max_block; Ok(ExecOutput { checkpoint: StageCheckpoint::new(stage_progress) .with_execution_stage_checkpoint(stage_checkpoint), - done, }) } } @@ -345,7 +339,7 @@ impl Stage for ExecutionStage { let mut account_changeset = tx.cursor_dup_write::()?; let mut storage_changeset = tx.cursor_dup_write::()?; - let (range, unwind_to, _) = + let (range, unwind_to) = input.unwind_block_range_with_threshold(self.thresholds.max_blocks.unwrap_or(u64::MAX)); if range.is_empty() { @@ -669,8 +663,7 @@ mod tests { total } })) - }, - done: true + } } if processed == total && total == block.gas_used); let mut provider = db.provider_rw().unwrap(); let tx = provider.tx_mut(); diff --git a/crates/stages/src/stages/finish.rs b/crates/stages/src/stages/finish.rs index bae21c8c76b8..4955565e0b46 100644 --- a/crates/stages/src/stages/finish.rs +++ b/crates/stages/src/stages/finish.rs @@ -21,7 +21,7 @@ impl Stage for FinishStage { _provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()), done: true }) + Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()) }) } async fn unwind( @@ -37,14 +37,12 @@ impl Stage for FinishStage { mod tests { use super::*; use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, - TestTransaction, UnwindStageTestRunner, + ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, + UnwindStageTestRunner, }; use reth_interfaces::test_utils::generators::{random_header, random_header_range}; use reth_primitives::SealedHeader; - stage_test_suite_ext!(FinishTestRunner, finish); - #[derive(Default)] struct FinishTestRunner { tx: TestTransaction, @@ -89,7 +87,7 @@ mod tests { output: Option, ) -> Result<(), TestRunnerError> { if let Some(output) = output { - assert!(output.done, "stage should always be done"); + assert!(output.is_done(input), "stage should always be done"); assert_eq!( output.checkpoint.block_number, input.target(), diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index ab56a3398494..75df5e9193a5 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -135,10 +135,6 @@ impl Stage for AccountHashingStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - let (from_block, to_block) = input.next_block_range().into_inner(); // if there are more blocks then threshold it is faster to go over Plain state and hash all @@ -236,7 +232,7 @@ impl Stage for AccountHashingStage { }, ); - return Ok(ExecOutput { checkpoint, done: false }) + return Ok(ExecOutput { checkpoint }) } } else { // Aggregate all transition changesets and make a list of accounts that have been @@ -258,7 +254,7 @@ impl Stage for AccountHashingStage { ..Default::default() }); - Ok(ExecOutput { checkpoint, done: true }) + Ok(ExecOutput { checkpoint }) } /// Unwind the stage. @@ -267,7 +263,7 @@ impl Stage for AccountHashingStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress, _) = + let (range, unwind_progress) = input.unwind_block_range_with_threshold(self.commit_threshold); // Aggregate all transition changesets and make a list of accounts that have been changed. @@ -297,15 +293,11 @@ fn stage_checkpoint_progress( #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, TestRunnerError, UnwindStageTestRunner, - }; + use crate::test_utils::{ExecuteStageTestRunner, TestRunnerError, UnwindStageTestRunner}; use assert_matches::assert_matches; use reth_primitives::{stage::StageUnitCheckpoint, Account, U256}; use test_utils::*; - stage_test_suite_ext!(AccountHashingTestRunner, account_hashing); - #[tokio::test] async fn execute_clean_account_hashing() { let (previous_stage, stage_progress) = (20, 10); @@ -335,8 +327,7 @@ mod tests { }, .. })), - }, - done: true, + } }) if block_number == previous_stage && processed == total && total == runner.tx.table::().unwrap().len() as u64 @@ -393,8 +384,7 @@ mod tests { progress: EntitiesCheckpoint { processed: 5, total } } )) - }, - done: false + } }) if address == fifth_address && total == runner.tx.table::().unwrap().len() as u64 ); @@ -420,8 +410,7 @@ mod tests { progress: EntitiesCheckpoint { processed, total } } )) - }, - done: true + } }) if processed == total && total == runner.tx.table::().unwrap().len() as u64 ); diff --git a/crates/stages/src/stages/hashing_storage.rs b/crates/stages/src/stages/hashing_storage.rs index acb109b0e9d6..3218fdfcf961 100644 --- a/crates/stages/src/stages/hashing_storage.rs +++ b/crates/stages/src/stages/hashing_storage.rs @@ -58,9 +58,6 @@ impl Stage for StorageHashingStage { input: ExecInput, ) -> Result { let tx = provider.tx_ref(); - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } let (from_block, to_block) = input.next_block_range().into_inner(); @@ -166,7 +163,7 @@ impl Stage for StorageHashingStage { }, ); - return Ok(ExecOutput { checkpoint, done: false }) + return Ok(ExecOutput { checkpoint }) } } else { // Aggregate all changesets and and make list of storages that have been @@ -188,7 +185,7 @@ impl Stage for StorageHashingStage { ..Default::default() }); - Ok(ExecOutput { checkpoint, done: true }) + Ok(ExecOutput { checkpoint }) } /// Unwind the stage. @@ -197,7 +194,7 @@ impl Stage for StorageHashingStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress, _) = + let (range, unwind_progress) = input.unwind_block_range_with_threshold(self.commit_threshold); provider.unwind_storage_hashing(BlockNumberAddress::range(range))?; @@ -227,8 +224,8 @@ fn stage_checkpoint_progress( mod tests { use super::*; use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, - TestTransaction, UnwindStageTestRunner, + ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, + UnwindStageTestRunner, }; use assert_matches::assert_matches; use reth_db::{ @@ -243,8 +240,6 @@ mod tests { stage::StageUnitCheckpoint, Address, SealedBlock, StorageEntry, H256, U256, }; - stage_test_suite_ext!(StorageHashingTestRunner, storage_hashing); - /// Execute with low clean threshold so as to hash whole storage #[tokio::test] async fn execute_clean_storage_hashing() { @@ -268,10 +263,8 @@ mod tests { runner.seed_execution(input).expect("failed to seed execution"); loop { - if let Ok(result @ ExecOutput { checkpoint, done }) = - runner.execute(input).await.unwrap() - { - if !done { + if let Ok(result @ ExecOutput { checkpoint }) = runner.execute(input).await.unwrap() { + if !result.is_done(input) { let previous_checkpoint = input .checkpoint .and_then(|checkpoint| checkpoint.storage_hashing_stage_checkpoint()) @@ -361,8 +354,7 @@ mod tests { total } })) - }, - done: false + } }) if address == progress_address && storage == progress_key && total == runner.tx.table::().unwrap().len() as u64 ); @@ -407,8 +399,7 @@ mod tests { } } )) - }, - done: false + } }) if address == progress_address && storage == progress_key && total == runner.tx.table::().unwrap().len() as u64 ); @@ -439,8 +430,7 @@ mod tests { } } )) - }, - done: true + } }) if processed == total && total == runner.tx.table::().unwrap().len() as u64 ); diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index ad857d63515b..21560ae0b462 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -210,7 +210,7 @@ where // Nothing to sync if gap.is_closed() { info!(target: "sync::stages::headers", checkpoint = %current_checkpoint, target = ?tip, "Target block already reached"); - return Ok(ExecOutput::done(current_checkpoint)) + return Ok(ExecOutput { checkpoint: current_checkpoint }) } debug!(target: "sync::stages::headers", ?tip, head = ?gap.local_head.hash(), "Commencing sync"); @@ -313,12 +313,10 @@ where Ok(ExecOutput { checkpoint: StageCheckpoint::new(checkpoint) .with_headers_stage_checkpoint(stage_checkpoint), - done: true, }) } else { Ok(ExecOutput { checkpoint: current_checkpoint.with_headers_stage_checkpoint(stage_checkpoint), - done: false, }) } } @@ -591,7 +589,7 @@ mod tests { total, } })) - }, done: true }) if block_number == tip.number && + }}) if block_number == tip.number && from == checkpoint && to == previous_stage && // -1 because we don't need to download the local head processed == checkpoint + headers.len() as u64 - 1 && total == tip.number); @@ -687,7 +685,7 @@ mod tests { total, } })) - }, done: false }) if block_number == checkpoint && + }}) if block_number == checkpoint && from == checkpoint && to == previous_stage && processed == checkpoint + 500 && total == tip.number); @@ -710,7 +708,7 @@ mod tests { total, } })) - }, done: true }) if block_number == tip.number && + }}) if block_number == tip.number && from == checkpoint && to == previous_stage && // -1 because we don't need to download the local head processed == checkpoint + headers.len() as u64 - 1 && total == tip.number); diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index f965009092ad..acbce16bd0be 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -38,11 +38,7 @@ impl Stage for IndexAccountHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - - let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); + let range = input.next_block_range_with_threshold(self.commit_threshold); let mut stage_checkpoint = stage_checkpoint( provider, @@ -63,7 +59,6 @@ impl Stage for IndexAccountHistoryStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()) .with_index_history_stage_checkpoint(stage_checkpoint), - done: is_final_range, }) } @@ -73,7 +68,7 @@ impl Stage for IndexAccountHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress, _) = + let (range, unwind_progress) = input.unwind_block_range_with_threshold(self.commit_threshold); let changesets = provider.unwind_account_history_indices(range)?; @@ -222,9 +217,9 @@ mod tests { progress: EntitiesCheckpoint { processed: 2, total: 2 } } ), - done: true } ); + assert!(out.is_done(input)); provider.commit().unwrap(); } @@ -462,10 +457,10 @@ mod tests { block_range: CheckpointBlockRange { from: 1, to: 5 }, progress: EntitiesCheckpoint { processed: 1, total: 2 } } - ), - done: false + ) } ); + assert!(!out.is_done(input)); input.checkpoint = Some(out.checkpoint); let out = stage.execute(&mut provider, input).await.unwrap(); @@ -477,10 +472,10 @@ mod tests { block_range: CheckpointBlockRange { from: 5, to: 5 }, progress: EntitiesCheckpoint { processed: 2, total: 2 } } - ), - done: true + ) } ); + assert!(out.is_done(input)); provider.commit().unwrap(); } diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index cc354a4daf97..1b4db0bb48da 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -41,11 +41,7 @@ impl Stage for IndexStorageHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - - let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); + let range = input.next_block_range_with_threshold(self.commit_threshold); let mut stage_checkpoint = stage_checkpoint( provider, @@ -65,7 +61,6 @@ impl Stage for IndexStorageHistoryStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()) .with_index_history_stage_checkpoint(stage_checkpoint), - done: is_final_range, }) } @@ -75,7 +70,7 @@ impl Stage for IndexStorageHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress, _) = + let (range, unwind_progress) = input.unwind_block_range_with_threshold(self.commit_threshold); let changesets = @@ -234,10 +229,10 @@ mod tests { block_range: CheckpointBlockRange { from: input.next_block(), to: run_to }, progress: EntitiesCheckpoint { processed: 2, total: 2 } } - ), - done: true + ) } ); + assert!(out.is_done(input)); provider.commit().unwrap(); } @@ -478,10 +473,10 @@ mod tests { block_range: CheckpointBlockRange { from: 1, to: 5 }, progress: EntitiesCheckpoint { processed: 1, total: 2 } } - ), - done: false + ) } ); + assert!(!out.is_done(input)); input.checkpoint = Some(out.checkpoint); let out = stage.execute(&mut provider, input).await.unwrap(); @@ -493,10 +488,10 @@ mod tests { block_range: CheckpointBlockRange { from: 5, to: 5 }, progress: EntitiesCheckpoint { processed: 2, total: 2 } } - ), - done: true + ) } ); + assert!(out.is_done(input)); provider.commit().unwrap(); } diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index bfb0344e4360..8e7ff439089a 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -144,7 +144,7 @@ impl Stage for MerkleStage { let threshold = match self { MerkleStage::Unwind => { info!(target: "sync::stages::merkle::unwind", "Stage is always skipped"); - return Ok(ExecOutput::done(StageCheckpoint::new(input.target()))) + return Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()) }) } MerkleStage::Execution { clean_threshold } => *clean_threshold, #[cfg(any(test, feature = "test-utils"))] @@ -226,7 +226,6 @@ impl Stage for MerkleStage { checkpoint: input .checkpoint() .with_entities_stage_checkpoint(entities_checkpoint), - done: false, }) } StateRootProgress::Complete(root, hashed_entries_walked, updates) => { @@ -267,7 +266,6 @@ impl Stage for MerkleStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(to_block) .with_entities_stage_checkpoint(entities_checkpoint), - done: true, }) } @@ -330,8 +328,8 @@ impl Stage for MerkleStage { mod tests { use super::*; use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, - TestTransaction, UnwindStageTestRunner, + ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, + UnwindStageTestRunner, }; use assert_matches::assert_matches; use reth_db::{ @@ -348,8 +346,6 @@ mod tests { use reth_trie::test_utils::{state_root, state_root_prehashed}; use std::collections::BTreeMap; - stage_test_suite_ext!(MerkleTestRunner, merkle); - /// Execute from genesis so as to merkelize whole state #[tokio::test] async fn execute_clean_merkle() { @@ -378,8 +374,7 @@ mod tests { processed, total })) - }, - done: true + } }) if block_number == previous_stage && processed == total && total == ( runner.tx.table::().unwrap().len() + @@ -418,8 +413,7 @@ mod tests { processed, total })) - }, - done: true + } }) if block_number == previous_stage && processed == total && total == ( runner.tx.table::().unwrap().len() + diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index a26cbad7a1fa..1347760b1cd5 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -59,11 +59,7 @@ impl Stage for SenderRecoveryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - - let (tx_range, block_range, is_final_range) = + let (tx_range, block_range) = input.next_block_range_with_transaction_threshold(provider, self.commit_threshold)?; let end_block = *block_range.end(); @@ -73,7 +69,6 @@ impl Stage for SenderRecoveryStage { return Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), - done: is_final_range, }) } @@ -155,7 +150,6 @@ impl Stage for SenderRecoveryStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), - done: is_final_range, }) } @@ -165,7 +159,7 @@ impl Stage for SenderRecoveryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); + let (_, unwind_to) = input.unwind_block_range_with_threshold(self.commit_threshold); // Lookup latest tx id that we should unwind to let latest_tx_id = provider.block_body_indices(unwind_to)?.last_tx_num(); @@ -233,12 +227,10 @@ mod tests { use super::*; use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, - TestTransaction, UnwindStageTestRunner, + ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, + UnwindStageTestRunner, }; - stage_test_suite_ext!(SenderRecoveryTestRunner, sender_recovery); - /// Execute a block range with a single transaction #[tokio::test] async fn execute_single_transaction() { @@ -272,7 +264,7 @@ mod tests { processed: 1, total: 1 })) - }, done: true }) if block_number == previous_stage + }}) if block_number == previous_stage ); // Validate the stage execution @@ -311,17 +303,17 @@ mod tests { .unwrap_or(previous_stage); assert_matches!(result, Ok(_)); assert_eq!( - result.unwrap(), - ExecOutput { + result.as_ref().unwrap(), + &ExecOutput { checkpoint: StageCheckpoint::new(expected_progress).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: runner.tx.table::().unwrap().len() as u64, total: total_transactions } - ), - done: false + ) } ); + assert!(!result.unwrap().is_done(first_input)); // Execute second time to completion runner.set_threshold(u64::MAX); @@ -336,8 +328,7 @@ mod tests { &ExecOutput { checkpoint: StageCheckpoint::new(previous_stage).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: total_transactions, total: total_transactions } - ), - done: true + ) } ); diff --git a/crates/stages/src/stages/total_difficulty.rs b/crates/stages/src/stages/total_difficulty.rs index 41afa821300e..9562016136b8 100644 --- a/crates/stages/src/stages/total_difficulty.rs +++ b/crates/stages/src/stages/total_difficulty.rs @@ -55,11 +55,8 @@ impl Stage for TotalDifficultyStage { input: ExecInput, ) -> Result { let tx = provider.tx_ref(); - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); + let range = input.next_block_range_with_threshold(self.commit_threshold); let (start_block, end_block) = range.clone().into_inner(); debug!(target: "sync::stages::total_difficulty", start_block, end_block, "Commencing sync"); @@ -91,7 +88,6 @@ impl Stage for TotalDifficultyStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), - done: is_final_range, }) } @@ -101,7 +97,7 @@ impl Stage for TotalDifficultyStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); + let (_, unwind_to) = input.unwind_block_range_with_threshold(self.commit_threshold); provider.unwind_table_by_num::(unwind_to)?; @@ -133,12 +129,10 @@ mod tests { use super::*; use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, - TestTransaction, UnwindStageTestRunner, + ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, + UnwindStageTestRunner, }; - stage_test_suite_ext!(TotalDifficultyTestRunner, total_difficulty); - #[tokio::test] async fn execute_with_intermediate_commit() { let threshold = 50; @@ -166,9 +160,10 @@ mod tests { processed, total })) - }, done: false }) if block_number == expected_progress && processed == 1 + threshold && + }}) if block_number == expected_progress && processed == 1 + threshold && total == runner.tx.table::().unwrap().len() as u64 ); + assert!(!result.unwrap().is_done(first_input)); // Execute second time let second_input = ExecInput { @@ -184,7 +179,7 @@ mod tests { processed, total })) - }, done: true }) if block_number == previous_stage && processed == total && + }}) if block_number == previous_stage && processed == total && total == runner.tx.table::().unwrap().len() as u64 ); diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index f379598d951e..acff8c05c770 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -54,10 +54,7 @@ impl Stage for TransactionLookupStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - if input.target_reached() { - return Ok(ExecOutput::done(input.checkpoint())) - } - let (tx_range, block_range, is_final_range) = + let (tx_range, block_range) = input.next_block_range_with_transaction_threshold(provider, self.commit_threshold)?; let end_block = *block_range.end(); @@ -138,7 +135,6 @@ impl Stage for TransactionLookupStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), - done: is_final_range, }) } @@ -149,7 +145,7 @@ impl Stage for TransactionLookupStage { input: UnwindInput, ) -> Result { let tx = provider.tx_ref(); - let (range, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); + let (range, unwind_to) = input.unwind_block_range_with_threshold(self.commit_threshold); // Cursors to unwind tx hash to number let mut body_cursor = tx.cursor_read::()?; @@ -192,16 +188,13 @@ fn stage_checkpoint( mod tests { use super::*; use crate::test_utils::{ - stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, - TestTransaction, UnwindStageTestRunner, + ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, + UnwindStageTestRunner, }; use assert_matches::assert_matches; use reth_interfaces::test_utils::generators::{random_block, random_block_range}; use reth_primitives::{stage::StageUnitCheckpoint, BlockNumber, SealedBlock, H256}; - // Implement stage test suite. - stage_test_suite_ext!(TransactionLookupTestRunner, transaction_lookup); - #[tokio::test] async fn execute_single_transaction_lookup() { let (previous_stage, stage_progress) = (500, 100); @@ -234,7 +227,7 @@ mod tests { processed, total })) - }, done: true }) if block_number == previous_stage && processed == total && + }}) if block_number == previous_stage && processed == total && total == runner.tx.table::().unwrap().len() as u64 ); @@ -273,17 +266,17 @@ mod tests { .unwrap_or(previous_stage); assert_matches!(result, Ok(_)); assert_eq!( - result.unwrap(), - ExecOutput { + result.as_ref().unwrap(), + &ExecOutput { checkpoint: StageCheckpoint::new(expected_progress).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: runner.tx.table::().unwrap().len() as u64, total: total_txs } - ), - done: false + ) } ); + assert!(!result.unwrap().is_done(first_input)); // Execute second time to completion runner.set_threshold(u64::MAX); @@ -298,8 +291,7 @@ mod tests { &ExecOutput { checkpoint: StageCheckpoint::new(previous_stage).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: total_txs, total: total_txs } - ), - done: true + ) } ); diff --git a/crates/stages/src/test_utils/macros.rs b/crates/stages/src/test_utils/macros.rs index f691d13711b1..533d6584756d 100644 --- a/crates/stages/src/test_utils/macros.rs +++ b/crates/stages/src/test_utils/macros.rs @@ -42,8 +42,8 @@ macro_rules! stage_test_suite { let result = rx.await.unwrap(); assert_matches::assert_matches!( result, - Ok(ExecOutput { done, checkpoint }) - if done && checkpoint.block_number == previous_stage + Ok(ref output @ ExecOutput { checkpoint }) + if output.is_done(input) && checkpoint.block_number == previous_stage ); // Validate the stage execution @@ -94,8 +94,8 @@ macro_rules! stage_test_suite { let result = rx.await.unwrap(); assert_matches::assert_matches!( result, - Ok(ExecOutput { done, checkpoint }) - if done && checkpoint.block_number == previous_stage + Ok(ref output @ ExecOutput { checkpoint }) + if output.is_done(execute_input) && checkpoint.block_number == previous_stage ); assert_matches::assert_matches!(runner.validate_execution(execute_input, result.ok()),Ok(_), "execution validation"); @@ -113,7 +113,8 @@ macro_rules! stage_test_suite { // Assert the successful unwind result assert_matches::assert_matches!( rx, - Ok(UnwindOutput { checkpoint }) if checkpoint.block_number == unwind_input.unwind_to + Ok(output @ UnwindOutput { checkpoint }) + if output.is_done(unwind_input) && checkpoint.block_number == unwind_input.unwind_to ); // Validate the stage unwind @@ -123,46 +124,4 @@ macro_rules! stage_test_suite { }; } -// `execute_already_reached_target` is not suitable for the headers stage thus -// included in the test suite extension -macro_rules! stage_test_suite_ext { - ($runner:ident, $name:ident) => { - crate::test_utils::stage_test_suite!($runner, $name); - - paste::item! { - /// Check that the execution is short-circuited if the target was already reached. - #[tokio::test] - async fn [< execute_already_reached_target_ $name>] () { - let stage_progress = 1000; - - // Set up the runner - let mut runner = $runner::default(); - let input = crate::stage::ExecInput { - target: Some(stage_progress), - checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(stage_progress)), - }; - let seed = runner.seed_execution(input).expect("failed to seed"); - - // Run stage execution - let rx = runner.execute(input); - - // Run `after_execution` hook - runner.after_execution(seed).await.expect("failed to run after execution hook"); - - // Assert the successful result - let result = rx.await.unwrap(); - assert_matches::assert_matches!( - result, - Ok(ExecOutput { done, checkpoint }) - if done && checkpoint.block_number == stage_progress - ); - - // Validate the stage execution - assert_matches::assert_matches!(runner.validate_execution(input, result.ok()),Ok(_), "execution validation"); - } - } - }; -} - pub(crate) use stage_test_suite; -pub(crate) use stage_test_suite_ext; diff --git a/crates/stages/src/test_utils/stage.rs b/crates/stages/src/test_utils/stage.rs index 028b74218fcb..231ce3880e00 100644 --- a/crates/stages/src/test_utils/stage.rs +++ b/crates/stages/src/test_utils/stage.rs @@ -1,19 +1,49 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::database::Database; -use reth_primitives::stage::StageId; -use reth_provider::DatabaseProviderRW; +use reth_primitives::{ + stage::{StageCheckpoint, StageId}, + MAINNET, +}; +use reth_provider::{DatabaseProviderRW, ShareableDatabase}; use std::collections::VecDeque; #[derive(Debug)] pub struct TestStage { id: StageId, + checkpoint: Option, exec_outputs: VecDeque>, unwind_outputs: VecDeque>, } impl TestStage { pub fn new(id: StageId) -> Self { - Self { id, exec_outputs: VecDeque::new(), unwind_outputs: VecDeque::new() } + Self { + id, + checkpoint: None, + exec_outputs: VecDeque::new(), + unwind_outputs: VecDeque::new(), + } + } + + pub fn with_checkpoint( + mut self, + checkpoint: Option, + provider: DatabaseProviderRW<'_, DB>, + ) -> Self { + if let Some(checkpoint) = checkpoint { + provider + .save_stage_checkpoint(self.id, checkpoint) + .unwrap_or_else(|_| panic!("save stage {} checkpoint", self.id)) + } else { + provider + .delete_stage_checkpoint(self.id) + .unwrap_or_else(|_| panic!("delete stage {} checkpoint", self.id)) + } + + provider.commit().expect("provider commit"); + + self.checkpoint = checkpoint; + self } pub fn with_exec(mut self, exec_outputs: VecDeque>) -> Self { diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index a355a09e6394..ffe173763ab3 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1108,6 +1108,12 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } + /// Delete stage checkpoint. + pub fn delete_stage_checkpoint(&self, id: StageId) -> std::result::Result<(), DatabaseError> { + self.tx.delete::(id.to_string(), None)?; + Ok(()) + } + /// Get stage checkpoint progress. pub fn get_stage_checkpoint_progress( &self, From 790b44393271c398ab0367757f2f7784923ec05e Mon Sep 17 00:00:00 2001 From: Waylon Jepsen <57912727+0xJepsen@users.noreply.github.com> Date: Tue, 13 Jun 2023 06:12:37 -0600 Subject: [PATCH 008/216] Update transactions.rs (#3094) Co-authored-by: Matthias Seitz --- crates/rpc/rpc/src/eth/api/transactions.rs | 48 +++++++++++++++------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index d0b9bbe0c5b8..2612b48287cc 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -197,22 +197,42 @@ where f(state) } - async fn evm_env_at(&self, mut at: BlockId) -> EthResult<(CfgEnv, BlockEnv, BlockId)> { + async fn evm_env_at(&self, at: BlockId) -> EthResult<(CfgEnv, BlockEnv, BlockId)> { if at.is_pending() { - if let Some(pending) = self.client().pending_header()? { - let mut cfg = CfgEnv::default(); - let mut block_env = BlockEnv::default(); - self.client().fill_block_env_with_header(&mut block_env, &pending.header)?; - self.client().fill_cfg_env_with_header(&mut cfg, &pending.header)?; - return Ok((cfg, block_env, pending.hash.into())) - } - // No pending block, use latest - at = BlockId::Number(BlockNumberOrTag::Latest); + let header = if let Some(pending) = self.client().pending_header()? { + pending + } else { + // no pending block from the CL yet, so we use the latest block and modify the env + // values that we can + let mut latest = self + .client() + .latest_header()? + .ok_or_else(|| EthApiError::UnknownBlockNumber)?; + + // child block + latest.number += 1; + // assumed child block is in the next slot + latest.timestamp += 12; + // base fee of the child block + latest.base_fee_per_gas = latest.next_block_base_fee(); + + latest + }; + + let mut cfg = CfgEnv::default(); + let mut block_env = BlockEnv::default(); + self.client().fill_block_env_with_header(&mut block_env, &header)?; + self.client().fill_cfg_env_with_header(&mut cfg, &header)?; + return Ok((cfg, block_env, header.hash.into())) + } else { + // Use cached values if there is no pending block + let block_hash = self + .client() + .block_hash_for_id(at)? + .ok_or_else(|| EthApiError::UnknownBlockNumber)?; + let (cfg, env) = self.cache().get_evm_env(block_hash).await?; + Ok((cfg, env, block_hash.into())) } - let block_hash = - self.client().block_hash_for_id(at)?.ok_or_else(|| EthApiError::UnknownBlockNumber)?; - let (cfg, env) = self.cache().get_evm_env(block_hash).await?; - Ok((cfg, env, block_hash.into())) } async fn evm_env_for_raw_block(&self, header: &Header) -> EthResult<(CfgEnv, BlockEnv)> { From e15805d9d1cb33a656bc51b3e7656df5ce7a6598 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Tue, 13 Jun 2023 14:42:13 +0100 Subject: [PATCH 009/216] chore: share provider on `find_canonical_header` (#3122) --- crates/blockchain-tree/src/blockchain_tree.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 034275b33afe..0ed28c89bfe4 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -358,8 +358,8 @@ impl BlockchainTree // https://github.com/paradigmxyz/reth/issues/1713 let (block_status, chain) = { - let db = self.externals.database(); - let provider = db + let factory = self.externals.database(); + let provider = factory .provider() .map_err(|err| InsertBlockError::new(block.block.clone(), err.into()))?; @@ -829,9 +829,12 @@ impl BlockchainTree // canonical, but in the db. If it is in a sidechain, it is not canonical. If it is not in // the db, then it is not canonical. + let factory = self.externals.database(); + let provider = factory.provider()?; + let mut header = None; if let Some(num) = self.block_indices.get_canonical_block_number(hash) { - header = self.externals.database().provider()?.header_by_number(num)?; + header = provider.header_by_number(num)?; } if header.is_none() && self.is_block_hash_inside_chain(*hash) { @@ -839,7 +842,7 @@ impl BlockchainTree } if header.is_none() { - header = self.externals.database().provider()?.header(hash)? + header = provider.header(hash)? } Ok(header.map(|header| header.seal(*hash))) From c928f2c18335ef5472b2487f9ba562dd5a790c47 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 13 Jun 2023 16:22:36 +0200 Subject: [PATCH 010/216] test: fix failing rpc test (#3127) --- crates/rpc/rpc-builder/tests/it/http.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 70b22fbe9458..baab8a3e577f 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -157,7 +157,9 @@ where TraceApiClient::trace_raw_transaction(client, Bytes::default(), HashSet::default(), None) .await .unwrap_err(); - TraceApiClient::trace_call_many(client, vec![], None).await.unwrap(); + TraceApiClient::trace_call_many(client, vec![], Some(BlockNumberOrTag::Latest.into())) + .await + .unwrap(); TraceApiClient::replay_transaction(client, H256::default(), HashSet::default()) .await .err() From 225e05267b897376688b9a87c42a773e29be7f17 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 13 Jun 2023 20:08:02 +0300 Subject: [PATCH 011/216] fix(cli): commit on stage run unwind (#3129) --- bin/reth/src/stage/run.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index a47c467ba79f..9f3ade81c4a1 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -230,6 +230,11 @@ impl Command { while unwind.checkpoint.block_number > self.from { let unwind_output = unwind_stage.unwind(&mut provider_rw, unwind).await?; unwind.checkpoint = unwind_output.checkpoint; + + if self.commit { + provider_rw.commit()?; + provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + } } } From 39c6b22829a3c7f420fba4c72f48ec340c73c1cf Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Tue, 13 Jun 2023 19:17:16 +0200 Subject: [PATCH 012/216] feat(rpc): rename `Client` generics to `Provider` (#3126) Co-authored-by: Matthias Seitz --- bin/reth/src/args/rpc_server_args.rs | 26 ++-- crates/rpc/rpc-api/src/engine.rs | 4 +- crates/rpc/rpc-builder/src/auth.rs | 23 ++-- crates/rpc/rpc-builder/src/eth.rs | 8 +- crates/rpc/rpc-builder/src/lib.rs | 137 +++++++++++--------- crates/rpc/rpc-builder/tests/it/utils.rs | 2 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 50 +++---- crates/rpc/rpc-testing-util/src/trace.rs | 6 +- crates/rpc/rpc/src/debug.rs | 40 +++--- crates/rpc/rpc/src/eth/api/block.rs | 20 +-- crates/rpc/rpc/src/eth/api/call.rs | 4 +- crates/rpc/rpc/src/eth/api/fees.rs | 12 +- crates/rpc/rpc/src/eth/api/mod.rs | 66 +++++----- crates/rpc/rpc/src/eth/api/server.rs | 4 +- crates/rpc/rpc/src/eth/api/sign.rs | 2 +- crates/rpc/rpc/src/eth/api/state.rs | 6 +- crates/rpc/rpc/src/eth/api/transactions.rs | 24 ++-- crates/rpc/rpc/src/eth/cache.rs | 50 +++---- crates/rpc/rpc/src/eth/filter.rs | 56 ++++---- crates/rpc/rpc/src/eth/gas_oracle.rs | 14 +- crates/rpc/rpc/src/eth/pubsub.rs | 53 ++++---- crates/rpc/rpc/src/trace.rs | 34 ++--- 22 files changed, 330 insertions(+), 311 deletions(-) diff --git a/bin/reth/src/args/rpc_server_args.rs b/bin/reth/src/args/rpc_server_args.rs index d1bb2f9c018f..fa16ee358863 100644 --- a/bin/reth/src/args/rpc_server_args.rs +++ b/bin/reth/src/args/rpc_server_args.rs @@ -237,9 +237,9 @@ impl RpcServerArgs { /// for the auth server that handles the `engine_` API that's accessed by the consensus /// layer. #[allow(clippy::too_many_arguments)] - pub async fn start_servers( + pub async fn start_servers( &self, - client: Client, + provider: Provider, pool: Pool, network: Network, executor: Tasks, @@ -248,7 +248,7 @@ impl RpcServerArgs { jwt_secret: JwtSecret, ) -> Result<(RpcServerHandle, AuthServerHandle), RpcError> where - Client: BlockProviderIdExt + Provider: BlockProviderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider @@ -267,7 +267,7 @@ impl RpcServerArgs { debug!(target: "reth::cli", http=?module_config.http(), ws=?module_config.ws(), "Using RPC module config"); let (rpc_modules, auth_module) = RpcModuleBuilder::default() - .with_client(client) + .with_provider(provider) .with_pool(pool) .with_network(network) .with_events(events) @@ -297,16 +297,16 @@ impl RpcServerArgs { } /// Convenience function for starting a rpc server with configs which extracted from cli args. - pub async fn start_rpc_server( + pub async fn start_rpc_server( &self, - client: Client, + provider: Provider, pool: Pool, network: Network, executor: Tasks, events: Events, ) -> Result where - Client: BlockProviderIdExt + Provider: BlockProviderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider @@ -319,7 +319,7 @@ impl RpcServerArgs { Events: CanonStateSubscriptions + Clone + 'static, { reth_rpc_builder::launch( - client, + provider, pool, network, self.transport_rpc_module_config(), @@ -331,17 +331,17 @@ impl RpcServerArgs { } /// Create Engine API server. - pub async fn start_auth_server( + pub async fn start_auth_server( &self, - client: Client, + provider: Provider, pool: Pool, network: Network, executor: Tasks, - engine_api: EngineApi, + engine_api: EngineApi, jwt_secret: JwtSecret, ) -> Result where - Client: BlockProviderIdExt + Provider: BlockProviderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider @@ -358,7 +358,7 @@ impl RpcServerArgs { ); reth_rpc_builder::auth::launch( - client, + provider, pool, network, executor, diff --git a/crates/rpc/rpc-api/src/engine.rs b/crates/rpc/rpc-api/src/engine.rs index 857e5634a4bb..be4825a5b6f0 100644 --- a/crates/rpc/rpc-api/src/engine.rs +++ b/crates/rpc/rpc-api/src/engine.rs @@ -47,7 +47,7 @@ pub trait EngineApi { /// Caution: This should not return the `withdrawals` field /// /// Note: - /// > Client software MAY stop the corresponding build process after serving this call. + /// > Provider software MAY stop the corresponding build process after serving this call. #[method(name = "getPayloadV1")] async fn get_payload_v1(&self, payload_id: PayloadId) -> RpcResult; @@ -55,7 +55,7 @@ pub trait EngineApi { /// /// Returns the most recent version of the payload that is available in the corresponding /// payload build process at the time of receiving this call. Note: - /// > Client software MAY stop the corresponding build process after serving this call. + /// > Provider software MAY stop the corresponding build process after serving this call. #[method(name = "getPayloadV2")] async fn get_payload_v2(&self, payload_id: PayloadId) -> RpcResult; diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index ac9bf3df78bf..11fb8185c86d 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -27,8 +27,8 @@ use std::{ /// Configure and launch a _standalone_ auth server with `engine` and a _new_ `eth` namespace. #[allow(clippy::too_many_arguments)] -pub async fn launch( - client: Client, +pub async fn launch( + provider: Provider, pool: Pool, network: Network, executor: Tasks, @@ -37,7 +37,7 @@ pub async fn launch( secret: JwtSecret, ) -> Result where - Client: BlockProviderIdExt + Provider: BlockProviderIdExt + ReceiptProviderIdExt + HeaderProvider + StateProviderFactory @@ -51,10 +51,11 @@ where EngineApi: EngineApiServer, { // spawn a new cache task - let eth_cache = EthStateCache::spawn_with(client.clone(), Default::default(), executor.clone()); - let gas_oracle = GasPriceOracle::new(client.clone(), Default::default(), eth_cache.clone()); + let eth_cache = + EthStateCache::spawn_with(provider.clone(), Default::default(), executor.clone()); + let gas_oracle = GasPriceOracle::new(provider.clone(), Default::default(), eth_cache.clone()); let eth_api = EthApi::with_spawner( - client.clone(), + provider.clone(), pool.clone(), network, eth_cache.clone(), @@ -62,7 +63,7 @@ where Box::new(executor.clone()), ); let eth_filter = EthFilter::new( - client, + provider, pool, eth_cache.clone(), DEFAULT_MAX_LOGS_IN_RESPONSE, @@ -72,15 +73,15 @@ where } /// Configure and launch a _standalone_ auth server with existing EthApi implementation. -pub async fn launch_with_eth_api( - eth_api: EthApi, - eth_filter: EthFilter, +pub async fn launch_with_eth_api( + eth_api: EthApi, + eth_filter: EthFilter, engine_api: EngineApi, socket_addr: SocketAddr, secret: JwtSecret, ) -> Result where - Client: BlockProviderIdExt + Provider: BlockProviderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider diff --git a/crates/rpc/rpc-builder/src/eth.rs b/crates/rpc/rpc-builder/src/eth.rs index 1f62fd2a123f..907740b76958 100644 --- a/crates/rpc/rpc-builder/src/eth.rs +++ b/crates/rpc/rpc-builder/src/eth.rs @@ -15,15 +15,15 @@ pub(crate) const DEFAULT_MAX_TRACING_REQUESTS: u32 = 25; /// All handlers for the `eth` namespace #[derive(Debug, Clone)] -pub struct EthHandlers { +pub struct EthHandlers { /// Main `eth_` request handler - pub api: EthApi, + pub api: EthApi, /// The async caching layer used by the eth handlers pub cache: EthStateCache, /// Polling based filter handler available on all transports - pub filter: EthFilter, + pub filter: EthFilter, /// Handler for subscriptions only available for transports that support it (ws, ipc) - pub pubsub: EthPubSub, + pub pubsub: EthPubSub, } /// Additional config values for the eth namespace diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index a3e6b6b28e50..c82573e7a5dd 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -29,9 +29,9 @@ //! use reth_rpc_builder::{RethRpcModule, RpcModuleBuilder, RpcServerConfig, ServerBuilder, TransportRpcModuleConfig}; //! use reth_tasks::TokioTaskExecutor; //! use reth_transaction_pool::TransactionPool; -//! pub async fn launch(client: Client, pool: Pool, network: Network, events: Events) +//! pub async fn launch(provider: Provider, pool: Pool, network: Network, events: Events) //! where -//! Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, +//! Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, //! Pool: TransactionPool + Clone + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static, @@ -43,7 +43,7 @@ //! RethRpcModule::Eth, //! RethRpcModule::Web3, //! ]); -//! let transport_modules = RpcModuleBuilder::new(client, pool, network, TokioTaskExecutor::default(), events).build(transports); +//! let transport_modules = RpcModuleBuilder::new(provider, pool, network, TokioTaskExecutor::default(), events).build(transports); //! let handle = RpcServerConfig::default() //! .with_http(ServerBuilder::default()) //! .start(transport_modules) @@ -65,9 +65,9 @@ //! use reth_transaction_pool::TransactionPool; //! use reth_rpc_api::EngineApiServer; //! use reth_rpc_builder::auth::AuthServerConfig; -//! pub async fn launch(client: Client, pool: Pool, network: Network, events: Events, engine_api: EngineApi) +//! pub async fn launch(provider: Provider, pool: Pool, network: Network, events: Events, engine_api: EngineApi) //! where -//! Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, +//! Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, //! Pool: TransactionPool + Clone + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static, @@ -80,7 +80,7 @@ //! RethRpcModule::Eth, //! RethRpcModule::Web3, //! ]); -//! let builder = RpcModuleBuilder::new(client, pool, network, TokioTaskExecutor::default(), events); +//! let builder = RpcModuleBuilder::new(provider, pool, network, TokioTaskExecutor::default(), events); //! //! // configure the server modules //! let (modules, auth_module) = builder.build_with_auth_server(transports, engine_api); @@ -154,8 +154,8 @@ pub use jsonrpsee::server::ServerBuilder; pub use reth_ipc::server::{Builder as IpcServerBuilder, Endpoint}; /// Convenience function for starting a server in one step. -pub async fn launch( - client: Client, +pub async fn launch( + provider: Provider, pool: Pool, network: Network, module_config: impl Into, @@ -164,7 +164,7 @@ pub async fn launch( events: Events, ) -> Result where - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, @@ -172,7 +172,7 @@ where { let module_config = module_config.into(); let server_config = server_config.into(); - RpcModuleBuilder::new(client, pool, network, executor, events) + RpcModuleBuilder::new(provider, pool, network, executor, events) .build(module_config) .start_server(server_config) .await @@ -182,9 +182,9 @@ where /// /// This is the main entrypoint for up RPC servers. #[derive(Debug, Clone)] -pub struct RpcModuleBuilder { - /// The Client type to when creating all rpc handlers - client: Client, +pub struct RpcModuleBuilder { + /// The Provider type to when creating all rpc handlers + provider: Provider, /// The Pool type to when creating all rpc handlers pool: Pool, /// The Network type to when creating all rpc handlers @@ -197,67 +197,73 @@ pub struct RpcModuleBuilder { // === impl RpcBuilder === -impl RpcModuleBuilder { +impl + RpcModuleBuilder +{ /// Create a new instance of the builder pub fn new( - client: Client, + provider: Provider, pool: Pool, network: Network, executor: Tasks, events: Events, ) -> Self { - Self { client, pool, network, executor, events } + Self { provider, pool, network, executor, events } } - /// Configure the client instance. - pub fn with_client(self, client: C) -> RpcModuleBuilder + /// Configure the provider instance. + pub fn with_provider

(self, provider: P) -> RpcModuleBuilder where - C: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + P: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, { let Self { pool, network, executor, events, .. } = self; - RpcModuleBuilder { client, network, pool, executor, events } + RpcModuleBuilder { provider, network, pool, executor, events } } /// Configure the transaction pool instance. - pub fn with_pool

(self, pool: P) -> RpcModuleBuilder + pub fn with_pool

(self, pool: P) -> RpcModuleBuilder where P: TransactionPool + 'static, { - let Self { client, network, executor, events, .. } = self; - RpcModuleBuilder { client, network, pool, executor, events } + let Self { provider, network, executor, events, .. } = self; + RpcModuleBuilder { provider, network, pool, executor, events } } /// Configure the network instance. - pub fn with_network(self, network: N) -> RpcModuleBuilder + pub fn with_network(self, network: N) -> RpcModuleBuilder where N: NetworkInfo + Peers + 'static, { - let Self { client, pool, executor, events, .. } = self; - RpcModuleBuilder { client, network, pool, executor, events } + let Self { provider, pool, executor, events, .. } = self; + RpcModuleBuilder { provider, network, pool, executor, events } } /// Configure the task executor to use for additional tasks. - pub fn with_executor(self, executor: T) -> RpcModuleBuilder + pub fn with_executor( + self, + executor: T, + ) -> RpcModuleBuilder where T: TaskSpawner + 'static, { - let Self { pool, network, client, events, .. } = self; - RpcModuleBuilder { client, network, pool, executor, events } + let Self { pool, network, provider, events, .. } = self; + RpcModuleBuilder { provider, network, pool, executor, events } } /// Configure the event subscriber instance - pub fn with_events(self, events: E) -> RpcModuleBuilder + pub fn with_events(self, events: E) -> RpcModuleBuilder where E: CanonStateSubscriptions + 'static, { - let Self { client, pool, executor, network, .. } = self; - RpcModuleBuilder { client, network, pool, executor, events } + let Self { provider, pool, executor, network, .. } = self; + RpcModuleBuilder { provider, network, pool, executor, events } } } -impl RpcModuleBuilder +impl + RpcModuleBuilder where - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, @@ -278,12 +284,12 @@ where { let mut modules = TransportRpcModules::default(); - let Self { client, pool, network, executor, events } = self; + let Self { provider, pool, network, executor, events } = self; let TransportRpcModuleConfig { http, ws, ipc, config } = module_config.clone(); let mut registry = RethModuleRegistry::new( - client, + provider, pool, network, executor, @@ -308,13 +314,13 @@ where pub fn build(self, module_config: TransportRpcModuleConfig) -> TransportRpcModules<()> { let mut modules = TransportRpcModules::default(); - let Self { client, pool, network, executor, events } = self; + let Self { provider, pool, network, executor, events } = self; if !module_config.is_empty() { let TransportRpcModuleConfig { http, ws, ipc, config } = module_config.clone(); let mut registry = RethModuleRegistry::new( - client, + provider, pool, network, executor, @@ -464,9 +470,9 @@ impl RpcModuleSelection { /// Note: This will always create new instance of the module handlers and is therefor only /// recommended for launching standalone transports. If multiple transports need to be /// configured it's recommended to use the [RpcModuleBuilder]. - pub fn standalone_module( + pub fn standalone_module( &self, - client: Client, + provider: Provider, pool: Pool, network: Network, executor: Tasks, @@ -474,14 +480,15 @@ impl RpcModuleSelection { config: RpcModuleConfig, ) -> RpcModule<()> where - Client: + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, Events: CanonStateSubscriptions + Clone + 'static, { - let mut registry = RethModuleRegistry::new(client, pool, network, executor, events, config); + let mut registry = + RethModuleRegistry::new(provider, pool, network, executor, events, config); registry.module_for(self) } @@ -599,8 +606,8 @@ impl Serialize for RethRpcModule { } /// A Helper type the holds instances of the configured modules. -pub struct RethModuleRegistry { - client: Client, +pub struct RethModuleRegistry { + provider: Provider, pool: Pool, network: Network, executor: Tasks, @@ -608,7 +615,7 @@ pub struct RethModuleRegistry { /// Additional settings for handlers. config: RpcModuleConfig, /// Holds a clone of all the eth namespace handlers - eth: Option>, + eth: Option>, /// to put trace calls behind semaphore tracing_call_guard: TracingCallGuard, /// Contains the [Methods] of a module @@ -617,12 +624,12 @@ pub struct RethModuleRegistry { // === impl RethModuleRegistry === -impl - RethModuleRegistry +impl + RethModuleRegistry { /// Creates a new, empty instance. pub fn new( - client: Client, + provider: Provider, pool: Pool, network: Network, executor: Tasks, @@ -630,7 +637,7 @@ impl config: RpcModuleConfig, ) -> Self { Self { - client, + provider, pool, network, eth: None, @@ -657,7 +664,8 @@ impl } } -impl RethModuleRegistry +impl + RethModuleRegistry where Network: NetworkInfo + Peers + Clone + 'static, { @@ -676,9 +684,10 @@ where } } -impl RethModuleRegistry +impl + RethModuleRegistry where - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, @@ -697,7 +706,7 @@ where self.modules.insert( RethRpcModule::Debug, DebugApi::new( - self.client.clone(), + self.provider.clone(), eth_api, Box::new(self.executor.clone()), self.tracing_call_guard.clone(), @@ -714,7 +723,7 @@ where self.modules.insert( RethRpcModule::Trace, TraceApi::new( - self.client.clone(), + self.provider.clone(), eth.api.clone(), eth.cache, Box::new(self.executor.clone()), @@ -800,7 +809,7 @@ where AdminApi::new(self.network.clone()).into_rpc().into() } RethRpcModule::Debug => DebugApi::new( - self.client.clone(), + self.provider.clone(), eth_api.clone(), Box::new(self.executor.clone()), self.tracing_call_guard.clone(), @@ -819,7 +828,7 @@ where NetApi::new(self.network.clone(), eth_api.clone()).into_rpc().into() } RethRpcModule::Trace => TraceApi::new( - self.client.clone(), + self.provider.clone(), eth_api.clone(), eth_cache.clone(), Box::new(self.executor.clone()), @@ -856,16 +865,16 @@ where /// Creates the [EthHandlers] type the first time this is called. fn with_eth(&mut self, f: F) -> R where - F: FnOnce(&EthHandlers) -> R, + F: FnOnce(&EthHandlers) -> R, { if self.eth.is_none() { let cache = EthStateCache::spawn_with( - self.client.clone(), + self.provider.clone(), self.config.eth.cache.clone(), self.executor.clone(), ); let gas_oracle = GasPriceOracle::new( - self.client.clone(), + self.provider.clone(), self.config.eth.gas_oracle.clone(), cache.clone(), ); @@ -880,7 +889,7 @@ where let executor = Box::new(self.executor.clone()); let api = EthApi::with_spawner( - self.client.clone(), + self.provider.clone(), self.pool.clone(), self.network.clone(), cache.clone(), @@ -888,7 +897,7 @@ where executor.clone(), ); let filter = EthFilter::new( - self.client.clone(), + self.provider.clone(), self.pool.clone(), cache.clone(), self.config.eth.max_logs_per_response, @@ -896,7 +905,7 @@ where ); let pubsub = EthPubSub::with_spawner( - self.client.clone(), + self.provider.clone(), self.pool.clone(), self.events.clone(), self.network.clone(), @@ -910,12 +919,12 @@ where } /// Returns the configured [EthHandlers] or creates it if it does not exist yet - fn eth_handlers(&mut self) -> EthHandlers { + fn eth_handlers(&mut self) -> EthHandlers { self.with_eth(|handlers| handlers.clone()) } /// Returns the configured [EthApi] or creates it if it does not exist yet - fn eth_api(&mut self) -> EthApi { + fn eth_api(&mut self) -> EthApi { self.with_eth(|handlers| handlers.api.clone()) } } diff --git a/crates/rpc/rpc-builder/tests/it/utils.rs b/crates/rpc/rpc-builder/tests/it/utils.rs index 9773d321322e..1281a8ae07df 100644 --- a/crates/rpc/rpc-builder/tests/it/utils.rs +++ b/crates/rpc/rpc-builder/tests/it/utils.rs @@ -99,7 +99,7 @@ pub fn test_rpc_builder() -> RpcModuleBuilder< TestCanonStateSubscriptions, > { RpcModuleBuilder::default() - .with_client(NoopProvider::default()) + .with_provider(NoopProvider::default()) .with_pool(testing_pool()) .with_network(NoopNetwork) .with_executor(TokioTaskExecutor::default()) diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 424658972a20..20a085f0aa0b 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -23,9 +23,9 @@ const MAX_PAYLOAD_BODIES_LIMIT: u64 = 1024; /// The Engine API implementation that grants the Consensus layer access to data and /// functions in the Execution layer that are crucial for the consensus process. -pub struct EngineApi { - /// The client to interact with the chain. - client: Client, +pub struct EngineApi { + /// The provider to interact with the chain. + provider: Provider, /// Consensus configuration chain_spec: Arc, /// The channel to send messages to the beacon consensus engine. @@ -34,18 +34,18 @@ pub struct EngineApi { payload_store: PayloadStore, } -impl EngineApi +impl EngineApi where - Client: HeaderProvider + BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: HeaderProvider + BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, { /// Create new instance of [EngineApi]. pub fn new( - client: Client, + provider: Provider, chain_spec: Arc, beacon_consensus: BeaconConsensusEngineHandle, payload_store: PayloadStore, ) -> Self { - Self { client, chain_spec, beacon_consensus, payload_store } + Self { provider, chain_spec, beacon_consensus, payload_store } } /// See also @@ -123,7 +123,7 @@ where /// Caution: This should not return the `withdrawals` field /// /// Note: - /// > Client software MAY stop the corresponding build process after serving this call. + /// > Provider software MAY stop the corresponding build process after serving this call. pub async fn get_payload_v1(&self, payload_id: PayloadId) -> EngineApiResult { Ok(self .payload_store @@ -139,7 +139,7 @@ where /// See also /// /// Note: - /// > Client software MAY stop the corresponding build process after serving this call. + /// > Provider software MAY stop the corresponding build process after serving this call. async fn get_payload_v2( &self, payload_id: PayloadId, @@ -180,7 +180,7 @@ where let end = start.saturating_add(count); for num in start..end { let block = self - .client + .provider .block(BlockHashOrNumber::Number(num)) .map_err(|err| EngineApiError::Internal(Box::new(err)))?; result.push(block.map(Into::into)); @@ -202,7 +202,7 @@ where let mut result = Vec::with_capacity(hashes.len()); for hash in hashes { let block = self - .client + .provider .block(BlockHashOrNumber::Hash(hash)) .map_err(|err| EngineApiError::Internal(Box::new(err)))?; result.push(block.map(Into::into)); @@ -249,7 +249,7 @@ where // Attempt to look up terminal block hash let local_hash = self - .client + .provider .block_hash(terminal_block_number.as_u64()) .map_err(|err| EngineApiError::Internal(Box::new(err)))?; @@ -302,9 +302,9 @@ where } #[async_trait] -impl EngineApiServer for EngineApi +impl EngineApiServer for EngineApi where - Client: HeaderProvider + BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: HeaderProvider + BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, { /// Handler for `engine_newPayloadV1` /// See also @@ -355,7 +355,7 @@ where /// Caution: This should not return the `withdrawals` field /// /// Note: - /// > Client software MAY stop the corresponding build process after serving this call. + /// > Provider software MAY stop the corresponding build process after serving this call. async fn get_payload_v1(&self, payload_id: PayloadId) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_getPayloadV1"); Ok(EngineApi::get_payload_v1(self, payload_id).await?) @@ -369,7 +369,7 @@ where /// See also /// /// Note: - /// > Client software MAY stop the corresponding build process after serving this call. + /// > Provider software MAY stop the corresponding build process after serving this call. async fn get_payload_v2(&self, payload_id: PayloadId) -> RpcResult { trace!(target: "rpc::engine", "Serving engine_getPayloadV2"); Ok(EngineApi::get_payload_v2(self, payload_id).await?) @@ -424,7 +424,7 @@ where } } -impl std::fmt::Debug for EngineApi { +impl std::fmt::Debug for EngineApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("EngineApi").finish_non_exhaustive() } @@ -444,22 +444,22 @@ mod tests { fn setup_engine_api() -> (EngineApiTestHandle, EngineApi>) { let chain_spec: Arc = MAINNET.clone(); - let client = Arc::new(MockEthProvider::default()); + let provider = Arc::new(MockEthProvider::default()); let payload_store = spawn_test_payload_service(); let (to_engine, engine_rx) = unbounded_channel(); let api = EngineApi::new( - client.clone(), + provider.clone(), chain_spec.clone(), BeaconConsensusEngineHandle::new(to_engine), payload_store.into(), ); - let handle = EngineApiTestHandle { chain_spec, client, from_api: engine_rx }; + let handle = EngineApiTestHandle { chain_spec, provider, from_api: engine_rx }; (handle, api) } struct EngineApiTestHandle { chain_spec: Arc, - client: Arc, + provider: Arc, from_api: UnboundedReceiver, } @@ -511,7 +511,7 @@ mod tests { let (start, count) = (1, 10); let blocks = random_block_range(start..=start + count - 1, H256::default(), 0..2); - handle.client.extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.unseal()))); + handle.provider.extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.unseal()))); let expected = blocks.iter().cloned().map(|b| Some(b.unseal().into())).collect::>(); @@ -530,7 +530,7 @@ mod tests { // Insert only blocks in ranges 1-25 and 50-75 let first_missing_range = 26..=50; let second_missing_range = 76..=100; - handle.client.extend_blocks( + handle.provider.extend_blocks( blocks .iter() .filter(|b| { @@ -611,7 +611,7 @@ mod tests { ); // Add block and to provider local store and test for mismatch - handle.client.add_block( + handle.provider.add_block( execution_terminal_block.hash(), execution_terminal_block.clone().unseal(), ); @@ -638,7 +638,7 @@ mod tests { terminal_block_number: terminal_block_number.into(), }; - handle.client.add_block(terminal_block.hash(), terminal_block.unseal()); + handle.provider.add_block(terminal_block.hash(), terminal_block.unseal()); let config = api.exchange_transition_configuration(transition_config.clone()).await.unwrap(); diff --git a/crates/rpc/rpc-testing-util/src/trace.rs b/crates/rpc/rpc-testing-util/src/trace.rs index cdf6ad81a808..d13a55971cc5 100644 --- a/crates/rpc/rpc-testing-util/src/trace.rs +++ b/crates/rpc/rpc-testing-util/src/trace.rs @@ -17,8 +17,8 @@ pub type TraceBlockResult = Result<(Vec, BlockId), (R /// An extension trait for the Trace API. #[async_trait::async_trait] pub trait TraceApiExt { - /// The client type that is used to make the requests. - type Client; + /// The provider type that is used to make the requests. + type Provider; /// Returns a new stream that yields the traces for the given blocks. /// @@ -39,7 +39,7 @@ pub trait TraceApiExt { #[async_trait::async_trait] impl TraceApiExt for T { - type Client = T; + type Provider = T; fn trace_block_buffered(&self, params: I, n: usize) -> TraceBlockStream<'_> where diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 6d0f84548300..a3116a26559c 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -35,31 +35,31 @@ use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit}; /// `debug` API implementation. /// /// This type provides the functionality for handling `debug` related requests. -pub struct DebugApi { - inner: Arc>, +pub struct DebugApi { + inner: Arc>, } // === impl DebugApi === -impl DebugApi { +impl DebugApi { /// Create a new instance of the [DebugApi] pub fn new( - client: Client, + provider: Provider, eth: Eth, task_spawner: Box, tracing_call_guard: TracingCallGuard, ) -> Self { let inner = - Arc::new(DebugApiInner { client, eth_api: eth, task_spawner, tracing_call_guard }); + Arc::new(DebugApiInner { provider, eth_api: eth, task_spawner, tracing_call_guard }); Self { inner } } } // === impl DebugApi === -impl DebugApi +impl DebugApi where - Client: BlockProviderIdExt + HeaderProvider + 'static, + Provider: BlockProviderIdExt + HeaderProvider + 'static, Eth: EthTransactions + 'static, { /// Executes the future on a new blocking task. @@ -171,7 +171,7 @@ where ) -> EthResult> { let block_hash = self .inner - .client + .provider .block_hash_for_id(block_id)? .ok_or_else(|| EthApiError::UnknownBlockNumber)?; @@ -324,23 +324,23 @@ where } #[async_trait] -impl DebugApiServer for DebugApi +impl DebugApiServer for DebugApi where - Client: BlockProviderIdExt + HeaderProvider + 'static, + Provider: BlockProviderIdExt + HeaderProvider + 'static, Eth: EthApiSpec + 'static, { /// Handler for `debug_getRawHeader` async fn raw_header(&self, block_id: BlockId) -> RpcResult { let header = match block_id { - BlockId::Hash(hash) => self.inner.client.header(&hash.into()).to_rpc_result()?, + BlockId::Hash(hash) => self.inner.provider.header(&hash.into()).to_rpc_result()?, BlockId::Number(number_or_tag) => { let number = self .inner - .client + .provider .convert_block_number(number_or_tag) .to_rpc_result()? .ok_or_else(|| internal_rpc_err("Pending block not supported".to_string()))?; - self.inner.client.header_by_number(number).to_rpc_result()? + self.inner.provider.header_by_number(number).to_rpc_result()? } }; @@ -354,7 +354,7 @@ where /// Handler for `debug_getRawBlock` async fn raw_block(&self, block_id: BlockId) -> RpcResult { - let block = self.inner.client.block_by_id(block_id).to_rpc_result()?; + let block = self.inner.provider.block_by_id(block_id).to_rpc_result()?; let mut res = Vec::new(); if let Some(mut block) = block { @@ -384,7 +384,7 @@ where /// Handler for `debug_getRawReceipts` async fn raw_receipts(&self, block_id: BlockId) -> RpcResult> { let receipts = - self.inner.client.receipts_by_block_id(block_id).to_rpc_result()?.unwrap_or_default(); + self.inner.provider.receipts_by_block_id(block_id).to_rpc_result()?.unwrap_or_default(); let mut all_receipts = Vec::with_capacity(receipts.len()); for receipt in receipts { @@ -464,21 +464,21 @@ where } } -impl std::fmt::Debug for DebugApi { +impl std::fmt::Debug for DebugApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DebugApi").finish_non_exhaustive() } } -impl Clone for DebugApi { +impl Clone for DebugApi { fn clone(&self) -> Self { Self { inner: Arc::clone(&self.inner) } } } -struct DebugApiInner { - /// The client that can interact with the chain. - client: Client, +struct DebugApiInner { + /// The provider that can interact with the chain. + provider: Provider, /// The implementation of `eth` API eth_api: Eth, // restrict the number of concurrent calls to tracing calls diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 218e180fa4cd..4c48c8e67435 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -8,9 +8,9 @@ use reth_primitives::BlockId; use reth_provider::{BlockProviderIdExt, EvmEnvProvider, StateProviderFactory}; use reth_rpc_types::{Block, Index, RichBlock}; -impl EthApi +impl EthApi where - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, { /// Returns the uncle headers of the given block /// @@ -20,7 +20,7 @@ where block_id: impl Into, ) -> EthResult>> { let block_id = block_id.into(); - Ok(self.client().ommers_by_id(block_id)?) + Ok(self.provider().ommers_by_id(block_id)?) } pub(crate) async fn ommer_by_block_and_index( @@ -32,9 +32,9 @@ where let uncles = if block_id.is_pending() { // Pending block can be fetched directly without need for caching - self.client().pending_block()?.map(|block| block.ommers) + self.provider().pending_block()?.map(|block| block.ommers) } else { - self.client().ommers_by_id(block_id)? + self.provider().ommers_by_id(block_id)? } .unwrap_or_default(); @@ -57,10 +57,10 @@ where if block_id.is_pending() { // Pending block can be fetched directly without need for caching - return Ok(self.client().pending_block()?.map(|block| block.body.len())) + return Ok(self.provider().pending_block()?.map(|block| block.body.len())) } - let block_hash = match self.client().block_hash_for_id(block_id)? { + let block_hash = match self.provider().block_hash_for_id(block_id)? { Some(block_hash) => block_hash, None => return Ok(None), }; @@ -77,10 +77,10 @@ where if block_id.is_pending() { // Pending block can be fetched directly without need for caching - return Ok(self.client().pending_block()?) + return Ok(self.provider().pending_block()?) } - let block_hash = match self.client().block_hash_for_id(block_id)? { + let block_hash = match self.provider().block_hash_for_id(block_id)? { Some(block_hash) => block_hash, None => return Ok(None), }; @@ -103,7 +103,7 @@ where }; let block_hash = block.hash; let total_difficulty = - self.client().header_td(&block_hash)?.ok_or(EthApiError::UnknownBlockNumber)?; + self.provider().header_td(&block_hash)?.ok_or(EthApiError::UnknownBlockNumber)?; let block = Block::from_block(block.into(), total_difficulty, full.into(), Some(block_hash))?; Ok(Some(block.into())) diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index bf1af26dd7d4..63d8d1d3d6ec 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -31,10 +31,10 @@ use tracing::trace; const MIN_TRANSACTION_GAS: u64 = 21_000u64; const MIN_CREATE_GAS: u64 = 53_000u64; -impl EthApi +impl EthApi where Pool: TransactionPool + Clone + 'static, - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + Send + Sync + 'static, { /// Estimate gas needed for execution of the `request` at the [BlockId]. diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs index 980961e73834..ebed8a30c11a 100644 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ b/crates/rpc/rpc/src/eth/api/fees.rs @@ -11,10 +11,10 @@ use reth_rpc_types::{FeeHistory, FeeHistoryCacheItem, TxGasAndReward}; use reth_transaction_pool::TransactionPool; use std::collections::BTreeMap; -impl EthApi +impl EthApi where Pool: TransactionPool + Clone + 'static, - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + Send + Sync + 'static, { /// Returns a suggestion for a gas price for legacy transactions. @@ -45,7 +45,7 @@ where return Ok(FeeHistory::default()) } - let Some(previous_to_end_block) = self.inner.client.block_number_for_id(newest_block)? else { return Err(EthApiError::UnknownBlockNumber)}; + let Some(previous_to_end_block) = self.inner.provider.block_number_for_id(newest_block)? else { return Err(EthApiError::UnknownBlockNumber)}; let end_block = previous_to_end_block + 1; if end_block < block_count { @@ -103,9 +103,9 @@ where { let header_range = start_block..=end_block; - let headers = self.inner.client.headers_range(header_range.clone())?; + let headers = self.inner.provider.headers_range(header_range.clone())?; let transactions_by_block = - self.inner.client.transactions_by_block_range(header_range)?; + self.inner.provider.transactions_by_block_range(header_range)?; let header_tx = headers.iter().zip(&transactions_by_block); @@ -168,7 +168,7 @@ where // get the first block in the range from the db let oldest_block_hash = - self.inner.client.block_hash(start_block)?.ok_or(EthApiError::UnknownBlockNumber)?; + self.inner.provider.block_hash(start_block)?.ok_or(EthApiError::UnknownBlockNumber)?; // Set the hash in cache items if the block is present in the cache if let Some(cache_item) = fee_history_cache_items.get_mut(&start_block) { diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 9c1609998320..4e50bb2f70f5 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -44,10 +44,10 @@ pub trait EthApiSpec: EthTransactions + Send + Sync { /// Returns the chain id fn chain_id(&self) -> U64; - /// Returns client chain info + /// Returns provider chain info fn chain_info(&self) -> Result; - /// Returns a list of addresses owned by client. + /// Returns a list of addresses owned by provider. fn accounts(&self) -> Vec

; /// Returns `true` if the network is undergoing sync. @@ -65,25 +65,25 @@ pub trait EthApiSpec: EthTransactions + Send + Sync { /// are implemented separately in submodules. The rpc handler implementation can then delegate to /// the main impls. This way [`EthApi`] is not limited to [`jsonrpsee`] and can be used standalone /// or in other network handlers (for example ipc). -pub struct EthApi { +pub struct EthApi { /// All nested fields bundled together. - inner: Arc>, + inner: Arc>, } -impl EthApi +impl EthApi where - Client: BlockProviderIdExt, + Provider: BlockProviderIdExt, { /// Creates a new, shareable instance using the default tokio task spawner. pub fn new( - client: Client, + provider: Provider, pool: Pool, network: Network, eth_cache: EthStateCache, - gas_oracle: GasPriceOracle, + gas_oracle: GasPriceOracle, ) -> Self { Self::with_spawner( - client, + provider, pool, network, eth_cache, @@ -94,15 +94,15 @@ where /// Creates a new, shareable instance. pub fn with_spawner( - client: Client, + provider: Provider, pool: Pool, network: Network, eth_cache: EthStateCache, - gas_oracle: GasPriceOracle, + gas_oracle: GasPriceOracle, task_spawner: Box, ) -> Self { // get the block number of the latest block - let latest_block = client + let latest_block = provider .header_by_number_or_tag(BlockNumberOrTag::Latest) .ok() .flatten() @@ -110,7 +110,7 @@ where .unwrap_or_default(); let inner = EthApiInner { - client, + provider, pool, network, signers: Default::default(), @@ -151,13 +151,13 @@ where } /// Returns the gas oracle frontend - pub(crate) fn gas_oracle(&self) -> &GasPriceOracle { + pub(crate) fn gas_oracle(&self) -> &GasPriceOracle { &self.inner.gas_oracle } - /// Returns the inner `Client` - pub fn client(&self) -> &Client { - &self.inner.client + /// Returns the inner `Provider` + pub fn provider(&self) -> &Provider { + &self.inner.provider } /// Returns the inner `Network` @@ -173,12 +173,12 @@ where // === State access helpers === -impl EthApi +impl EthApi where - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, { fn convert_block_number(&self, num: BlockNumberOrTag) -> Result> { - self.client().convert_block_number(num) + self.provider().convert_block_number(num) } /// Returns the state at the given [BlockId] enum. @@ -219,40 +219,40 @@ where /// Returns the state at the given block number pub fn state_at_hash(&self, block_hash: H256) -> Result> { - self.client().history_by_block_hash(block_hash) + self.provider().history_by_block_hash(block_hash) } /// Returns the state at the given block number pub fn state_at_number(&self, block_number: u64) -> Result> { match self.convert_block_number(BlockNumberOrTag::Latest)? { Some(num) if num == block_number => self.latest_state(), - _ => self.client().history_by_block_number(block_number), + _ => self.provider().history_by_block_number(block_number), } } /// Returns the _latest_ state pub fn latest_state(&self) -> Result> { - self.client().latest() + self.provider().latest() } } -impl std::fmt::Debug for EthApi { +impl std::fmt::Debug for EthApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("EthApi").finish_non_exhaustive() } } -impl Clone for EthApi { +impl Clone for EthApi { fn clone(&self) -> Self { Self { inner: Arc::clone(&self.inner) } } } #[async_trait] -impl EthApiSpec for EthApi +impl EthApiSpec for EthApi where Pool: TransactionPool + Clone + 'static, - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + 'static, { /// Returns the current ethereum protocol version. @@ -270,7 +270,7 @@ where /// Returns the current info for the chain fn chain_info(&self) -> Result { - self.client().chain_info() + self.provider().chain_info() } fn accounts(&self) -> Vec
{ @@ -285,7 +285,7 @@ where fn sync_status(&self) -> Result { let status = if self.is_syncing() { let current_block = U256::from( - self.client().chain_info().map(|info| info.best_number).unwrap_or_default(), + self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(), ); SyncStatus::Info(SyncInfo { starting_block: self.inner.starting_block, @@ -302,11 +302,11 @@ where } /// Container type `EthApi` -struct EthApiInner { +struct EthApiInner { /// The transaction pool. pool: Pool, - /// The client that can interact with the chain. - client: Client, + /// The provider that can interact with the chain. + provider: Provider, /// An interface to interact with the network network: Network, /// All configured Signers @@ -314,7 +314,7 @@ struct EthApiInner { /// The async cache frontend for eth related data eth_cache: EthStateCache, /// The async gas oracle frontend for gas price suggestions - gas_oracle: GasPriceOracle, + gas_oracle: GasPriceOracle, /// The block number at which the node started starting_block: U256, /// The type that can spawn tasks which would otherwise block. diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 315a90096399..6d093cb01fb3 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -29,11 +29,11 @@ use serde_json::Value; use tracing::trace; #[async_trait::async_trait] -impl EthApiServer for EthApi +impl EthApiServer for EthApi where Self: EthApiSpec + EthTransactions, Pool: TransactionPool + 'static, - Client: BlockProvider + Provider: BlockProvider + BlockIdProvider + BlockProviderIdExt + HeaderProvider diff --git a/crates/rpc/rpc/src/eth/api/sign.rs b/crates/rpc/rpc/src/eth/api/sign.rs index edd03e4655a8..48164a600723 100644 --- a/crates/rpc/rpc/src/eth/api/sign.rs +++ b/crates/rpc/rpc/src/eth/api/sign.rs @@ -11,7 +11,7 @@ use reth_primitives::{Address, Bytes}; use serde_json::Value; use std::ops::Deref; -impl EthApi { +impl EthApi { pub(crate) async fn sign(&self, account: Address, message: Bytes) -> EthResult { let signer = self.find_signer(&account)?; let signature = signer.sign(account, &message).await?; diff --git a/crates/rpc/rpc/src/eth/api/state.rs b/crates/rpc/rpc/src/eth/api/state.rs index 92f1caa9a50d..9a9669c3fb6d 100644 --- a/crates/rpc/rpc/src/eth/api/state.rs +++ b/crates/rpc/rpc/src/eth/api/state.rs @@ -14,9 +14,9 @@ use reth_provider::{ use reth_rpc_types::{EIP1186AccountProofResponse, StorageProof}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -impl EthApi +impl EthApi where - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Pool: TransactionPool + Clone + 'static, Network: Send + Sync + 'static, { @@ -89,7 +89,7 @@ where keys: Vec, block_id: Option, ) -> EthResult { - let chain_info = self.client().chain_info()?; + let chain_info = self.provider().chain_info()?; let block_id = block_id.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); // if we are trying to create a proof for the latest block, but have a BlockId as input diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 2612b48287cc..266906679739 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -179,10 +179,10 @@ pub trait EthTransactions: Send + Sync { } #[async_trait] -impl EthTransactions for EthApi +impl EthTransactions for EthApi where Pool: TransactionPool + Clone + 'static, - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + Send + Sync + 'static, { fn state_at(&self, at: BlockId) -> EthResult> { @@ -199,13 +199,13 @@ where async fn evm_env_at(&self, at: BlockId) -> EthResult<(CfgEnv, BlockEnv, BlockId)> { if at.is_pending() { - let header = if let Some(pending) = self.client().pending_header()? { + let header = if let Some(pending) = self.provider().pending_header()? { pending } else { // no pending block from the CL yet, so we use the latest block and modify the env // values that we can let mut latest = self - .client() + .provider() .latest_header()? .ok_or_else(|| EthApiError::UnknownBlockNumber)?; @@ -221,13 +221,13 @@ where let mut cfg = CfgEnv::default(); let mut block_env = BlockEnv::default(); - self.client().fill_block_env_with_header(&mut block_env, &header)?; - self.client().fill_cfg_env_with_header(&mut cfg, &header)?; + self.provider().fill_block_env_with_header(&mut block_env, &header)?; + self.provider().fill_cfg_env_with_header(&mut cfg, &header)?; return Ok((cfg, block_env, header.hash.into())) } else { // Use cached values if there is no pending block let block_hash = self - .client() + .provider() .block_hash_for_id(at)? .ok_or_else(|| EthApiError::UnknownBlockNumber)?; let (cfg, env) = self.cache().get_evm_env(block_hash).await?; @@ -267,7 +267,7 @@ where // Try to find the transaction on disk let mut resp = self .on_blocking_task(|this| async move { - match this.client().transaction_by_hash_with_meta(hash)? { + match this.provider().transaction_by_hash_with_meta(hash)? { None => Ok(None), Some((tx, meta)) => { let transaction = tx @@ -345,12 +345,12 @@ where async fn transaction_receipt(&self, hash: H256) -> EthResult> { self.on_blocking_task(|this| async move { - let (tx, meta) = match this.client().transaction_by_hash_with_meta(hash)? { + let (tx, meta) = match this.provider().transaction_by_hash_with_meta(hash)? { Some((tx, meta)) => (tx, meta), None => return Ok(None), }; - let receipt = match this.client().receipt_by_hash(hash)? { + let receipt = match this.provider().receipt_by_hash(hash)? { Some(recpt) => recpt, None => return Ok(None), }; @@ -566,10 +566,10 @@ where // === impl EthApi === -impl EthApi +impl EthApi where Pool: TransactionPool + 'static, - Client: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: 'static, { pub(crate) fn sign_request( diff --git a/crates/rpc/rpc/src/eth/cache.rs b/crates/rpc/rpc/src/eth/cache.rs index 55f50103a177..717ef540254f 100644 --- a/crates/rpc/rpc/src/eth/cache.rs +++ b/crates/rpc/rpc/src/eth/cache.rs @@ -94,16 +94,16 @@ pub struct EthStateCache { impl EthStateCache { /// Creates and returns both [EthStateCache] frontend and the memory bound service. - fn create( - client: Client, + fn create( + provider: Provider, action_task_spawner: Tasks, max_block_bytes: usize, max_receipt_bytes: usize, max_env_bytes: usize, - ) -> (Self, EthStateCacheService) { + ) -> (Self, EthStateCacheService) { let (to_service, rx) = unbounded_channel(); let service = EthStateCacheService { - client, + provider, full_block_cache: BlockLruCache::with_memory_budget(max_block_bytes), receipts_cache: ReceiptsLruCache::with_memory_budget(max_receipt_bytes), evm_env_cache: EnvLruCache::with_memory_budget(max_env_bytes), @@ -119,29 +119,29 @@ impl EthStateCache { /// [tokio::spawn]. /// /// See also [Self::spawn_with] - pub fn spawn(client: Client, config: EthStateCacheConfig) -> Self + pub fn spawn(provider: Provider, config: EthStateCacheConfig) -> Self where - Client: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, { - Self::spawn_with(client, config, TokioTaskExecutor::default()) + Self::spawn_with(provider, config, TokioTaskExecutor::default()) } /// Creates a new async LRU backed cache service task and spawns it to a new task via the given /// spawner. /// /// The cache is memory limited by the given max bytes values. - pub fn spawn_with( - client: Client, + pub fn spawn_with( + provider: Provider, config: EthStateCacheConfig, executor: Tasks, ) -> Self where - Client: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, { let EthStateCacheConfig { max_block_bytes, max_receipt_bytes, max_env_bytes } = config; let (this, service) = Self::create( - client, + provider, executor.clone(), max_block_bytes, max_receipt_bytes, @@ -216,7 +216,7 @@ impl EthStateCache { /// to limit concurrent requests. #[must_use = "Type does nothing unless spawned"] pub(crate) struct EthStateCacheService< - Client, + Provider, Tasks, LimitBlocks = ByMemoryUsage, LimitReceipts = ByMemoryUsage, @@ -227,7 +227,7 @@ pub(crate) struct EthStateCacheService< LimitEnvs: Limiter, { /// The type used to lookup data from disk - client: Client, + provider: Provider, /// The LRU cache for full blocks grouped by their hash. full_block_cache: BlockLruCache, /// The LRU cache for full blocks grouped by their hash. @@ -242,9 +242,9 @@ pub(crate) struct EthStateCacheService< action_task_spawner: Tasks, } -impl EthStateCacheService +impl EthStateCacheService where - Client: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, { fn on_new_block(&mut self, block_hash: H256, res: Result>) { @@ -285,9 +285,9 @@ where } } -impl Future for EthStateCacheService +impl Future for EthStateCacheService where - Client: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, { type Output = (); @@ -313,10 +313,10 @@ where // block is not in the cache, request it if this is the first consumer if this.full_block_cache.queue(block_hash, Either::Left(response_tx)) { - let client = this.client.clone(); + let provider = this.provider.clone(); let action_tx = this.action_tx.clone(); this.action_task_spawner.spawn_blocking(Box::pin(async move { - let res = client.block_by_hash(block_hash); + let res = provider.block_by_hash(block_hash); let _ = action_tx .send(CacheAction::BlockResult { block_hash, res }); })); @@ -331,10 +331,10 @@ where // block is not in the cache, request it if this is the first consumer if this.full_block_cache.queue(block_hash, Either::Right(response_tx)) { - let client = this.client.clone(); + let provider = this.provider.clone(); let action_tx = this.action_tx.clone(); this.action_task_spawner.spawn_blocking(Box::pin(async move { - let res = client.block_by_hash(block_hash); + let res = provider.block_by_hash(block_hash); let _ = action_tx .send(CacheAction::BlockResult { block_hash, res }); })); @@ -351,10 +351,10 @@ where // block is not in the cache, request it if this is the first consumer if this.receipts_cache.queue(block_hash, response_tx) { - let client = this.client.clone(); + let provider = this.provider.clone(); let action_tx = this.action_tx.clone(); this.action_task_spawner.spawn_blocking(Box::pin(async move { - let res = client.receipts_by_block(block_hash.into()); + let res = provider.receipts_by_block(block_hash.into()); let _ = action_tx .send(CacheAction::ReceiptsResult { block_hash, res }); })); @@ -370,12 +370,12 @@ where // env data is not in the cache, request it if this is the first // consumer if this.evm_env_cache.queue(block_hash, response_tx) { - let client = this.client.clone(); + let provider = this.provider.clone(); let action_tx = this.action_tx.clone(); this.action_task_spawner.spawn_blocking(Box::pin(async move { let mut cfg = CfgEnv::default(); let mut block_env = BlockEnv::default(); - let res = client + let res = provider .fill_env_at(&mut cfg, &mut block_env, block_hash.into()) .map(|_| (cfg, block_env)); let _ = action_tx.send(CacheAction::EnvResult { diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index cfd61e0f05cf..c6eea1fbe193 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -26,27 +26,27 @@ use tracing::trace; const MAX_HEADERS_RANGE: u64 = 1_000; // with ~530bytes per header this is ~500kb /// `Eth` filter RPC implementation. -pub struct EthFilter { +pub struct EthFilter { /// All nested fields bundled together. - inner: Arc>, + inner: Arc>, } -impl EthFilter { +impl EthFilter { /// Creates a new, shareable instance. /// - /// This uses the given pool to get notified about new transactions, the client to interact with - /// the blockchain, the cache to fetch cacheable data, like the logs and the + /// This uses the given pool to get notified about new transactions, the provider to interact + /// with the blockchain, the cache to fetch cacheable data, like the logs and the /// max_logs_per_response to limit the amount of logs returned in a single response /// `eth_getLogs` pub fn new( - client: Client, + provider: Provider, pool: Pool, eth_cache: EthStateCache, max_logs_per_response: usize, task_spawner: Box, ) -> Self { let inner = EthFilterInner { - client, + provider, active_filters: Default::default(), pool, id_provider: Arc::new(EthSubscriptionIdProvider::default()), @@ -64,9 +64,9 @@ impl EthFilter { } } -impl EthFilter +impl EthFilter where - Client: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, + Provider: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, Pool: TransactionPool + 'static, { /// Executes the given filter on a new task. @@ -91,7 +91,7 @@ where /// Returns all the filter changes for the given id, if any pub async fn filter_changes(&self, id: FilterId) -> Result { - let info = self.inner.client.chain_info()?; + let info = self.inner.provider.chain_info()?; let best_number = info.best_number; let (start_block, kind) = { @@ -117,7 +117,7 @@ where for block_num in start_block..best_number { let block_hash = self .inner - .client + .provider .block_hash(block_num)? .ok_or(EthApiError::UnknownBlockNumber)?; block_hashes.push(block_hash); @@ -128,11 +128,11 @@ where let (from_block_number, to_block_number) = match filter.block_option { FilterBlockOption::Range { from_block, to_block } => { let from = from_block - .map(|num| self.inner.client.convert_block_number(num)) + .map(|num| self.inner.provider.convert_block_number(num)) .transpose()? .flatten(); let to = to_block - .map(|num| self.inner.client.convert_block_number(num)) + .map(|num| self.inner.provider.convert_block_number(num)) .transpose()? .flatten(); logs_utils::get_filter_block_range(from, to, start_block, info) @@ -176,9 +176,9 @@ where } #[async_trait] -impl EthFilterApiServer for EthFilter +impl EthFilterApiServer for EthFilter where - Client: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, + Provider: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, Pool: TransactionPool + 'static, { /// Handler for `eth_newFilter` @@ -238,13 +238,13 @@ where } } -impl std::fmt::Debug for EthFilter { +impl std::fmt::Debug for EthFilter { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("EthFilter").finish_non_exhaustive() } } -impl Clone for EthFilter { +impl Clone for EthFilter { fn clone(&self) -> Self { Self { inner: Arc::clone(&self.inner) } } @@ -252,12 +252,12 @@ impl Clone for EthFilter { /// Container type `EthFilter` #[derive(Debug)] -struct EthFilterInner { +struct EthFilterInner { /// The transaction pool. #[allow(unused)] // we need this for non standard full transactions eventually pool: Pool, - /// The client that can interact with the chain. - client: Client, + /// The provider that can interact with the chain. + provider: Provider, /// All currently installed filters. active_filters: ActiveFilters, /// Provides ids to identify filters @@ -272,9 +272,9 @@ struct EthFilterInner { task_spawner: Box, } -impl EthFilterInner +impl EthFilterInner where - Client: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, + Provider: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, Pool: TransactionPool + 'static, { /// Returns logs matching given filter object. @@ -298,16 +298,16 @@ where } FilterBlockOption::Range { from_block, to_block } => { // compute the range - let info = self.client.chain_info()?; + let info = self.provider.chain_info()?; // we start at the most recent block if unset in filter let start_block = info.best_number; let from = from_block - .map(|num| self.client.convert_block_number(num)) + .map(|num| self.provider.convert_block_number(num)) .transpose()? .flatten(); let to = to_block - .map(|num| self.client.convert_block_number(num)) + .map(|num| self.provider.convert_block_number(num)) .transpose()? .flatten(); let (from_block_number, to_block_number) = @@ -319,7 +319,7 @@ where /// Installs a new filter and returns the new identifier. async fn install_filter(&self, kind: FilterKind) -> RpcResult { - let last_poll_block_number = self.client.best_block_number().to_rpc_result()?; + let last_poll_block_number = self.provider.best_block_number().to_rpc_result()?; let id = FilterId::from(self.id_provider.next_id()); let mut filters = self.active_filters.inner.lock().await; filters.insert( @@ -338,7 +338,7 @@ where &self, hash_or_number: BlockHashOrNumber, ) -> EthResult)>> { - let block_hash = match self.client.convert_block_hash(hash_or_number)? { + let block_hash = match self.provider.convert_block_hash(hash_or_number)? { Some(hash) => hash, None => return Ok(None), }; @@ -386,7 +386,7 @@ where for (from, to) in BlockRangeInclusiveIter::new(from_block..=to_block, self.max_headers_range) { - let headers = self.client.headers_range(from..=to)?; + let headers = self.provider.headers_range(from..=to)?; for (idx, header) in headers.iter().enumerate() { // these are consecutive headers, so we can use the parent hash of the next block to diff --git a/crates/rpc/rpc/src/eth/gas_oracle.rs b/crates/rpc/rpc/src/eth/gas_oracle.rs index 98cf756d7aea..fd5ab1942877 100644 --- a/crates/rpc/rpc/src/eth/gas_oracle.rs +++ b/crates/rpc/rpc/src/eth/gas_oracle.rs @@ -81,9 +81,9 @@ impl GasPriceOracleConfig { /// Calculates a gas price depending on recent blocks. #[derive(Debug)] -pub struct GasPriceOracle { +pub struct GasPriceOracle { /// The type used to subscribe to block events and get block info - client: Client, + provider: Provider, /// The cache for blocks cache: EthStateCache, /// The config for the oracle @@ -92,13 +92,13 @@ pub struct GasPriceOracle { last_price: Mutex, } -impl GasPriceOracle +impl GasPriceOracle where - Client: BlockProviderIdExt + 'static, + Provider: BlockProviderIdExt + 'static, { /// Creates and returns the [GasPriceOracle]. pub fn new( - client: Client, + provider: Provider, mut oracle_config: GasPriceOracleConfig, cache: EthStateCache, ) -> Self { @@ -108,13 +108,13 @@ where oracle_config.percentile = 100; } - Self { client, oracle_config, last_price: Default::default(), cache } + Self { provider, oracle_config, last_price: Default::default(), cache } } /// Suggests a gas price estimate based on recent blocks, using the configured percentile. pub async fn suggest_tip_cap(&self) -> EthResult { let header = self - .client + .provider .sealed_header_by_number_or_tag(BlockNumberOrTag::Latest)? .ok_or(EthApiError::UnknownBlockNumber)?; diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs index 4fe9db4d203d..f4a4ccebb1b5 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -27,40 +27,47 @@ use tokio_stream::{ /// /// This handles `eth_subscribe` RPC calls. #[derive(Clone)] -pub struct EthPubSub { +pub struct EthPubSub { /// All nested fields bundled together. - inner: EthPubSubInner, + inner: EthPubSubInner, /// The type that's used to spawn subscription tasks. subscription_task_spawner: Box, } // === impl EthPubSub === -impl EthPubSub { +impl EthPubSub { /// Creates a new, shareable instance. /// /// Subscription tasks are spawned via [tokio::task::spawn] - pub fn new(client: Client, pool: Pool, chain_events: Events, network: Network) -> Self { - Self::with_spawner(client, pool, chain_events, network, Box::::default()) + pub fn new(provider: Provider, pool: Pool, chain_events: Events, network: Network) -> Self { + Self::with_spawner( + provider, + pool, + chain_events, + network, + Box::::default(), + ) } /// Creates a new, shareable instance. pub fn with_spawner( - client: Client, + provider: Provider, pool: Pool, chain_events: Events, network: Network, subscription_task_spawner: Box, ) -> Self { - let inner = EthPubSubInner { client, pool, chain_events, network }; + let inner = EthPubSubInner { provider, pool, chain_events, network }; Self { inner, subscription_task_spawner } } } #[async_trait::async_trait] -impl EthPubSubApiServer for EthPubSub +impl EthPubSubApiServer + for EthPubSub where - Client: BlockProvider + EvmEnvProvider + Clone + 'static, + Provider: BlockProvider + EvmEnvProvider + Clone + 'static, Pool: TransactionPool + 'static, Events: CanonStateSubscriptions + Clone + 'static, Network: NetworkInfo + Clone + 'static, @@ -83,14 +90,14 @@ where } /// The actual handler for and accepted [`EthPubSub::subscribe`] call. -async fn handle_accepted( - pubsub: EthPubSubInner, +async fn handle_accepted( + pubsub: EthPubSubInner, accepted_sink: SubscriptionSink, kind: SubscriptionKind, params: Option, ) -> Result<(), jsonrpsee::core::Error> where - Client: BlockProvider + EvmEnvProvider + Clone + 'static, + Provider: BlockProvider + EvmEnvProvider + Clone + 'static, Pool: TransactionPool + 'static, Events: CanonStateSubscriptions + Clone + 'static, Network: NetworkInfo + Clone + 'static, @@ -185,7 +192,9 @@ where } } -impl std::fmt::Debug for EthPubSub { +impl std::fmt::Debug + for EthPubSub +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("EthPubSub").finish_non_exhaustive() } @@ -193,11 +202,11 @@ impl std::fmt::Debug for EthPubSub { +struct EthPubSubInner { /// The transaction pool. pool: Pool, - /// The client that can interact with the chain. - client: Client, + /// The provider that can interact with the chain. + provider: Provider, /// A type that allows to create new event subscriptions. chain_events: Events, /// The network. @@ -206,15 +215,15 @@ struct EthPubSubInner { // == impl EthPubSubInner === -impl EthPubSubInner +impl EthPubSubInner where - Client: BlockProvider + 'static, + Provider: BlockProvider + 'static, { /// Returns the current sync status for the `syncing` subscription async fn sync_status(&self, is_syncing: bool) -> EthSubscriptionResult { if is_syncing { let current_block = - self.client.chain_info().map(|info| info.best_number).unwrap_or_default(); + self.provider.chain_info().map(|info| info.best_number).unwrap_or_default(); EthSubscriptionResult::SyncState(PubSubSyncStatus::Detailed(SyncStatusMetadata { syncing: true, starting_block: 0, @@ -227,7 +236,7 @@ where } } -impl EthPubSubInner +impl EthPubSubInner where Pool: TransactionPool + 'static, { @@ -237,9 +246,9 @@ where } } -impl EthPubSubInner +impl EthPubSubInner where - Client: BlockProvider + EvmEnvProvider + 'static, + Provider: BlockProvider + EvmEnvProvider + 'static, Events: CanonStateSubscriptions + 'static, Network: NetworkInfo + 'static, Pool: 'static, diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 81d0e39e520e..45ff282eb64e 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -33,28 +33,28 @@ use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit}; /// `trace` API implementation. /// /// This type provides the functionality for handling `trace` related requests. -pub struct TraceApi { - inner: Arc>, +pub struct TraceApi { + inner: Arc>, } // === impl TraceApi === -impl TraceApi { - /// The client that can interact with the chain. - pub fn client(&self) -> &Client { - &self.inner.client +impl TraceApi { + /// The provider that can interact with the chain. + pub fn provider(&self) -> &Provider { + &self.inner.provider } /// Create a new instance of the [TraceApi] pub fn new( - client: Client, + provider: Provider, eth_api: Eth, eth_cache: EthStateCache, task_spawner: Box, tracing_call_guard: TracingCallGuard, ) -> Self { let inner = Arc::new(TraceApiInner { - client, + provider, eth_api, eth_cache, task_spawner, @@ -73,9 +73,9 @@ impl TraceApi { // === impl TraceApi === -impl TraceApi +impl TraceApi where - Client: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, Eth: EthTransactions + 'static, { /// Executes the future on a new blocking task. @@ -382,9 +382,9 @@ where } #[async_trait] -impl TraceApiServer for TraceApi +impl TraceApiServer for TraceApi where - Client: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, Eth: EthTransactions + 'static, { /// Executes the given call and returns a number of possible traces for it. @@ -486,20 +486,20 @@ where } } -impl std::fmt::Debug for TraceApi { +impl std::fmt::Debug for TraceApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("TraceApi").finish_non_exhaustive() } } -impl Clone for TraceApi { +impl Clone for TraceApi { fn clone(&self) -> Self { Self { inner: Arc::clone(&self.inner) } } } -struct TraceApiInner { - /// The client that can interact with the chain. - client: Client, +struct TraceApiInner { + /// The provider that can interact with the chain. + provider: Provider, /// Access to commonly used code of the `eth` namespace eth_api: Eth, /// The async cache frontend for eth-related data From 3637482f3a26f9e0ff964ec60d1facb516b978f6 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Wed, 14 Jun 2023 01:38:56 +0300 Subject: [PATCH 013/216] chore(stages): raise transaction based commit thresholds (#3128) --- crates/config/src/config.rs | 2 +- crates/stages/src/stages/sender_recovery.rs | 2 +- crates/stages/src/stages/tx_lookup.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index cd8751e347a9..fa126cd9468b 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -184,7 +184,7 @@ pub struct SenderRecoveryConfig { impl Default for SenderRecoveryConfig { fn default() -> Self { - Self { commit_threshold: 50_000 } + Self { commit_threshold: 5_000_000 } } } diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index 1347760b1cd5..0b243726627c 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -38,7 +38,7 @@ impl SenderRecoveryStage { impl Default for SenderRecoveryStage { fn default() -> Self { - Self { commit_threshold: 50_000 } + Self { commit_threshold: 5_000_000 } } } diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index acff8c05c770..3d77fc6af648 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -30,7 +30,7 @@ pub struct TransactionLookupStage { impl Default for TransactionLookupStage { fn default() -> Self { - Self { commit_threshold: 100_000 } + Self { commit_threshold: 5_000_000 } } } From fe40a92f060604adb98f5df5113594485fb637ad Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 14 Jun 2023 02:37:25 +0400 Subject: [PATCH 014/216] feat(bin): sort `db stats` rows (#3125) --- bin/reth/src/db/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index eff705a164a1..864fd7b8ddf9 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -117,7 +117,10 @@ impl Command { ]); tool.db.view(|tx| { - for table in tables::TABLES.iter().map(|(_, name)| name) { + let mut tables = + tables::TABLES.iter().map(|(_, name)| name).collect::>(); + tables.sort(); + for table in tables { let table_db = tx.inner.open_db(Some(table)).wrap_err("Could not open db.")?; From ea2fcee995fac3bc5194e4dcafd42620a3650274 Mon Sep 17 00:00:00 2001 From: Waylon Jepsen <57912727+0xJepsen@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:46:26 -0600 Subject: [PATCH 015/216] chore: use workspace dependencies (#3132) --- Cargo.toml | 29 ++++++++++++++++ bin/reth/Cargo.toml | 32 ++++++++--------- crates/blockchain-tree/Cargo.toml | 14 ++++---- crates/config/Cargo.toml | 6 ++-- crates/consensus/auto-seal/Cargo.toml | 16 ++++----- crates/consensus/beacon/Cargo.toml | 28 +++++++-------- crates/consensus/common/Cargo.toml | 10 +++--- crates/interfaces/Cargo.toml | 26 +++++++------- crates/metrics/Cargo.toml | 2 +- crates/net/common/Cargo.toml | 6 ++-- crates/net/discv4/Cargo.toml | 20 +++++------ crates/net/dns/Cargo.toml | 18 +++++----- crates/net/downloaders/Cargo.toml | 30 ++++++++-------- crates/net/ecies/Cargo.toml | 20 +++++------ crates/net/eth-wire/Cargo.toml | 30 ++++++++-------- crates/net/nat/Cargo.toml | 6 ++-- crates/net/network-api/Cargo.toml | 12 +++---- crates/net/network/Cargo.toml | 46 ++++++++++++------------- crates/payload/basic/Cargo.toml | 18 +++++----- crates/payload/builder/Cargo.toml | 18 +++++----- crates/primitives/Cargo.toml | 20 +++++------ crates/revm/Cargo.toml | 8 ++--- crates/revm/revm-inspectors/Cargo.toml | 6 ++-- crates/revm/revm-primitives/Cargo.toml | 2 +- crates/rlp/Cargo.toml | 2 +- crates/rpc/ipc/Cargo.toml | 18 +++++----- crates/rpc/rpc-api/Cargo.toml | 6 ++-- crates/rpc/rpc-builder/Cargo.toml | 32 ++++++++--------- crates/rpc/rpc-engine-api/Cargo.toml | 22 ++++++------ crates/rpc/rpc-testing-util/Cargo.toml | 10 +++--- crates/rpc/rpc-types/Cargo.toml | 16 ++++----- crates/rpc/rpc/Cargo.toml | 36 +++++++++---------- crates/staged-sync/Cargo.toml | 26 +++++++------- crates/stages/Cargo.toml | 30 ++++++++-------- crates/storage/codecs/derive/Cargo.toml | 2 +- crates/storage/db/Cargo.toml | 30 ++++++++-------- crates/storage/libmdbx-rs/Cargo.toml | 4 +-- crates/storage/provider/Cargo.toml | 18 +++++----- crates/tasks/Cargo.toml | 10 +++--- crates/transaction-pool/Cargo.toml | 22 ++++++------ crates/trie/Cargo.toml | 18 +++++----- testing/ef-tests/Cargo.toml | 12 +++---- 42 files changed, 383 insertions(+), 354 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f11dcb6d8965..3c38e1da55ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,6 +81,35 @@ revm-primitives = { git = "https://github.com/bluealloy/revm/", branch = "releas ## eth revm = { version = "3" } revm-primitives = "1.1" +reth-primitives = { path = "./crates/primitives" } +reth-interfaces = { path = "./crates/interfaces" } +reth-provider = { path = "./crates/storage/provider" } +reth-rlp = { path = "./crates/rlp" } +reth-rpc-types = { path = "./crates/rpc/rpc-types" } +reth-metrics = { path = "./crates/metrics" } +reth-payload-builder = { path = "./crates/payload/builder" } +reth-transaction-pool = { path = "./crates/transaction-pool" } +reth-tasks = { path = "./crates/tasks" } +reth-network-api = { path = "./crates/net/network-api" } ## misc tracing = "^0.1.0" +thiserror = "1.0.37" +serde_json = "1.0.94" +serde = { version = "1.0", default-features = false } +rand = "0.8.5" + + +## tokio +tokio-stream = "0.1.11" +tokio = { version = "1.21", default-features = false } +tokio-util = { version = "0.7.4", features = ["codec"] } + +## async +async-trait = "0.1.58" +futures = "0.3.26" +pin-project = "1.0.12" +futures-util = "0.3.25" + +## crypto +secp256k1 = { version = "0.27.0", default-features = false, features = ["global-context", "rand-std", "recovery"] } \ No newline at end of file diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index bed0b3d4d650..3f96e825b903 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -10,45 +10,45 @@ repository.workspace = true [dependencies] # reth reth-config = { path = "../../crates/config" } -reth-primitives = { path = "../../crates/primitives", features = ["arbitrary"] } +reth-primitives = { workspace = true, features = ["arbitrary"] } reth-db = { path = "../../crates/storage/db", features = ["mdbx", "test-utils"] } # TODO: Temporary use of the test-utils feature -reth-provider = { path = "../../crates/storage/provider", features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } reth-revm = { path = "../../crates/revm" } reth-revm-inspectors = { path = "../../crates/revm/revm-inspectors" } reth-staged-sync = { path = "../../crates/staged-sync" } reth-stages = { path = "../../crates/stages" } -reth-interfaces = { path = "../../crates/interfaces", features = ["test-utils"] } -reth-transaction-pool = { path = "../../crates/transaction-pool" } +reth-interfaces = { workspace = true, features = ["test-utils"] } +reth-transaction-pool = { workspace = true } reth-beacon-consensus = { path = "../../crates/consensus/beacon" } reth-auto-seal-consensus = { path = "../../crates/consensus/auto-seal" } reth-blockchain-tree = { path = "../../crates/blockchain-tree" } reth-rpc-engine-api = { path = "../../crates/rpc/rpc-engine-api" } reth-rpc-builder = { path = "../../crates/rpc/rpc-builder" } reth-rpc = { path = "../../crates/rpc/rpc" } -reth-rlp = { path = "../../crates/rlp" } +reth-rlp = { workspace = true } reth-network = { path = "../../crates/net/network", features = ["serde"] } -reth-network-api = { path = "../../crates/net/network-api" } +reth-network-api = { workspace = true } reth-downloaders = { path = "../../crates/net/downloaders", features = ["test-utils"] } reth-tracing = { path = "../../crates/tracing" } -reth-tasks = { path = "../../crates/tasks" } +reth-tasks = { workspace = true } reth-net-nat = { path = "../../crates/net/nat" } -reth-payload-builder = { path = "../../crates/payload/builder" } +reth-payload-builder = { workspace = true } reth-basic-payload-builder = { path = "../../crates/payload/basic" } reth-discv4 = { path = "../../crates/net/discv4" } -reth-metrics = { path = "../../crates/metrics" } +reth-metrics = { workspace = true } jemallocator = { version = "0.5.0", optional = true } # crypto -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } # tracing tracing = { workspace = true } # io fdlimit = "0.2.1" -serde = "1.0" -serde_json = "1.0" +serde = { workspace = true } +serde_json = { workspace = true } shellexpand = "3.0.0" dirs-next = "2.0.0" confy = "0.5" @@ -68,9 +68,9 @@ tui = "0.19.0" human_bytes = "0.4.1" # async -tokio = { version = "1.21", features = ["sync", "macros", "time", "rt-multi-thread"] } -futures = "0.3.25" -pin-project = "1.0" +tokio = { workspace = true, features = ["sync", "macros", "time", "rt-multi-thread"] } +futures = { workspace = true } +pin-project = { workspace = true } # http/rpc hyper = "0.14.25" @@ -81,7 +81,7 @@ clap = { version = "4", features = ["derive"] } tempfile = { version = "3.3.0" } backon = "0.4" hex = "0.4" -thiserror = "1.0" +thiserror = { workspace = true } pretty_assertions = "1.3.0" [features] diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index 477abfec31fe..a95766ee4c98 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -15,10 +15,10 @@ normal = [ [dependencies] # reth -reth-primitives = { path = "../primitives" } -reth-interfaces = { path = "../interfaces" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } reth-db = { path = "../storage/db" } -reth-provider = { path = "../storage/provider" } +reth-provider = { workspace = true } # common parking_lot = { version = "0.12" } @@ -31,12 +31,12 @@ linked_hash_set = "0.1.4" [dev-dependencies] reth-db = { path = "../storage/db", features = ["test-utils"] } -reth-interfaces = { path = "../interfaces", features = ["test-utils"] } -reth-primitives = { path = "../primitives", features = ["test-utils"] } -reth-provider = { path = "../storage/provider", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } +reth-primitives = { workspace = true , features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } parking_lot = "0.12" assert_matches = "1.5" -tokio = { version = "1", features = ["macros", "sync"] } +tokio = { workspace = true, features = ["macros", "sync"] } [features] test-utils = [] diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 36fd50fc04d3..c87a361487fe 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -15,11 +15,11 @@ reth-discv4 = { path = "../../crates/net/discv4" } reth-downloaders = { path = "../../crates/net/downloaders" } # io -serde = "1.0" -serde_json = "1.0.91" +serde = { workspace = true } +serde_json = { workspace = true } #crypto -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } confy = "0.5" diff --git a/crates/consensus/auto-seal/Cargo.toml b/crates/consensus/auto-seal/Cargo.toml index 381cdacdfd88..d4890b9cc5a5 100644 --- a/crates/consensus/auto-seal/Cargo.toml +++ b/crates/consensus/auto-seal/Cargo.toml @@ -11,18 +11,18 @@ description = "A consensus impl for local testing purposes" [dependencies] # reth reth-beacon-consensus = { path = "../beacon" } -reth-primitives = { path = "../../primitives" } -reth-interfaces = { path = "../../interfaces" } -reth-provider = { path = "../../storage/provider" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } +reth-provider = { workspace = true } reth-stages = { path = "../../stages" } reth-revm = { path = "../../revm" } -reth-transaction-pool = { path = "../../transaction-pool" } +reth-transaction-pool = { workspace = true } # async -futures-util = "0.3" -tokio = { version = "1", features = ["sync", "time"] } -tokio-stream = "0.1" +futures-util = { workspace = true } +tokio = { workspace = true, features = ["sync", "time"] } +tokio-stream = { workspace = true } tracing = { workspace = true } [dev-dependencies] -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index 178d5da5e264..74a2a9094770 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -10,34 +10,34 @@ repository.workspace = true [dependencies] # reth reth-consensus-common = { path = "../common" } -reth-primitives = { path = "../../primitives" } -reth-interfaces = { path = "../../interfaces" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } reth-stages = { path = "../../stages" } reth-db = { path = "../../storage/db" } -reth-provider = { path = "../../storage/provider" } -reth-rpc-types = { path = "../../rpc/rpc-types" } -reth-tasks = { path = "../../tasks" } -reth-payload-builder = { path = "../../payload/builder" } -reth-metrics = { path = "../../metrics" } +reth-provider = { workspace = true } +reth-rpc-types = { workspace = true } +reth-tasks = { workspace = true } +reth-payload-builder = { workspace = true } +reth-metrics = { workspace = true } # async -tokio = { version = "1.21.2", features = ["sync"] } -tokio-stream = "0.1.10" -futures = "0.3" +tokio = { workspace = true, features = ["sync"] } +tokio-stream = { workspace = true } +futures = { workspace = true } # misc tracing = { workspace = true } -thiserror = "1.0" +thiserror = { workspace = true } schnellru = "0.2" [dev-dependencies] # reth -reth-payload-builder = { path = "../../payload/builder", features = ["test-utils"] } -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } +reth-payload-builder = { workspace = true, features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } reth-stages = { path = "../../stages", features = ["test-utils"] } reth-blockchain-tree = { path = "../../blockchain-tree", features = ["test-utils"] } reth-db = { path = "../../storage/db", features = ["test-utils"] } -reth-provider = { path = "../../storage/provider", features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } reth-tracing = { path = "../../tracing" } assert_matches = "1.5" diff --git a/crates/consensus/common/Cargo.toml b/crates/consensus/common/Cargo.toml index d74d1bf31b78..82023d756970 100644 --- a/crates/consensus/common/Cargo.toml +++ b/crates/consensus/common/Cargo.toml @@ -9,12 +9,12 @@ repository.workspace = true [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-interfaces = { path = "../../interfaces" } -reth-provider = { path = "../../storage/provider" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } +reth-provider = { workspace = true } [dev-dependencies] -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } -reth-provider = { path = "../../storage/provider", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } assert_matches = "1.5.0" mockall = "0.11.3" diff --git a/crates/interfaces/Cargo.toml b/crates/interfaces/Cargo.toml index c4680ceddf11..8cdcbaabb8bc 100644 --- a/crates/interfaces/Cargo.toml +++ b/crates/interfaces/Cargo.toml @@ -9,9 +9,9 @@ repository.workspace = true [dependencies] reth-codecs = { path = "../storage/codecs" } -reth-primitives = { path = "../primitives" } -reth-rpc-types = { path = "../rpc/rpc-types" } -reth-network-api = { path = "../net/network-api" } +reth-primitives = { workspace = true } +reth-rpc-types = { workspace = true } +reth-network-api = { workspace = true } # TODO(onbjerg): We only need this for [BlockBody] reth-eth-wire = { path = "../net/eth-wire" } @@ -20,18 +20,18 @@ revm-primitives = { workspace = true } parity-scale-codec = { version = "3.2.1", features = ["bytes"] } # async -async-trait = "0.1.57" -futures = "0.3" -tokio = { version = "1.21.2", features = ["sync"] } -tokio-stream = "0.1.11" +async-trait = { workspace = true } +futures = { workspace = true } +tokio = { workspace = true, features = ["sync"] } +tokio-stream = { workspace = true } # misc auto_impl = "1.0" -thiserror = "1.0.37" +thiserror = { workspace = true } tracing = { workspace = true } -rand = "0.8.5" +rand = { workspace = true } arbitrary = { version = "1.1.7", features = ["derive"], optional = true } -secp256k1 = { version = "0.27.0", default-features = false, features = [ +secp256k1 = { workspace = true, default-features = false, features = [ "alloc", "recovery", "rand", @@ -41,11 +41,11 @@ parking_lot = "0.12.1" [dev-dependencies] reth-db = { path = "../storage/db", features = ["test-utils"] } -tokio = { version = "1.21.2", features = ["full"] } -tokio-stream = { version = "0.1.11", features = ["sync"] } +tokio = { workspace = true, features = ["full"] } +tokio-stream = { workspace = true, features = ["sync"] } arbitrary = { version = "1.1.7", features = ["derive"] } hex-literal = "0.3" -secp256k1 = { version = "0.27.0", default-features = false, features = [ +secp256k1 = { workspace = true, features = [ "alloc", "recovery", "rand", diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml index e556062db0cb..e1e01e8c0800 100644 --- a/crates/metrics/Cargo.toml +++ b/crates/metrics/Cargo.toml @@ -16,7 +16,7 @@ reth-metrics-derive = { path = "./metrics-derive" } metrics = "0.20.1" # async -tokio = { version = "1.21.2", features = ["full"], optional = true } +tokio = { workspace = true, features = ["full"], optional = true } [features] common = ["tokio"] diff --git a/crates/net/common/Cargo.toml b/crates/net/common/Cargo.toml index 28cfc002ac48..8cfdd2a5384a 100644 --- a/crates/net/common/Cargo.toml +++ b/crates/net/common/Cargo.toml @@ -12,8 +12,8 @@ Types shared across network code [dependencies] # reth -reth-primitives = { path = "../../primitives" } +reth-primitives = { workspace = true } # async -pin-project = "1.0" -tokio = { version = "1.21.2", features = ["full"] } +pin-project = { workspace = true } +tokio = { workspace = true, features = ["full"] } diff --git a/crates/net/discv4/Cargo.toml b/crates/net/discv4/Cargo.toml index 48f7f50fdff9..9c80b638460e 100644 --- a/crates/net/discv4/Cargo.toml +++ b/crates/net/discv4/Cargo.toml @@ -12,32 +12,32 @@ Ethereum network discovery [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-rlp = { path = "../../rlp" } +reth-primitives = { workspace = true } +reth-rlp = { workspace = true } reth-rlp-derive = { path = "../../rlp/rlp-derive" } reth-net-common = { path = "../common" } reth-net-nat = { path = "../nat" } # ethereum discv5 = { git = "https://github.com/sigp/discv5" } -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery", "serde"] } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery", "serde"] } enr = { version = "0.8.1", default-features = false, features = ["rust-secp256k1"] } # async/futures -tokio = { version = "1", features = ["io-util", "net", "time"] } -tokio-stream = "0.1" +tokio = { workspace = true, features = ["io-util", "net", "time"] } +tokio-stream = { workspace = true } # misc tracing = { workspace = true } -thiserror = "1.0" +thiserror = { workspace = true } hex = "0.4" -rand = { version = "0.8", optional = true } +rand = { workspace = true, optional = true } generic-array = "0.14" -serde = { version = "1.0", optional = true } +serde = { workspace = true, optional = true } [dev-dependencies] -rand = "0.8" -tokio = { version = "1", features = ["macros"] } +rand = { workspace = true } +tokio = { workspace = true, features = ["macros"] } reth-tracing = { path = "../../tracing" } [features] diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index 5f29e5ae1983..da6c70f233d7 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -10,34 +10,34 @@ description = "Support for EIP-1459 Node Discovery via DNS" [dependencies] # reth -reth-primitives = { path = "../../primitives" } +reth-primitives = { workspace = true } reth-net-common = { path = "../common" } -reth-rlp = { path = "../../rlp" } +reth-rlp = { workspace = true } # ethereum -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery", "serde"] } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery", "serde"] } enr = { version = "0.8.1", default-features = false, features = ["rust-secp256k1"] } # async/futures -tokio = { version = "1", features = ["io-util", "net", "time"] } -tokio-stream = "0.1" +tokio = { workspace = true, features = ["io-util", "net", "time"] } +tokio-stream = { workspace = true } # trust-dns trust-dns-resolver = "0.22" # misc data-encoding = "2" -async-trait = "0.1" +async-trait = { workspace = true } linked_hash_set = "0.1" schnellru = "0.2" -thiserror = "1.0" +thiserror = { workspace = true } tracing = { workspace = true } parking_lot = "0.12" -serde = { version = "1.0", optional = true } +serde = { workspace = true, optional = true } serde_with = { version = "2.1.0", optional = true } [dev-dependencies] -tokio = { version = "1", features = ["sync", "rt", "rt-multi-thread"] } +tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] } reth-tracing = { path = "../../tracing" } [features] diff --git a/crates/net/downloaders/Cargo.toml b/crates/net/downloaders/Cargo.toml index 713a3254367d..6554005db5e9 100644 --- a/crates/net/downloaders/Cargo.toml +++ b/crates/net/downloaders/Cargo.toml @@ -10,38 +10,38 @@ description = "Implementations of various block downloaders" [dependencies] # reth -reth-interfaces = { path = "../../interfaces" } -reth-primitives = { path = "../../primitives" } +reth-interfaces = { workspace = true } +reth-primitives = { workspace = true } reth-db = { path = "../../storage/db" } -reth-tasks = { path = "../../tasks" } -reth-metrics = { path = "../../metrics" } +reth-tasks = { workspace = true } +reth-metrics = { workspace = true } # async -futures = "0.3" -futures-util = "0.3.25" -pin-project = "1.0" -tokio = { version = "1.0", features = ["sync"] } -tokio-stream = "0.1" -tokio-util = { version = "0.7", features = ["codec"] } +futures = { workspace = true } +futures-util = { workspace = true } +pin-project = { workspace = true } +tokio = { workspace = true, features = ["sync"] } +tokio-stream = { workspace = true } +tokio-util = { workspace = true, features = ["codec"] } # misc tracing = { workspace = true } rayon = "1.6.0" -thiserror = "1" +thiserror = { workspace = true } # optional deps for the test-utils feature -reth-rlp = { path = "../../rlp", optional = true } +reth-rlp = { workspace = true, optional = true } tempfile = { version = "3.3", optional = true } itertools = { version = "0.10", optional = true } [dev-dependencies] reth-db = { path = "../../storage/db", features = ["test-utils"] } -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } reth-tracing = { path = "../../tracing" } assert_matches = "1.5.0" -tokio = { version = "1", features = ["macros", "rt-multi-thread"] } -reth-rlp = { path = "../../rlp" } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +reth-rlp = { workspace = true } itertools = "0.10" tempfile = "3.3" diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index 1cedb1ea3a74..1b3421d2541d 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -8,16 +8,16 @@ homepage.workspace = true repository.workspace = true [dependencies] -reth-rlp = { path = "../../rlp", features = ["derive", "ethereum-types", "std"] } -reth-primitives = { path = "../../primitives" } +reth-rlp = { workspace = true, features = ["derive", "ethereum-types", "std"] } +reth-primitives = { workspace = true } reth-net-common = { path = "../common" } -futures = "0.3.24" -thiserror = "1.0.37" -tokio = { version = "1.21.2", features = ["full"] } -tokio-stream = "0.1.11" -tokio-util = { version = "0.7.4", features = ["codec"] } -pin-project = "1.0" +futures = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["full"] } +tokio-stream = { workspace = true } +tokio-util = { workspace = true, features = ["codec"] } +pin-project = { workspace = true } educe = "0.4.19" tracing = { workspace = true } @@ -28,10 +28,10 @@ typenum = "1.15.0" byteorder = "1.4.3" # crypto -rand = "0.8.5" +rand = { workspace = true } ctr = "0.9.2" digest = "0.10.5" -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } sha2 = "0.10.6" sha3 = "0.10.5" aes = "0.8.1" diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index cd63e424291c..bdb0ed5c6317 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -10,34 +10,34 @@ repository.workspace = true [dependencies] bytes = "1.4" -thiserror = "1" -serde = { version = "1", optional = true } +thiserror = { workspace = true } +serde = { workspace = true, optional = true } # reth reth-codecs = { path = "../../storage/codecs" } -reth-primitives = { path = "../../primitives" } +reth-primitives = { workspace = true } reth-ecies = { path = "../ecies" } -reth-rlp = { path = "../../rlp", features = [ +reth-rlp = { workspace = true, features = [ "alloc", "derive", "std", "ethereum-types", "smol_str", ] } -reth-metrics = { path = "../../metrics" } +reth-metrics = { workspace = true } # used for Chain and builders ethers-core = { version = "2.0.7", default-features = false } -tokio = { version = "1.21.2", features = ["full"] } -tokio-util = { version = "0.7.4", features = ["io", "codec"] } -futures = "0.3.24" -tokio-stream = "0.1.11" -pin-project = "1.0" +tokio = { workspace = true, features = ["full"] } +tokio-util = { workspace = true, features = ["io", "codec"] } +futures = { workspace = true } +tokio-stream = { workspace = true } +pin-project = { workspace = true } tracing = { workspace = true } snap = "1.0.5" smol_str = "0.1" -async-trait = "0.1" +async-trait = { workspace = true } # arbitrary utils arbitrary = { version = "1.1.7", features = ["derive"], optional = true } @@ -45,16 +45,16 @@ proptest = { version = "1.0", optional = true } proptest-derive = { version = "0.3", optional = true } [dev-dependencies] -reth-primitives = { path = "../../primitives", features = ["arbitrary"] } +reth-primitives = { workspace = true, features = ["arbitrary"] } reth-tracing = { path = "../../tracing" } ethers-core = { version = "2.0.7", default-features = false } test-fuzz = "3.0.4" -tokio-util = { version = "0.7.4", features = ["io", "codec"] } +tokio-util = { workspace = true, features = ["io", "codec"] } hex-literal = "0.3" hex = "0.4" -rand = "0.8" -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } +rand = { workspace = true } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } arbitrary = { version = "1.1.7", features = ["derive"] } proptest = { version = "1.0" } diff --git a/crates/net/nat/Cargo.toml b/crates/net/nat/Cargo.toml index 0b146f836c06..7db0e6d99019 100644 --- a/crates/net/nat/Cargo.toml +++ b/crates/net/nat/Cargo.toml @@ -20,13 +20,13 @@ igd = { git = "https://github.com/stevefan1999-personal/rust-igd", features = [" # misc tracing = { workspace = true } pin-project-lite = "0.2.9" -tokio = { version = "1", features = ["time"] } -thiserror = "1.0" +tokio = { workspace = true, features = ["time"] } +thiserror = { workspace = true } serde_with = { version = "2.1.0", optional = true } [dev-dependencies] reth-tracing = { path = "../../tracing" } -tokio = { version = "1", features = ["macros"] } +tokio = { workspace = true, features = ["macros"] } [features] default = ["serde"] diff --git a/crates/net/network-api/Cargo.toml b/crates/net/network-api/Cargo.toml index 44cd4b1b3600..f5fb4e62a828 100644 --- a/crates/net/network-api/Cargo.toml +++ b/crates/net/network-api/Cargo.toml @@ -10,17 +10,17 @@ description = "Network interfaces" [dependencies] # reth -reth-primitives = { path = "../../primitives" } +reth-primitives = { workspace = true } reth-eth-wire = { path = "../eth-wire" } -reth-rpc-types = { path = "../../rpc/rpc-types" } +reth-rpc-types = { workspace = true } # io -serde = { version = "1.0", features = ["derive"], optional = true } +serde = { workspace = true, features = ["derive"], optional = true } # misc -async-trait = "0.1" -thiserror = "1.0.37" -tokio = { version = "1.21.2", features = ["sync"] } +async-trait = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true, features = ["sync"] } [features] default = ["serde"] diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 740941e69c42..cb1d90983075 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -18,46 +18,46 @@ normal = [ [dependencies] # reth -reth-interfaces = { path = "../../interfaces" } -reth-primitives = { path = "../../primitives" } +reth-interfaces = { workspace = true } +reth-primitives = { workspace = true } reth-net-common = { path = "../common" } -reth-network-api = { path = "../network-api" } +reth-network-api = { workspace = true } reth-discv4 = { path = "../discv4" } reth-dns-discovery = { path = "../dns" } reth-eth-wire = { path = "../eth-wire" } reth-ecies = { path = "../ecies" } -reth-rlp = { path = "../../rlp" } +reth-rlp = { workspace = true } reth-rlp-derive = { path = "../../rlp/rlp-derive" } -reth-tasks = { path = "../../tasks" } -reth-transaction-pool = { path = "../../transaction-pool" } -reth-provider = { path = "../../storage/provider" } -reth-metrics = { path = "../../metrics", features = ["common"] } -reth-rpc-types = { path = "../../rpc/rpc-types" } +reth-tasks = { workspace = true } +reth-transaction-pool = { workspace = true } +reth-provider = { workspace = true } +reth-metrics = { workspace = true, features = ["common"] } +reth-rpc-types = { workspace = true } # async/futures -futures = "0.3" -pin-project = "1.0" -tokio = { version = "1", features = ["io-util", "net", "macros", "rt-multi-thread", "time"] } -tokio-stream = "0.1" -tokio-util = { version = "0.7", features = ["codec"] } +futures = { workspace = true } +pin-project = { workspace = true } +tokio = { workspace = true, features = ["io-util", "net", "macros", "rt-multi-thread", "time"] } +tokio-stream = { workspace = true } +tokio-util = { workspace = true, features = ["codec"] } # io -serde = { version = "1.0", optional = true } +serde = { workspace = true, optional = true } humantime-serde = { version = "1.1", optional = true } -serde_json = { version = "1.0", optional = true } +serde_json = { workspace = true, optional = true } # misc auto_impl = "1" aquamarine = "0.3.0" tracing = { workspace = true } fnv = "1.0" -thiserror = "1.0" +thiserror = { workspace = true } parking_lot = "0.12" -async-trait = "0.1" +async-trait = { workspace = true } linked_hash_set = "0.1" linked-hash-map = "0.5.6" -rand = "0.8" -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } +rand = { workspace = true } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } enr = { version = "0.8.1", features = ["rust-secp256k1"], optional = true } ethers-core = { version = "2.0.7", default-features = false, optional = true } @@ -66,15 +66,15 @@ tempfile = { version = "3.3", optional = true } [dev-dependencies] # reth reth-discv4 = { path = "../discv4", features = ["test-utils"] } -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } # we need to enable the test-utils feature in our own crate to use utils in # integration tests reth-network = { path = ".", features = ["test-utils"] } -reth-provider = { path = "../../storage/provider", features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } reth-tracing = { path = "../../tracing" } -reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"] } +reth-transaction-pool = { workspace = true, features = ["test-utils"] } ethers-core = { version = "2.0.7", default-features = false } ethers-providers = { version = "2.0.7", default-features = false } diff --git a/crates/payload/basic/Cargo.toml b/crates/payload/basic/Cargo.toml index a939c6378da7..4f3c19ceaf24 100644 --- a/crates/payload/basic/Cargo.toml +++ b/crates/payload/basic/Cargo.toml @@ -10,22 +10,22 @@ description = "A basic payload builder for reth that uses the txpool API to buil [dependencies] ## reth -reth-primitives = { path = "../../primitives" } +reth-primitives = { workspace = true } reth-revm = { path = "../../revm" } -reth-transaction-pool = { path = "../../transaction-pool" } -reth-rlp = { path = "../../rlp" } -reth-provider = { path = "../../storage/provider" } -reth-payload-builder = { path = "../builder" } -reth-tasks = { path = "../../tasks" } -reth-metrics = { path = "../../metrics" } +reth-transaction-pool = { workspace = true } +reth-rlp = { workspace = true } +reth-provider = { workspace = true } +reth-payload-builder = { workspace = true } +reth-tasks = { workspace = true } +reth-metrics = { workspace = true } ## ethereum revm = { workspace = true } ## async -tokio = { version = "1", features = ["sync", "time"] } +tokio = { workspace = true, features = ["sync", "time"] } futures-core = "0.3" -futures-util = "0.3" +futures-util = { workspace = true } ## misc tracing = { workspace = true } diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index 650d8a5125de..f180c6cdaaed 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -10,23 +10,23 @@ description = "reth payload builder" [dependencies] ## reth -reth-primitives = { path = "../../primitives" } -reth-rpc-types = { path = "../../rpc/rpc-types" } -reth-rlp = { path = "../../rlp" } -reth-interfaces = { path = "../../interfaces" } +reth-primitives = { workspace = true } +reth-rpc-types = { workspace = true } +reth-rlp = { workspace = true } +reth-interfaces = { workspace = true } reth-revm-primitives = { path = "../../revm/revm-primitives" } -reth-metrics = { path = "../../metrics" } +reth-metrics = { workspace = true } ## ethereum revm-primitives = { workspace = true } ## async -tokio = { version = "1", features = ["sync"] } -tokio-stream = "0.1" -futures-util = "0.3" +tokio = { workspace = true, features = ["sync"] } +tokio-stream = { workspace = true } +futures-util = { workspace = true } ## misc -thiserror = "1.0" +thiserror = { workspace = true } sha2 = { version = "0.10", default-features = false } tracing = { workspace = true } hashbrown = "0.13" diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index c7ab1044e48f..a47af1c2010d 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -10,7 +10,7 @@ description = "Commonly used types in reth." [dependencies] # reth -reth-rlp = { path = "../rlp", features = ["std", "derive", "ethereum-types"] } +reth-rlp = { workspace = true, features = ["std", "derive", "ethereum-types"] } reth-rlp-derive = { path = "../rlp/rlp-derive" } reth-codecs = { version = "0.1.0", path = "../storage/codecs" } @@ -26,7 +26,7 @@ ruint = { version = "1.7.0", features = ["primitive-types", "rlp"] } fixed-hash = { version = "0.8", default-features = false, features = ["rustc-hex"] } # crypto -secp256k1 = { version = "0.27.0", default-features = false, features = [ +secp256k1 = { workspace = true, default-features = false, features = [ "global-context", "alloc", "recovery", @@ -39,15 +39,15 @@ crc = "3" tracing = { workspace = true } # tokio -tokio = { version = "1", default-features = false, features = ["sync"] } -tokio-stream = "0.1" +tokio = { workspace = true, default-features = false, features = ["sync"] } +tokio-stream = { workspace = true } # misc bytes = "1.4" -serde = "1.0" -serde_json = "1.0" +serde = { workspace = true } +serde_json = { workspace = true } serde_with = "2.1.0" -thiserror = "1" +thiserror = { workspace = true } sucds = "0.5.0" hex = "0.4" hex-literal = "0.3" @@ -71,10 +71,10 @@ proptest-derive = { version = "0.3", optional = true } strum = { version = "0.24", features = ["derive"] } [dev-dependencies] -serde_json = "1.0" +serde_json = { workspace = true } hex-literal = "0.3" test-fuzz = "3.0.4" -rand = "0.8" +rand = { workspace = true } revm-primitives = { workspace = true, features = ["arbitrary"] } arbitrary = { version = "1.1.7", features = ["derive"] } proptest = { version = "1.0" } @@ -82,7 +82,7 @@ proptest-derive = "0.3" # necessary so we don't hit a "undeclared 'std'": # https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198 -secp256k1 = "0.27.0" +secp256k1 = { workspace = true } criterion = "0.4.0" pprof = { version = "0.11", features = ["flamegraph", "frame-pointer", "criterion"] } diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index cb9e0a0600a9..5e3f9673454a 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -10,9 +10,9 @@ description = "reth specific revm utilities" [dependencies] # reth -reth-primitives = { path = "../primitives" } -reth-interfaces = { path = "../interfaces" } -reth-provider = { path = "../storage/provider" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } +reth-provider = { workspace = true } reth-revm-primitives = { path = "./revm-primitives" } reth-revm-inspectors = { path = "./revm-inspectors" } reth-consensus-common = { path = "../consensus/common" } @@ -24,5 +24,5 @@ revm = { workspace = true } tracing = { workspace = true } [dev-dependencies] -reth-rlp = { path = "../rlp" } +reth-rlp = { workspace = true } once_cell = "1.17.0" diff --git a/crates/revm/revm-inspectors/Cargo.toml b/crates/revm/revm-inspectors/Cargo.toml index 5d8730750752..066d652149aa 100644 --- a/crates/revm/revm-inspectors/Cargo.toml +++ b/crates/revm/revm-inspectors/Cargo.toml @@ -10,11 +10,11 @@ description = "revm inspector implementations used by reth" [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-rpc-types = { path = "../../rpc/rpc-types" } +reth-primitives = { workspace = true } +reth-rpc-types = { workspace = true } revm = { workspace = true } # remove from reth and reexport from revm hashbrown = "0.13" -serde = { version = "1.0", features = ["derive"] } +serde = { workspace = true, features = ["derive"] } diff --git a/crates/revm/revm-primitives/Cargo.toml b/crates/revm/revm-primitives/Cargo.toml index 8fd10a6b5fff..7ada96d4b9cc 100644 --- a/crates/revm/revm-primitives/Cargo.toml +++ b/crates/revm/revm-primitives/Cargo.toml @@ -10,6 +10,6 @@ description = "core reth specific revm utilities" [dependencies] # reth -reth-primitives = { path = "../../primitives" } +reth-primitives = { workspace = true } revm = { workspace = true } diff --git a/crates/rlp/Cargo.toml b/crates/rlp/Cargo.toml index 3e31af61418e..8ac894e3f16d 100644 --- a/crates/rlp/Cargo.toml +++ b/crates/rlp/Cargo.toml @@ -19,7 +19,7 @@ revm-primitives = { workspace = true, features = ["serde"] } reth-rlp-derive = { version = "0.1", path = "./rlp-derive", optional = true } [dev-dependencies] -reth-rlp = { path = ".", package = "reth-rlp", features = [ +reth-rlp = { workspace = true, features = [ "derive", "std", "ethnum", diff --git a/crates/rpc/ipc/Cargo.toml b/crates/rpc/ipc/Cargo.toml index 28124197b039..78aef3dc37c4 100644 --- a/crates/rpc/ipc/Cargo.toml +++ b/crates/rpc/ipc/Cargo.toml @@ -13,25 +13,25 @@ IPC support for reth [dependencies] # async/net -futures = "0.3" +futures = { workspace = true } parity-tokio-ipc = "0.9.0" -tokio = { version = "1", features = ["net", "time", "rt-multi-thread"] } -tokio-util = { version = "0.7", features = ["codec"] } -tokio-stream = "0.1" -async-trait = "0.1" -pin-project = "1.0" +tokio = { workspace = true, features = ["net", "time", "rt-multi-thread"] } +tokio-util = { workspace = true, features = ["codec"] } +tokio-stream = { workspace = true } +async-trait = { workspace = true } +pin-project = { workspace = true } tower = "0.4" # misc jsonrpsee = { version = "0.18", features = ["server", "client"] } -serde_json = "1.0" +serde_json = { workspace = true } tracing = { workspace = true } bytes = "1.4" -thiserror = "1.0.37" +thiserror = { workspace = true } [dev-dependencies] tracing-test = "0.2" -tokio-stream = { version = "0.1", features = ["sync"] } +tokio-stream = { workspace = true, features = ["sync"] } [features] client = ["jsonrpsee/client", "jsonrpsee/async-client"] diff --git a/crates/rpc/rpc-api/Cargo.toml b/crates/rpc/rpc-api/Cargo.toml index 1162c334618f..63aa8974bf05 100644 --- a/crates/rpc/rpc-api/Cargo.toml +++ b/crates/rpc/rpc-api/Cargo.toml @@ -12,12 +12,12 @@ Reth RPC interfaces [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-rpc-types = { path = "../rpc-types" } +reth-primitives = { workspace = true } +reth-rpc-types = { workspace = true } # misc jsonrpsee = { version = "0.18", features = ["server", "macros"] } -serde_json = "1.0" +serde_json = { workspace = true } [features] client = ["jsonrpsee/client", "jsonrpsee/async-client"] diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index 951eb3cdecde..b318f539f426 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -10,17 +10,17 @@ description = "Helpers for configuring RPC" [dependencies] # reth -reth-primitives = { path = "../../primitives" } +reth-primitives = { workspace = true } reth-ipc = { path = "../ipc" } -reth-interfaces = { path = "../../interfaces" } -reth-network-api = { path = "../../net/network-api" } -reth-provider = { path = "../../storage/provider" } +reth-interfaces = { workspace = true } +reth-network-api = { workspace = true } +reth-provider = { workspace = true } reth-rpc = { path = "../rpc" } reth-rpc-api = { path = "../rpc-api" } reth-rpc-engine-api = { path = "../rpc-engine-api" } -reth-rpc-types = { path = "../rpc-types" } -reth-tasks = { path = "../../tasks" } -reth-transaction-pool = { path = "../../transaction-pool" } +reth-rpc-types = { workspace = true } +reth-tasks = { workspace = true } +reth-transaction-pool = { workspace = true } # rpc/net jsonrpsee = { version = "0.18", features = ["server"] } @@ -30,19 +30,19 @@ hyper = "0.14" # misc strum = { version = "0.24", features = ["derive"] } -serde = { version = "1.0", features = ["derive"] } -thiserror = "1.0" +serde = { workspace = true, features = ["derive"] } +thiserror = { workspace = true } tracing = { workspace = true } [dev-dependencies] reth-tracing = { path = "../../tracing" } reth-rpc-api = { path = "../rpc-api", features = ["client"] } -reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"] } -reth-provider = { path = "../../storage/provider", features = ["test-utils"] } -reth-network-api = { path = "../../net/network-api", features = ["test-utils"] } -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } +reth-transaction-pool = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } +reth-network-api = { workspace = true, features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } reth-beacon-consensus = { path = "../../consensus/beacon" } -reth-payload-builder = { path = "../../payload/builder", features = ["test-utils"] } +reth-payload-builder = { workspace = true, features = ["test-utils"] } -tokio = { version = "1", features = ["rt", "rt-multi-thread"] } -serde_json = "1.0.94" +tokio = { workspace = true, features = ["rt", "rt-multi-thread"] } +serde_json = { workspace = true } diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index a66a752210b4..bd1a4afac889 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -10,26 +10,26 @@ description = "Implementation of Engine API" [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-interfaces = { path = "../../interfaces" } -reth-provider = { path = "../../storage/provider" } -reth-rpc-types = { path = "../rpc-types" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } +reth-provider = { workspace = true } +reth-rpc-types = { workspace = true } reth-rpc-api = { path = "../rpc-api" } reth-beacon-consensus = { path = "../../consensus/beacon" } -reth-payload-builder = { path = "../../payload/builder" } +reth-payload-builder = { workspace = true } # async -tokio = { version = "1", features = ["sync"] } +tokio = { workspace = true, features = ["sync"] } # misc -async-trait = "0.1" -thiserror = "1.0.37" +async-trait = { workspace = true } +thiserror = { workspace = true } jsonrpsee-types = "0.18" jsonrpsee-core = "0.18" tracing = { workspace = true } [dev-dependencies] -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } -reth-provider = { path = "../../storage/provider", features = ["test-utils"] } -reth-payload-builder = { path = "../../payload/builder", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } +reth-provider = { workspace = true, features = ["test-utils"] } +reth-payload-builder = { workspace = true, features = ["test-utils"] } assert_matches = "1.5.0" diff --git a/crates/rpc/rpc-testing-util/Cargo.toml b/crates/rpc/rpc-testing-util/Cargo.toml index dd785dace20a..e9c7b8aec1b2 100644 --- a/crates/rpc/rpc-testing-util/Cargo.toml +++ b/crates/rpc/rpc-testing-util/Cargo.toml @@ -12,17 +12,17 @@ Reth RPC testing helpers [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-rpc-types = { path = "../rpc-types" } +reth-primitives = { workspace = true } +reth-rpc-types = { workspace = true } reth-rpc-api = { path = "../rpc-api", default-features = false, features = ["client"] } # async -async-trait = "0.1" -futures = "0.3" +async-trait = { workspace = true } +futures = { workspace = true } # misc jsonrpsee = { version = "0.18", features = ["client", "async-client"] } [dev-dependencies] -tokio = { version = "1", features = ["rt-multi-thread", "macros", "rt"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros", "rt"] } diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index a4c7f9446414..f92ced2a42b1 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -12,26 +12,26 @@ Reth RPC types [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-rlp = { path = "../../rlp" } +reth-primitives = { workspace = true } +reth-rlp = { workspace = true } # errors -thiserror = "1.0" +thiserror = { workspace = true } # async -tokio = { version = "1", features = ["sync"] } +tokio = { workspace = true, features = ["sync"] } # misc -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } jsonrpsee-types = { version = "0.18" } lru = "0.9" [dev-dependencies] # reth -reth-interfaces = { path = "../../interfaces", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } # misc -rand = "0.8" +rand = { workspace = true } assert_matches = "1.5" similar-asserts = "1.4" diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 0ef5352503d9..b39f3e6a6d16 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -11,17 +11,17 @@ Reth RPC implementation """ [dependencies] # reth -reth-interfaces = { path = "../../interfaces" } -reth-primitives = { path = "../../primitives" } +reth-interfaces = { workspace = true } +reth-primitives = { workspace = true } reth-rpc-api = { path = "../rpc-api" } -reth-rlp = { path = "../../rlp" } -reth-rpc-types = { path = "../rpc-types" } -reth-provider = { path = "../../storage/provider", features = ["test-utils"] } -reth-transaction-pool = { path = "../../transaction-pool", features = ["test-utils"] } -reth-network-api = { path = "../../net/network-api", features = ["test-utils"] } +reth-rlp = { workspace = true } +reth-rpc-types = { workspace = true } +reth-provider = { workspace = true, features = ["test-utils"] } +reth-transaction-pool = { workspace = true, features = ["test-utils"] } +reth-network-api = { workspace = true, features = ["test-utils"] } reth-rpc-engine-api = { path = "../rpc-engine-api" } reth-revm = { path = "../../revm" } -reth-tasks = { path = "../../tasks" } +reth-tasks = { workspace = true } # eth revm = { workspace = true, features = [ @@ -40,24 +40,24 @@ hyper = "0.14.24" jsonwebtoken = "8" # async -async-trait = "0.1" -tokio = { version = "1", features = ["sync"] } +async-trait = { workspace = true } +tokio = { workspace = true, features = ["sync"] } tower = "0.4" -tokio-stream = { version = "0.1", features = ["sync"] } +tokio-stream = { workspace = true, features = ["sync"] } tokio-util = "0.7" -pin-project = "1.0" +pin-project = { workspace = true } bytes = "1.4" -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -thiserror = "1.0" +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } +thiserror = { workspace = true } hex = "0.4" -rand = "0.8.5" +rand = { workspace = true } tracing = { workspace = true } tracing-futures = "0.2" schnellru = "0.2" -futures = "0.3.26" +futures = { workspace = true } [dev-dependencies] jsonrpsee = { version = "0.18", features = ["client"] } diff --git a/crates/staged-sync/Cargo.toml b/crates/staged-sync/Cargo.toml index b224a4a9de38..f0e98d825721 100644 --- a/crates/staged-sync/Cargo.toml +++ b/crates/staged-sync/Cargo.toml @@ -12,18 +12,18 @@ description = "Puts together all the Reth stages in a unified abstraction" # reth reth-db = { path = "../../crates/storage/db", features = ["mdbx", "test-utils"] } reth-discv4 = { path = "../../crates/net/discv4" } -reth-network-api = { path = "../../crates/net/network-api" } +reth-network-api = { workspace = true } reth-network = { path = "../../crates/net/network", features = ["serde"] } reth-downloaders = { path = "../../crates/net/downloaders" } -reth-primitives = { path = "../../crates/primitives" } -reth-provider = { path = "../../crates/storage/provider", features = ["test-utils"] } +reth-primitives = { workspace = true } +reth-provider = { workspace = true, features = ["test-utils"] } reth-net-nat = { path = "../../crates/net/nat" } reth-stages = { path = "../stages" } -reth-interfaces = { path = "../interfaces" } +reth-interfaces = { workspace = true } # io serde = "1.0" -serde_json = "1.0.91" +serde_json = { workspace = true } # misc walkdir = "2.3.2" @@ -32,11 +32,11 @@ shellexpand = "3.0.0" tracing = { workspace = true } # crypto -rand = { version = "0.8", optional = true } -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } +rand = { workspace = true, optional = true } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } # errors -thiserror = "1" +thiserror = { workspace = true } # enr enr = { version = "0.8.1", features = ["serde", "rust-secp256k1"], optional = true } @@ -50,8 +50,8 @@ ethers-middleware = { version = "2.0.7", default-features = false, optional = tr ethers-signers = { version = "2.0.7", default-features = false, optional = true } # async / futures -async-trait = { version = "0.1", optional = true } -tokio = { version = "1", features = [ +async-trait = { workspace = true, optional = true } +tokio = { workspace = true, features = [ "io-util", "net", "macros", @@ -72,11 +72,11 @@ reth-tracing = { path = "../tracing" } reth-downloaders = { path = "../net/downloaders" } # async/futures -futures = "0.3" -tokio = { version = "1", features = ["io-util", "net", "macros", "rt-multi-thread", "time"] } +futures = { workspace = true } +tokio = { workspace = true, features = ["io-util", "net", "macros", "rt-multi-thread", "time"] } # crypto -secp256k1 = { version = "0.27.0", features = ["global-context", "rand-std", "recovery"] } +secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } confy = "0.5" diff --git a/crates/stages/Cargo.toml b/crates/stages/Cargo.toml index af129c86e24a..e8db9ce29748 100644 --- a/crates/stages/Cargo.toml +++ b/crates/stages/Cargo.toml @@ -16,26 +16,26 @@ normal = [ [dependencies] # reth -reth-primitives = { path = "../primitives" } -reth-interfaces = { path = "../interfaces" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } reth-db = { path = "../storage/db" } reth-codecs = { path = "../storage/codecs" } -reth-provider = { path = "../storage/provider" } -reth-metrics = { path = "../metrics" } +reth-provider = { workspace = true } +reth-metrics = { workspace = true } reth-trie = { path = "../trie" } # async -tokio = { version = "1.21.2", features = ["sync"] } -tokio-stream = "0.1.10" -async-trait = "0.1.57" -futures-util = "0.3.25" -pin-project = "1.0.12" +tokio = { workspace = true, features = ["sync"] } +tokio-stream = { workspace = true } +async-trait = { workspace = true } +futures-util = { workspace = true } +pin-project = { workspace = true } # observability tracing = { workspace = true } # misc -thiserror = "1.0.37" +thiserror = { workspace = true } aquamarine = "0.3.0" itertools = "0.10.5" rayon = "1.6.0" @@ -43,20 +43,20 @@ num-traits = "0.2.15" [dev-dependencies] # reth -reth-primitives = { path = "../primitives", features = ["arbitrary"] } +reth-primitives = { workspace = true, features = ["arbitrary"] } reth-db = { path = "../storage/db", features = ["test-utils", "mdbx"] } -reth-interfaces = { path = "../interfaces", features = ["test-utils"] } +reth-interfaces = { workspace = true, features = ["test-utils"] } reth-downloaders = { path = "../net/downloaders" } reth-eth-wire = { path = "../net/eth-wire" } # TODO(onbjerg): We only need this for [BlockBody] reth-blockchain-tree = { path = "../blockchain-tree" } -reth-rlp = { path = "../rlp" } +reth-rlp = { workspace = true } reth-revm = { path = "../revm" } reth-trie = { path = "../trie", features = ["test-utils"] } itertools = "0.10.5" -tokio = { version = "*", features = ["rt", "sync", "macros"] } +tokio = { workspace = true, features = ["rt", "sync", "macros"] } assert_matches = "1.5.0" -rand = "0.8.5" +rand = { workspace = true } paste = "1.0" # Stage benchmarks diff --git a/crates/storage/codecs/derive/Cargo.toml b/crates/storage/codecs/derive/Cargo.toml index 7cf9700ee57f..09f9f16855b9 100644 --- a/crates/storage/codecs/derive/Cargo.toml +++ b/crates/storage/codecs/derive/Cargo.toml @@ -26,7 +26,7 @@ syn = { version = "2.0", features = ["full"] } convert_case = "0.6.0" # codecs -serde = { version = "1.0.*", default-features = false } +serde = { workspace = true, default-features = false } parity-scale-codec = { version = "3.2.1", features = ["derive", "bytes"] } [features] diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 8d91fe853e9c..c32d17b4aa5c 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -10,21 +10,21 @@ description = "Staged syncing primitives used in reth." [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-interfaces = { path = "../../interfaces" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } reth-codecs = { path = "../codecs" } reth-libmdbx = { path = "../libmdbx-rs", optional = true, features = ["return-borrowed"] } -reth-metrics = { path = "../../metrics" } +reth-metrics = { workspace = true } # codecs -serde = { version = "1.0.*", default-features = false } +serde = { workspace = true, default-features = false } postcard = { version = "1.0.2", features = ["alloc"] } heapless = "0.7.16" parity-scale-codec = { version = "3.2.1", features = ["bytes"] } -futures = "0.3.25" -tokio-stream = "0.1.11" -rand = "0.8.5" -secp256k1 = { version = "0.27.0", default-features = false, features = [ +futures = { workspace = true } +tokio-stream = { workspace = true } +rand = { workspace = true } +secp256k1 = { workspace = true, default-features = false, features = [ "alloc", "recovery", "rand", @@ -34,7 +34,7 @@ modular-bitfield = "0.11.2" # misc bytes = "1.4" page_size = "0.4.2" -thiserror = "1.0.37" +thiserror = { workspace = true } tempfile = { version = "3.3.0", optional = true } parking_lot = "0.12" @@ -45,9 +45,9 @@ proptest-derive = { version = "0.3", optional = true } [dev-dependencies] # reth libs with arbitrary -reth-primitives = { path = "../../primitives", features = ["arbitrary"] } +reth-primitives = { workspace = true, features = ["arbitrary"] } reth-codecs = { path = "../codecs", features = ["arbitrary"] } -reth-interfaces = { path = "../../interfaces", features = ["bench"] } +reth-interfaces = { workspace = true, features = ["bench"] } tempfile = "3.3.0" test-fuzz = "3.0.4" @@ -55,19 +55,19 @@ test-fuzz = "3.0.4" pprof = { version = "0.11", features = ["flamegraph", "frame-pointer", "criterion"] } criterion = "0.4.0" iai = "0.1.1" -tokio = { version = "1.21.2", features = ["full"] } +tokio = { workspace = true, features = ["full"] } reth-db = { path = ".", features = ["test-utils", "bench"] } # needed for test-fuzz to work properly, see https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198 -secp256k1 = "0.27.0" +secp256k1 = { workspace = true } -async-trait = "0.1.58" +async-trait = { workspace = true } arbitrary = { version = "1.1.7", features = ["derive"] } proptest = { version = "1.0" } proptest-derive = "0.3" -serde_json = "1.0" +serde_json = { workspace = true } paste = "1.0" diff --git a/crates/storage/libmdbx-rs/Cargo.toml b/crates/storage/libmdbx-rs/Cargo.toml index 85ca8b962e74..8cfd762329c6 100644 --- a/crates/storage/libmdbx-rs/Cargo.toml +++ b/crates/storage/libmdbx-rs/Cargo.toml @@ -18,7 +18,7 @@ derive_more = "0.99" indexmap = "1" libc = "0.2" parking_lot = "0.12" -thiserror = "1" +thiserror = { workspace = true } ffi = { package = "reth-mdbx-sys", path = "./mdbx-sys" } @@ -31,7 +31,7 @@ return-borrowed = [] [dev-dependencies] pprof = { version = "0.11", features = ["flamegraph", "frame-pointer", "criterion"] } criterion = "0.4" -rand = "0.8" +rand = { workspace = true } rand_xorshift = "0.3" tempfile = "3" diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index b2c88deed7d5..9c39874e9d84 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -10,34 +10,34 @@ description = "Reth storage provider." [dependencies] # reth -reth-primitives = { path = "../../primitives" } -reth-interfaces = { path = "../../interfaces" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } reth-revm-primitives = { path = "../../revm/revm-primitives" } reth-db = { path = "../db" } reth-trie = { path = "../../trie" } # async -tokio = { version = "1.21", features = ["sync", "macros", "rt-multi-thread"] } -tokio-stream = { version = "0.1", features = ["sync"] } +tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] } +tokio-stream = { workspace = true, features = ["sync"] } # tracing tracing = { workspace = true } # misc -thiserror = "1.0.37" +thiserror = { workspace = true } auto_impl = "1.0" itertools = "0.10" -pin-project = "1.0" +pin-project = { workspace = true } derive_more = "0.99" parking_lot = "0.12" # test-utils -reth-rlp = { path = "../../rlp", optional = true } +reth-rlp = { workspace = true, optional = true } [dev-dependencies] reth-db = { path = "../db", features = ["test-utils"] } -reth-primitives = { path = "../../primitives", features = ["arbitrary", "test-utils"] } -reth-rlp = { path = "../../rlp" } +reth-primitives = { workspace = true, features = ["arbitrary", "test-utils"] } +reth-rlp = { workspace = true } reth-trie = { path = "../../trie", features = ["test-utils"] } parking_lot = "0.12" diff --git a/crates/tasks/Cargo.toml b/crates/tasks/Cargo.toml index f1c2ecc24e01..d0f5a3206178 100644 --- a/crates/tasks/Cargo.toml +++ b/crates/tasks/Cargo.toml @@ -11,17 +11,17 @@ description = "Task management" [dependencies] ## async -tokio = { version = "1", features = ["sync", "rt"] } +tokio = { workspace = true, features = ["sync", "rt"] } tracing-futures = "0.2" -futures-util = "0.3" +futures-util = { workspace = true } ## misc tracing = { workspace = true } -thiserror = "1.0" +thiserror = { workspace = true } dyn-clone = "1.0" ## rpc/metrics -reth-metrics = { path = "../metrics" } +reth-metrics = { workspace = true } [dev-dependencies] -tokio = { version = "1", features = ["sync", "rt", "rt-multi-thread", "time", "macros"] } +tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time", "macros"] } diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index 4f5f03790a57..4a9df81266a9 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -18,29 +18,29 @@ normal = [ [dependencies] # reth -reth-primitives = { path = "../primitives" } -reth-provider = { path = "../storage/provider" } -reth-interfaces = { path = "../interfaces" } -reth-rlp = { path = "../rlp" } -reth-metrics = { path = "../metrics" } +reth-primitives = { workspace = true } +reth-provider = { workspace = true } +reth-interfaces = { workspace = true } +reth-rlp = { workspace = true } +reth-metrics = { workspace = true } # async/futures -async-trait = "0.1" -futures-util = "0.3" +async-trait = { workspace = true} +futures-util = { workspace = true } parking_lot = "0.12" -tokio = { version = "1", default-features = false, features = ["sync"] } +tokio = { workspace = true, default-features = false, features = ["sync"] } # misc aquamarine = "0.3.0" -thiserror = "1.0" +thiserror = { workspace = true } tracing = { workspace = true } -serde = { version = "1.0", features = ["derive", "rc"], optional = true } +serde = { workspace = true, features = ["derive", "rc"], optional = true } fnv = "1.0.7" bitflags = "1.3" auto_impl = "1.0" # testing -rand = { version = "0.8", optional = true } +rand = { workspace = true, optional = true } paste = { version = "1.0", optional = true } [dev-dependencies] diff --git a/crates/trie/Cargo.toml b/crates/trie/Cargo.toml index 994a1ae7bc54..d772cffd22be 100644 --- a/crates/trie/Cargo.toml +++ b/crates/trie/Cargo.toml @@ -12,20 +12,20 @@ Merkle trie implementation [dependencies] # reth -reth-primitives = { path = "../primitives" } -reth-interfaces = { path = "../interfaces" } -reth-rlp = { path = "../rlp" } +reth-primitives = { workspace = true } +reth-interfaces = { workspace = true } +reth-rlp = { workspace = true } reth-db = { path = "../storage/db" } # tokio -tokio = { version = "1.21.2", default-features = false, features = ["sync"] } +tokio = { workspace = true, default-features = false, features = ["sync"] } # tracing tracing = { workspace = true } # misc hex = "0.4" -thiserror = "1.0" +thiserror = { workspace = true } derive_more = "0.99" # test-utils @@ -33,17 +33,17 @@ triehash = { version = "0.8", optional = true } [dev-dependencies] # reth -reth-primitives = { path = "../primitives", features = ["test-utils", "arbitrary"] } +reth-primitives = { workspace = true, features = ["test-utils", "arbitrary"] } reth-db = { path = "../storage/db", features = ["test-utils"] } -reth-provider = { path = "../storage/provider" } +reth-provider = { workspace = true } # trie triehash = "0.8" # misc proptest = "1.0" -tokio = { version = "1.21.2", default-features = false, features = ["sync", "rt", "macros"] } -tokio-stream = "0.1.10" +tokio = { workspace = true, default-features = false, features = ["sync", "rt", "macros"] } +tokio-stream = { workspace = true } criterion = "0.4" [features] diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index 9071b5202dba..b6dae6194823 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -12,15 +12,15 @@ repository.workspace = true ef-tests = [] [dependencies] -reth-primitives = { path = "../../crates/primitives" } +reth-primitives = { workspace = true } reth-db = { path = "../../crates/storage/db", features = ["mdbx", "test-utils"] } -reth-provider = { path = "../../crates/storage/provider" } +reth-provider = { workspace = true } reth-stages = { path = "../../crates/stages" } -reth-rlp = { path = "../../crates/rlp" } -reth-interfaces = { path = "../../crates/interfaces" } +reth-rlp = { workspace = true } +reth-interfaces = { workspace = true } reth-revm = { path = "../../crates/revm" } tokio = "1.28.1" walkdir = "2.3.3" serde = "1.0.163" -serde_json = "1.0.96" -thiserror = "1.0.40" +serde_json = { workspace = true } +thiserror = { workspace = true } From 724f480bbb702f5958f8b53aebe12e401454e64b Mon Sep 17 00:00:00 2001 From: Bjerg Date: Wed, 14 Jun 2023 08:49:11 +0200 Subject: [PATCH 016/216] Revert "refactor(stages): input target reached & output done checks" (#3136) --- bin/reth/src/debug_cmd/merkle.rs | 34 +++-- bin/reth/src/node/events.rs | 3 +- bin/reth/src/stage/dump/hashing_account.rs | 15 +- bin/reth/src/stage/dump/hashing_storage.rs | 15 +- bin/reth/src/stage/dump/merkle.rs | 19 ++- bin/reth/src/stage/run.rs | 13 +- crates/consensus/beacon/src/engine/mod.rs | 87 +++++------ crates/consensus/beacon/src/engine/sync.rs | 6 + crates/stages/src/pipeline/event.rs | 4 - crates/stages/src/pipeline/mod.rs | 138 +++++++----------- crates/stages/src/stage.rs | 54 +++---- crates/stages/src/stages/bodies.rs | 23 ++- crates/stages/src/stages/execution.rs | 11 +- crates/stages/src/stages/finish.rs | 10 +- crates/stages/src/stages/hashing_account.rs | 25 +++- crates/stages/src/stages/hashing_storage.rs | 30 ++-- crates/stages/src/stages/headers.rs | 10 +- .../src/stages/index_account_history.rs | 19 ++- .../src/stages/index_storage_history.rs | 21 ++- crates/stages/src/stages/merkle.rs | 16 +- crates/stages/src/stages/sender_recovery.rs | 29 ++-- crates/stages/src/stages/total_difficulty.rs | 19 ++- crates/stages/src/stages/tx_lookup.rs | 28 ++-- crates/stages/src/test_utils/macros.rs | 53 ++++++- crates/stages/src/test_utils/stage.rs | 36 +---- .../src/providers/database/provider.rs | 6 - 26 files changed, 392 insertions(+), 332 deletions(-) diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index 909203e02b79..c05fc6c54b94 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -120,22 +120,30 @@ impl Command { let mut account_hashing_done = false; while !account_hashing_done { - let input = ExecInput { - target: Some(block), - checkpoint: progress.map(StageCheckpoint::new), - }; - let output = account_hashing_stage.execute(&mut provider_rw, input).await?; - account_hashing_done = output.is_done(input); + let output = account_hashing_stage + .execute( + &mut provider_rw, + ExecInput { + target: Some(block), + checkpoint: progress.map(StageCheckpoint::new), + }, + ) + .await?; + account_hashing_done = output.done; } let mut storage_hashing_done = false; while !storage_hashing_done { - let input = ExecInput { - target: Some(block), - checkpoint: progress.map(StageCheckpoint::new), - }; - let output = storage_hashing_stage.execute(&mut provider_rw, input).await?; - storage_hashing_done = output.is_done(input); + let output = storage_hashing_stage + .execute( + &mut provider_rw, + ExecInput { + target: Some(block), + checkpoint: progress.map(StageCheckpoint::new), + }, + ) + .await?; + storage_hashing_done = output.done; } let incremental_result = merkle_stage @@ -165,7 +173,7 @@ impl Command { loop { let clean_result = merkle_stage.execute(&mut provider_rw, clean_input).await; assert!(clean_result.is_ok(), "Clean state root calculation failed"); - if clean_result.unwrap().is_done(clean_input) { + if clean_result.unwrap().done { break } } diff --git a/bin/reth/src/node/events.rs b/bin/reth/src/node/events.rs index fdc56ebd86bd..04b36b59cc65 100644 --- a/bin/reth/src/node/events.rs +++ b/bin/reth/src/node/events.rs @@ -72,8 +72,7 @@ impl NodeState { pipeline_position, pipeline_total, stage_id, - result: ExecOutput { checkpoint }, - done, + result: ExecOutput { checkpoint, done }, } => { self.current_checkpoint = checkpoint; diff --git a/bin/reth/src/stage/dump/hashing_account.rs b/bin/reth/src/stage/dump/hashing_account.rs index 690c1f40e634..d63a14cc82c1 100644 --- a/bin/reth/src/stage/dump/hashing_account.rs +++ b/bin/reth/src/stage/dump/hashing_account.rs @@ -77,11 +77,16 @@ async fn dry_run( let mut exec_output = false; while !exec_output { - let exec_input = reth_stages::ExecInput { - target: Some(to), - checkpoint: Some(StageCheckpoint::new(from)), - }; - exec_output = exec_stage.execute(&mut provider, exec_input).await?.is_done(exec_input); + exec_output = exec_stage + .execute( + &mut provider, + reth_stages::ExecInput { + target: Some(to), + checkpoint: Some(StageCheckpoint::new(from)), + }, + ) + .await? + .done; } info!(target: "reth::cli", "Success."); diff --git a/bin/reth/src/stage/dump/hashing_storage.rs b/bin/reth/src/stage/dump/hashing_storage.rs index a022ef30dda1..6e717544c7ad 100644 --- a/bin/reth/src/stage/dump/hashing_storage.rs +++ b/bin/reth/src/stage/dump/hashing_storage.rs @@ -76,11 +76,16 @@ async fn dry_run( let mut exec_output = false; while !exec_output { - let exec_input = reth_stages::ExecInput { - target: Some(to), - checkpoint: Some(StageCheckpoint::new(from)), - }; - exec_output = exec_stage.execute(&mut provider, exec_input).await?.is_done(exec_input); + exec_output = exec_stage + .execute( + &mut provider, + reth_stages::ExecInput { + target: Some(to), + checkpoint: Some(StageCheckpoint::new(from)), + }, + ) + .await? + .done; } info!(target: "reth::cli", "Success."); diff --git a/bin/reth/src/stage/dump/merkle.rs b/bin/reth/src/stage/dump/merkle.rs index 1d2e05005fff..3eb38283be12 100644 --- a/bin/reth/src/stage/dump/merkle.rs +++ b/bin/reth/src/stage/dump/merkle.rs @@ -119,17 +119,20 @@ async fn dry_run( let mut provider = shareable_db.provider_rw()?; let mut exec_output = false; while !exec_output { - let exec_input = reth_stages::ExecInput { - target: Some(to), - checkpoint: Some(StageCheckpoint::new(from)), - }; exec_output = MerkleStage::Execution { - // Forces updating the root instead of calculating from scratch - clean_threshold: u64::MAX, + clean_threshold: u64::MAX, /* Forces updating the root instead of calculating + * from + * scratch */ } - .execute(&mut provider, exec_input) + .execute( + &mut provider, + reth_stages::ExecInput { + target: Some(to), + checkpoint: Some(StageCheckpoint::new(from)), + }, + ) .await? - .is_done(exec_input); + .done; } info!(target: "reth::cli", "Success."); diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 9f3ade81c4a1..cc3cb3eb3b03 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -20,7 +20,7 @@ use reth_stages::{ IndexAccountHistoryStage, IndexStorageHistoryStage, MerkleStage, SenderRecoveryStage, StorageHashingStage, TransactionLookupStage, }, - ExecInput, PipelineError, Stage, UnwindInput, + ExecInput, ExecOutput, PipelineError, Stage, UnwindInput, }; use std::{any::Any, net::SocketAddr, path::PathBuf, sync::Arc}; use tracing::*; @@ -243,13 +243,10 @@ impl Command { checkpoint: Some(checkpoint.with_block_number(self.from)), }; - loop { - let result = exec_stage.execute(&mut provider_rw, input).await?; - if result.is_done(input) { - break - } - - input.checkpoint = Some(result.checkpoint); + while let ExecOutput { checkpoint: stage_progress, done: false } = + exec_stage.execute(&mut provider_rw, input).await? + { + input.checkpoint = Some(stage_progress); if self.commit { provider_rw.commit()?; diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 0875c3f7fb4e..70f555555256 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1369,7 +1369,6 @@ mod tests { chain_spec: Arc, pipeline_exec_outputs: VecDeque>, executor_results: Vec, - max_block: Option, ) -> (TestBeaconConsensusEngine, TestEnv>>) { reth_tracing::init_test_tracing(); let db = create_test_rw_db(); @@ -1381,13 +1380,10 @@ mod tests { // Setup pipeline let (tip_tx, tip_rx) = watch::channel(H256::default()); - let mut pipeline_builder = Pipeline::builder() + let pipeline = Pipeline::builder() .add_stages(TestStages::new(pipeline_exec_outputs, Default::default())) - .with_tip_sender(tip_tx); - if let Some(max_block) = max_block { - pipeline_builder = pipeline_builder.with_max_block(max_block); - } - let pipeline = pipeline_builder.build(db.clone(), chain_spec.clone()); + .with_tip_sender(tip_tx) + .build(db.clone(), chain_spec.clone()); // Setup blockchain tree let externals = @@ -1407,7 +1403,7 @@ mod tests { blockchain_provider, Box::::default(), Box::::default(), - max_block, + None, false, payload_builder, None, @@ -1442,7 +1438,6 @@ mod tests { chain_spec.clone(), VecDeque::from([Err(StageError::ChannelClosed)]), Vec::default(), - Some(1), ); let res = spawn_consensus_engine(consensus_engine); @@ -1472,7 +1467,6 @@ mod tests { chain_spec.clone(), VecDeque::from([Err(StageError::ChannelClosed)]), Vec::default(), - Some(1), ); let mut rx = spawn_consensus_engine(consensus_engine); @@ -1512,11 +1506,10 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { checkpoint: StageCheckpoint::new(1) }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(1), done: true }), Err(StageError::ChannelClosed), ]), Vec::default(), - Some(2), ); let rx = spawn_consensus_engine(consensus_engine); @@ -1529,9 +1522,7 @@ mod tests { assert_matches!( rx.await, - Ok( - Err(BeaconConsensusEngineError::Pipeline(n)) - ) if matches!(*n.as_ref(),PipelineError::Stage(StageError::ChannelClosed)) + Ok(Err(BeaconConsensusEngineError::Pipeline(n))) if matches!(*n.as_ref(),PipelineError::Stage(StageError::ChannelClosed)) ); } @@ -1545,12 +1536,15 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( + let (mut consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(max_block) })]), + VecDeque::from([Ok(ExecOutput { + checkpoint: StageCheckpoint::new(max_block), + done: true, + })]), Vec::default(), - Some(max_block), ); + consensus_engine.sync.set_max_block(max_block); let rx = spawn_consensus_engine(consensus_engine); let _ = env @@ -1590,9 +1584,11 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), + VecDeque::from([Ok(ExecOutput { + done: true, + checkpoint: StageCheckpoint::new(0), + })]), Vec::default(), - None, ); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1619,9 +1615,11 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), + VecDeque::from([Ok(ExecOutput { + done: true, + checkpoint: StageCheckpoint::new(0), + })]), Vec::default(), - None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1666,11 +1664,10 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), ]), Vec::default(), - None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1715,9 +1712,11 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), + VecDeque::from([Ok(ExecOutput { + done: true, + checkpoint: StageCheckpoint::new(0), + })]), Vec::default(), - None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1751,11 +1750,10 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), ]), Vec::default(), - None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1805,11 +1803,10 @@ mod tests { let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), VecDeque::from([ - Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), + Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), ]), Vec::default(), - None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1852,9 +1849,11 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), + VecDeque::from([Ok(ExecOutput { + done: true, + checkpoint: StageCheckpoint::new(0), + })]), Vec::default(), - None, ); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1883,9 +1882,11 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), + VecDeque::from([Ok(ExecOutput { + done: true, + checkpoint: StageCheckpoint::new(0), + })]), Vec::default(), - None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1931,9 +1932,11 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), + VecDeque::from([Ok(ExecOutput { + done: true, + checkpoint: StageCheckpoint::new(0), + })]), Vec::default(), - None, ); let genesis = random_block(0, None, None, Some(0)); @@ -1986,9 +1989,11 @@ mod tests { ); let (consensus_engine, env) = setup_consensus_engine( chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0) })]), + VecDeque::from([Ok(ExecOutput { + done: true, + checkpoint: StageCheckpoint::new(0), + })]), Vec::from([exec_result2]), - None, ); insert_blocks( diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index a5097c4c939b..a093b57bae8c 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -83,6 +83,12 @@ where self.metrics.active_block_downloads.set(self.inflight_full_block_requests.len() as f64); } + /// Sets the max block value for testing + #[cfg(test)] + pub(crate) fn set_max_block(&mut self, block: BlockNumber) { + self.max_block = Some(block); + } + /// Cancels all full block requests that are in progress. pub(crate) fn clear_full_block_requests(&mut self) { self.inflight_full_block_requests.clear(); diff --git a/crates/stages/src/pipeline/event.rs b/crates/stages/src/pipeline/event.rs index 6133d89fa172..2230c4075e04 100644 --- a/crates/stages/src/pipeline/event.rs +++ b/crates/stages/src/pipeline/event.rs @@ -31,8 +31,6 @@ pub enum PipelineEvent { stage_id: StageId, /// The result of executing the stage. result: ExecOutput, - /// Stage completed executing the whole block range - done: bool, }, /// Emitted when a stage is about to be unwound. Unwinding { @@ -47,8 +45,6 @@ pub enum PipelineEvent { stage_id: StageId, /// The result of unwinding the stage. result: UnwindOutput, - /// Stage completed unwinding the whole block range - done: bool, }, /// Emitted when a stage encounters an error either during execution or unwinding. Error { diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index a4b6c681e06d..6586365e84fa 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -262,10 +262,8 @@ where continue } - let mut done = UnwindInput { checkpoint, unwind_to: to, bad_block }.target_reached(); - debug!(target: "sync::pipeline", from = %checkpoint, %to, ?bad_block, "Starting unwind"); - while !done { + while checkpoint.block_number > to { let input = UnwindInput { checkpoint, unwind_to: to, bad_block }; self.listeners.notify(PipelineEvent::Unwinding { stage_id, input }); @@ -273,7 +271,6 @@ where match output { Ok(unwind_output) => { checkpoint = unwind_output.checkpoint; - done = unwind_output.is_done(input); info!( target: "sync::pipeline", stage = %stage_id, @@ -290,11 +287,8 @@ where ); provider_rw.save_stage_checkpoint(stage_id, checkpoint)?; - self.listeners.notify(PipelineEvent::Unwound { - stage_id, - result: unwind_output, - done, - }); + self.listeners + .notify(PipelineEvent::Unwound { stage_id, result: unwind_output }); provider_rw.commit()?; provider_rw = @@ -355,18 +349,11 @@ where checkpoint: prev_checkpoint, }); - let input = ExecInput { target, checkpoint: prev_checkpoint }; - let result = if input.target_reached() { - Ok(ExecOutput { checkpoint: input.checkpoint() }) - } else { - stage - .execute(&mut provider_rw, ExecInput { target, checkpoint: prev_checkpoint }) - .await - }; - - match result { - Ok(out @ ExecOutput { checkpoint }) => { - let done = out.is_done(input); + match stage + .execute(&mut provider_rw, ExecInput { target, checkpoint: prev_checkpoint }) + .await + { + Ok(out @ ExecOutput { checkpoint, done }) => { made_progress |= checkpoint.block_number != prev_checkpoint.unwrap_or_default().block_number; info!( @@ -385,7 +372,6 @@ where pipeline_total: total_stages, stage_id, result: out.clone(), - done, }); // TODO: Make the commit interval configurable @@ -484,10 +470,7 @@ impl std::fmt::Debug for Pipeline { #[cfg(test)] mod tests { use super::*; - use crate::{ - test_utils::{TestStage, TestTransaction}, - UnwindOutput, - }; + use crate::{test_utils::TestStage, UnwindOutput}; use assert_matches::assert_matches; use reth_db::mdbx::{self, test_utils, EnvKind}; use reth_interfaces::{ @@ -526,23 +509,19 @@ mod tests { /// Runs a simple pipeline. #[tokio::test] async fn run_pipeline() { - let tx = TestTransaction::default(); + let db = test_utils::create_test_db::(EnvKind::RW); let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20) })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true })), ) .add_stage( TestStage::new(StageId::Other("B")) - .with_checkpoint(Some(StageCheckpoint::new(10)), tx.inner()), - ) - .add_stage( - TestStage::new(StageId::Other("C")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .with_max_block(10) - .build(tx.inner_raw(), MAINNET.clone()); + .build(db, MAINNET.clone()); let events = pipeline.events(); // Run pipeline @@ -556,30 +535,27 @@ mod tests { vec![ PipelineEvent::Running { pipeline_position: 1, - pipeline_total: 3, + pipeline_total: 2, stage_id: StageId::Other("A"), checkpoint: None }, PipelineEvent::Ran { pipeline_position: 1, - pipeline_total: 3, + pipeline_total: 2, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(20) }, - done: true, + result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }, }, - PipelineEvent::Skipped { stage_id: StageId::Other("B") }, PipelineEvent::Running { - pipeline_position: 3, - pipeline_total: 3, - stage_id: StageId::Other("C"), + pipeline_position: 2, + pipeline_total: 2, + stage_id: StageId::Other("B"), checkpoint: None }, PipelineEvent::Ran { - pipeline_position: 3, - pipeline_total: 3, - stage_id: StageId::Other("C"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, - done: true, + pipeline_position: 2, + pipeline_total: 2, + stage_id: StageId::Other("B"), + result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, }, ] ); @@ -593,17 +569,17 @@ mod tests { let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100) })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) })), ) .add_stage( TestStage::new(StageId::Other("B")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) })), ) .add_stage( TestStage::new(StageId::Other("C")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20) })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(20), done: true })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(1) })), ) .with_max_block(10) @@ -634,8 +610,7 @@ mod tests { pipeline_position: 1, pipeline_total: 3, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(100) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }, }, PipelineEvent::Running { pipeline_position: 2, @@ -647,8 +622,7 @@ mod tests { pipeline_position: 2, pipeline_total: 3, stage_id: StageId::Other("B"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, }, PipelineEvent::Running { pipeline_position: 3, @@ -660,8 +634,7 @@ mod tests { pipeline_position: 3, pipeline_total: 3, stage_id: StageId::Other("C"), - result: ExecOutput { checkpoint: StageCheckpoint::new(20) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(20), done: true }, }, // Unwinding PipelineEvent::Unwinding { @@ -675,7 +648,6 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("C"), result: UnwindOutput { checkpoint: StageCheckpoint::new(1) }, - done: true }, PipelineEvent::Unwinding { stage_id: StageId::Other("B"), @@ -688,7 +660,6 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("B"), result: UnwindOutput { checkpoint: StageCheckpoint::new(1) }, - done: true }, PipelineEvent::Unwinding { stage_id: StageId::Other("A"), @@ -701,7 +672,6 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("A"), result: UnwindOutput { checkpoint: StageCheckpoint::new(1) }, - done: true }, ] ); @@ -715,12 +685,12 @@ mod tests { let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100) })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(100), done: true })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(50) })), ) .add_stage( TestStage::new(StageId::Other("B")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .with_max_block(10) .build(db, MAINNET.clone()); @@ -750,8 +720,7 @@ mod tests { pipeline_position: 1, pipeline_total: 2, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(100) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(100), done: true }, }, PipelineEvent::Running { pipeline_position: 2, @@ -763,8 +732,7 @@ mod tests { pipeline_position: 2, pipeline_total: 2, stage_id: StageId::Other("B"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, }, // Unwinding // Nothing to unwind in stage "B" @@ -780,7 +748,6 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("A"), result: UnwindOutput { checkpoint: StageCheckpoint::new(50) }, - done: true }, ] ); @@ -805,9 +772,9 @@ mod tests { let mut pipeline = Pipeline::builder() .add_stage( TestStage::new(StageId::Other("A")) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) })) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .add_stage( TestStage::new(StageId::Other("B")) @@ -816,7 +783,7 @@ mod tests { error: consensus::ConsensusError::BaseFeeMissing, })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) })) - .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), ) .with_max_block(10) .build(db, MAINNET.clone()); @@ -841,8 +808,7 @@ mod tests { pipeline_position: 1, pipeline_total: 2, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, }, PipelineEvent::Running { pipeline_position: 2, @@ -862,7 +828,6 @@ mod tests { PipelineEvent::Unwound { stage_id: StageId::Other("A"), result: UnwindOutput { checkpoint: StageCheckpoint::new(0) }, - done: true }, PipelineEvent::Running { pipeline_position: 1, @@ -874,8 +839,7 @@ mod tests { pipeline_position: 1, pipeline_total: 2, stage_id: StageId::Other("A"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, }, PipelineEvent::Running { pipeline_position: 2, @@ -887,8 +851,7 @@ mod tests { pipeline_position: 2, pipeline_total: 2, stage_id: StageId::Other("B"), - result: ExecOutput { checkpoint: StageCheckpoint::new(10) }, - done: true + result: ExecOutput { checkpoint: StageCheckpoint::new(10), done: true }, }, ] ); @@ -898,17 +861,17 @@ mod tests { #[tokio::test] async fn pipeline_error_handling() { // Non-fatal - // let db = test_utils::create_test_db::(EnvKind::RW); - // let mut pipeline = Pipeline::builder() - // .add_stage( - // TestStage::new(StageId::Other("NonFatal")) - // .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error)))) - // .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10) })), - // ) - // .with_max_block(10) - // .build(db, MAINNET.clone()); - // let result = pipeline.run().await; - // assert_matches!(result, Ok(())); + let db = test_utils::create_test_db::(EnvKind::RW); + let mut pipeline = Pipeline::builder() + .add_stage( + TestStage::new(StageId::Other("NonFatal")) + .add_exec(Err(StageError::Recoverable(Box::new(std::fmt::Error)))) + .add_exec(Ok(ExecOutput { checkpoint: StageCheckpoint::new(10), done: true })), + ) + .with_max_block(10) + .build(db, MAINNET.clone()); + let result = pipeline.run().await; + assert_matches!(result, Ok(())); // Fatal let db = test_utils::create_test_db::(EnvKind::RW); @@ -916,7 +879,6 @@ mod tests { .add_stage(TestStage::new(StageId::Other("Fatal")).add_exec(Err( StageError::DatabaseIntegrity(ProviderError::BlockBodyIndicesNotFound(5)), ))) - .with_max_block(1) .build(db, MAINNET.clone()); let result = pipeline.run().await; assert_matches!( diff --git a/crates/stages/src/stage.rs b/crates/stages/src/stage.rs index b5f1311baf5a..a7de484994cd 100644 --- a/crates/stages/src/stage.rs +++ b/crates/stages/src/stage.rs @@ -10,7 +10,6 @@ use std::{ cmp::{max, min}, ops::RangeInclusive, }; -use tracing::warn; /// Stage execution input, see [Stage::execute]. #[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] @@ -36,7 +35,7 @@ impl ExecInput { /// Returns `true` if the target block number has already been reached. pub fn target_reached(&self) -> bool { - ExecOutput { checkpoint: self.checkpoint.unwrap_or_default() }.is_done(*self) + self.checkpoint().block_number >= self.target() } /// Return the target block number or default. @@ -46,7 +45,8 @@ impl ExecInput { /// Return next block range that needs to be executed. pub fn next_block_range(&self) -> RangeInclusive { - self.next_block_range_with_threshold(u64::MAX) + let (range, _) = self.next_block_range_with_threshold(u64::MAX); + range } /// Return true if this is the first block range to execute. @@ -55,15 +55,19 @@ impl ExecInput { } /// Return the next block range to execute. - /// Return pair of the block range. - pub fn next_block_range_with_threshold(&self, threshold: u64) -> RangeInclusive { + /// Return pair of the block range and if this is final block range. + pub fn next_block_range_with_threshold( + &self, + threshold: u64, + ) -> (RangeInclusive, bool) { let current_block = self.checkpoint(); let start = current_block.block_number + 1; let target = self.target(); let end = min(target, current_block.block_number.saturating_add(threshold)); - start..=end + let is_final_range = end == target; + (start..=end, is_final_range) } /// Return the next block range determined the number of transactions within it. @@ -73,7 +77,7 @@ impl ExecInput { &self, provider: &DatabaseProviderRW<'_, DB>, tx_threshold: u64, - ) -> Result<(RangeInclusive, RangeInclusive), StageError> { + ) -> Result<(RangeInclusive, RangeInclusive, bool), StageError> { let start_block = self.next_block(); let start_block_body = provider .tx_ref() @@ -96,7 +100,8 @@ impl ExecInput { break } } - Ok((first_tx_number..=last_tx_number, start_block..=end_block_number)) + let is_final_range = end_block_number >= target_block; + Ok((first_tx_number..=last_tx_number, start_block..=end_block_number, is_final_range)) } } @@ -112,11 +117,6 @@ pub struct UnwindInput { } impl UnwindInput { - /// Returns `true` if the target block number has already been reached. - pub fn target_reached(&self) -> bool { - UnwindOutput { checkpoint: self.checkpoint }.is_done(*self) - } - /// Return next block range that needs to be unwound. pub fn unwind_block_range(&self) -> RangeInclusive { self.unwind_block_range_with_threshold(u64::MAX).0 @@ -126,7 +126,7 @@ impl UnwindInput { pub fn unwind_block_range_with_threshold( &self, threshold: u64, - ) -> (RangeInclusive, BlockNumber) { + ) -> (RangeInclusive, BlockNumber, bool) { // +1 is to skip the block we're unwinding to let mut start = self.unwind_to + 1; let end = self.checkpoint; @@ -135,7 +135,8 @@ impl UnwindInput { let unwind_to = start - 1; - (start..=end.block_number, unwind_to) + let is_final_range = unwind_to == self.unwind_to; + (start..=end.block_number, unwind_to, is_final_range) } } @@ -144,16 +145,14 @@ impl UnwindInput { pub struct ExecOutput { /// How far the stage got. pub checkpoint: StageCheckpoint, + /// Whether or not the stage is done. + pub done: bool, } impl ExecOutput { - /// Returns `true` if the target block number has already been reached, - /// i.e. `checkpoint.block_number >= target`. - pub fn is_done(&self, input: ExecInput) -> bool { - if self.checkpoint.block_number > input.target() { - warn!(target: "sync::pipeline", ?input, output = ?self, "Checkpoint is beyond the execution target"); - } - self.checkpoint.block_number >= input.target() + /// Mark the stage as done, checkpointing at the given place. + pub fn done(checkpoint: StageCheckpoint) -> Self { + Self { checkpoint, done: true } } } @@ -164,17 +163,6 @@ pub struct UnwindOutput { pub checkpoint: StageCheckpoint, } -impl UnwindOutput { - /// Returns `true` if the target block number has already been reached, - /// i.e. `checkpoint.block_number <= unwind_to`. - pub fn is_done(&self, input: UnwindInput) -> bool { - if self.checkpoint.block_number < input.unwind_to { - warn!(target: "sync::pipeline", ?input, output = ?self, "Checkpoint is beyond the unwind target"); - } - self.checkpoint.block_number <= input.unwind_to - } -} - /// A stage is a segmented part of the syncing process of the node. /// /// Each stage takes care of a well-defined task, such as downloading headers or executing diff --git a/crates/stages/src/stages/bodies.rs b/crates/stages/src/stages/bodies.rs index cdeca70e0bd1..0108f782804d 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -70,6 +70,10 @@ impl Stage for BodyStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } + let range = input.next_block_range(); // Update the header range on the downloader self.downloader.set_download_range(range.clone())?; @@ -148,9 +152,11 @@ impl Stage for BodyStage { // The stage is "done" if: // - We got fewer blocks than our target // - We reached our target and the target was not limited by the batch size of the stage + let done = highest_block == to_block; Ok(ExecOutput { checkpoint: StageCheckpoint::new(highest_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), + done, }) } @@ -226,11 +232,15 @@ fn stage_checkpoint( #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{ExecuteStageTestRunner, StageTestRunner, UnwindStageTestRunner}; + use crate::test_utils::{ + stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, UnwindStageTestRunner, + }; use assert_matches::assert_matches; use reth_primitives::stage::StageUnitCheckpoint; use test_utils::*; + stage_test_suite_ext!(BodyTestRunner, body); + /// Checks that the stage downloads at most `batch_size` blocks. #[tokio::test] async fn partial_body_download() { @@ -263,7 +273,7 @@ mod tests { processed, // 1 seeded block body + batch size total // seeded headers })) - }}) if block_number < 200 && + }, done: false }) if block_number < 200 && processed == 1 + batch_size && total == previous_stage ); assert!(runner.validate_execution(input, output.ok()).is_ok(), "execution validation"); @@ -300,7 +310,8 @@ mod tests { processed, total })) - } + }, + done: true }) if processed == total && total == previous_stage ); assert!(runner.validate_execution(input, output.ok()).is_ok(), "execution validation"); @@ -335,7 +346,7 @@ mod tests { processed, total })) - }}) if block_number >= 10 && + }, done: false }) if block_number >= 10 && processed == 1 + batch_size && total == previous_stage ); let first_run_checkpoint = first_run.unwrap().checkpoint; @@ -355,7 +366,7 @@ mod tests { processed, total })) - }}) if block_number > first_run_checkpoint.block_number && + }, done: true }) if block_number > first_run_checkpoint.block_number && processed == total && total == previous_stage ); assert_matches!( @@ -395,7 +406,7 @@ mod tests { processed, total })) - }}) if block_number == previous_stage && + }, done: true }) if block_number == previous_stage && processed == total && total == previous_stage ); let checkpoint = output.unwrap().checkpoint; diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index b58dd568873d..f6b40ee05ad9 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -143,6 +143,10 @@ impl ExecutionStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } + let start_block = input.next_block(); let max_block = input.target(); @@ -195,9 +199,11 @@ impl ExecutionStage { state.write_to_db(provider.tx_ref())?; trace!(target: "sync::stages::execution", took = ?start.elapsed(), "Wrote state"); + let done = stage_progress == max_block; Ok(ExecOutput { checkpoint: StageCheckpoint::new(stage_progress) .with_execution_stage_checkpoint(stage_checkpoint), + done, }) } } @@ -339,7 +345,7 @@ impl Stage for ExecutionStage { let mut account_changeset = tx.cursor_dup_write::()?; let mut storage_changeset = tx.cursor_dup_write::()?; - let (range, unwind_to) = + let (range, unwind_to, _) = input.unwind_block_range_with_threshold(self.thresholds.max_blocks.unwrap_or(u64::MAX)); if range.is_empty() { @@ -663,7 +669,8 @@ mod tests { total } })) - } + }, + done: true } if processed == total && total == block.gas_used); let mut provider = db.provider_rw().unwrap(); let tx = provider.tx_mut(); diff --git a/crates/stages/src/stages/finish.rs b/crates/stages/src/stages/finish.rs index 4955565e0b46..bae21c8c76b8 100644 --- a/crates/stages/src/stages/finish.rs +++ b/crates/stages/src/stages/finish.rs @@ -21,7 +21,7 @@ impl Stage for FinishStage { _provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()) }) + Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()), done: true }) } async fn unwind( @@ -37,12 +37,14 @@ impl Stage for FinishStage { mod tests { use super::*; use crate::test_utils::{ - ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, - UnwindStageTestRunner, + stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, + TestTransaction, UnwindStageTestRunner, }; use reth_interfaces::test_utils::generators::{random_header, random_header_range}; use reth_primitives::SealedHeader; + stage_test_suite_ext!(FinishTestRunner, finish); + #[derive(Default)] struct FinishTestRunner { tx: TestTransaction, @@ -87,7 +89,7 @@ mod tests { output: Option, ) -> Result<(), TestRunnerError> { if let Some(output) = output { - assert!(output.is_done(input), "stage should always be done"); + assert!(output.done, "stage should always be done"); assert_eq!( output.checkpoint.block_number, input.target(), diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 75df5e9193a5..ab56a3398494 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -135,6 +135,10 @@ impl Stage for AccountHashingStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } + let (from_block, to_block) = input.next_block_range().into_inner(); // if there are more blocks then threshold it is faster to go over Plain state and hash all @@ -232,7 +236,7 @@ impl Stage for AccountHashingStage { }, ); - return Ok(ExecOutput { checkpoint }) + return Ok(ExecOutput { checkpoint, done: false }) } } else { // Aggregate all transition changesets and make a list of accounts that have been @@ -254,7 +258,7 @@ impl Stage for AccountHashingStage { ..Default::default() }); - Ok(ExecOutput { checkpoint }) + Ok(ExecOutput { checkpoint, done: true }) } /// Unwind the stage. @@ -263,7 +267,7 @@ impl Stage for AccountHashingStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress) = + let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); // Aggregate all transition changesets and make a list of accounts that have been changed. @@ -293,11 +297,15 @@ fn stage_checkpoint_progress( #[cfg(test)] mod tests { use super::*; - use crate::test_utils::{ExecuteStageTestRunner, TestRunnerError, UnwindStageTestRunner}; + use crate::test_utils::{ + stage_test_suite_ext, ExecuteStageTestRunner, TestRunnerError, UnwindStageTestRunner, + }; use assert_matches::assert_matches; use reth_primitives::{stage::StageUnitCheckpoint, Account, U256}; use test_utils::*; + stage_test_suite_ext!(AccountHashingTestRunner, account_hashing); + #[tokio::test] async fn execute_clean_account_hashing() { let (previous_stage, stage_progress) = (20, 10); @@ -327,7 +335,8 @@ mod tests { }, .. })), - } + }, + done: true, }) if block_number == previous_stage && processed == total && total == runner.tx.table::().unwrap().len() as u64 @@ -384,7 +393,8 @@ mod tests { progress: EntitiesCheckpoint { processed: 5, total } } )) - } + }, + done: false }) if address == fifth_address && total == runner.tx.table::().unwrap().len() as u64 ); @@ -410,7 +420,8 @@ mod tests { progress: EntitiesCheckpoint { processed, total } } )) - } + }, + done: true }) if processed == total && total == runner.tx.table::().unwrap().len() as u64 ); diff --git a/crates/stages/src/stages/hashing_storage.rs b/crates/stages/src/stages/hashing_storage.rs index 3218fdfcf961..acb109b0e9d6 100644 --- a/crates/stages/src/stages/hashing_storage.rs +++ b/crates/stages/src/stages/hashing_storage.rs @@ -58,6 +58,9 @@ impl Stage for StorageHashingStage { input: ExecInput, ) -> Result { let tx = provider.tx_ref(); + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } let (from_block, to_block) = input.next_block_range().into_inner(); @@ -163,7 +166,7 @@ impl Stage for StorageHashingStage { }, ); - return Ok(ExecOutput { checkpoint }) + return Ok(ExecOutput { checkpoint, done: false }) } } else { // Aggregate all changesets and and make list of storages that have been @@ -185,7 +188,7 @@ impl Stage for StorageHashingStage { ..Default::default() }); - Ok(ExecOutput { checkpoint }) + Ok(ExecOutput { checkpoint, done: true }) } /// Unwind the stage. @@ -194,7 +197,7 @@ impl Stage for StorageHashingStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress) = + let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); provider.unwind_storage_hashing(BlockNumberAddress::range(range))?; @@ -224,8 +227,8 @@ fn stage_checkpoint_progress( mod tests { use super::*; use crate::test_utils::{ - ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, - UnwindStageTestRunner, + stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, + TestTransaction, UnwindStageTestRunner, }; use assert_matches::assert_matches; use reth_db::{ @@ -240,6 +243,8 @@ mod tests { stage::StageUnitCheckpoint, Address, SealedBlock, StorageEntry, H256, U256, }; + stage_test_suite_ext!(StorageHashingTestRunner, storage_hashing); + /// Execute with low clean threshold so as to hash whole storage #[tokio::test] async fn execute_clean_storage_hashing() { @@ -263,8 +268,10 @@ mod tests { runner.seed_execution(input).expect("failed to seed execution"); loop { - if let Ok(result @ ExecOutput { checkpoint }) = runner.execute(input).await.unwrap() { - if !result.is_done(input) { + if let Ok(result @ ExecOutput { checkpoint, done }) = + runner.execute(input).await.unwrap() + { + if !done { let previous_checkpoint = input .checkpoint .and_then(|checkpoint| checkpoint.storage_hashing_stage_checkpoint()) @@ -354,7 +361,8 @@ mod tests { total } })) - } + }, + done: false }) if address == progress_address && storage == progress_key && total == runner.tx.table::().unwrap().len() as u64 ); @@ -399,7 +407,8 @@ mod tests { } } )) - } + }, + done: false }) if address == progress_address && storage == progress_key && total == runner.tx.table::().unwrap().len() as u64 ); @@ -430,7 +439,8 @@ mod tests { } } )) - } + }, + done: true }) if processed == total && total == runner.tx.table::().unwrap().len() as u64 ); diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index 21560ae0b462..ad857d63515b 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -210,7 +210,7 @@ where // Nothing to sync if gap.is_closed() { info!(target: "sync::stages::headers", checkpoint = %current_checkpoint, target = ?tip, "Target block already reached"); - return Ok(ExecOutput { checkpoint: current_checkpoint }) + return Ok(ExecOutput::done(current_checkpoint)) } debug!(target: "sync::stages::headers", ?tip, head = ?gap.local_head.hash(), "Commencing sync"); @@ -313,10 +313,12 @@ where Ok(ExecOutput { checkpoint: StageCheckpoint::new(checkpoint) .with_headers_stage_checkpoint(stage_checkpoint), + done: true, }) } else { Ok(ExecOutput { checkpoint: current_checkpoint.with_headers_stage_checkpoint(stage_checkpoint), + done: false, }) } } @@ -589,7 +591,7 @@ mod tests { total, } })) - }}) if block_number == tip.number && + }, done: true }) if block_number == tip.number && from == checkpoint && to == previous_stage && // -1 because we don't need to download the local head processed == checkpoint + headers.len() as u64 - 1 && total == tip.number); @@ -685,7 +687,7 @@ mod tests { total, } })) - }}) if block_number == checkpoint && + }, done: false }) if block_number == checkpoint && from == checkpoint && to == previous_stage && processed == checkpoint + 500 && total == tip.number); @@ -708,7 +710,7 @@ mod tests { total, } })) - }}) if block_number == tip.number && + }, done: true }) if block_number == tip.number && from == checkpoint && to == previous_stage && // -1 because we don't need to download the local head processed == checkpoint + headers.len() as u64 - 1 && total == tip.number); diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index acbce16bd0be..f965009092ad 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -38,7 +38,11 @@ impl Stage for IndexAccountHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - let range = input.next_block_range_with_threshold(self.commit_threshold); + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } + + let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); let mut stage_checkpoint = stage_checkpoint( provider, @@ -59,6 +63,7 @@ impl Stage for IndexAccountHistoryStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()) .with_index_history_stage_checkpoint(stage_checkpoint), + done: is_final_range, }) } @@ -68,7 +73,7 @@ impl Stage for IndexAccountHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress) = + let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); let changesets = provider.unwind_account_history_indices(range)?; @@ -217,9 +222,9 @@ mod tests { progress: EntitiesCheckpoint { processed: 2, total: 2 } } ), + done: true } ); - assert!(out.is_done(input)); provider.commit().unwrap(); } @@ -457,10 +462,10 @@ mod tests { block_range: CheckpointBlockRange { from: 1, to: 5 }, progress: EntitiesCheckpoint { processed: 1, total: 2 } } - ) + ), + done: false } ); - assert!(!out.is_done(input)); input.checkpoint = Some(out.checkpoint); let out = stage.execute(&mut provider, input).await.unwrap(); @@ -472,10 +477,10 @@ mod tests { block_range: CheckpointBlockRange { from: 5, to: 5 }, progress: EntitiesCheckpoint { processed: 2, total: 2 } } - ) + ), + done: true } ); - assert!(out.is_done(input)); provider.commit().unwrap(); } diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index 1b4db0bb48da..cc354a4daf97 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -41,7 +41,11 @@ impl Stage for IndexStorageHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - let range = input.next_block_range_with_threshold(self.commit_threshold); + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } + + let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); let mut stage_checkpoint = stage_checkpoint( provider, @@ -61,6 +65,7 @@ impl Stage for IndexStorageHistoryStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()) .with_index_history_stage_checkpoint(stage_checkpoint), + done: is_final_range, }) } @@ -70,7 +75,7 @@ impl Stage for IndexStorageHistoryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (range, unwind_progress) = + let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); let changesets = @@ -229,10 +234,10 @@ mod tests { block_range: CheckpointBlockRange { from: input.next_block(), to: run_to }, progress: EntitiesCheckpoint { processed: 2, total: 2 } } - ) + ), + done: true } ); - assert!(out.is_done(input)); provider.commit().unwrap(); } @@ -473,10 +478,10 @@ mod tests { block_range: CheckpointBlockRange { from: 1, to: 5 }, progress: EntitiesCheckpoint { processed: 1, total: 2 } } - ) + ), + done: false } ); - assert!(!out.is_done(input)); input.checkpoint = Some(out.checkpoint); let out = stage.execute(&mut provider, input).await.unwrap(); @@ -488,10 +493,10 @@ mod tests { block_range: CheckpointBlockRange { from: 5, to: 5 }, progress: EntitiesCheckpoint { processed: 2, total: 2 } } - ) + ), + done: true } ); - assert!(out.is_done(input)); provider.commit().unwrap(); } diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index 8e7ff439089a..bfb0344e4360 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -144,7 +144,7 @@ impl Stage for MerkleStage { let threshold = match self { MerkleStage::Unwind => { info!(target: "sync::stages::merkle::unwind", "Stage is always skipped"); - return Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()) }) + return Ok(ExecOutput::done(StageCheckpoint::new(input.target()))) } MerkleStage::Execution { clean_threshold } => *clean_threshold, #[cfg(any(test, feature = "test-utils"))] @@ -226,6 +226,7 @@ impl Stage for MerkleStage { checkpoint: input .checkpoint() .with_entities_stage_checkpoint(entities_checkpoint), + done: false, }) } StateRootProgress::Complete(root, hashed_entries_walked, updates) => { @@ -266,6 +267,7 @@ impl Stage for MerkleStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(to_block) .with_entities_stage_checkpoint(entities_checkpoint), + done: true, }) } @@ -328,8 +330,8 @@ impl Stage for MerkleStage { mod tests { use super::*; use crate::test_utils::{ - ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, - UnwindStageTestRunner, + stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, + TestTransaction, UnwindStageTestRunner, }; use assert_matches::assert_matches; use reth_db::{ @@ -346,6 +348,8 @@ mod tests { use reth_trie::test_utils::{state_root, state_root_prehashed}; use std::collections::BTreeMap; + stage_test_suite_ext!(MerkleTestRunner, merkle); + /// Execute from genesis so as to merkelize whole state #[tokio::test] async fn execute_clean_merkle() { @@ -374,7 +378,8 @@ mod tests { processed, total })) - } + }, + done: true }) if block_number == previous_stage && processed == total && total == ( runner.tx.table::().unwrap().len() + @@ -413,7 +418,8 @@ mod tests { processed, total })) - } + }, + done: true }) if block_number == previous_stage && processed == total && total == ( runner.tx.table::().unwrap().len() + diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index 0b243726627c..bafa6938f223 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -59,7 +59,11 @@ impl Stage for SenderRecoveryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - let (tx_range, block_range) = + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } + + let (tx_range, block_range, is_final_range) = input.next_block_range_with_transaction_threshold(provider, self.commit_threshold)?; let end_block = *block_range.end(); @@ -69,6 +73,7 @@ impl Stage for SenderRecoveryStage { return Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), + done: is_final_range, }) } @@ -150,6 +155,7 @@ impl Stage for SenderRecoveryStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), + done: is_final_range, }) } @@ -159,7 +165,7 @@ impl Stage for SenderRecoveryStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (_, unwind_to) = input.unwind_block_range_with_threshold(self.commit_threshold); + let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); // Lookup latest tx id that we should unwind to let latest_tx_id = provider.block_body_indices(unwind_to)?.last_tx_num(); @@ -227,10 +233,12 @@ mod tests { use super::*; use crate::test_utils::{ - ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, - UnwindStageTestRunner, + stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, + TestTransaction, UnwindStageTestRunner, }; + stage_test_suite_ext!(SenderRecoveryTestRunner, sender_recovery); + /// Execute a block range with a single transaction #[tokio::test] async fn execute_single_transaction() { @@ -264,7 +272,7 @@ mod tests { processed: 1, total: 1 })) - }}) if block_number == previous_stage + }, done: true }) if block_number == previous_stage ); // Validate the stage execution @@ -303,17 +311,17 @@ mod tests { .unwrap_or(previous_stage); assert_matches!(result, Ok(_)); assert_eq!( - result.as_ref().unwrap(), - &ExecOutput { + result.unwrap(), + ExecOutput { checkpoint: StageCheckpoint::new(expected_progress).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: runner.tx.table::().unwrap().len() as u64, total: total_transactions } - ) + ), + done: false } ); - assert!(!result.unwrap().is_done(first_input)); // Execute second time to completion runner.set_threshold(u64::MAX); @@ -328,7 +336,8 @@ mod tests { &ExecOutput { checkpoint: StageCheckpoint::new(previous_stage).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: total_transactions, total: total_transactions } - ) + ), + done: true } ); diff --git a/crates/stages/src/stages/total_difficulty.rs b/crates/stages/src/stages/total_difficulty.rs index 9562016136b8..41afa821300e 100644 --- a/crates/stages/src/stages/total_difficulty.rs +++ b/crates/stages/src/stages/total_difficulty.rs @@ -55,8 +55,11 @@ impl Stage for TotalDifficultyStage { input: ExecInput, ) -> Result { let tx = provider.tx_ref(); + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } - let range = input.next_block_range_with_threshold(self.commit_threshold); + let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); let (start_block, end_block) = range.clone().into_inner(); debug!(target: "sync::stages::total_difficulty", start_block, end_block, "Commencing sync"); @@ -88,6 +91,7 @@ impl Stage for TotalDifficultyStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), + done: is_final_range, }) } @@ -97,7 +101,7 @@ impl Stage for TotalDifficultyStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { - let (_, unwind_to) = input.unwind_block_range_with_threshold(self.commit_threshold); + let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); provider.unwind_table_by_num::(unwind_to)?; @@ -129,10 +133,12 @@ mod tests { use super::*; use crate::test_utils::{ - ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, - UnwindStageTestRunner, + stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, + TestTransaction, UnwindStageTestRunner, }; + stage_test_suite_ext!(TotalDifficultyTestRunner, total_difficulty); + #[tokio::test] async fn execute_with_intermediate_commit() { let threshold = 50; @@ -160,10 +166,9 @@ mod tests { processed, total })) - }}) if block_number == expected_progress && processed == 1 + threshold && + }, done: false }) if block_number == expected_progress && processed == 1 + threshold && total == runner.tx.table::().unwrap().len() as u64 ); - assert!(!result.unwrap().is_done(first_input)); // Execute second time let second_input = ExecInput { @@ -179,7 +184,7 @@ mod tests { processed, total })) - }}) if block_number == previous_stage && processed == total && + }, done: true }) if block_number == previous_stage && processed == total && total == runner.tx.table::().unwrap().len() as u64 ); diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index 3d77fc6af648..11757dc40d1d 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -54,7 +54,10 @@ impl Stage for TransactionLookupStage { provider: &mut DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { - let (tx_range, block_range) = + if input.target_reached() { + return Ok(ExecOutput::done(input.checkpoint())) + } + let (tx_range, block_range, is_final_range) = input.next_block_range_with_transaction_threshold(provider, self.commit_threshold)?; let end_block = *block_range.end(); @@ -135,6 +138,7 @@ impl Stage for TransactionLookupStage { Ok(ExecOutput { checkpoint: StageCheckpoint::new(end_block) .with_entities_stage_checkpoint(stage_checkpoint(provider)?), + done: is_final_range, }) } @@ -145,7 +149,7 @@ impl Stage for TransactionLookupStage { input: UnwindInput, ) -> Result { let tx = provider.tx_ref(); - let (range, unwind_to) = input.unwind_block_range_with_threshold(self.commit_threshold); + let (range, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); // Cursors to unwind tx hash to number let mut body_cursor = tx.cursor_read::()?; @@ -188,13 +192,16 @@ fn stage_checkpoint( mod tests { use super::*; use crate::test_utils::{ - ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, - UnwindStageTestRunner, + stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, + TestTransaction, UnwindStageTestRunner, }; use assert_matches::assert_matches; use reth_interfaces::test_utils::generators::{random_block, random_block_range}; use reth_primitives::{stage::StageUnitCheckpoint, BlockNumber, SealedBlock, H256}; + // Implement stage test suite. + stage_test_suite_ext!(TransactionLookupTestRunner, transaction_lookup); + #[tokio::test] async fn execute_single_transaction_lookup() { let (previous_stage, stage_progress) = (500, 100); @@ -227,7 +234,7 @@ mod tests { processed, total })) - }}) if block_number == previous_stage && processed == total && + }, done: true }) if block_number == previous_stage && processed == total && total == runner.tx.table::().unwrap().len() as u64 ); @@ -266,17 +273,17 @@ mod tests { .unwrap_or(previous_stage); assert_matches!(result, Ok(_)); assert_eq!( - result.as_ref().unwrap(), - &ExecOutput { + result.unwrap(), + ExecOutput { checkpoint: StageCheckpoint::new(expected_progress).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: runner.tx.table::().unwrap().len() as u64, total: total_txs } - ) + ), + done: false } ); - assert!(!result.unwrap().is_done(first_input)); // Execute second time to completion runner.set_threshold(u64::MAX); @@ -291,7 +298,8 @@ mod tests { &ExecOutput { checkpoint: StageCheckpoint::new(previous_stage).with_entities_stage_checkpoint( EntitiesCheckpoint { processed: total_txs, total: total_txs } - ) + ), + done: true } ); diff --git a/crates/stages/src/test_utils/macros.rs b/crates/stages/src/test_utils/macros.rs index 533d6584756d..f691d13711b1 100644 --- a/crates/stages/src/test_utils/macros.rs +++ b/crates/stages/src/test_utils/macros.rs @@ -42,8 +42,8 @@ macro_rules! stage_test_suite { let result = rx.await.unwrap(); assert_matches::assert_matches!( result, - Ok(ref output @ ExecOutput { checkpoint }) - if output.is_done(input) && checkpoint.block_number == previous_stage + Ok(ExecOutput { done, checkpoint }) + if done && checkpoint.block_number == previous_stage ); // Validate the stage execution @@ -94,8 +94,8 @@ macro_rules! stage_test_suite { let result = rx.await.unwrap(); assert_matches::assert_matches!( result, - Ok(ref output @ ExecOutput { checkpoint }) - if output.is_done(execute_input) && checkpoint.block_number == previous_stage + Ok(ExecOutput { done, checkpoint }) + if done && checkpoint.block_number == previous_stage ); assert_matches::assert_matches!(runner.validate_execution(execute_input, result.ok()),Ok(_), "execution validation"); @@ -113,8 +113,7 @@ macro_rules! stage_test_suite { // Assert the successful unwind result assert_matches::assert_matches!( rx, - Ok(output @ UnwindOutput { checkpoint }) - if output.is_done(unwind_input) && checkpoint.block_number == unwind_input.unwind_to + Ok(UnwindOutput { checkpoint }) if checkpoint.block_number == unwind_input.unwind_to ); // Validate the stage unwind @@ -124,4 +123,46 @@ macro_rules! stage_test_suite { }; } +// `execute_already_reached_target` is not suitable for the headers stage thus +// included in the test suite extension +macro_rules! stage_test_suite_ext { + ($runner:ident, $name:ident) => { + crate::test_utils::stage_test_suite!($runner, $name); + + paste::item! { + /// Check that the execution is short-circuited if the target was already reached. + #[tokio::test] + async fn [< execute_already_reached_target_ $name>] () { + let stage_progress = 1000; + + // Set up the runner + let mut runner = $runner::default(); + let input = crate::stage::ExecInput { + target: Some(stage_progress), + checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(stage_progress)), + }; + let seed = runner.seed_execution(input).expect("failed to seed"); + + // Run stage execution + let rx = runner.execute(input); + + // Run `after_execution` hook + runner.after_execution(seed).await.expect("failed to run after execution hook"); + + // Assert the successful result + let result = rx.await.unwrap(); + assert_matches::assert_matches!( + result, + Ok(ExecOutput { done, checkpoint }) + if done && checkpoint.block_number == stage_progress + ); + + // Validate the stage execution + assert_matches::assert_matches!(runner.validate_execution(input, result.ok()),Ok(_), "execution validation"); + } + } + }; +} + pub(crate) use stage_test_suite; +pub(crate) use stage_test_suite_ext; diff --git a/crates/stages/src/test_utils/stage.rs b/crates/stages/src/test_utils/stage.rs index 231ce3880e00..028b74218fcb 100644 --- a/crates/stages/src/test_utils/stage.rs +++ b/crates/stages/src/test_utils/stage.rs @@ -1,49 +1,19 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::database::Database; -use reth_primitives::{ - stage::{StageCheckpoint, StageId}, - MAINNET, -}; -use reth_provider::{DatabaseProviderRW, ShareableDatabase}; +use reth_primitives::stage::StageId; +use reth_provider::DatabaseProviderRW; use std::collections::VecDeque; #[derive(Debug)] pub struct TestStage { id: StageId, - checkpoint: Option, exec_outputs: VecDeque>, unwind_outputs: VecDeque>, } impl TestStage { pub fn new(id: StageId) -> Self { - Self { - id, - checkpoint: None, - exec_outputs: VecDeque::new(), - unwind_outputs: VecDeque::new(), - } - } - - pub fn with_checkpoint( - mut self, - checkpoint: Option, - provider: DatabaseProviderRW<'_, DB>, - ) -> Self { - if let Some(checkpoint) = checkpoint { - provider - .save_stage_checkpoint(self.id, checkpoint) - .unwrap_or_else(|_| panic!("save stage {} checkpoint", self.id)) - } else { - provider - .delete_stage_checkpoint(self.id) - .unwrap_or_else(|_| panic!("delete stage {} checkpoint", self.id)) - } - - provider.commit().expect("provider commit"); - - self.checkpoint = checkpoint; - self + Self { id, exec_outputs: VecDeque::new(), unwind_outputs: VecDeque::new() } } pub fn with_exec(mut self, exec_outputs: VecDeque>) -> Self { diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index ffe173763ab3..a355a09e6394 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1108,12 +1108,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// Delete stage checkpoint. - pub fn delete_stage_checkpoint(&self, id: StageId) -> std::result::Result<(), DatabaseError> { - self.tx.delete::(id.to_string(), None)?; - Ok(()) - } - /// Get stage checkpoint progress. pub fn get_stage_checkpoint_progress( &self, From 209d2445b0c05e2e6585d88e06104286150242cf Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 14 Jun 2023 07:49:32 +0100 Subject: [PATCH 017/216] chore: rename `ShareableDatabase` to `ProviderFactory` (#3121) --- bin/reth/src/debug_cmd/execution.rs | 8 ++-- bin/reth/src/debug_cmd/merkle.rs | 6 +-- bin/reth/src/node/mod.rs | 10 ++-- bin/reth/src/p2p/mod.rs | 4 +- bin/reth/src/stage/dump/execution.rs | 10 ++-- bin/reth/src/stage/dump/hashing_account.rs | 10 ++-- bin/reth/src/stage/dump/hashing_storage.rs | 10 ++-- bin/reth/src/stage/dump/merkle.rs | 10 ++-- bin/reth/src/stage/run.rs | 10 ++-- bin/reth/src/stage/unwind.rs | 6 +-- crates/blockchain-tree/src/externals.rs | 6 +-- crates/consensus/beacon/src/engine/mod.rs | 8 ++-- crates/staged-sync/src/utils/init.rs | 6 +-- crates/stages/benches/criterion.rs | 4 +- crates/stages/benches/setup/mod.rs | 6 +-- crates/stages/src/pipeline/mod.rs | 15 +++--- crates/stages/src/stages/execution.rs | 46 +++++++++---------- crates/stages/src/stages/headers.rs | 4 +- .../src/stages/index_account_history.rs | 10 ++-- .../src/stages/index_storage_history.rs | 10 ++-- crates/stages/src/test_utils/runner.rs | 6 +-- crates/stages/src/test_utils/test_db.rs | 8 ++-- crates/storage/provider/src/lib.rs | 2 +- .../provider/src/providers/database/mod.rs | 42 ++++++++--------- crates/storage/provider/src/providers/mod.rs | 6 +-- crates/storage/provider/src/transaction.rs | 6 +-- crates/trie/src/trie.rs | 26 +++++------ crates/trie/src/trie_cursor/account_cursor.rs | 4 +- crates/trie/src/trie_cursor/storage_cursor.rs | 4 +- crates/trie/src/walker.rs | 6 +-- testing/ef-tests/src/cases/blockchain_test.rs | 5 +- 31 files changed, 156 insertions(+), 158 deletions(-) diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index bb2a1cecfb4e..82e62f9253ea 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -26,7 +26,7 @@ use reth_interfaces::{ use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256}; -use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase}; +use reth_provider::{providers::get_stage_checkpoint, ProviderFactory}; use reth_staged_sync::utils::init::{init_db, init_genesis}; use reth_stages::{ sets::DefaultStages, @@ -170,7 +170,7 @@ impl Command { Ipv4Addr::UNSPECIFIED, self.network.discovery.port.unwrap_or(DEFAULT_DISCOVERY_PORT), ))) - .build(ShareableDatabase::new(db, self.chain.clone())) + .build(ProviderFactory::new(db, self.chain.clone())) .start_network() .await?; info!(target: "reth::cli", peer_id = %network.peer_id(), local_addr = %network.local_addr(), "Connected to P2P network"); @@ -250,7 +250,7 @@ impl Command { } let mut current_max_block = latest_block_number; - let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); + let factory = ProviderFactory::new(&db, self.chain.clone()); while current_max_block < self.to { let next_block = current_max_block + 1; @@ -266,7 +266,7 @@ impl Command { // Unwind the pipeline without committing. { - shareable_db + factory .provider_rw() .map_err(PipelineError::Interface)? .take_block_and_execution_range(&self.chain, next_block..=target_block)?; diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index c05fc6c54b94..58e4a5df368a 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -9,7 +9,7 @@ use reth_primitives::{ stage::{StageCheckpoint, StageId}, ChainSpec, }; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ @@ -68,8 +68,8 @@ impl Command { std::fs::create_dir_all(&db_path)?; let db = Arc::new(init_db(db_path)?); - let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); - let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + let factory = ProviderFactory::new(&db, self.chain.clone()); + let mut provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; let execution_checkpoint_block = provider_rw.get_stage_checkpoint(StageId::Execution)?.unwrap_or_default().block_number; diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 17163ed09c6f..4a90aa5e9210 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -46,7 +46,7 @@ use reth_primitives::{ }; use reth_provider::{ providers::get_stage_checkpoint, BlockProvider, CanonStateSubscriptions, HeaderProvider, - ShareableDatabase, + ProviderFactory, }; use reth_revm::Factory; use reth_revm_inspectors::stack::Hook; @@ -199,8 +199,8 @@ impl Command { )?); // setup the blockchain provider - let shareable_db = ShareableDatabase::new(Arc::clone(&db), Arc::clone(&self.chain)); - let blockchain_db = BlockchainProvider::new(shareable_db, blockchain_tree.clone())?; + let factory = ProviderFactory::new(Arc::clone(&db), Arc::clone(&self.chain)); + let blockchain_db = BlockchainProvider::new(factory, blockchain_tree.clone())?; let transaction_pool = reth_transaction_pool::Pool::eth_pool( EthTransactionValidator::new(blockchain_db.clone(), Arc::clone(&self.chain)), @@ -600,7 +600,7 @@ impl Command { executor: TaskExecutor, secret_key: SecretKey, default_peers_path: PathBuf, - ) -> NetworkConfig>>> { + ) -> NetworkConfig>>> { let head = self.lookup_head(Arc::clone(&db)).expect("the head block is missing"); self.network @@ -615,7 +615,7 @@ impl Command { Ipv4Addr::UNSPECIFIED, self.network.discovery.port.unwrap_or(DEFAULT_DISCOVERY_PORT), ))) - .build(ShareableDatabase::new(db, self.chain.clone())) + .build(ProviderFactory::new(db, self.chain.clone())) } #[allow(clippy::too_many_arguments)] diff --git a/bin/reth/src/p2p/mod.rs b/bin/reth/src/p2p/mod.rs index ef940f96f2af..48c6d2f0e232 100644 --- a/bin/reth/src/p2p/mod.rs +++ b/bin/reth/src/p2p/mod.rs @@ -15,7 +15,7 @@ use reth_db::mdbx::{Env, EnvKind, WriteMap}; use reth_discv4::NatResolver; use reth_interfaces::p2p::bodies::client::BodiesClient; use reth_primitives::{BlockHashOrNumber, ChainSpec, NodeRecord}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use std::{path::PathBuf, sync::Arc}; /// `reth p2p` command @@ -129,7 +129,7 @@ impl Command { network_config_builder = self.discovery.apply_to_builder(network_config_builder); let network = network_config_builder - .build(Arc::new(ShareableDatabase::new(noop_db, self.chain.clone()))) + .build(Arc::new(ProviderFactory::new(noop_db, self.chain.clone()))) .start_network() .await?; diff --git a/bin/reth/src/stage/dump/execution.rs b/bin/reth/src/stage/dump/execution.rs index 8af0e225e8ec..e5ce855bd882 100644 --- a/bin/reth/src/stage/dump/execution.rs +++ b/bin/reth/src/stage/dump/execution.rs @@ -5,7 +5,7 @@ use reth_db::{ cursor::DbCursorRO, database::Database, table::TableImporter, tables, transaction::DbTx, }; use reth_primitives::{stage::StageCheckpoint, ChainSpec}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_revm::Factory; use reth_stages::{stages::ExecutionStage, Stage, UnwindInput}; use std::{path::PathBuf, sync::Arc}; @@ -94,8 +94,8 @@ async fn unwind_and_copy( tip_block_number: u64, output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { - let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); + let mut provider = factory.provider_rw()?; let mut exec_stage = ExecutionStage::new_with_factory(Factory::new(db_tool.chain.clone())); @@ -129,8 +129,8 @@ async fn dry_run( ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage. [dry-run]"); - let shareable_db = ShareableDatabase::new(&output_db, chain.clone()); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(&output_db, chain.clone()); + let mut provider = factory.provider_rw()?; let mut exec_stage = ExecutionStage::new_with_factory(Factory::new(chain.clone())); exec_stage diff --git a/bin/reth/src/stage/dump/hashing_account.rs b/bin/reth/src/stage/dump/hashing_account.rs index d63a14cc82c1..29711c5bc107 100644 --- a/bin/reth/src/stage/dump/hashing_account.rs +++ b/bin/reth/src/stage/dump/hashing_account.rs @@ -3,7 +3,7 @@ use crate::utils::DbTool; use eyre::Result; use reth_db::{database::Database, table::TableImporter, tables}; use reth_primitives::{stage::StageCheckpoint, BlockNumber, ChainSpec}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_stages::{stages::AccountHashingStage, Stage, UnwindInput}; use std::{path::PathBuf, sync::Arc}; use tracing::info; @@ -38,8 +38,8 @@ async fn unwind_and_copy( tip_block_number: u64, output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { - let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); + let mut provider = factory.provider_rw()?; let mut exec_stage = AccountHashingStage::default(); exec_stage @@ -68,8 +68,8 @@ async fn dry_run( ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage."); - let shareable_db = ShareableDatabase::new(&output_db, chain); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(&output_db, chain); + let mut provider = factory.provider_rw()?; let mut exec_stage = AccountHashingStage { clean_threshold: 1, // Forces hashing from scratch ..Default::default() diff --git a/bin/reth/src/stage/dump/hashing_storage.rs b/bin/reth/src/stage/dump/hashing_storage.rs index 6e717544c7ad..c9f12a958127 100644 --- a/bin/reth/src/stage/dump/hashing_storage.rs +++ b/bin/reth/src/stage/dump/hashing_storage.rs @@ -3,7 +3,7 @@ use crate::utils::DbTool; use eyre::Result; use reth_db::{database::Database, table::TableImporter, tables}; use reth_primitives::{stage::StageCheckpoint, ChainSpec}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_stages::{stages::StorageHashingStage, Stage, UnwindInput}; use std::{path::PathBuf, sync::Arc}; use tracing::info; @@ -33,8 +33,8 @@ async fn unwind_and_copy( tip_block_number: u64, output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { - let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); + let mut provider = factory.provider_rw()?; let mut exec_stage = StorageHashingStage::default(); @@ -67,8 +67,8 @@ async fn dry_run( ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage."); - let shareable_db = ShareableDatabase::new(&output_db, chain); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(&output_db, chain); + let mut provider = factory.provider_rw()?; let mut exec_stage = StorageHashingStage { clean_threshold: 1, // Forces hashing from scratch ..Default::default() diff --git a/bin/reth/src/stage/dump/merkle.rs b/bin/reth/src/stage/dump/merkle.rs index 3eb38283be12..1c7b32fb0129 100644 --- a/bin/reth/src/stage/dump/merkle.rs +++ b/bin/reth/src/stage/dump/merkle.rs @@ -3,7 +3,7 @@ use crate::utils::DbTool; use eyre::Result; use reth_db::{database::Database, table::TableImporter, tables}; use reth_primitives::{stage::StageCheckpoint, BlockNumber, ChainSpec}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_stages::{ stages::{ AccountHashingStage, ExecutionStage, ExecutionStageThresholds, MerkleStage, @@ -48,8 +48,8 @@ async fn unwind_and_copy( output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { let (from, to) = range; - let shareable_db = ShareableDatabase::new(db_tool.db, db_tool.chain.clone()); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); + let mut provider = factory.provider_rw()?; let unwind = UnwindInput { unwind_to: from, @@ -115,8 +115,8 @@ async fn dry_run( from: u64, ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage."); - let shareable_db = ShareableDatabase::new(&output_db, chain); - let mut provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(&output_db, chain); + let mut provider = factory.provider_rw()?; let mut exec_output = false; while !exec_output { exec_output = MerkleStage::Execution { diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index cc3cb3eb3b03..573c7be4041e 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -12,7 +12,7 @@ use reth_beacon_consensus::BeaconConsensus; use reth_config::Config; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_primitives::ChainSpec; -use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase}; +use reth_provider::{providers::get_stage_checkpoint, ProviderFactory}; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ @@ -120,8 +120,8 @@ impl Command { info!(target: "reth::cli", path = ?db_path, "Opening database"); let db = Arc::new(init_db(db_path)?); - let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); - let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + let factory = ProviderFactory::new(&db, self.chain.clone()); + let mut provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; if let Some(listen_addr) = self.metrics { info!(target: "reth::cli", "Starting metrics endpoint at {}", listen_addr); @@ -160,7 +160,7 @@ impl Command { p2p_secret_key, default_peers_path, ) - .build(Arc::new(ShareableDatabase::new(db.clone(), self.chain.clone()))) + .build(Arc::new(ProviderFactory::new(db.clone(), self.chain.clone()))) .start_network() .await?; let fetch_client = Arc::new(network.fetch_client().await?); @@ -250,7 +250,7 @@ impl Command { if self.commit { provider_rw.commit()?; - provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; } } diff --git a/bin/reth/src/stage/unwind.rs b/bin/reth/src/stage/unwind.rs index 12ba4e7edee5..cd78c0a2c9ce 100644 --- a/bin/reth/src/stage/unwind.rs +++ b/bin/reth/src/stage/unwind.rs @@ -13,7 +13,7 @@ use reth_db::{ transaction::DbTx, }; use reth_primitives::{BlockHashOrNumber, ChainSpec}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use std::{ops::RangeInclusive, sync::Arc}; /// `reth stage unwind` command @@ -69,8 +69,8 @@ impl Command { eyre::bail!("Cannot unwind genesis block") } - let shareable_db = ShareableDatabase::new(&db, self.chain.clone()); - let provider = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(&db, self.chain.clone()); + let provider = factory.provider_rw()?; let blocks_and_execution = provider .take_block_and_execution_range(&self.chain, range) diff --git a/crates/blockchain-tree/src/externals.rs b/crates/blockchain-tree/src/externals.rs index 269ebe7509ee..b73f0258ebc8 100644 --- a/crates/blockchain-tree/src/externals.rs +++ b/crates/blockchain-tree/src/externals.rs @@ -2,7 +2,7 @@ use reth_db::database::Database; use reth_primitives::ChainSpec; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use std::sync::Arc; /// A container for external components. @@ -35,7 +35,7 @@ impl TreeExternals { impl TreeExternals { /// Return shareable database helper structure. - pub fn database(&self) -> ShareableDatabase<&DB> { - ShareableDatabase::new(&self.db, self.chain_spec.clone()) + pub fn database(&self) -> ProviderFactory<&DB> { + ProviderFactory::new(&self.db, self.chain_spec.clone()) } } diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 70f555555256..5dbc09124ad8 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1286,7 +1286,7 @@ mod tests { use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_primitives::{stage::StageCheckpoint, ChainSpec, ChainSpecBuilder, H256, MAINNET}; use reth_provider::{ - providers::BlockchainProvider, test_utils::TestExecutorFactory, ShareableDatabase, + providers::BlockchainProvider, test_utils::TestExecutorFactory, ProviderFactory, }; use reth_stages::{test_utils::TestStages, ExecOutput, PipelineError, StageError}; use reth_tasks::TokioTaskExecutor; @@ -1394,9 +1394,9 @@ mod tests { BlockchainTree::new(externals, canon_state_notification_sender, config) .expect("failed to create tree"), ); - let shareable_db = ShareableDatabase::new(db.clone(), chain_spec.clone()); + let factory = ProviderFactory::new(db.clone(), chain_spec.clone()); let latest = chain_spec.genesis_header().seal_slow(); - let blockchain_provider = BlockchainProvider::with_latest(shareable_db, tree, latest); + let blockchain_provider = BlockchainProvider::with_latest(factory, tree, latest); let (engine, handle) = BeaconConsensusEngine::new( NoopFullBlockClient::default(), pipeline, @@ -1561,7 +1561,7 @@ mod tests { chain: Arc, mut blocks: impl Iterator, ) { - let factory = ShareableDatabase::new(db, chain); + let factory = ProviderFactory::new(db, chain); let mut provider = factory.provider_rw().unwrap(); blocks.try_for_each(|b| provider.insert_block(b.clone(), None)).expect("failed to insert"); provider.commit().unwrap(); diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index 323858049fe3..4f6eebb469c5 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -6,7 +6,7 @@ use reth_db::{ transaction::{DbTx, DbTxMut}, }; use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, H256, U256}; -use reth_provider::{DatabaseProviderRW, PostState, ShareableDatabase, TransactionError}; +use reth_provider::{DatabaseProviderRW, PostState, ProviderFactory, TransactionError}; use std::{path::Path, sync::Arc}; use tracing::debug; @@ -72,8 +72,8 @@ pub fn init_genesis( debug!("Writing genesis block."); // use transaction to insert genesis header - let shareable_db = ShareableDatabase::new(&db, chain.clone()); - let provider_rw = shareable_db.provider_rw()?; + let factory = ProviderFactory::new(&db, chain.clone()); + let provider_rw = factory.provider_rw()?; insert_genesis_hashes(provider_rw, genesis)?; // Insert header diff --git a/crates/stages/benches/criterion.rs b/crates/stages/benches/criterion.rs index fb252ed2f2b0..6509ef48bbf9 100644 --- a/crates/stages/benches/criterion.rs +++ b/crates/stages/benches/criterion.rs @@ -6,7 +6,7 @@ use pprof::criterion::{Output, PProfProfiler}; use reth_db::mdbx::{Env, WriteMap}; use reth_interfaces::test_utils::TestConsensus; use reth_primitives::{stage::StageCheckpoint, MAINNET}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_stages::{ stages::{MerkleStage, SenderRecoveryStage, TotalDifficultyStage, TransactionLookupStage}, test_utils::TestTransaction, @@ -136,7 +136,7 @@ fn measure_stage_with_path( }, |_| async { let mut stage = stage.clone(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); stage.execute(&mut provider, input).await.unwrap(); provider.commit().unwrap(); diff --git a/crates/stages/benches/setup/mod.rs b/crates/stages/benches/setup/mod.rs index 5639a0983ef9..ee0c394d4736 100644 --- a/crates/stages/benches/setup/mod.rs +++ b/crates/stages/benches/setup/mod.rs @@ -10,7 +10,7 @@ use reth_interfaces::test_utils::generators::{ random_transition_range, }; use reth_primitives::{Account, Address, SealedBlock, H256, MAINNET}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_stages::{ stages::{AccountHashingStage, StorageHashingStage}, test_utils::TestTransaction, @@ -38,7 +38,7 @@ pub(crate) fn stage_unwind>>( tokio::runtime::Runtime::new().unwrap().block_on(async { let mut stage = stage.clone(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); // Clear previous run @@ -66,7 +66,7 @@ pub(crate) fn unwind_hashes>>( tokio::runtime::Runtime::new().unwrap().block_on(async { let mut stage = stage.clone(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); StorageHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index 6586365e84fa..e88b23773514 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -6,7 +6,7 @@ use reth_primitives::{ constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH, listener::EventListeners, stage::StageId, BlockNumber, ChainSpec, H256, }; -use reth_provider::{providers::get_stage_checkpoint, ShareableDatabase}; +use reth_provider::{providers::get_stage_checkpoint, ProviderFactory}; use std::{pin::Pin, sync::Arc}; use tokio::sync::watch; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -247,8 +247,8 @@ where // Unwind stages in reverse order of execution let unwind_pipeline = self.stages.iter_mut().rev(); - let shareable_db = ShareableDatabase::new(&self.db, self.chain_spec.clone()); - let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + let factory = ProviderFactory::new(&self.db, self.chain_spec.clone()); + let mut provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; for stage in unwind_pipeline { let stage_id = stage.id(); @@ -291,8 +291,7 @@ where .notify(PipelineEvent::Unwound { stage_id, result: unwind_output }); provider_rw.commit()?; - provider_rw = - shareable_db.provider_rw().map_err(PipelineError::Interface)?; + provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; } Err(err) => { self.listeners.notify(PipelineEvent::Error { stage_id }); @@ -317,8 +316,8 @@ where let mut made_progress = false; let target = self.max_block.or(previous_stage); - let shareable_db = ShareableDatabase::new(&self.db, self.chain_spec.clone()); - let mut provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + let factory = ProviderFactory::new(&self.db, self.chain_spec.clone()); + let mut provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; loop { let prev_checkpoint = provider_rw.get_stage_checkpoint(stage_id)?; @@ -376,7 +375,7 @@ where // TODO: Make the commit interval configurable provider_rw.commit()?; - provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; if done { let stage_progress = checkpoint.block_number; diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index f6b40ee05ad9..1759da6997a0 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -470,7 +470,7 @@ mod tests { hex_literal::hex, keccak256, stage::StageUnitCheckpoint, Account, Bytecode, ChainSpecBuilder, SealedBlock, StorageEntry, H160, H256, MAINNET, U256, }; - use reth_provider::{insert_canonical_block, ShareableDatabase}; + use reth_provider::{insert_canonical_block, ProviderFactory}; use reth_revm::Factory; use reth_rlp::Decodable; use std::sync::Arc; @@ -487,8 +487,8 @@ mod tests { #[test] fn execution_checkpoint_matches() { let state_db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); - let tx = db.provider_rw().unwrap(); + let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); + let tx = factory.provider_rw().unwrap(); let previous_stage_checkpoint = ExecutionCheckpoint { block_range: CheckpointBlockRange { from: 0, to: 0 }, @@ -512,8 +512,8 @@ mod tests { #[test] fn execution_checkpoint_precedes() { let state_db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = db.provider_rw().unwrap(); + let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); @@ -532,7 +532,7 @@ mod tests { stage_checkpoint: Some(StageUnitCheckpoint::Execution(previous_stage_checkpoint)), }; - let provider = db.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let stage_checkpoint = execution_checkpoint(&provider, 1, 1, previous_checkpoint); assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint { @@ -548,8 +548,8 @@ mod tests { #[test] fn execution_checkpoint_recalculate_full_previous_some() { let state_db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = db.provider_rw().unwrap(); + let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); @@ -568,7 +568,7 @@ mod tests { stage_checkpoint: Some(StageUnitCheckpoint::Execution(previous_stage_checkpoint)), }; - let provider = db.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let stage_checkpoint = execution_checkpoint(&provider, 1, 1, previous_checkpoint); assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint { @@ -584,8 +584,8 @@ mod tests { #[test] fn execution_checkpoint_recalculate_full_previous_none() { let state_db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = db.provider_rw().unwrap(); + let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); @@ -597,7 +597,7 @@ mod tests { let previous_checkpoint = StageCheckpoint { block_number: 1, stage_checkpoint: None }; - let provider = db.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let stage_checkpoint = execution_checkpoint(&provider, 1, 1, previous_checkpoint); assert_matches!(stage_checkpoint, Ok(ExecutionCheckpoint { @@ -614,8 +614,8 @@ mod tests { // TODO cleanup the setup after https://github.com/paradigmxyz/reth/issues/332 // is merged as it has similar framework let state_db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = db.provider_rw().unwrap(); + let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -630,7 +630,7 @@ mod tests { provider.commit().unwrap(); // insert pre state - let mut provider = db.provider_rw().unwrap(); + let mut provider = factory.provider_rw().unwrap(); let db_tx = provider.tx_mut(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); @@ -652,7 +652,7 @@ mod tests { db_tx.put::(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap(); provider.commit().unwrap(); - let mut provider = db.provider_rw().unwrap(); + let mut provider = factory.provider_rw().unwrap(); let mut execution_stage = stage(); let output = execution_stage.execute(&mut provider, input).await.unwrap(); provider.commit().unwrap(); @@ -672,7 +672,7 @@ mod tests { }, done: true } if processed == total && total == block.gas_used); - let mut provider = db.provider_rw().unwrap(); + let mut provider = factory.provider_rw().unwrap(); let tx = provider.tx_mut(); // check post state let account1 = H160(hex!("1000000000000000000000000000000000000000")); @@ -722,8 +722,8 @@ mod tests { // is merged as it has similar framework let state_db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = db.provider_rw().unwrap(); + let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); + let mut provider = factory.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -742,7 +742,7 @@ mod tests { let balance = U256::from(0x3635c9adc5dea00000u128); let code_hash = keccak256(code); // pre state - let mut provider = db.provider_rw().unwrap(); + let mut provider = factory.provider_rw().unwrap(); let db_tx = provider.tx_mut(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc1_info = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }; @@ -755,12 +755,12 @@ mod tests { provider.commit().unwrap(); // execute - let mut provider = db.provider_rw().unwrap(); + let mut provider = factory.provider_rw().unwrap(); let mut execution_stage = stage(); let result = execution_stage.execute(&mut provider, input).await.unwrap(); provider.commit().unwrap(); - let mut provider = db.provider_rw().unwrap(); + let mut provider = factory.provider_rw().unwrap(); let mut stage = stage(); let result = stage .unwind( @@ -812,7 +812,7 @@ mod tests { #[tokio::test] async fn test_selfdestruct() { let test_tx = TestTransaction::default(); - let factory = ShareableDatabase::new(test_tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(test_tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let input = ExecInput { target: Some(1), diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index ad857d63515b..6b57dc4009da 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -390,7 +390,7 @@ mod tests { use assert_matches::assert_matches; use reth_interfaces::test_utils::generators::random_header; use reth_primitives::{stage::StageUnitCheckpoint, H256, MAINNET}; - use reth_provider::ShareableDatabase; + use reth_provider::ProviderFactory; use test_runner::HeadersTestRunner; mod test_runner { @@ -602,7 +602,7 @@ mod tests { #[tokio::test] async fn head_and_tip_lookup() { let runner = HeadersTestRunner::default(); - let factory = ShareableDatabase::new(runner.tx().tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(runner.tx().tx.as_ref(), MAINNET.clone()); let provider = factory.provider_rw().unwrap(); let tx = provider.tx_ref(); let mut stage = runner.stage(); diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index f965009092ad..2261da83a842 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -140,7 +140,7 @@ fn stage_checkpoint( #[cfg(test)] mod tests { use assert_matches::assert_matches; - use reth_provider::ShareableDatabase; + use reth_provider::ProviderFactory; use std::collections::BTreeMap; use super::*; @@ -210,7 +210,7 @@ mod tests { async fn run(tx: &TestTransaction, run_to: u64) { let input = ExecInput { target: Some(run_to), ..Default::default() }; let mut stage = IndexAccountHistoryStage::default(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( @@ -235,7 +235,7 @@ mod tests { ..Default::default() }; let mut stage = IndexAccountHistoryStage::default(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let out = stage.unwind(&mut provider, input).await.unwrap(); assert_eq!(out, UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) }); @@ -449,7 +449,7 @@ mod tests { // run { let mut stage = IndexAccountHistoryStage { commit_threshold: 4 }; // Two runs required - let factory = ShareableDatabase::new(&test_tx.tx, MAINNET.clone()); + let factory = ProviderFactory::new(&test_tx.tx, MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let mut input = ExecInput { target: Some(5), ..Default::default() }; @@ -538,7 +538,7 @@ mod tests { }) .unwrap(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let provider = factory.provider_rw().unwrap(); assert_matches!( diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index cc354a4daf97..e54d080b66a1 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -143,7 +143,7 @@ fn stage_checkpoint( mod tests { use assert_matches::assert_matches; - use reth_provider::ShareableDatabase; + use reth_provider::ProviderFactory; use std::collections::BTreeMap; use super::*; @@ -223,7 +223,7 @@ mod tests { async fn run(tx: &TestTransaction, run_to: u64) { let input = ExecInput { target: Some(run_to), ..Default::default() }; let mut stage = IndexStorageHistoryStage::default(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let out = stage.execute(&mut provider, input).await.unwrap(); assert_eq!( @@ -248,7 +248,7 @@ mod tests { ..Default::default() }; let mut stage = IndexStorageHistoryStage::default(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let out = stage.unwind(&mut provider, input).await.unwrap(); assert_eq!(out, UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) }); @@ -465,7 +465,7 @@ mod tests { // run { let mut stage = IndexStorageHistoryStage { commit_threshold: 4 }; // Two runs required - let factory = ShareableDatabase::new(&test_tx.tx, MAINNET.clone()); + let factory = ProviderFactory::new(&test_tx.tx, MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let mut input = ExecInput { target: Some(5), ..Default::default() }; @@ -564,7 +564,7 @@ mod tests { }) .unwrap(); - let factory = ShareableDatabase::new(tx.tx.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let provider = factory.provider_rw().unwrap(); assert_matches!( diff --git a/crates/stages/src/test_utils/runner.rs b/crates/stages/src/test_utils/runner.rs index 7666e0755e8c..ff2f0133faa1 100644 --- a/crates/stages/src/test_utils/runner.rs +++ b/crates/stages/src/test_utils/runner.rs @@ -2,7 +2,7 @@ use super::TestTransaction; use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::mdbx::{Env, WriteMap}; use reth_primitives::MAINNET; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use std::{borrow::Borrow, sync::Arc}; use tokio::sync::oneshot; @@ -45,7 +45,7 @@ pub(crate) trait ExecuteStageTestRunner: StageTestRunner { let (tx, rx) = oneshot::channel(); let (db, mut stage) = (self.tx().inner_raw(), self.stage()); tokio::spawn(async move { - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let result = stage.execute(&mut provider, input).await; @@ -71,7 +71,7 @@ pub(crate) trait UnwindStageTestRunner: StageTestRunner { let (tx, rx) = oneshot::channel(); let (db, mut stage) = (self.tx().inner_raw(), self.stage()); tokio::spawn(async move { - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let result = stage.unwind(&mut provider, input).await; diff --git a/crates/stages/src/test_utils/test_db.rs b/crates/stages/src/test_utils/test_db.rs index 9345441cb2d6..43763983a80c 100644 --- a/crates/stages/src/test_utils/test_db.rs +++ b/crates/stages/src/test_utils/test_db.rs @@ -16,7 +16,7 @@ use reth_primitives::{ keccak256, Account, Address, BlockNumber, SealedBlock, SealedHeader, StorageEntry, H256, MAINNET, U256, }; -use reth_provider::{DatabaseProviderRW, ShareableDatabase}; +use reth_provider::{DatabaseProviderRW, ProviderFactory}; use std::{ borrow::Borrow, collections::BTreeMap, @@ -37,14 +37,14 @@ pub struct TestTransaction { /// WriteMap DB pub tx: Arc>, pub path: Option, - factory: ShareableDatabase>>, + factory: ProviderFactory>>, } impl Default for TestTransaction { /// Create a new instance of [TestTransaction] fn default() -> Self { let tx = create_test_db::(EnvKind::RW); - Self { tx: tx.clone(), path: None, factory: ShareableDatabase::new(tx, MAINNET.clone()) } + Self { tx: tx.clone(), path: None, factory: ProviderFactory::new(tx, MAINNET.clone()) } } } @@ -54,7 +54,7 @@ impl TestTransaction { Self { tx: tx.clone(), path: Some(path.to_path_buf()), - factory: ShareableDatabase::new(tx, MAINNET.clone()), + factory: ProviderFactory::new(tx, MAINNET.clone()), } } diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index b00e6646fc8a..868f668453b9 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -24,7 +24,7 @@ pub use traits::{ pub mod providers; pub use providers::{ DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW, HistoricalStateProvider, - HistoricalStateProviderRef, LatestStateProvider, LatestStateProviderRef, ShareableDatabase, + HistoricalStateProviderRef, LatestStateProvider, LatestStateProviderRef, ProviderFactory, }; /// Execution result diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 54c418336b91..30aa60298634 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -24,14 +24,14 @@ pub use provider::{DatabaseProvider, DatabaseProviderRO, DatabaseProviderRW}; /// /// This provider implements most provider or provider factory traits. #[derive(Debug)] -pub struct ShareableDatabase { +pub struct ProviderFactory { /// Database db: DB, /// Chain spec chain_spec: Arc, } -impl ShareableDatabase { +impl ProviderFactory { /// Returns a provider with a created `DbTx` inside, which allows fetching data from the /// database using different types of providers. Example: [`HeaderProvider`] /// [`BlockHashProvider`]. This may fail if the inner read database transaction fails to open. @@ -48,20 +48,20 @@ impl ShareableDatabase { } } -impl ShareableDatabase { +impl ProviderFactory { /// create new database provider pub fn new(db: DB, chain_spec: Arc) -> Self { Self { db, chain_spec } } } -impl Clone for ShareableDatabase { +impl Clone for ProviderFactory { fn clone(&self) -> Self { Self { db: self.db.clone(), chain_spec: Arc::clone(&self.chain_spec) } } } -impl ShareableDatabase { +impl ProviderFactory { /// Storage provider for latest block pub fn latest(&self) -> Result> { trace!(target: "providers::db", "Returning latest state provider"); @@ -111,7 +111,7 @@ impl ShareableDatabase { } } -impl HeaderProvider for ShareableDatabase { +impl HeaderProvider for ProviderFactory { fn header(&self, block_hash: &BlockHash) -> Result> { self.provider()?.header(block_hash) } @@ -144,7 +144,7 @@ impl HeaderProvider for ShareableDatabase { } } -impl BlockHashProvider for ShareableDatabase { +impl BlockHashProvider for ProviderFactory { fn block_hash(&self, number: u64) -> Result> { self.provider()?.block_hash(number) } @@ -154,7 +154,7 @@ impl BlockHashProvider for ShareableDatabase { } } -impl BlockNumProvider for ShareableDatabase { +impl BlockNumProvider for ProviderFactory { fn chain_info(&self) -> Result { self.provider()?.chain_info() } @@ -172,7 +172,7 @@ impl BlockNumProvider for ShareableDatabase { } } -impl BlockProvider for ShareableDatabase { +impl BlockProvider for ProviderFactory { fn find_block_by_hash(&self, hash: H256, source: BlockSource) -> Result> { self.provider()?.find_block_by_hash(hash, source) } @@ -194,7 +194,7 @@ impl BlockProvider for ShareableDatabase { } } -impl TransactionsProvider for ShareableDatabase { +impl TransactionsProvider for ProviderFactory { fn transaction_id(&self, tx_hash: TxHash) -> Result> { self.provider()?.transaction_id(tx_hash) } @@ -233,7 +233,7 @@ impl TransactionsProvider for ShareableDatabase { } } -impl ReceiptProvider for ShareableDatabase { +impl ReceiptProvider for ProviderFactory { fn receipt(&self, id: TxNumber) -> Result> { self.provider()?.receipt(id) } @@ -247,7 +247,7 @@ impl ReceiptProvider for ShareableDatabase { } } -impl WithdrawalsProvider for ShareableDatabase { +impl WithdrawalsProvider for ProviderFactory { fn withdrawals_by_block( &self, id: BlockHashOrNumber, @@ -261,13 +261,13 @@ impl WithdrawalsProvider for ShareableDatabase { } } -impl StageCheckpointProvider for ShareableDatabase { +impl StageCheckpointProvider for ProviderFactory { fn get_stage_checkpoint(&self, id: StageId) -> Result> { self.provider()?.get_stage_checkpoint(id) } } -impl EvmEnvProvider for ShareableDatabase { +impl EvmEnvProvider for ProviderFactory { fn fill_env_at( &self, cfg: &mut CfgEnv, @@ -317,7 +317,7 @@ where #[cfg(test)] mod tests { - use super::ShareableDatabase; + use super::ProviderFactory; use crate::{BlockHashProvider, BlockNumProvider}; use reth_db::mdbx::{test_utils::create_test_db, EnvKind, WriteMap}; use reth_primitives::{ChainSpecBuilder, H256}; @@ -327,7 +327,7 @@ mod tests { fn common_history_provider() { let chain_spec = ChainSpecBuilder::mainnet().build(); let db = create_test_db::(EnvKind::RW); - let provider = ShareableDatabase::new(db, Arc::new(chain_spec)); + let provider = ProviderFactory::new(db, Arc::new(chain_spec)); let _ = provider.latest(); } @@ -335,8 +335,8 @@ mod tests { fn default_chain_info() { let chain_spec = ChainSpecBuilder::mainnet().build(); let db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(db, Arc::new(chain_spec)); - let provider = db.provider().unwrap(); + let factory = ProviderFactory::new(db, Arc::new(chain_spec)); + let provider = factory.provider().unwrap(); let chain_info = provider.chain_info().expect("should be ok"); assert_eq!(chain_info.best_number, 0); @@ -347,10 +347,10 @@ mod tests { fn provider_flow() { let chain_spec = ChainSpecBuilder::mainnet().build(); let db = create_test_db::(EnvKind::RW); - let db = ShareableDatabase::new(db, Arc::new(chain_spec)); - let provider = db.provider().unwrap(); + let factory = ProviderFactory::new(db, Arc::new(chain_spec)); + let provider = factory.provider().unwrap(); provider.block_hash(0).unwrap(); - let provider_rw = db.provider_rw().unwrap(); + let provider_rw = factory.provider_rw().unwrap(); provider_rw.block_hash(0).unwrap(); provider.block_hash(0).unwrap(); } diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 0915a72e5e90..7bb70825e431 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -46,7 +46,7 @@ use reth_interfaces::blockchain_tree::{error::InsertBlockError, CanonicalOutcome #[derive(Clone)] pub struct BlockchainProvider { /// Provider type used to access the database. - database: ShareableDatabase, + database: ProviderFactory, /// The blockchain tree instance. tree: Tree, /// Tracks the chain info wrt forkchoice updates @@ -56,7 +56,7 @@ pub struct BlockchainProvider { impl BlockchainProvider { /// Create new provider instance that wraps the database and the blockchain tree, using the /// provided latest header to initialize the chain info tracker. - pub fn with_latest(database: ShareableDatabase, tree: Tree, latest: SealedHeader) -> Self { + pub fn with_latest(database: ProviderFactory, tree: Tree, latest: SealedHeader) -> Self { Self { database, tree, chain_info: ChainInfoTracker::new(latest) } } } @@ -67,7 +67,7 @@ where { /// Create a new provider using only the database and the tree, fetching the latest header from /// the database to initialize the provider. - pub fn new(database: ShareableDatabase, tree: Tree) -> Result { + pub fn new(database: ProviderFactory, tree: Tree) -> Result { let provider = database.provider()?; let best: ChainInfo = provider.chain_info()?; match provider.header_by_number(best.best_number)? { diff --git a/crates/storage/provider/src/transaction.rs b/crates/storage/provider/src/transaction.rs index 24a470ebdfd1..b75c87deca58 100644 --- a/crates/storage/provider/src/transaction.rs +++ b/crates/storage/provider/src/transaction.rs @@ -44,7 +44,7 @@ pub enum TransactionError { #[cfg(test)] mod test { use crate::{ - insert_canonical_block, test_utils::blocks::*, ShareableDatabase, TransactionsProvider, + insert_canonical_block, test_utils::blocks::*, ProviderFactory, TransactionsProvider, }; use reth_db::{ mdbx::test_utils::create_test_rw_db, @@ -65,7 +65,7 @@ mod test { .shanghai_activated() .build(); - let factory = ShareableDatabase::new(db.as_ref(), Arc::new(chain_spec.clone())); + let factory = ProviderFactory::new(db.as_ref(), Arc::new(chain_spec.clone())); let mut provider = factory.provider_rw().unwrap(); let data = BlockChainTestData::default(); @@ -183,7 +183,7 @@ mod test { .build(), ); - let factory = ShareableDatabase::new(db.as_ref(), chain_spec.clone()); + let factory = ProviderFactory::new(db.as_ref(), chain_spec.clone()); let mut provider = factory.provider_rw().unwrap(); let data = BlockChainTestData::default(); diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index 1ad0baa6ea60..6348e8f1a332 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -525,7 +525,7 @@ mod tests { trie::{BranchNodeCompact, TrieMask}, Account, Address, H256, MAINNET, U256, }; - use reth_provider::{DatabaseProviderRW, ShareableDatabase}; + use reth_provider::{DatabaseProviderRW, ProviderFactory}; use std::{collections::BTreeMap, ops::Mul, str::FromStr}; fn insert_account<'a, TX: DbTxMut<'a>>( @@ -555,7 +555,7 @@ mod tests { fn incremental_vs_full_root(inputs: &[&str], modified: &str) { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let hashed_address = H256::from_low_u64_be(1); @@ -622,7 +622,7 @@ mod tests { let hashed_address = keccak256(address); let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let tx = factory.provider_rw().unwrap(); for (key, value) in &storage { tx.tx_ref().put::( @@ -680,7 +680,7 @@ mod tests { // This ensures we return an empty root when there are no storage entries fn test_empty_storage_root() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let address = Address::random(); @@ -702,7 +702,7 @@ mod tests { // This ensures that the walker goes over all the storage slots fn test_storage_root() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let address = Address::random(); @@ -746,7 +746,7 @@ mod tests { state.values().map(|(_, slots)| slots.len()).sum::(); let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); for (address, (account, storage)) in &state { @@ -785,7 +785,7 @@ mod tests { fn test_state_root_with_state(state: State) { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); for (address, (account, storage)) in &state { @@ -812,7 +812,7 @@ mod tests { #[test] fn storage_root_regression() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let tx = factory.provider_rw().unwrap(); // Some address whose hash starts with 0xB041 let address3 = Address::from_str("16b07afd1c635f77172e842a000ead9a2a222459").unwrap(); @@ -857,7 +857,7 @@ mod tests { ); let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let mut hashed_account_cursor = @@ -1164,7 +1164,7 @@ mod tests { #[test] fn account_trie_around_extension_node() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let expected = extension_node_trie(&mut tx); @@ -1190,7 +1190,7 @@ mod tests { fn account_trie_around_extension_node_with_dbtrie() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let expected = extension_node_trie(&mut tx); @@ -1218,7 +1218,7 @@ mod tests { tokio::runtime::Runtime::new().unwrap().block_on(async { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let mut hashed_account_cursor = tx.tx_ref().cursor_write::().unwrap(); @@ -1252,7 +1252,7 @@ mod tests { #[test] fn storage_trie_around_extension_node() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let mut tx = factory.provider_rw().unwrap(); let hashed_address = H256::random(); diff --git a/crates/trie/src/trie_cursor/account_cursor.rs b/crates/trie/src/trie_cursor/account_cursor.rs index a74789e7c4bb..e34b074ef645 100644 --- a/crates/trie/src/trie_cursor/account_cursor.rs +++ b/crates/trie/src/trie_cursor/account_cursor.rs @@ -47,12 +47,12 @@ mod tests { transaction::DbTxMut, }; use reth_primitives::{hex_literal::hex, MAINNET}; - use reth_provider::ShareableDatabase; + use reth_provider::ProviderFactory; #[test] fn test_account_trie_order() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let provider = factory.provider_rw().unwrap(); let mut cursor = provider.tx_ref().cursor_write::().unwrap(); diff --git a/crates/trie/src/trie_cursor/storage_cursor.rs b/crates/trie/src/trie_cursor/storage_cursor.rs index 677cae49796e..800691d1c645 100644 --- a/crates/trie/src/trie_cursor/storage_cursor.rs +++ b/crates/trie/src/trie_cursor/storage_cursor.rs @@ -64,13 +64,13 @@ mod tests { trie::{BranchNodeCompact, StorageTrieEntry}, MAINNET, }; - use reth_provider::ShareableDatabase; + use reth_provider::ProviderFactory; // tests that upsert and seek match on the storagetrie cursor #[test] fn test_storage_cursor_abstraction() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let provider = factory.provider_rw().unwrap(); let mut cursor = provider.tx_ref().cursor_dup_write::().unwrap(); diff --git a/crates/trie/src/walker.rs b/crates/trie/src/walker.rs index 284ad4f2b240..f10c1cee2275 100644 --- a/crates/trie/src/walker.rs +++ b/crates/trie/src/walker.rs @@ -263,7 +263,7 @@ mod tests { cursor::DbCursorRW, mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut, }; use reth_primitives::{trie::StorageTrieEntry, MAINNET}; - use reth_provider::ShareableDatabase; + use reth_provider::ProviderFactory; #[test] fn walk_nodes_with_common_prefix() { @@ -290,7 +290,7 @@ mod tests { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let tx = factory.provider_rw().unwrap(); let mut account_cursor = tx.tx_ref().cursor_write::().unwrap(); @@ -336,7 +336,7 @@ mod tests { #[test] fn cursor_rootnode_with_changesets() { let db = create_test_rw_db(); - let factory = ShareableDatabase::new(db.as_ref(), MAINNET.clone()); + let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); let tx = factory.provider_rw().unwrap(); let mut cursor = tx.tx_ref().cursor_dup_write::().unwrap(); diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index e57ebfc3e877..88ecb8ee0a32 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -6,7 +6,7 @@ use crate::{ }; use reth_db::mdbx::test_utils::create_test_rw_db; use reth_primitives::{BlockBody, SealedBlock}; -use reth_provider::ShareableDatabase; +use reth_provider::ProviderFactory; use reth_stages::{stages::ExecutionStage, ExecInput, Stage}; use std::{collections::BTreeMap, ffi::OsStr, fs, path::Path, sync::Arc}; @@ -75,8 +75,7 @@ impl Case for BlockchainTestCase { // Create the database let db = create_test_rw_db(); - let factory = - ShareableDatabase::new(db.as_ref(), Arc::new(case.network.clone().into())); + let factory = ProviderFactory::new(db.as_ref(), Arc::new(case.network.clone().into())); let mut provider = factory.provider_rw().unwrap(); // Insert test state From 76302d945c667b4ac7fc252f075d9c4e7bd61e0a Mon Sep 17 00:00:00 2001 From: Gaon3 <89095198+Gaon3@users.noreply.github.com> Date: Wed, 14 Jun 2023 12:21:01 +0300 Subject: [PATCH 018/216] Fixed compilation error (rename shareable_db to factory) (#3138) --- bin/reth/src/stage/run.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 573c7be4041e..592d0d66c91d 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -233,7 +233,7 @@ impl Command { if self.commit { provider_rw.commit()?; - provider_rw = shareable_db.provider_rw().map_err(PipelineError::Interface)?; + provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; } } } From cf10590e4a8f2a35a1826149a746bedb8fd2e000 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 14 Jun 2023 14:42:00 +0200 Subject: [PATCH 019/216] feat: add js tracer (#3100) Co-authored-by: Georgios Konstantopoulos --- Cargo.lock | 795 +++++++++++++---- crates/revm/revm-inspectors/Cargo.toml | 11 + .../revm-inspectors/src/tracing/js/bigint.js | 1 + .../src/tracing/js/bindings.rs | 797 ++++++++++++++++++ .../src/tracing/js/builtins.rs | 163 ++++ .../revm-inspectors/src/tracing/js/mod.rs | 549 ++++++++++++ .../revm/revm-inspectors/src/tracing/mod.rs | 3 + crates/revm/src/database.rs | 1 + .../rpc/rpc-types/src/eth/trace/geth/mod.rs | 9 + crates/rpc/rpc/src/debug.rs | 253 ++++-- crates/rpc/rpc/src/eth/error.rs | 19 + 11 files changed, 2364 insertions(+), 237 deletions(-) create mode 100644 crates/revm/revm-inspectors/src/tracing/js/bigint.js create mode 100644 crates/revm/revm-inspectors/src/tracing/js/bindings.rs create mode 100644 crates/revm/revm-inspectors/src/tracing/js/builtins.rs create mode 100644 crates/revm/revm-inspectors/src/tracing/js/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 222fe94c252c..eca32685989b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,12 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -159,8 +165,8 @@ checksum = "759d98a5db12e9c9d98ef2b92f794ae5c7ded6ec18d21c3fa485c9c65bec237d" dependencies = [ "itertools", "proc-macro-error", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -216,9 +222,9 @@ version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -241,6 +247,15 @@ dependencies = [ "critical-section", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c314e70d181aa6053b26e3f7fbf86d1dfff84f816a6175b967666b3506ef7289" +dependencies = [ + "critical-section", +] + [[package]] name = "attohttpc" version = "0.16.3" @@ -271,8 +286,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ "proc-macro-error", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -384,19 +399,19 @@ version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cexpr", "clang-sys", "lazy_static", "lazycell", "peeking_take_while", "prettyplease", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "regex", "rustc-hash", "shlex", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -420,6 +435,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" + [[package]] name = "bitvec" version = "0.17.4" @@ -470,6 +491,131 @@ dependencies = [ "generic-array", ] +[[package]] +name = "boa_ast" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +dependencies = [ + "bitflags 2.3.1", + "boa_interner", + "boa_macros", + "indexmap", + "num-bigint", + "rustc-hash", +] + +[[package]] +name = "boa_engine" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +dependencies = [ + "bitflags 2.3.1", + "boa_ast", + "boa_gc", + "boa_icu_provider", + "boa_interner", + "boa_macros", + "boa_parser", + "boa_profiler", + "chrono", + "dashmap", + "fast-float", + "icu_normalizer", + "indexmap", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "num_enum", + "once_cell", + "pollster", + "rand 0.8.5", + "regress", + "rustc-hash", + "ryu-js", + "serde", + "serde_json", + "sptr", + "static_assertions", + "tap", + "thin-vec", + "thiserror", +] + +[[package]] +name = "boa_gc" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +dependencies = [ + "boa_macros", + "boa_profiler", + "thin-vec", +] + +[[package]] +name = "boa_icu_provider" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +dependencies = [ + "icu_collections", + "icu_normalizer", + "icu_properties", + "icu_provider", + "once_cell", + "zerovec", +] + +[[package]] +name = "boa_interner" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +dependencies = [ + "boa_gc", + "boa_macros", + "hashbrown 0.14.0", + "indexmap", + "once_cell", + "phf", + "rustc-hash", + "static_assertions", +] + +[[package]] +name = "boa_macros" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", + "synstructure 0.13.0", +] + +[[package]] +name = "boa_parser" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +dependencies = [ + "bitflags 2.3.1", + "boa_ast", + "boa_icu_provider", + "boa_interner", + "boa_macros", + "boa_profiler", + "fast-float", + "icu_properties", + "num-bigint", + "num-traits", + "once_cell", + "regress", + "rustc-hash", +] + +[[package]] +name = "boa_profiler" +version = "0.16.0" +source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" + [[package]] name = "brotli" version = "3.3.4" @@ -624,12 +770,12 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", - "num-integer", "num-traits", "serde", "winapi", @@ -698,7 +844,7 @@ version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ - "bitflags", + "bitflags 1.3.2", "clap_lex 0.2.4", "indexmap", "textwrap", @@ -710,7 +856,7 @@ version = "4.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5" dependencies = [ - "bitflags", + "bitflags 1.3.2", "clap_derive", "clap_lex 0.3.2", "is-terminal", @@ -727,8 +873,8 @@ checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0" dependencies = [ "heck", "proc-macro-error", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -762,10 +908,10 @@ version = "0.1.0" dependencies = [ "convert_case 0.6.0", "parity-scale-codec", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "serde", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -1041,7 +1187,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crossterm_winapi", "libc", "mio", @@ -1106,7 +1252,7 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ - "quote 1.0.26", + "quote 1.0.28", "syn 1.0.109", ] @@ -1162,8 +1308,8 @@ dependencies = [ "cc", "codespan-reporting", "once_cell", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "scratch", "syn 1.0.109", ] @@ -1180,8 +1326,8 @@ version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -1213,8 +1359,8 @@ checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "strsim 0.9.3", "syn 1.0.109", ] @@ -1227,8 +1373,8 @@ checksum = "001d80444f28e193f30c2f293455da62dcf9a6b29918a4253152ae2b1de592cb" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "strsim 0.10.0", "syn 1.0.109", ] @@ -1240,7 +1386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core 0.10.2", - "quote 1.0.26", + "quote 1.0.28", "syn 1.0.109", ] @@ -1251,7 +1397,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685" dependencies = [ "darling_core 0.14.3", - "quote 1.0.26", + "quote 1.0.28", "syn 1.0.109", ] @@ -1319,8 +1465,8 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cdeb9ec472d588e539a818b2dee436825730da08ad0017c4b1a17676bdc8b7" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -1332,8 +1478,8 @@ checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" dependencies = [ "darling 0.10.2", "derive_builder_core", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -1344,8 +1490,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ "darling 0.10.2", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -1356,8 +1502,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case 0.4.0", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "rustc_version", "syn 1.0.109", ] @@ -1477,6 +1623,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", +] + [[package]] name = "dns-lookup" version = "1.0.8" @@ -1562,8 +1719,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0188e3c3ba8df5753894d54461f0e39bc91741dc5b22e1c46999ec2c71f4e4" dependencies = [ "enum-ordinalize", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -1697,8 +1854,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" dependencies = [ "heck", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -1709,8 +1866,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9720bba047d567ffc8a3cba48bf19126600e249ab7f128e9233e6376976a116" dependencies = [ "heck", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -1722,8 +1879,8 @@ checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a" dependencies = [ "num-bigint", "num-traits", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "rustc_version", "syn 1.0.109", ] @@ -1734,9 +1891,9 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48016319042fb7c87b78d2993084a831793a897a5cd1a2a67cab9d1eeb4b7d76" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -1872,12 +2029,12 @@ dependencies = [ "eyre", "hex", "prettyplease", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "regex", "serde", "serde_json", - "syn 2.0.15", + "syn 2.0.18", "toml 0.7.3", "walkdir", ] @@ -1892,10 +2049,10 @@ dependencies = [ "ethers-contract-abigen", "ethers-core", "hex", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "serde_json", - "syn 2.0.15", + "syn 2.0.18", ] [[package]] @@ -1921,7 +2078,7 @@ dependencies = [ "serde", "serde_json", "strum", - "syn 2.0.15", + "syn 2.0.18", "tempfile", "thiserror", "tiny-keccak", @@ -2048,6 +2205,12 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fast-float" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" + [[package]] name = "fastrand" version = "1.9.0" @@ -2220,9 +2383,9 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -2479,6 +2642,12 @@ dependencies = [ "serde", ] +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "hashers" version = "1.0.1" @@ -2513,7 +2682,7 @@ version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db04bc24a18b9ea980628ecf00e6c0264f3c1426dac36c00cb49b6fbad8b0743" dependencies = [ - "atomic-polyfill", + "atomic-polyfill 0.1.11", "hash32", "rustc_version", "serde", @@ -2755,6 +2924,88 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "icu_collections" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8302d8dfd6044d3ddb3f807a5ef3d7bbca9a574959c6d6e4dc39aa7012d0d5" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3003f85dccfc0e238ff567693248c59153a46f4e6125ba4020b973cef4d1d335" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", +] + +[[package]] +name = "icu_normalizer" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "652869735c9fb9f5a64ba180ee16f2c848390469c116deef517ecc53f4343598" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_properties" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce0e1aa26851f16c9e04412a5911c86b7f8768dac8f8d4c5f1c568a7e5d7a434" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_provider" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc312a7b6148f7dfe098047ae2494d12d4034f48ade58d4f353000db376e305" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "serde", + "stable_deref_trait", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b728b9421e93eff1d9f8681101b78fa745e0748c95c655c83f337044a7e10" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 1.0.109", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2838,8 +3089,8 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -3095,8 +3346,8 @@ checksum = "c6027ac0b197ce9543097d02a290f550ce1d9432bf301524b013053c0b75cc94" dependencies = [ "heck", "proc-macro-crate", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -3288,6 +3539,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" +[[package]] +name = "litemap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a04a5b2b6f54acba899926491d0a6c59d98012938ca2ab5befb281c034e8f94" + [[package]] name = "lock_api" version = "0.4.9" @@ -3433,8 +3690,8 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -3524,8 +3781,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" dependencies = [ "cfg-if", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -3545,8 +3802,8 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -3571,7 +3828,7 @@ version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cfg-if", "libc", "static_assertions", @@ -3626,6 +3883,7 @@ dependencies = [ "autocfg", "num-integer", "num-traits", + "serde", ] [[package]] @@ -3716,9 +3974,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -3732,9 +3990,13 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +dependencies = [ + "atomic-polyfill 1.0.2", + "critical-section", +] [[package]] name = "oorandom" @@ -3768,8 +4030,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" dependencies = [ "bytes", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -3841,8 +4103,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -3974,6 +4236,48 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "phf" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 1.0.109", +] + +[[package]] +name = "phf_shared" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.0" @@ -3989,9 +4293,9 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -4069,6 +4373,12 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pollster" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" + [[package]] name = "polyval" version = "0.5.3" @@ -4174,8 +4484,8 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ceca8aaf45b5c46ec7ed39fff75f57290368c1846d33d24a122ca81416ab058" dependencies = [ - "proc-macro2 1.0.56", - "syn 2.0.15", + "proc-macro2 1.0.60", + "syn 2.0.18", ] [[package]] @@ -4209,8 +4519,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", "version_check", ] @@ -4221,8 +4531,8 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "version_check", ] @@ -4237,9 +4547,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -4251,7 +4561,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70" dependencies = [ "bit-set", - "bitflags", + "bitflags 1.3.2", "byteorder", "lazy_static", "num-traits", @@ -4345,11 +4655,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ - "proc-macro2 1.0.56", + "proc-macro2 1.0.60", ] [[package]] @@ -4460,7 +4770,7 @@ version = "10.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -4491,7 +4801,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -4500,7 +4810,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] [[package]] @@ -4546,6 +4856,16 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +[[package]] +name = "regress" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82a9ecfa0cb04d0b04dddb99b8ccf4f66bc8dfd23df694b398570bd8ae3a50fb" +dependencies = [ + "hashbrown 0.13.2", + "memchr", +] + [[package]] name = "reqwest" version = "0.11.18" @@ -4995,7 +5315,7 @@ dependencies = [ name = "reth-libmdbx" version = "0.1.6" dependencies = [ - "bitflags", + "bitflags 1.3.2", "byteorder", "criterion", "derive_more", @@ -5035,11 +5355,11 @@ version = "0.1.0" dependencies = [ "metrics", "once_cell", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "regex", "serial_test", - "syn 2.0.15", + "syn 2.0.18", "trybuild", ] @@ -5235,11 +5555,16 @@ dependencies = [ name = "reth-revm-inspectors" version = "0.1.0" dependencies = [ + "boa_engine", + "boa_gc", "hashbrown 0.13.2", "reth-primitives", "reth-rpc-types", "revm", "serde", + "serde_json", + "thiserror", + "tokio", ] [[package]] @@ -5272,9 +5597,9 @@ dependencies = [ name = "reth-rlp-derive" version = "0.1.1" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -5512,7 +5837,7 @@ dependencies = [ "aquamarine", "async-trait", "auto_impl", - "bitflags", + "bitflags 1.3.2", "fnv", "futures-util", "parking_lot 0.12.1", @@ -5681,8 +6006,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -5740,7 +6065,7 @@ version = "0.36.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.2.8", "io-lifetimes", "libc", @@ -5754,7 +6079,7 @@ version = "0.37.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85597d61f83914ddeba6a47b3b8ffe7365107221c2e557ed94426489fefb5f77" dependencies = [ - "bitflags", + "bitflags 1.3.2", "errno 0.3.1", "io-lifetimes", "libc", @@ -5829,6 +6154,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "ryu-js" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f" + [[package]] name = "salsa20" version = "0.10.2" @@ -5866,8 +6197,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61fa974aea2d63dd18a4ec3a49d59af9f34178c73a4f56d2f18205628d00681e" dependencies = [ "proc-macro-crate", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -5979,7 +6310,7 @@ version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" dependencies = [ - "bitflags", + "bitflags 1.3.2", "core-foundation", "core-foundation-sys", "libc", @@ -6037,22 +6368,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -6110,8 +6441,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1966009f3c05f095697c537312f5415d1e3ed31ce0a56942bac4c771c5c335e" dependencies = [ "darling 0.14.3", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -6135,8 +6466,8 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -6315,6 +6646,12 @@ dependencies = [ "time", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "sketches-ddsketch" version = "0.2.0" @@ -6412,6 +6749,12 @@ dependencies = [ "der 0.7.3", ] +[[package]] +name = "sptr" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -6458,8 +6801,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "rustversion", "syn 1.0.109", ] @@ -6542,22 +6885,46 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.15" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 1.0.109", + "unicode-xid 0.2.4", +] + +[[package]] +name = "synstructure" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", + "unicode-xid 0.2.4", +] + [[package]] name = "tap" version = "1.0.1" @@ -6611,8 +6978,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9186daca5c58cb307d09731e0ba06b13fd6c036c90672b9bfc31cecf76cf689" dependencies = [ "cargo_metadata", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "serde", "strum_macros", ] @@ -6626,8 +6993,8 @@ dependencies = [ "darling 0.14.3", "if_chain", "lazy_static", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "subprocess", "syn 1.0.109", "test-fuzz-internal", @@ -6655,6 +7022,12 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +[[package]] +name = "thin-vec" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81b6fd6beb5884b0cf3321b8117e6e5d47ecb6fc89f414cfdcca8b2fe2dd8" + [[package]] name = "thiserror" version = "1.0.40" @@ -6670,9 +7043,9 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -6721,6 +7094,16 @@ dependencies = [ "crunchy", ] +[[package]] +name = "tinystr" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -6771,9 +7154,9 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -6926,7 +7309,7 @@ checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" dependencies = [ "async-compression", "base64 0.20.0", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-core", "futures-util", @@ -6990,8 +7373,8 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -7076,7 +7459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "258bc1c4f8e2e73a977812ab339d503e6feeb92700f6d07a6de4d321522d5c08" dependencies = [ "lazy_static", - "quote 1.0.26", + "quote 1.0.28", "syn 1.0.109", ] @@ -7207,7 +7590,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "cassowary", "crossterm", "unicode-segmentation", @@ -7339,8 +7722,8 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e7e85a0596447f0f2ac090e16bc4c516c6fe91771fb0c0ccf7fa3dae896b9c" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -7361,6 +7744,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52df8b7fb78e7910d776fccf2e42ceaf3604d55e8e7eb2dbd183cb1441d8a692" + +[[package]] +name = "utf8_iter" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a8922555b9500e3d865caed19330172cd67cbf82203f1a3311d8c305cc9f33" + [[package]] name = "uuid" version = "0.8.2" @@ -7469,8 +7864,8 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", "wasm-bindgen-shared", ] @@ -7493,7 +7888,7 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" dependencies = [ - "quote 1.0.26", + "quote 1.0.28", "wasm-bindgen-macro-support", ] @@ -7503,8 +7898,8 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", + "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -7743,6 +8138,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60e49e42bdb1d5dc76f4cd78102f8f0714d32edfa3efb82286eb0f0b1fc0da0f" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -7792,6 +8199,51 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yoke" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1848075a23a28f9773498ee9a0f2cf58fcbad4f8c0ccf84a210ab33c6ae495de" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af46c169923ed7516eef0aa32b56d2651b229f57458ebe46b49ddd6efef5b7a2" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 1.0.109", + "synstructure 0.12.6", +] + +[[package]] +name = "zerofrom" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d76c3251de27615dfcce21e636c172dafb2549cd7fd93e21c66f6ca6bea2" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4eae7c1f7d4b8eafce526bc0771449ddc2f250881ae31c50d22c032b5a1c499" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 1.0.109", + "synstructure 0.12.6", +] + [[package]] name = "zeroize" version = "1.6.0" @@ -7807,9 +8259,32 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.56", - "quote 1.0.26", - "syn 2.0.15", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 2.0.18", +] + +[[package]] +name = "zerovec" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "198f54134cd865f437820aa3b43d0ad518af4e68ee161b444cdd15d8e567c8ea" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "486558732d5dde10d0f8cb2936507c1bb21bc539d924c949baf5f36a58e51bac" +dependencies = [ + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 1.0.109", + "synstructure 0.12.6", ] [[package]] diff --git a/crates/revm/revm-inspectors/Cargo.toml b/crates/revm/revm-inspectors/Cargo.toml index 066d652149aa..8e17b60f0664 100644 --- a/crates/revm/revm-inspectors/Cargo.toml +++ b/crates/revm/revm-inspectors/Cargo.toml @@ -18,3 +18,14 @@ revm = { workspace = true } hashbrown = "0.13" serde = { workspace = true, features = ["derive"] } +thiserror = {version = "1.0", optional = true } +serde_json = { version = "1.0", optional = true } + +# js-tracing-inspector +boa_engine = { git = "https://github.com/boa-dev/boa", optional = true } +boa_gc = { git = "https://github.com/boa-dev/boa", optional = true } +tokio = { version = "1", features = ["sync"], optional = true } + +[features] +default = ["js-tracer"] +js-tracer = ["boa_engine", "boa_gc", "tokio","thiserror", "serde_json"] diff --git a/crates/revm/revm-inspectors/src/tracing/js/bigint.js b/crates/revm/revm-inspectors/src/tracing/js/bigint.js new file mode 100644 index 000000000000..d9a8411b2682 --- /dev/null +++ b/crates/revm/revm-inspectors/src/tracing/js/bigint.js @@ -0,0 +1 @@ +var bigInt=function(undefined){"use strict";var BASE=1e7,LOG_BASE=7,MAX_INT=9007199254740992,MAX_INT_ARR=smallToArray(MAX_INT),LOG_MAX_INT=Math.log(MAX_INT);function Integer(v,radix){if(typeof v==="undefined")return Integer[0];if(typeof radix!=="undefined")return+radix===10?parseValue(v):parseBase(v,radix);return parseValue(v)}function BigInteger(value,sign){this.value=value;this.sign=sign;this.isSmall=false}BigInteger.prototype=Object.create(Integer.prototype);function SmallInteger(value){this.value=value;this.sign=value<0;this.isSmall=true}SmallInteger.prototype=Object.create(Integer.prototype);function isPrecise(n){return-MAX_INT0)return Math.floor(n);return Math.ceil(n)}function add(a,b){var l_a=a.length,l_b=b.length,r=new Array(l_a),carry=0,base=BASE,sum,i;for(i=0;i=base?1:0;r[i]=sum-carry*base}while(i0)r.push(carry);return r}function addAny(a,b){if(a.length>=b.length)return add(a,b);return add(b,a)}function addSmall(a,carry){var l=a.length,r=new Array(l),base=BASE,sum,i;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}BigInteger.prototype.add=function(v){var n=parseValue(v);if(this.sign!==n.sign){return this.subtract(n.negate())}var a=this.value,b=n.value;if(n.isSmall){return new BigInteger(addSmall(a,Math.abs(b)),this.sign)}return new BigInteger(addAny(a,b),this.sign)};BigInteger.prototype.plus=BigInteger.prototype.add;SmallInteger.prototype.add=function(v){var n=parseValue(v);var a=this.value;if(a<0!==n.sign){return this.subtract(n.negate())}var b=n.value;if(n.isSmall){if(isPrecise(a+b))return new SmallInteger(a+b);b=smallToArray(Math.abs(b))}return new BigInteger(addSmall(b,Math.abs(a)),a<0)};SmallInteger.prototype.plus=SmallInteger.prototype.add;function subtract(a,b){var a_l=a.length,b_l=b.length,r=new Array(a_l),borrow=0,base=BASE,i,difference;for(i=0;i=0){value=subtract(a,b)}else{value=subtract(b,a);sign=!sign}value=arrayToSmall(value);if(typeof value==="number"){if(sign)value=-value;return new SmallInteger(value)}return new BigInteger(value,sign)}function subtractSmall(a,b,sign){var l=a.length,r=new Array(l),carry=-b,base=BASE,i,difference;for(i=0;i=0)};SmallInteger.prototype.minus=SmallInteger.prototype.subtract;BigInteger.prototype.negate=function(){return new BigInteger(this.value,!this.sign)};SmallInteger.prototype.negate=function(){var sign=this.sign;var small=new SmallInteger(-this.value);small.sign=!sign;return small};BigInteger.prototype.abs=function(){return new BigInteger(this.value,false)};SmallInteger.prototype.abs=function(){return new SmallInteger(Math.abs(this.value))};function multiplyLong(a,b){var a_l=a.length,b_l=b.length,l=a_l+b_l,r=createArray(l),base=BASE,product,carry,i,a_i,b_j;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}function shiftLeft(x,n){var r=[];while(n-- >0)r.push(0);return r.concat(x)}function multiplyKaratsuba(x,y){var n=Math.max(x.length,y.length);if(n<=30)return multiplyLong(x,y);n=Math.ceil(n/2);var b=x.slice(n),a=x.slice(0,n),d=y.slice(n),c=y.slice(0,n);var ac=multiplyKaratsuba(a,c),bd=multiplyKaratsuba(b,d),abcd=multiplyKaratsuba(addAny(a,b),addAny(c,d));var product=addAny(addAny(ac,shiftLeft(subtract(subtract(abcd,ac),bd),n)),shiftLeft(bd,2*n));trim(product);return product}function useKaratsuba(l1,l2){return-.012*l1-.012*l2+15e-6*l1*l2>0}BigInteger.prototype.multiply=function(v){var n=parseValue(v),a=this.value,b=n.value,sign=this.sign!==n.sign,abs;if(n.isSmall){if(b===0)return Integer[0];if(b===1)return this;if(b===-1)return this.negate();abs=Math.abs(b);if(abs=0;shift--){quotientDigit=base-1;if(remainder[shift+b_l]!==divisorMostSignificantDigit){quotientDigit=Math.floor((remainder[shift+b_l]*base+remainder[shift+b_l-1])/divisorMostSignificantDigit)}carry=0;borrow=0;l=divisor.length;for(i=0;ib_l){highx=(highx+1)*base}guess=Math.ceil(highx/highy);do{check=multiplySmall(b,guess);if(compareAbs(check,part)<=0)break;guess--}while(guess);result.push(guess);part=subtract(part,check)}result.reverse();return[arrayToSmall(result),arrayToSmall(part)]}function divModSmall(value,lambda){var length=value.length,quotient=createArray(length),base=BASE,i,q,remainder,divisor;remainder=0;for(i=length-1;i>=0;--i){divisor=remainder*base+value[i];q=truncate(divisor/lambda);remainder=divisor-q*lambda;quotient[i]=q|0}return[quotient,remainder|0]}function divModAny(self,v){var value,n=parseValue(v);var a=self.value,b=n.value;var quotient;if(b===0)throw new Error("Cannot divide by zero");if(self.isSmall){if(n.isSmall){return[new SmallInteger(truncate(a/b)),new SmallInteger(a%b)]}return[Integer[0],self]}if(n.isSmall){if(b===1)return[self,Integer[0]];if(b==-1)return[self.negate(),Integer[0]];var abs=Math.abs(b);if(absb.length?1:-1}for(var i=a.length-1;i>=0;i--){if(a[i]!==b[i])return a[i]>b[i]?1:-1}return 0}BigInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall)return 1;return compareAbs(a,b)};SmallInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=Math.abs(this.value),b=n.value;if(n.isSmall){b=Math.abs(b);return a===b?0:a>b?1:-1}return-1};BigInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(this.sign!==n.sign){return n.sign?1:-1}if(n.isSmall){return this.sign?-1:1}return compareAbs(a,b)*(this.sign?-1:1)};BigInteger.prototype.compareTo=BigInteger.prototype.compare;SmallInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall){return a==b?0:a>b?1:-1}if(a<0!==n.sign){return a<0?-1:1}return a<0?1:-1};SmallInteger.prototype.compareTo=SmallInteger.prototype.compare;BigInteger.prototype.equals=function(v){return this.compare(v)===0};SmallInteger.prototype.eq=SmallInteger.prototype.equals=BigInteger.prototype.eq=BigInteger.prototype.equals;BigInteger.prototype.notEquals=function(v){return this.compare(v)!==0};SmallInteger.prototype.neq=SmallInteger.prototype.notEquals=BigInteger.prototype.neq=BigInteger.prototype.notEquals;BigInteger.prototype.greater=function(v){return this.compare(v)>0};SmallInteger.prototype.gt=SmallInteger.prototype.greater=BigInteger.prototype.gt=BigInteger.prototype.greater;BigInteger.prototype.lesser=function(v){return this.compare(v)<0};SmallInteger.prototype.lt=SmallInteger.prototype.lesser=BigInteger.prototype.lt=BigInteger.prototype.lesser;BigInteger.prototype.greaterOrEquals=function(v){return this.compare(v)>=0};SmallInteger.prototype.geq=SmallInteger.prototype.greaterOrEquals=BigInteger.prototype.geq=BigInteger.prototype.greaterOrEquals;BigInteger.prototype.lesserOrEquals=function(v){return this.compare(v)<=0};SmallInteger.prototype.leq=SmallInteger.prototype.lesserOrEquals=BigInteger.prototype.leq=BigInteger.prototype.lesserOrEquals;BigInteger.prototype.isEven=function(){return(this.value[0]&1)===0};SmallInteger.prototype.isEven=function(){return(this.value&1)===0};BigInteger.prototype.isOdd=function(){return(this.value[0]&1)===1};SmallInteger.prototype.isOdd=function(){return(this.value&1)===1};BigInteger.prototype.isPositive=function(){return!this.sign};SmallInteger.prototype.isPositive=function(){return this.value>0};BigInteger.prototype.isNegative=function(){return this.sign};SmallInteger.prototype.isNegative=function(){return this.value<0};BigInteger.prototype.isUnit=function(){return false};SmallInteger.prototype.isUnit=function(){return Math.abs(this.value)===1};BigInteger.prototype.isZero=function(){return false};SmallInteger.prototype.isZero=function(){return this.value===0};BigInteger.prototype.isDivisibleBy=function(v){var n=parseValue(v);var value=n.value;if(value===0)return false;if(value===1)return true;if(value===2)return this.isEven();return this.mod(n).equals(Integer[0])};SmallInteger.prototype.isDivisibleBy=BigInteger.prototype.isDivisibleBy;function isBasicPrime(v){var n=v.abs();if(n.isUnit())return false;if(n.equals(2)||n.equals(3)||n.equals(5))return true;if(n.isEven()||n.isDivisibleBy(3)||n.isDivisibleBy(5))return false;if(n.lesser(25))return true}BigInteger.prototype.isPrime=function(){var isPrime=isBasicPrime(this);if(isPrime!==undefined)return isPrime;var n=this.abs(),nPrev=n.prev();var a=[2,3,5,7,11,13,17,19],b=nPrev,d,t,i,x;while(b.isEven())b=b.divide(2);for(i=0;i-MAX_INT)return new SmallInteger(value-1);return new BigInteger(MAX_INT_ARR,true)};var powersOfTwo=[1];while(2*powersOfTwo[powersOfTwo.length-1]<=BASE)powersOfTwo.push(2*powersOfTwo[powersOfTwo.length-1]);var powers2Length=powersOfTwo.length,highestPower2=powersOfTwo[powers2Length-1];function shift_isSmall(n){return(typeof n==="number"||typeof n==="string")&&+Math.abs(n)<=BASE||n instanceof BigInteger&&n.value.length<=1}BigInteger.prototype.shiftLeft=function(n){if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftRight(-n);var result=this;while(n>=powers2Length){result=result.multiply(highestPower2);n-=powers2Length-1}return result.multiply(powersOfTwo[n])};SmallInteger.prototype.shiftLeft=BigInteger.prototype.shiftLeft;BigInteger.prototype.shiftRight=function(n){var remQuo;if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftLeft(-n);var result=this;while(n>=powers2Length){if(result.isZero())return result;remQuo=divModAny(result,highestPower2);result=remQuo[1].isNegative()?remQuo[0].prev():remQuo[0];n-=powers2Length-1}remQuo=divModAny(result,powersOfTwo[n]);return remQuo[1].isNegative()?remQuo[0].prev():remQuo[0]};SmallInteger.prototype.shiftRight=BigInteger.prototype.shiftRight;function bitwise(x,y,fn){y=parseValue(y);var xSign=x.isNegative(),ySign=y.isNegative();var xRem=xSign?x.not():x,yRem=ySign?y.not():y;var xDigit=0,yDigit=0;var xDivMod=null,yDivMod=null;var result=[];while(!xRem.isZero()||!yRem.isZero()){xDivMod=divModAny(xRem,highestPower2);xDigit=xDivMod[1].toJSNumber();if(xSign){xDigit=highestPower2-1-xDigit}yDivMod=divModAny(yRem,highestPower2);yDigit=yDivMod[1].toJSNumber();if(ySign){yDigit=highestPower2-1-yDigit}xRem=xDivMod[0];yRem=yDivMod[0];result.push(fn(xDigit,yDigit))}var sum=fn(xSign?1:0,ySign?1:0)!==0?bigInt(-1):bigInt(0);for(var i=result.length-1;i>=0;i-=1){sum=sum.multiply(highestPower2).add(bigInt(result[i]))}return sum}BigInteger.prototype.not=function(){return this.negate().prev()};SmallInteger.prototype.not=BigInteger.prototype.not;BigInteger.prototype.and=function(n){return bitwise(this,n,function(a,b){return a&b})};SmallInteger.prototype.and=BigInteger.prototype.and;BigInteger.prototype.or=function(n){return bitwise(this,n,function(a,b){return a|b})};SmallInteger.prototype.or=BigInteger.prototype.or;BigInteger.prototype.xor=function(n){return bitwise(this,n,function(a,b){return a^b})};SmallInteger.prototype.xor=BigInteger.prototype.xor;var LOBMASK_I=1<<30,LOBMASK_BI=(BASE&-BASE)*(BASE&-BASE)|LOBMASK_I;function roughLOB(n){var v=n.value,x=typeof v==="number"?v|LOBMASK_I:v[0]+v[1]*BASE|LOBMASK_BI;return x&-x}function max(a,b){a=parseValue(a);b=parseValue(b);return a.greater(b)?a:b}function min(a,b){a=parseValue(a);b=parseValue(b);return a.lesser(b)?a:b}function gcd(a,b){a=parseValue(a).abs();b=parseValue(b).abs();if(a.equals(b))return a;if(a.isZero())return b;if(b.isZero())return a;var c=Integer[1],d,t;while(a.isEven()&&b.isEven()){d=Math.min(roughLOB(a),roughLOB(b));a=a.divide(d);b=b.divide(d);c=c.multiply(d)}while(a.isEven()){a=a.divide(roughLOB(a))}do{while(b.isEven()){b=b.divide(roughLOB(b))}if(a.greater(b)){t=b;b=a;a=t}b=b.subtract(a)}while(!b.isZero());return c.isUnit()?a:a.multiply(c)}function lcm(a,b){a=parseValue(a).abs();b=parseValue(b).abs();return a.divide(gcd(a,b)).multiply(b)}function randBetween(a,b){a=parseValue(a);b=parseValue(b);var low=min(a,b),high=max(a,b);var range=high.subtract(low).add(1);if(range.isSmall)return low.add(Math.floor(Math.random()*range));var length=range.value.length-1;var result=[],restricted=true;for(var i=length;i>=0;i--){var top=restricted?range.value[i]:BASE;var digit=truncate(Math.random()*top);result.unshift(digit);if(digit=absBase){if(c==="1"&&absBase===1)continue;throw new Error(c+" is not a valid digit in base "+base+".")}else if(c.charCodeAt(0)-87>=absBase){throw new Error(c+" is not a valid digit in base "+base+".")}}}if(2<=base&&base<=36){if(length<=LOG_MAX_INT/Math.log(base)){var result=parseInt(text,base);if(isNaN(result)){throw new Error(c+" is not a valid digit in base "+base+".")}return new SmallInteger(parseInt(text,base))}}base=parseValue(base);var digits=[];var isNegative=text[0]==="-";for(i=isNegative?1:0;i");digits.push(parseValue(text.slice(start+1,i)))}else throw new Error(c+" is not a valid character")}return parseBaseFromArray(digits,base,isNegative)};function parseBaseFromArray(digits,base,isNegative){var val=Integer[0],pow=Integer[1],i;for(i=digits.length-1;i>=0;i--){val=val.add(digits[i].times(pow));pow=pow.times(base)}return isNegative?val.negate():val}function stringify(digit){var v=digit.value;if(typeof v==="number")v=[v];if(v.length===1&&v[0]<=35){return"0123456789abcdefghijklmnopqrstuvwxyz".charAt(v[0])}return"<"+v+">"}function toBase(n,base){base=bigInt(base);if(base.isZero()){if(n.isZero())return"0";throw new Error("Cannot convert nonzero numbers to base 0.")}if(base.equals(-1)){if(n.isZero())return"0";if(n.isNegative())return new Array(1-n).join("10");return"1"+new Array(+n).join("01")}var minusSign="";if(n.isNegative()&&base.isPositive()){minusSign="-";n=n.abs()}if(base.equals(1)){if(n.isZero())return"0";return minusSign+new Array(+n+1).join(1)}var out=[];var left=n,divmod;while(left.isNegative()||left.compareAbs(base)>=0){divmod=left.divmod(base);left=divmod.quotient;var digit=divmod.remainder;if(digit.isNegative()){digit=base.minus(digit).abs();left=left.next()}out.push(stringify(digit))}out.push(stringify(left));return minusSign+out.reverse().join("")}BigInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!==10)return toBase(this,radix);var v=this.value,l=v.length,str=String(v[--l]),zeros="0000000",digit;while(--l>=0){digit=String(v[l]);str+=zeros.slice(digit.length)+digit}var sign=this.sign?"-":"";return sign+str};SmallInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!=10)return toBase(this,radix);return String(this.value)};BigInteger.prototype.toJSON=SmallInteger.prototype.toJSON=function(){return this.toString()};BigInteger.prototype.valueOf=function(){return+this.toString()};BigInteger.prototype.toJSNumber=BigInteger.prototype.valueOf;SmallInteger.prototype.valueOf=function(){return this.value};SmallInteger.prototype.toJSNumber=SmallInteger.prototype.valueOf;function parseStringValue(v){if(isPrecise(+v)){var x=+v;if(x===truncate(x))return new SmallInteger(x);throw"Invalid integer: "+v}var sign=v[0]==="-";if(sign)v=v.slice(1);var split=v.split(/e/i);if(split.length>2)throw new Error("Invalid integer: "+split.join("e"));if(split.length===2){var exp=split[1];if(exp[0]==="+")exp=exp.slice(1);exp=+exp;if(exp!==truncate(exp)||!isPrecise(exp))throw new Error("Invalid integer: "+exp+" is not a valid exponent.");var text=split[0];var decimalPlace=text.indexOf(".");if(decimalPlace>=0){exp-=text.length-decimalPlace-1;text=text.slice(0,decimalPlace)+text.slice(decimalPlace+1)}if(exp<0)throw new Error("Cannot include negative exponent part for integers");text+=new Array(exp+1).join("0");v=text}var isValid=/^([0-9][0-9]*)$/.test(v);if(!isValid)throw new Error("Invalid integer: "+v);var r=[],max=v.length,l=LOG_BASE,min=max-l;while(max>0){r.push(+v.slice(min,max));min-=l;if(min<0)min=0;max-=l}trim(r);return new BigInteger(r,sign)}function parseNumberValue(v){if(isPrecise(v)){if(v!==truncate(v))throw new Error(v+" is not an integer.");return new SmallInteger(v)}return parseStringValue(v.toString())}function parseValue(v){if(typeof v==="number"){return parseNumberValue(v)}if(typeof v==="string"){return parseStringValue(v)}return v}for(var i=0;i<1e3;i++){Integer[i]=new SmallInteger(i);if(i>0)Integer[-i]=new SmallInteger(-i)}Integer.one=Integer[1];Integer.zero=Integer[0];Integer.minusOne=Integer[-1];Integer.max=max;Integer.min=min;Integer.gcd=gcd;Integer.lcm=lcm;Integer.isInstance=function(x){return x instanceof BigInteger||x instanceof SmallInteger};Integer.randBetween=randBetween;Integer.fromArray=function(digits,base,isNegative){return parseBaseFromArray(digits.map(parseValue),parseValue(base||10),isNegative)};return Integer}();if(typeof module!=="undefined"&&module.hasOwnProperty("exports")){module.exports=bigInt}if(typeof define==="function"&&define.amd){define("big-integer",[],function(){return bigInt})}; bigInt \ No newline at end of file diff --git a/crates/revm/revm-inspectors/src/tracing/js/bindings.rs b/crates/revm/revm-inspectors/src/tracing/js/bindings.rs new file mode 100644 index 000000000000..5e988c955c61 --- /dev/null +++ b/crates/revm/revm-inspectors/src/tracing/js/bindings.rs @@ -0,0 +1,797 @@ +//! Type bindings for js tracing inspector + +use crate::tracing::{ + js::{ + builtins::{ + address_to_buf, bytes_to_address, bytes_to_hash, from_buf, to_bigint, to_bigint_array, + to_buf, to_buf_value, + }, + JsDbRequest, + }, + types::CallKind, +}; +use boa_engine::{ + native_function::NativeFunction, + object::{builtins::JsArrayBuffer, FunctionObjectBuilder}, + Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsValue, +}; +use boa_gc::{empty_trace, Finalize, Gc, Trace}; +use reth_primitives::{bytes::Bytes, Account, Address, H256, KECCAK_EMPTY, U256}; +use revm::{ + interpreter::{ + opcode::{PUSH0, PUSH32}, + Memory, OpCode, Stack, + }, + primitives::State, +}; +use std::{borrow::Borrow, sync::mpsc::channel}; +use tokio::sync::mpsc; + +/// A macro that creates a native function that returns via [JsValue::from] +macro_rules! js_value_getter { + ($value:ident, $ctx:ident) => { + FunctionObjectBuilder::new( + $ctx, + NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from($value))), + ) + .length(0) + .build() + }; +} + +/// A macro that creates a native function that returns a captured JsValue +macro_rules! js_value_capture_getter { + ($value:ident, $ctx:ident) => { + FunctionObjectBuilder::new( + $ctx, + NativeFunction::from_copy_closure_with_captures( + move |_this, _args, input, _ctx| Ok(JsValue::from(input.clone())), + $value, + ), + ) + .length(0) + .build() + }; +} + +/// The Log object that is passed to the javascript inspector. +#[derive(Debug)] +pub(crate) struct StepLog { + /// Stack before step execution + pub(crate) stack: StackObj, + /// Opcode to be executed + pub(crate) op: OpObj, + /// All allocated memory in a step + pub(crate) memory: MemoryObj, + /// Program counter before step execution + pub(crate) pc: u64, + /// Remaining gas before step execution + pub(crate) gas_remaining: u64, + /// Gas cost of step execution + pub(crate) cost: u64, + /// Call depth + pub(crate) depth: u64, + /// Gas refund counter before step execution + pub(crate) refund: u64, + /// returns information about the error if one occurred, otherwise returns undefined + pub(crate) error: Option, + /// The contract object available to the js inspector + pub(crate) contract: Contract, +} + +impl StepLog { + /// Converts the contract object into a js object + /// + /// Caution: this expects a global property `bigint` to be present. + pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { + let Self { + stack, + op, + memory, + pc, + gas_remaining: gas, + cost, + depth, + refund, + error, + contract, + } = self; + let obj = JsObject::default(); + + // fields + let op = op.into_js_object(context)?; + let memory = memory.into_js_object(context)?; + let stack = stack.into_js_object(context)?; + let contract = contract.into_js_object(context)?; + + obj.set("op", op, false, context)?; + obj.set("memory", memory, false, context)?; + obj.set("stack", stack, false, context)?; + obj.set("contract", contract, false, context)?; + + // methods + let error = + if let Some(error) = error { JsValue::from(error) } else { JsValue::undefined() }; + let get_error = js_value_capture_getter!(error, context); + let get_pc = js_value_getter!(pc, context); + let get_gas = js_value_getter!(gas, context); + let get_cost = js_value_getter!(cost, context); + let get_refund = js_value_getter!(refund, context); + let get_depth = js_value_getter!(depth, context); + + obj.set("getPc", get_pc, false, context)?; + obj.set("getError", get_error, false, context)?; + obj.set("getGas", get_gas, false, context)?; + obj.set("getCost", get_cost, false, context)?; + obj.set("getDepth", get_depth, false, context)?; + obj.set("getRefund", get_refund, false, context)?; + + Ok(obj) + } +} + +/// Represents the memory object +#[derive(Debug)] +pub(crate) struct MemoryObj(pub(crate) Memory); + +impl MemoryObj { + pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { + let obj = JsObject::default(); + let len = self.0.len(); + // TODO: add into data + let value = to_buf(self.0.data().clone(), context)?; + + let length = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, _ctx| { + Ok(JsValue::from(len as u64)) + }), + ) + .length(0) + .build(); + + let slice = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + |_this, args, memory, ctx| { + let start = args.get_or_undefined(0).to_number(ctx)?; + let end = args.get_or_undefined(1).to_number(ctx)?; + if end < start || start < 0. { + return Err(JsError::from_native(JsNativeError::typ().with_message( + format!( + "tracer accessed out of bound memory: offset {start}, end {end}" + ), + ))) + } + let start = start as usize; + let end = end as usize; + + let mut mem = memory.take()?; + let slice = mem.drain(start..end).collect::>(); + to_buf_value(slice, ctx) + }, + value.clone(), + ), + ) + .length(2) + .build(); + + let get_uint = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + |_this, args, memory, ctx| { + let offset_f64 = args.get_or_undefined(0).to_number(ctx)?; + + let mut mem = memory.take()?; + let offset = offset_f64 as usize; + if mem.len() < offset+32 || offset_f64 < 0. { + return Err(JsError::from_native( + JsNativeError::typ().with_message(format!("tracer accessed out of bound memory: available {}, offset {}, size 32", mem.len(), offset)) + )); + } + + let slice = mem.drain(offset..offset+32).collect::>(); + to_buf_value(slice, ctx) + }, + value + ), + ) + .length(1) + .build(); + + obj.set("slice", slice, false, context)?; + obj.set("getUint", get_uint, false, context)?; + obj.set("length", length, false, context)?; + Ok(obj) + } +} + +/// Represents the opcode object +#[derive(Debug)] +pub(crate) struct OpObj(pub(crate) OpCode); + +impl OpObj { + pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { + let obj = JsObject::default(); + let value = self.0.u8(); + let is_push = (PUSH0..=PUSH32).contains(&value); + + let to_number = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from(value))), + ) + .length(0) + .build(); + + let is_push = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from(is_push))), + ) + .length(0) + .build(); + + let to_string = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, _ctx| { + let s = OpCode::try_from_u8(value).expect("invalid opcode").to_string(); + Ok(JsValue::from(s)) + }), + ) + .length(0) + .build(); + + obj.set("toNumber", to_number, false, context)?; + obj.set("toString", to_string, false, context)?; + obj.set("isPush", is_push, false, context)?; + Ok(obj) + } +} + +/// Represents the stack object +#[derive(Debug, Clone)] +pub(crate) struct StackObj(pub(crate) Stack); + +impl StackObj { + pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { + let obj = JsObject::default(); + let stack = self.0; + let len = stack.len(); + let stack_arr = to_bigint_array(stack.data(), context)?; + let length = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, _ctx| Ok(JsValue::from(len))), + ) + .length(0) + .build(); + + let peek = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + move |_this, args, stack_arr, ctx| { + let idx_f64 = args.get_or_undefined(0).to_number(ctx)?; + let idx = idx_f64 as usize; + if len <= idx || idx_f64 < 0. { + return Err(JsError::from_native(JsNativeError::typ().with_message( + format!( + "tracer accessed out of bound stack: size {len}, index {idx_f64}" + ), + ))) + } + stack_arr.get(idx as u64, ctx) + }, + stack_arr, + ), + ) + .length(1) + .build(); + + obj.set("length", length, false, context)?; + obj.set("peek", peek, false, context)?; + Ok(obj) + } +} + +/// Represents the contract object +#[derive(Debug, Clone, Default)] +pub(crate) struct Contract { + pub(crate) caller: Address, + pub(crate) contract: Address, + pub(crate) value: U256, + pub(crate) input: Bytes, +} + +impl Contract { + /// Converts the contract object into a js object + /// + /// Caution: this expects a global property `bigint` to be present. + pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { + let Contract { caller, contract, value, input } = self; + let obj = JsObject::default(); + + let get_caller = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, ctx| { + to_buf_value(caller.as_bytes().to_vec(), ctx) + }), + ) + .length(0) + .build(); + + let get_address = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, ctx| { + to_buf_value(contract.as_bytes().to_vec(), ctx) + }), + ) + .length(0) + .build(); + + let get_value = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure(move |_this, _args, ctx| to_bigint(value, ctx)), + ) + .length(0) + .build(); + + let input = to_buf_value(input.to_vec(), context)?; + let get_input = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + move |_this, _args, input, _ctx| Ok(input.clone()), + input, + ), + ) + .length(0) + .build(); + + obj.set("getCaller", get_caller, false, context)?; + obj.set("getAddress", get_address, false, context)?; + obj.set("getValue", get_value, false, context)?; + obj.set("getInput", get_input, false, context)?; + + Ok(obj) + } +} + +/// Represents the call frame object for exit functions +pub(crate) struct FrameResult { + pub(crate) gas_used: u64, + pub(crate) output: Bytes, + pub(crate) error: Option, +} + +impl FrameResult { + pub(crate) fn into_js_object(self, ctx: &mut Context<'_>) -> JsResult { + let Self { gas_used, output, error } = self; + let obj = JsObject::default(); + + let output = to_buf_value(output.to_vec(), ctx)?; + let get_output = FunctionObjectBuilder::new( + ctx, + NativeFunction::from_copy_closure_with_captures( + move |_this, _args, output, _ctx| Ok(output.clone()), + output, + ), + ) + .length(0) + .build(); + + let error = error.map(JsValue::from).unwrap_or_default(); + let get_error = js_value_capture_getter!(error, ctx); + let get_gas_used = js_value_getter!(gas_used, ctx); + + obj.set("getGasUsed", get_gas_used, false, ctx)?; + obj.set("getOutput", get_output, false, ctx)?; + obj.set("getError", get_error, false, ctx)?; + + Ok(obj) + } +} + +/// Represents the call frame object for enter functions +pub(crate) struct CallFrame { + pub(crate) contract: Contract, + pub(crate) kind: CallKind, + pub(crate) gas: u64, +} + +impl CallFrame { + pub(crate) fn into_js_object(self, ctx: &mut Context<'_>) -> JsResult { + let CallFrame { contract: Contract { caller, contract, value, input }, kind, gas } = self; + let obj = JsObject::default(); + + let get_from = FunctionObjectBuilder::new( + ctx, + NativeFunction::from_copy_closure(move |_this, _args, ctx| { + to_buf_value(caller.as_bytes().to_vec(), ctx) + }), + ) + .length(0) + .build(); + + let get_to = FunctionObjectBuilder::new( + ctx, + NativeFunction::from_copy_closure(move |_this, _args, ctx| { + to_buf_value(contract.as_bytes().to_vec(), ctx) + }), + ) + .length(0) + .build(); + + let get_value = FunctionObjectBuilder::new( + ctx, + NativeFunction::from_copy_closure(move |_this, _args, ctx| to_bigint(value, ctx)), + ) + .length(0) + .build(); + + let input = to_buf_value(input.to_vec(), ctx)?; + let get_input = FunctionObjectBuilder::new( + ctx, + NativeFunction::from_copy_closure_with_captures( + move |_this, _args, input, _ctx| Ok(input.clone()), + input, + ), + ) + .length(0) + .build(); + + let get_gas = js_value_getter!(gas, ctx); + let ty = kind.to_string(); + let get_type = js_value_capture_getter!(ty, ctx); + + obj.set("getFrom", get_from, false, ctx)?; + obj.set("getTo", get_to, false, ctx)?; + obj.set("getValue", get_value, false, ctx)?; + obj.set("getInput", get_input, false, ctx)?; + obj.set("getGas", get_gas, false, ctx)?; + obj.set("getType", get_type, false, ctx)?; + + Ok(obj) + } +} + +/// The `ctx` object that represents the context in which the transaction is executed. +pub(crate) struct EvmContext { + /// String, one of the two values CALL and CREATE + pub(crate) r#type: String, + /// Sender of the transaction + pub(crate) from: Address, + /// Target of the transaction + pub(crate) to: Option
, + pub(crate) input: Bytes, + /// Gas limit + pub(crate) gas: u64, + /// Number, amount of gas used in executing the transaction (excludes txdata costs) + pub(crate) gas_used: u64, + /// Number, gas price configured in the transaction being executed + pub(crate) gas_price: u64, + /// Number, intrinsic gas for the transaction being executed + pub(crate) intrinsic_gas: u64, + /// big.int Amount to be transferred in wei + pub(crate) value: U256, + /// Number, block number + pub(crate) block: u64, + pub(crate) output: Bytes, + /// Number, block number + pub(crate) time: String, + // TODO more fields + pub(crate) block_hash: Option, + pub(crate) tx_index: Option, + pub(crate) tx_hash: Option, +} + +impl EvmContext { + pub(crate) fn into_js_object(self, ctx: &mut Context<'_>) -> JsResult { + let Self { + r#type, + from, + to, + input, + gas, + gas_used, + gas_price, + intrinsic_gas, + value, + block, + output, + time, + block_hash, + tx_index, + tx_hash, + } = self; + let obj = JsObject::default(); + + // add properties + + obj.set("type", r#type, false, ctx)?; + obj.set("from", address_to_buf(from, ctx)?, false, ctx)?; + if let Some(to) = to { + obj.set("to", address_to_buf(to, ctx)?, false, ctx)?; + } else { + obj.set("to", JsValue::null(), false, ctx)?; + } + + obj.set("input", to_buf(input.to_vec(), ctx)?, false, ctx)?; + obj.set("gas", gas, false, ctx)?; + obj.set("gasUsed", gas_used, false, ctx)?; + obj.set("gasPrice", gas_price, false, ctx)?; + obj.set("intrinsicGas", intrinsic_gas, false, ctx)?; + obj.set("value", to_bigint(value, ctx)?, false, ctx)?; + obj.set("block", block, false, ctx)?; + obj.set("output", to_buf(output.to_vec(), ctx)?, false, ctx)?; + obj.set("time", time, false, ctx)?; + if let Some(block_hash) = block_hash { + obj.set("blockHash", to_buf(block_hash.as_bytes().to_vec(), ctx)?, false, ctx)?; + } + if let Some(tx_index) = tx_index { + obj.set("txIndex", tx_index as u64, false, ctx)?; + } + if let Some(tx_hash) = tx_hash { + obj.set("txHash", to_buf(tx_hash.as_bytes().to_vec(), ctx)?, false, ctx)?; + } + + Ok(obj) + } +} + +/// DB is the object that allows the js inspector to interact with the database. +pub(crate) struct EvmDb { + db: EvmDBInner, +} + +impl EvmDb { + pub(crate) fn new(state: State, to_db: mpsc::Sender) -> Self { + Self { db: EvmDBInner { state, to_db } } + } +} + +impl EvmDb { + pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { + let obj = JsObject::default(); + + let db = Gc::new(self.db); + let exists = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + move |_this, args, db, ctx| { + let val = args.get_or_undefined(0).clone(); + let db: &EvmDBInner = db.borrow(); + let acc = db.read_basic(val, ctx)?; + let exists = acc.is_some(); + Ok(JsValue::from(exists)) + }, + db.clone(), + ), + ) + .length(1) + .build(); + + let get_balance = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + move |_this, args, db, ctx| { + let val = args.get_or_undefined(0).clone(); + let db: &EvmDBInner = db.borrow(); + let acc = db.read_basic(val, ctx)?; + let balance = acc.map(|acc| acc.balance).unwrap_or_default(); + to_bigint(balance, ctx) + }, + db.clone(), + ), + ) + .length(1) + .build(); + + let get_nonce = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + move |_this, args, db, ctx| { + let val = args.get_or_undefined(0).clone(); + let db: &EvmDBInner = db.borrow(); + let acc = db.read_basic(val, ctx)?; + let nonce = acc.map(|acc| acc.nonce).unwrap_or_default(); + Ok(JsValue::from(nonce)) + }, + db.clone(), + ), + ) + .length(1) + .build(); + + let get_code = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + move |_this, args, db, ctx| { + let val = args.get_or_undefined(0).clone(); + let db: &EvmDBInner = db.borrow(); + Ok(db.read_code(val, ctx)?.into()) + }, + db.clone(), + ), + ) + .length(1) + .build(); + + let get_state = FunctionObjectBuilder::new( + context, + NativeFunction::from_copy_closure_with_captures( + move |_this, args, db, ctx| { + let addr = args.get_or_undefined(0).clone(); + let slot = args.get_or_undefined(1).clone(); + let db: &EvmDBInner = db.borrow(); + Ok(db.read_state(addr, slot, ctx)?.into()) + }, + db, + ), + ) + .length(2) + .build(); + + obj.set("getBalance", get_balance, false, context)?; + obj.set("getNonce", get_nonce, false, context)?; + obj.set("getCode", get_code, false, context)?; + obj.set("getState", get_state, false, context)?; + obj.set("exists", exists, false, context)?; + Ok(obj) + } +} + +#[derive(Clone)] +struct EvmDBInner { + state: State, + to_db: mpsc::Sender, +} + +impl EvmDBInner { + fn read_basic(&self, address: JsValue, ctx: &mut Context<'_>) -> JsResult> { + let buf = from_buf(address, ctx)?; + let address = bytes_to_address(buf); + if let Some(acc) = self.state.get(&address) { + return Ok(Some(Account { + nonce: acc.info.nonce, + balance: acc.info.balance, + bytecode_hash: Some(acc.info.code_hash), + })) + } + let (tx, rx) = channel(); + if self.to_db.try_send(JsDbRequest::Basic { address, resp: tx }).is_err() { + return Err(JsError::from_native( + JsNativeError::error() + .with_message(format!("Failed to read address {address:?} from database",)), + )) + } + + match rx.recv() { + Ok(Ok(maybe_acc)) => Ok(maybe_acc), + _ => Err(JsError::from_native( + JsNativeError::error() + .with_message(format!("Failed to read address {address:?} from database",)), + )), + } + } + + fn read_code(&self, address: JsValue, ctx: &mut Context<'_>) -> JsResult { + let acc = self.read_basic(address, ctx)?; + let code_hash = acc.and_then(|acc| acc.bytecode_hash).unwrap_or(KECCAK_EMPTY); + if code_hash == KECCAK_EMPTY { + return JsArrayBuffer::new(0, ctx) + } + + let (tx, rx) = channel(); + if self.to_db.try_send(JsDbRequest::Code { code_hash, resp: tx }).is_err() { + return Err(JsError::from_native( + JsNativeError::error() + .with_message(format!("Failed to read code hash {code_hash:?} from database",)), + )) + } + + let code = match rx.recv() { + Ok(Ok(code)) => code, + _ => { + return Err(JsError::from_native(JsNativeError::error().with_message(format!( + "Failed to read code hash {code_hash:?} from database", + )))) + } + }; + + to_buf(code.to_vec(), ctx) + } + + fn read_state( + &self, + address: JsValue, + slot: JsValue, + ctx: &mut Context<'_>, + ) -> JsResult { + let buf = from_buf(address, ctx)?; + let address = bytes_to_address(buf); + + let buf = from_buf(slot, ctx)?; + let slot = bytes_to_hash(buf); + + let (tx, rx) = channel(); + if self.to_db.try_send(JsDbRequest::StorageAt { address, index: slot, resp: tx }).is_err() { + return Err(JsError::from_native(JsNativeError::error().with_message(format!( + "Failed to read state for {address:?} at {slot:?} from database", + )))) + } + + let value = match rx.recv() { + Ok(Ok(value)) => value, + _ => { + return Err(JsError::from_native(JsNativeError::error().with_message(format!( + "Failed to read state for {address:?} at {slot:?} from database", + )))) + } + }; + let value: H256 = value.into(); + to_buf(value.as_bytes().to_vec(), ctx) + } +} + +impl Finalize for EvmDBInner {} + +unsafe impl Trace for EvmDBInner { + empty_trace!(); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tracing::js::builtins::BIG_INT_JS; + use boa_engine::{object::builtins::JsArrayBuffer, property::Attribute, Source}; + + #[test] + fn test_contract() { + let mut ctx = Context::default(); + let contract = Contract { + caller: Address::zero(), + contract: Address::zero(), + value: U256::from(1337u64), + input: vec![0x01, 0x02, 0x03].into(), + }; + let big_int = ctx.eval(Source::from_bytes(BIG_INT_JS.as_bytes())).unwrap(); + ctx.register_global_property("bigint", big_int, Attribute::all()).unwrap(); + + let obj = contract.clone().into_js_object(&mut ctx).unwrap(); + let s = r#"({ + call: function(contract) { return contract.getCaller(); }, + value: function(contract) { return contract.getValue(); }, + input: function(contract) { return contract.getInput(); } + })"#; + + let eval_obj = ctx.eval(Source::from_bytes(s.as_bytes())).unwrap(); + + let call = eval_obj.as_object().unwrap().get("call", &mut ctx).unwrap(); + let res = call + .as_callable() + .unwrap() + .call(&JsValue::undefined(), &[obj.clone().into()], &mut ctx) + .unwrap(); + assert!(res.is_object()); + assert!(res.as_object().unwrap().is_array_buffer()); + + let call = eval_obj.as_object().unwrap().get("value", &mut ctx).unwrap(); + let res = call + .as_callable() + .unwrap() + .call(&JsValue::undefined(), &[obj.clone().into()], &mut ctx) + .unwrap(); + assert_eq!( + res.to_string(&mut ctx).unwrap().to_std_string().unwrap(), + contract.value.to_string() + ); + + let call = eval_obj.as_object().unwrap().get("input", &mut ctx).unwrap(); + let res = call + .as_callable() + .unwrap() + .call(&JsValue::undefined(), &[obj.into()], &mut ctx) + .unwrap(); + + let buffer = JsArrayBuffer::from_object(res.as_object().unwrap().clone()).unwrap(); + let input = buffer.take().unwrap(); + assert_eq!(input, contract.input); + } +} diff --git a/crates/revm/revm-inspectors/src/tracing/js/builtins.rs b/crates/revm/revm-inspectors/src/tracing/js/builtins.rs new file mode 100644 index 000000000000..f083ba5aab8c --- /dev/null +++ b/crates/revm/revm-inspectors/src/tracing/js/builtins.rs @@ -0,0 +1,163 @@ +//! Builtin functions + +use boa_engine::{ + object::builtins::{JsArray, JsArrayBuffer}, + property::Attribute, + Context, JsArgs, JsError, JsNativeError, JsResult, JsString, JsValue, NativeFunction, Source, +}; +use reth_primitives::{hex, Address, H256, U256}; + +/// bigIntegerJS is the minified version of . +pub(crate) const BIG_INT_JS: &str = include_str!("bigint.js"); + +/// Registers all the builtin functions and global bigint property +pub(crate) fn register_builtins(ctx: &mut Context<'_>) -> JsResult<()> { + let big_int = ctx.eval(Source::from_bytes(BIG_INT_JS.as_bytes()))?; + ctx.register_global_property("bigint", big_int, Attribute::all())?; + ctx.register_global_builtin_callable("toHex", 1, NativeFunction::from_fn_ptr(to_hex))?; + + // TODO: register toWord, toAddress toContract toContract2 isPrecompiled slice + + Ok(()) +} + +/// Converts an array, hex string or Uint8Array to a []byte +pub(crate) fn from_buf(val: JsValue, context: &mut Context<'_>) -> JsResult> { + if let Some(obj) = val.as_object().cloned() { + if obj.is_array_buffer() { + let buf = JsArrayBuffer::from_object(obj)?; + return buf.take() + } else if obj.is_string() { + let js_string = obj.borrow().as_string().unwrap(); + return hex_decode_js_string(js_string) + } else if obj.is_array() { + let array = JsArray::from_object(obj)?; + let len = array.length(context)?; + let mut buf = Vec::with_capacity(len as usize); + for i in 0..len { + let val = array.get(i, context)?; + buf.push(val.to_number(context)? as u8); + } + return Ok(buf) + } + } + + Err(JsError::from_native(JsNativeError::typ().with_message("invalid buffer type"))) +} + +/// Create a new array buffer from the address' bytes. +pub(crate) fn address_to_buf(addr: Address, context: &mut Context<'_>) -> JsResult { + to_buf(addr.0.to_vec(), context) +} + +/// Create a new array buffer from byte block. +pub(crate) fn to_buf(bytes: Vec, context: &mut Context<'_>) -> JsResult { + JsArrayBuffer::from_byte_block(bytes, context) +} + +/// Create a new array buffer object from byte block. +pub(crate) fn to_buf_value(bytes: Vec, context: &mut Context<'_>) -> JsResult { + Ok(to_buf(bytes, context)?.into()) +} + +/// Create a new array buffer object from byte block. +pub(crate) fn to_bigint_array(items: &[U256], ctx: &mut Context<'_>) -> JsResult { + let arr = JsArray::new(ctx); + let bigint = ctx.global_object().get("bigint", ctx)?; + if !bigint.is_callable() { + return Err(JsError::from_native( + JsNativeError::typ().with_message("global object bigint is not callable"), + )) + } + let bigint = bigint.as_callable().unwrap(); + + for item in items { + let val = bigint.call(&JsValue::undefined(), &[JsValue::from(item.to_string())], ctx)?; + arr.push(val, ctx)?; + } + Ok(arr) +} + +/// Converts a buffer type to an address. +/// +/// If the buffer is larger than the address size, it will be cropped from the left +pub(crate) fn bytes_to_address(buf: Vec) -> Address { + let mut address = Address::default(); + let mut buf = &buf[..]; + let address_len = address.0.len(); + if buf.len() > address_len { + // crop from left + buf = &buf[buf.len() - address.0.len()..]; + } + let address_slice = &mut address.0[address_len - buf.len()..]; + address_slice.copy_from_slice(buf); + address +} + +/// Converts a buffer type to a hash. +/// +/// If the buffer is larger than the hash size, it will be cropped from the left +pub(crate) fn bytes_to_hash(buf: Vec) -> H256 { + let mut hash = H256::default(); + let mut buf = &buf[..]; + let hash_len = hash.0.len(); + if buf.len() > hash_len { + // crop from left + buf = &buf[buf.len() - hash.0.len()..]; + } + let hash_slice = &mut hash.0[hash_len - buf.len()..]; + hash_slice.copy_from_slice(buf); + hash +} + +/// Converts a U256 to a bigint using the global bigint property +pub(crate) fn to_bigint(value: U256, ctx: &mut Context<'_>) -> JsResult { + let bigint = ctx.global_object().get("bigint", ctx)?; + if !bigint.is_callable() { + return Ok(JsValue::undefined()) + } + bigint.as_callable().unwrap().call( + &JsValue::undefined(), + &[JsValue::from(value.to_string())], + ctx, + ) +} + +/// Converts a buffer type to a hex string +pub(crate) fn to_hex(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult { + let val = args.get_or_undefined(0).clone(); + let buf = from_buf(val, ctx)?; + Ok(JsValue::from(hex::encode(buf))) +} + +/// Decodes a hex decoded js-string +fn hex_decode_js_string(js_string: JsString) -> JsResult> { + match js_string.to_std_string() { + Ok(s) => match hex::decode(s.as_str()) { + Ok(data) => Ok(data), + Err(err) => Err(JsError::from_native( + JsNativeError::error().with_message(format!("invalid hex string {s}: {err}",)), + )), + }, + Err(err) => Err(JsError::from_native( + JsNativeError::error() + .with_message(format!("invalid utf8 string {js_string:?}: {err}",)), + )), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use boa_engine::Source; + + #[test] + fn test_install_bigint() { + let mut ctx = Context::default(); + let big_int = ctx.eval(Source::from_bytes(BIG_INT_JS.as_bytes())).unwrap(); + let value = JsValue::from(100); + let result = + big_int.as_callable().unwrap().call(&JsValue::undefined(), &[value], &mut ctx).unwrap(); + assert_eq!(result.to_string(&mut ctx).unwrap().to_std_string().unwrap(), "100"); + } +} diff --git a/crates/revm/revm-inspectors/src/tracing/js/mod.rs b/crates/revm/revm-inspectors/src/tracing/js/mod.rs new file mode 100644 index 000000000000..a80a76cb993b --- /dev/null +++ b/crates/revm/revm-inspectors/src/tracing/js/mod.rs @@ -0,0 +1,549 @@ +//! Javascript inspector + +use crate::tracing::{ + js::{ + bindings::{ + CallFrame, Contract, EvmContext, EvmDb, FrameResult, MemoryObj, OpObj, StackObj, + StepLog, + }, + builtins::register_builtins, + }, + types::CallKind, + utils::get_create_address, +}; +use boa_engine::{Context, JsError, JsObject, JsResult, JsValue, Source}; +use reth_primitives::{bytes::Bytes, Account, Address, H256, U256}; +use revm::{ + interpreter::{ + return_revert, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, + OpCode, + }, + primitives::{Env, ExecutionResult, Output, ResultAndState, TransactTo, B160, B256}, + Database, EVMData, Inspector, +}; +use tokio::sync::mpsc; + +pub(crate) mod bindings; +pub(crate) mod builtins; + +/// A javascript inspector that will delegate inspector functions to javascript functions +/// +/// See also +#[derive(Debug)] +pub struct JsInspector { + ctx: Context<'static>, + /// The javascript config provided to the inspector. + _config: JsValue, + /// The evaluated object that contains the inspector functions. + obj: JsObject, + + /// The javascript function that will be called when the result is requested. + result_fn: JsObject, + fault_fn: JsObject, + + /// EVM inspector hook functions + enter_fn: Option, + exit_fn: Option, + /// Executed before each instruction is executed. + step_fn: Option, + /// Keeps track of the current call stack. + call_stack: Vec, + /// sender half of a channel to communicate with the database service. + to_db_service: mpsc::Sender, +} + +impl JsInspector { + /// Creates a new inspector from a javascript code snipped that evaluates to an object with the + /// expected fields and a config object. + /// + /// The object must have the following fields: + /// - `result`: a function that will be called when the result is requested. + /// - `fault`: a function that will be called when the transaction fails. + /// + /// Optional functions are invoked during inspection: + /// - `enter`: a function that will be called when the execution enters a new call. + /// - `exit`: a function that will be called when the execution exits a call. + /// - `step`: a function that will be called when the execution steps to the next instruction. + /// + /// This also accepts a sender half of a channel to communicate with the database service so the + /// DB can be queried from inside the inspector. + pub fn new( + code: String, + config: serde_json::Value, + to_db_service: mpsc::Sender, + ) -> Result { + // Instantiate the execution context + let mut ctx = Context::default(); + register_builtins(&mut ctx)?; + + // evaluate the code + let code = format!("({})", code); + let obj = + ctx.eval(Source::from_bytes(code.as_bytes())).map_err(JsInspectorError::EvalCode)?; + + let obj = obj.as_object().cloned().ok_or(JsInspectorError::ExpectedJsObject)?; + + // ensure all the fields are callables, if present + + let result_fn = obj + .get("result", &mut ctx)? + .as_object() + .cloned() + .ok_or(JsInspectorError::ResultFunctionMissing)?; + if !result_fn.is_callable() { + return Err(JsInspectorError::ResultFunctionMissing) + } + + let fault_fn = obj + .get("fault", &mut ctx)? + .as_object() + .cloned() + .ok_or(JsInspectorError::ResultFunctionMissing)?; + if !result_fn.is_callable() { + return Err(JsInspectorError::ResultFunctionMissing) + } + + let enter_fn = obj.get("enter", &mut ctx)?.as_object().cloned().filter(|o| o.is_callable()); + let exit_fn = obj.get("exit", &mut ctx)?.as_object().cloned().filter(|o| o.is_callable()); + let step_fn = obj.get("step", &mut ctx)?.as_object().cloned().filter(|o| o.is_callable()); + + let config = + JsValue::from_json(&config, &mut ctx).map_err(JsInspectorError::InvalidJsonConfig)?; + + if let Some(setup_fn) = obj.get("setup", &mut ctx)?.as_object() { + if !setup_fn.is_callable() { + return Err(JsInspectorError::SetupFunctionNotCallable) + } + + // call setup() + setup_fn + .call(&(obj.clone().into()), &[config.clone()], &mut ctx) + .map_err(JsInspectorError::SetupCallFailed)?; + } + + Ok(Self { + ctx, + _config: config, + obj, + result_fn, + fault_fn, + enter_fn, + exit_fn, + step_fn, + call_stack: Default::default(), + to_db_service, + }) + } + + /// Calls the result function and returns the result as [serde_json::Value]. + /// + /// Note: This is supposed to be called after the inspection has finished. + pub fn json_result( + &mut self, + res: ResultAndState, + env: &Env, + ) -> Result { + Ok(self.result(res, env)?.to_json(&mut self.ctx)?) + } + + /// Calls the result function and returns the result. + pub fn result(&mut self, res: ResultAndState, env: &Env) -> Result { + let ResultAndState { result, state } = res; + let db = EvmDb::new(state, self.to_db_service.clone()); + + let gas_used = result.gas_used(); + let mut to = None; + let mut output_bytes = None; + match result { + ExecutionResult::Success { output, .. } => match output { + Output::Call(out) => { + output_bytes = Some(out); + } + Output::Create(out, addr) => { + to = addr; + output_bytes = Some(out); + } + }, + ExecutionResult::Revert { output, .. } => { + output_bytes = Some(output); + } + ExecutionResult::Halt { .. } => {} + }; + + let ctx = EvmContext { + r#type: match env.tx.transact_to { + TransactTo::Call(target) => { + to = Some(target); + "CALL" + } + TransactTo::Create(_) => "CREATE", + } + .to_string(), + from: env.tx.caller, + to, + input: env.tx.data.clone(), + gas: env.tx.gas_limit, + gas_used, + gas_price: env.tx.gas_price.try_into().unwrap_or(u64::MAX), + value: env.tx.value, + block: env.block.number.try_into().unwrap_or(u64::MAX), + output: output_bytes.unwrap_or_default(), + time: env.block.timestamp.to_string(), + // TODO: fill in the following fields + intrinsic_gas: 0, + block_hash: None, + tx_index: None, + tx_hash: None, + }; + let ctx = ctx.into_js_object(&mut self.ctx)?; + let db = db.into_js_object(&mut self.ctx)?; + Ok(self.result_fn.call( + &(self.obj.clone().into()), + &[ctx.into(), db.into()], + &mut self.ctx, + )?) + } + + fn try_fault(&mut self, step: StepLog, db: EvmDb) -> JsResult<()> { + let step = step.into_js_object(&mut self.ctx)?; + let db = db.into_js_object(&mut self.ctx)?; + self.fault_fn.call(&(self.obj.clone().into()), &[step.into(), db.into()], &mut self.ctx)?; + Ok(()) + } + + fn try_step(&mut self, step: StepLog, db: EvmDb) -> JsResult<()> { + if let Some(step_fn) = &self.step_fn { + let step = step.into_js_object(&mut self.ctx)?; + let db = db.into_js_object(&mut self.ctx)?; + step_fn.call(&(self.obj.clone().into()), &[step.into(), db.into()], &mut self.ctx)?; + } + Ok(()) + } + + fn try_enter(&mut self, frame: CallFrame, db: EvmDb) -> JsResult<()> { + if let Some(enter_fn) = &self.enter_fn { + let frame = frame.into_js_object(&mut self.ctx)?; + let db = db.into_js_object(&mut self.ctx)?; + enter_fn.call(&(self.obj.clone().into()), &[frame.into(), db.into()], &mut self.ctx)?; + } + Ok(()) + } + + fn try_exit(&mut self, frame: FrameResult, db: EvmDb) -> JsResult<()> { + if let Some(exit_fn) = &self.exit_fn { + let frame = frame.into_js_object(&mut self.ctx)?; + let db = db.into_js_object(&mut self.ctx)?; + exit_fn.call(&(self.obj.clone().into()), &[frame.into(), db.into()], &mut self.ctx)?; + } + Ok(()) + } + + /// Returns the currently active call + /// + /// Panics: if there's no call yet + #[track_caller] + fn active_call(&self) -> &CallStackItem { + self.call_stack.last().expect("call stack is empty") + } + + /// Pushes a new call to the stack + fn push_call( + &mut self, + address: Address, + data: Bytes, + value: U256, + kind: CallKind, + caller: Address, + gas_limit: u64, + ) -> &CallStackItem { + let call = CallStackItem { + contract: Contract { caller, contract: address, value, input: data }, + kind, + gas_limit, + }; + self.call_stack.push(call); + self.active_call() + } + + fn pop_call(&mut self) { + self.call_stack.pop(); + } +} + +impl Inspector for JsInspector +where + DB: Database, +{ + fn initialize_interp( + &mut self, + _interp: &mut Interpreter, + _data: &mut EVMData<'_, DB>, + _is_static: bool, + ) -> InstructionResult { + InstructionResult::Continue + } + + fn step( + &mut self, + interp: &mut Interpreter, + data: &mut EVMData<'_, DB>, + _is_static: bool, + ) -> InstructionResult { + if self.step_fn.is_none() { + return InstructionResult::Continue + } + + let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); + + let pc = interp.program_counter(); + let step = StepLog { + stack: StackObj(interp.stack.clone()), + op: OpObj( + OpCode::try_from_u8(interp.contract.bytecode.bytecode()[pc]) + .expect("is valid opcode;"), + ), + memory: MemoryObj(interp.memory.clone()), + pc: pc as u64, + gas_remaining: interp.gas.remaining(), + cost: interp.gas.spend(), + depth: data.journaled_state.depth(), + refund: interp.gas.refunded() as u64, + error: None, + contract: self.active_call().contract.clone(), + }; + + if self.try_step(step, db).is_err() { + return InstructionResult::Revert + } + InstructionResult::Continue + } + + fn log( + &mut self, + _evm_data: &mut EVMData<'_, DB>, + _address: &B160, + _topics: &[B256], + _data: &Bytes, + ) { + } + + fn step_end( + &mut self, + interp: &mut Interpreter, + data: &mut EVMData<'_, DB>, + _is_static: bool, + eval: InstructionResult, + ) -> InstructionResult { + if self.step_fn.is_none() { + return InstructionResult::Continue + } + + if matches!(eval, return_revert!()) { + let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); + + let pc = interp.program_counter(); + let step = StepLog { + stack: StackObj(interp.stack.clone()), + op: OpObj( + OpCode::try_from_u8(interp.contract.bytecode.bytecode()[pc]) + .expect("is valid opcode;"), + ), + memory: MemoryObj(interp.memory.clone()), + pc: pc as u64, + gas_remaining: interp.gas.remaining(), + cost: interp.gas.spend(), + depth: data.journaled_state.depth(), + refund: interp.gas.refunded() as u64, + error: Some(format!("{:?}", eval)), + contract: self.active_call().contract.clone(), + }; + + let _ = self.try_fault(step, db); + } + + InstructionResult::Continue + } + + fn call( + &mut self, + data: &mut EVMData<'_, DB>, + inputs: &mut CallInputs, + _is_static: bool, + ) -> (InstructionResult, Gas, Bytes) { + // determine correct `from` and `to` based on the call scheme + let (from, to) = match inputs.context.scheme { + CallScheme::DelegateCall | CallScheme::CallCode => { + (inputs.context.address, inputs.context.code_address) + } + _ => (inputs.context.caller, inputs.context.address), + }; + + let value = inputs.transfer.value; + self.push_call( + to, + inputs.input.clone(), + value, + inputs.context.scheme.into(), + from, + inputs.gas_limit, + ); + + if self.enter_fn.is_some() { + let call = self.active_call(); + let frame = CallFrame { + contract: call.contract.clone(), + kind: call.kind, + gas: inputs.gas_limit, + }; + let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); + if let Err(err) = self.try_enter(frame, db) { + return (InstructionResult::Revert, Gas::new(0), err.to_string().into()) + } + } + + (InstructionResult::Continue, Gas::new(0), Bytes::new()) + } + + fn call_end( + &mut self, + data: &mut EVMData<'_, DB>, + _inputs: &CallInputs, + remaining_gas: Gas, + ret: InstructionResult, + out: Bytes, + _is_static: bool, + ) -> (InstructionResult, Gas, Bytes) { + if self.exit_fn.is_some() { + let frame_result = + FrameResult { gas_used: remaining_gas.spend(), output: out.clone(), error: None }; + let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); + if let Err(err) = self.try_exit(frame_result, db) { + return (InstructionResult::Revert, Gas::new(0), err.to_string().into()) + } + } + + self.pop_call(); + + (ret, remaining_gas, out) + } + + fn create( + &mut self, + data: &mut EVMData<'_, DB>, + inputs: &mut CreateInputs, + ) -> (InstructionResult, Option, Gas, Bytes) { + let _ = data.journaled_state.load_account(inputs.caller, data.db); + let nonce = data.journaled_state.account(inputs.caller).info.nonce; + let address = get_create_address(inputs, nonce); + self.push_call( + address, + inputs.init_code.clone(), + inputs.value, + inputs.scheme.into(), + inputs.caller, + inputs.gas_limit, + ); + + if self.enter_fn.is_some() { + let call = self.active_call(); + let frame = + CallFrame { contract: call.contract.clone(), kind: call.kind, gas: call.gas_limit }; + let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); + if let Err(err) = self.try_enter(frame, db) { + return (InstructionResult::Revert, None, Gas::new(0), err.to_string().into()) + } + } + + (InstructionResult::Continue, None, Gas::new(inputs.gas_limit), Bytes::default()) + } + + fn create_end( + &mut self, + data: &mut EVMData<'_, DB>, + _inputs: &CreateInputs, + ret: InstructionResult, + address: Option, + remaining_gas: Gas, + out: Bytes, + ) -> (InstructionResult, Option, Gas, Bytes) { + if self.exit_fn.is_some() { + let frame_result = + FrameResult { gas_used: remaining_gas.spend(), output: out.clone(), error: None }; + let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); + if let Err(err) = self.try_exit(frame_result, db) { + return (InstructionResult::Revert, None, Gas::new(0), err.to_string().into()) + } + } + + self.pop_call(); + + (ret, address, remaining_gas, out) + } + + fn selfdestruct(&mut self, _contract: B160, _target: B160) { + if self.enter_fn.is_some() { + let call = self.active_call(); + let frame = + CallFrame { contract: call.contract.clone(), kind: call.kind, gas: call.gas_limit }; + let db = EvmDb::new(Default::default(), self.to_db_service.clone()); + let _ = self.try_enter(frame, db); + } + } +} + +/// Request variants to be sent from the inspector to the database +#[derive(Debug, Clone)] +pub enum JsDbRequest { + /// Bindings for [Database::basic] + Basic { + /// The address of the account to be loaded + address: Address, + /// The response channel + resp: std::sync::mpsc::Sender, String>>, + }, + /// Bindings for [Database::code_by_hash] + Code { + /// The code hash of the code to be loaded + code_hash: H256, + /// The response channel + resp: std::sync::mpsc::Sender>, + }, + /// Bindings for [Database::storage] + StorageAt { + /// The address of the account + address: Address, + /// Index of the storage slot + index: H256, + /// The response channel + resp: std::sync::mpsc::Sender>, + }, +} + +/// Represents an active call +#[derive(Debug)] +struct CallStackItem { + contract: Contract, + kind: CallKind, + gas_limit: u64, +} + +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum JsInspectorError { + #[error(transparent)] + JsError(#[from] JsError), + #[error("Failed to eval js code: {0}")] + EvalCode(JsError), + #[error("The evaluated code is not a JS object")] + ExpectedJsObject, + #[error("trace object must expose a function result()")] + ResultFunctionMissing, + #[error("trace object must expose a function fault()")] + FaultFunctionMissing, + #[error("setup object must be a function")] + SetupFunctionNotCallable, + #[error("Failed to call setup(): {0}")] + SetupCallFailed(JsError), + #[error("Invalid JSON config: {0}")] + InvalidJsonConfig(JsError), +} diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index 090ed7f3e1ea..26504a71d6eb 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -27,6 +27,9 @@ pub use config::TracingInspectorConfig; pub use fourbyte::FourByteInspector; pub use opcount::OpcodeCountInspector; +#[cfg(feature = "js-tracer")] +pub mod js; + /// An inspector that collects call traces. /// /// This [Inspector] can be hooked into the [EVM](revm::EVM) which then calls the inspector diff --git a/crates/revm/src/database.rs b/crates/revm/src/database.rs index 1698b5500d60..1cbb304acd85 100644 --- a/crates/revm/src/database.rs +++ b/crates/revm/src/database.rs @@ -10,6 +10,7 @@ use revm::{ pub type SubState = CacheDB>; /// Wrapper around StateProvider that implements revm database trait +#[derive(Debug, Clone)] pub struct State(pub DB); impl State { diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs index 62f72b275786..05c6e1d9f913 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs @@ -100,6 +100,7 @@ pub enum GethTraceFrame { FourByteTracer(FourByteFrame), CallTracer(CallFrame), PreStateTracer(PreStateFrame), + JS(serde_json::Value), } impl From for GethTraceFrame { @@ -241,6 +242,14 @@ impl GethDebugTracerConfig { } } + /// Returns the json config if this config is a JS tracer. + pub fn into_js_config(self) -> Option { + match self { + GethDebugTracerConfig::JsTracer(cfg) => Some(cfg), + _ => None, + } + } + /// Returns the [PreStateConfig] if it is a call config. pub fn into_pre_state_config(self) -> Option { match self { diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index a3116a26559c..76911cb6f748 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,7 +1,7 @@ use crate::{ eth::{ error::{EthApiError, EthResult}, - revm_utils::{inspect, replay_transactions_until, EvmOverrides}, + revm_utils::{inspect, prepare_call_env, replay_transactions_until, EvmOverrides}, EthTransactions, TransactionSource, }, result::{internal_rpc_err, ToRpcResult}, @@ -10,11 +10,16 @@ use crate::{ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_primitives::{Block, BlockId, BlockNumberOrTag, Bytes, TransactionSigned, H256}; -use reth_provider::{BlockProviderIdExt, HeaderProvider, ReceiptProviderIdExt, StateProviderBox}; +use reth_provider::{ + BlockProviderIdExt, HeaderProvider, ReceiptProviderIdExt, StateProvider, StateProviderBox, +}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, - tracing::{FourByteInspector, TracingInspector, TracingInspectorConfig}, + tracing::{ + js::{JsDbRequest, JsInspector}, + FourByteInspector, TracingInspector, TracingInspectorConfig, + }, }; use reth_rlp::{Decodable, Encodable}; use reth_rpc_api::DebugApiServer; @@ -30,7 +35,8 @@ use reth_tasks::TaskSpawner; use revm::primitives::Env; use revm_primitives::{db::DatabaseCommit, BlockEnv, CfgEnv}; use std::{future::Future, sync::Arc}; -use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit}; +use tokio::sync::{mpsc, oneshot, AcquireError, OwnedSemaphorePermit}; +use tokio_stream::{wrappers::ReceiverStream, StreamExt}; /// `debug` API implementation. /// @@ -94,6 +100,7 @@ where opts: GethDebugTracingOptions, ) -> EthResult> { // replay all transactions of the block + let this = self.clone(); self.inner.eth_api.with_state_at_block(at, move |state| { let mut results = Vec::with_capacity(transactions.len()); let mut db = SubState::new(State::new(state)); @@ -103,7 +110,8 @@ where let tx = tx.into_ecrecovered().ok_or(BlockError::InvalidSignature)?; let tx = tx_env_with_recovered(&tx); let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx }; - let (result, state_changes) = trace_transaction(opts.clone(), env, &mut db)?; + let (result, state_changes) = + this.trace_transaction(opts.clone(), env, at, &mut db)?; results.push(TraceResult::Success { result }); if transactions.peek().is_some() { @@ -204,11 +212,11 @@ where // we need to get the state of the parent block because we're essentially replaying the // block the transaction is included in - let state_at = block.parent_hash; + let state_at: BlockId = block.parent_hash.into(); let block_txs = block.body; self.on_blocking_task(|this| async move { - this.inner.eth_api.with_state_at_block(state_at.into(), |state| { + this.inner.eth_api.with_state_at_block(state_at, |state| { // configure env for the target transaction let tx = transaction.into_recovered(); @@ -223,7 +231,7 @@ where )?; let env = Env { cfg, block: block_env, tx: tx_env_with_recovered(&tx) }; - trace_transaction(opts, env, &mut db).map(|(trace, _)| trace) + this.trace_transaction(opts, env, state_at, &mut db).map(|(trace, _)| trace) }) }) .await @@ -302,8 +310,23 @@ where } GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), }, - GethDebugTracerType::JsTracer(_) => { - Err(EthApiError::Unsupported("javascript tracers are unsupported.")) + GethDebugTracerType::JsTracer(code) => { + let config = tracer_config.and_then(|c| c.into_js_config()).unwrap_or_default(); + + // for JS tracing we need to setup all async work before we can start tracing + // because JSTracer and all JS types are not Send + let (cfg, block_env, at) = self.inner.eth_api.evm_env_at(at).await?; + let state = self.inner.eth_api.state_at(at)?; + let mut db = SubState::new(State::new(state)); + let env = prepare_call_env(cfg, block_env, call, &mut db, overrides)?; + + let to_db_service = self.spawn_js_trace_service(at)?; + + let mut inspector = JsInspector::new(code, config, to_db_service)?; + let (res, env) = inspect(db, env, &mut inspector)?; + + let result = inspector.json_result(res, &env)?; + Ok(GethTraceFrame::JS(result)) } } } @@ -321,6 +344,149 @@ where Ok(frame.into()) } + + /// 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. + /// + /// Note: this does not apply any state overrides if they're configured in the `opts`. + fn trace_transaction( + &self, + opts: GethDebugTracingOptions, + env: Env, + at: BlockId, + db: &mut SubState>, + ) -> EthResult<(GethTraceFrame, revm_primitives::State)> { + let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; + + if let Some(tracer) = tracer { + // valid matching config + if let Some(ref config) = tracer_config { + if !config.matches_tracer(&tracer) { + return Err(EthApiError::InvalidTracerConfig) + } + } + + return match tracer { + GethDebugTracerType::BuiltInTracer(tracer) => match tracer { + GethDebugBuiltInTracerType::FourByteTracer => { + let mut inspector = FourByteInspector::default(); + let (res, _) = inspect(db, env, &mut inspector)?; + return Ok((FourByteFrame::from(inspector).into(), res.state)) + } + GethDebugBuiltInTracerType::CallTracer => { + // we validated the config above + let call_config = + tracer_config.and_then(|c| c.into_call_config()).unwrap_or_default(); + + let mut inspector = TracingInspector::new( + TracingInspectorConfig::from_geth_config(&config), + ); + + let (res, _) = inspect(db, env, &mut inspector)?; + + let frame = inspector.into_geth_builder().geth_call_traces(call_config); + + return Ok((frame.into(), res.state)) + } + GethDebugBuiltInTracerType::PreStateTracer => { + Err(EthApiError::Unsupported("prestate tracer is unimplemented yet.")) + } + GethDebugBuiltInTracerType::NoopTracer => { + Ok((NoopFrame::default().into(), Default::default())) + } + }, + GethDebugTracerType::JsTracer(code) => { + let config = tracer_config.and_then(|c| c.into_js_config()).unwrap_or_default(); + + // we spawn the database service that will be used by the JS tracer + // TODO(mattsse) this is not quite accurate when tracing a block inside a + // transaction because the service needs access to the committed state changes + let to_db_service = self.spawn_js_trace_service(at)?; + + let mut inspector = JsInspector::new(code, config, to_db_service)?; + let (res, env) = inspect(db, env, &mut inspector)?; + + let state = res.state.clone(); + let result = inspector.json_result(res, &env)?; + Ok((GethTraceFrame::JS(result), state)) + } + } + } + + // default structlog tracer + let inspector_config = TracingInspectorConfig::from_geth_config(&config); + + let mut inspector = TracingInspector::new(inspector_config); + + let (res, _) = inspect(db, env, &mut inspector)?; + let gas_used = res.result.gas_used(); + + let frame = inspector.into_geth_builder().geth_traces(gas_used, config); + + Ok((frame.into(), res.state)) + } + + /// Spawns [Self::js_trace_db_service_task] on a new task and returns a channel to send requests + /// to it. + /// + /// Note: This blocks until the service is ready to receive requests. + fn spawn_js_trace_service(&self, at: BlockId) -> EthResult> { + let (to_db_service, rx) = mpsc::channel(1); + let (ready_tx, ready_rx) = std::sync::mpsc::channel(); + let this = self.clone(); + self.inner + .task_spawner + .spawn(Box::pin(async move { this.js_trace_db_service_task(at, rx, ready_tx).await })); + // wait for initialization + ready_rx.recv().map_err(|_| { + EthApiError::InternalJsTracerError("js tracer initialization failed".to_string()) + })??; + Ok(to_db_service) + } + + /// A services that handles database requests issued from inside the JavaScript tracing engine. + async fn js_trace_db_service_task( + self, + at: BlockId, + rx: mpsc::Receiver, + on_ready: std::sync::mpsc::Sender>, + ) { + let state = match self.inner.eth_api.state_at(at) { + Ok(state) => { + let _ = on_ready.send(Ok(())); + state + } + Err(err) => { + let _ = on_ready.send(Err(err)); + return + } + }; + + let mut stream = ReceiverStream::new(rx); + while let Some(req) = stream.next().await { + match req { + JsDbRequest::Basic { address, resp } => { + let acc = state.basic_account(address).map_err(|err| err.to_string()); + let _ = resp.send(acc); + } + JsDbRequest::Code { code_hash, resp } => { + let code = state + .bytecode_by_hash(code_hash) + .map(|code| code.map(|c| c.bytecode.clone()).unwrap_or_default()) + .map_err(|err| err.to_string()); + let _ = resp.send(code); + } + JsDbRequest::StorageAt { address, index, resp } => { + let value = state + .storage(address, index) + .map(|val| val.unwrap_or_default()) + .map_err(|err| err.to_string()); + let _ = resp.send(value); + } + } + } + } } #[async_trait] @@ -486,70 +652,3 @@ struct DebugApiInner { /// The type that can spawn tasks which would otherwise block. task_spawner: Box, } - -/// 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. -/// -/// Note: this does not apply any state overrides if they're configured in the `opts`. -fn trace_transaction( - opts: GethDebugTracingOptions, - env: Env, - db: &mut SubState>, -) -> EthResult<(GethTraceFrame, revm_primitives::State)> { - let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; - - if let Some(tracer) = tracer { - // valid matching config - if let Some(ref config) = tracer_config { - if !config.matches_tracer(&tracer) { - return Err(EthApiError::InvalidTracerConfig) - } - } - - return match tracer { - GethDebugTracerType::BuiltInTracer(tracer) => match tracer { - GethDebugBuiltInTracerType::FourByteTracer => { - let mut inspector = FourByteInspector::default(); - let (res, _) = inspect(db, env, &mut inspector)?; - return Ok((FourByteFrame::from(inspector).into(), res.state)) - } - GethDebugBuiltInTracerType::CallTracer => { - // we validated the config above - let call_config = - tracer_config.and_then(|c| c.into_call_config()).unwrap_or_default(); - - let mut inspector = - TracingInspector::new(TracingInspectorConfig::from_geth_config(&config)); - - let (res, _) = inspect(db, env, &mut inspector)?; - - let frame = inspector.into_geth_builder().geth_call_traces(call_config); - - return Ok((frame.into(), res.state)) - } - GethDebugBuiltInTracerType::PreStateTracer => { - Err(EthApiError::Unsupported("prestate tracer is unimplemented yet.")) - } - GethDebugBuiltInTracerType::NoopTracer => { - Ok((NoopFrame::default().into(), Default::default())) - } - }, - GethDebugTracerType::JsTracer(_) => { - Err(EthApiError::Unsupported("javascript tracers are unsupported.")) - } - } - } - - // default structlog tracer - let inspector_config = TracingInspectorConfig::from_geth_config(&config); - - let mut inspector = TracingInspector::new(inspector_config); - - let (res, _) = inspect(db, env, &mut inspector)?; - let gas_used = res.result.gas_used(); - - let frame = inspector.into_geth_builder().geth_traces(gas_used, config); - - Ok((frame.into(), res.state)) -} diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index 8517d0025db4..625c0e2bd552 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -3,6 +3,7 @@ use crate::result::{internal_rpc_err, invalid_params_rpc_err, rpc_err, rpc_error_with_code}; use jsonrpsee::{core::Error as RpcError, types::ErrorObject}; use reth_primitives::{abi::decode_revert_reason, Address, Bytes, U256}; +use reth_revm::tracing::js::JsInspectorError; use reth_rpc_types::{error::EthRpcErrorCode, BlockError}; use reth_transaction_pool::error::{InvalidPoolTransactionError, PoolError}; use revm::primitives::{EVMError, ExecutionResult, Halt, OutOfGasError}; @@ -57,6 +58,9 @@ pub enum EthApiError { /// Some feature is unsupported #[error("unsupported")] Unsupported(&'static str), + /// General purpose error for invalid params + #[error("{0}")] + InvalidParams(String), /// When tracer config does not match the tracer #[error("invalid tracer config")] InvalidTracerConfig, @@ -69,6 +73,9 @@ pub enum EthApiError { /// Error thrown when a spawned blocking task failed to deliver an anticipated response. #[error("internal eth error")] InternalEthError, + /// Internal Error thrown by the javascript tracer + #[error("{0}")] + InternalJsTracerError(String), } impl From for ErrorObject<'static> { @@ -92,6 +99,8 @@ impl From for ErrorObject<'static> { rpc_error_with_code(EthRpcErrorCode::ResourceNotFound.code(), error.to_string()) } EthApiError::Unsupported(msg) => internal_rpc_err(msg), + EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg), + EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg), EthApiError::InvalidRewardPercentile(msg) => internal_rpc_err(msg.to_string()), err @ EthApiError::InternalTracingError => internal_rpc_err(err.to_string()), err @ EthApiError::InternalEthError => internal_rpc_err(err.to_string()), @@ -104,6 +113,16 @@ impl From for RpcError { RpcError::Call(error.into()) } } +impl From for EthApiError { + fn from(error: JsInspectorError) -> Self { + match error { + err @ JsInspectorError::JsError(_) => { + EthApiError::InternalJsTracerError(err.to_string()) + } + err => EthApiError::InvalidParams(err.to_string()), + } + } +} impl From for EthApiError { fn from(error: reth_interfaces::Error) -> Self { From 3bba41a209e308abdbe8e88f601f365566befa7d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 14 Jun 2023 18:00:15 +0200 Subject: [PATCH 020/216] fix: chunk size edge case (#3143) --- crates/stages/src/stages/sender_recovery.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index bafa6938f223..9f74ebcd2085 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -101,6 +101,11 @@ impl Stage for SenderRecoveryStage { // Chunks are submitted instead of individual transactions to reduce the overhead of work // stealing in the threadpool workers. let chunk_size = self.commit_threshold as usize / rayon::current_num_threads(); + // prevents an edge case + // where the chunk size is either 0 or too small + // to gain anything from using more than 1 thread + let chunk_size = chunk_size.max(16); + for chunk in &tx_walker.chunks(chunk_size) { // An _unordered_ channel to receive results from a rayon job let (recovered_senders_tx, recovered_senders_rx) = mpsc::unbounded_channel(); From 518784205f2a9a9593240b6694d855bcadbc2a9e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 14 Jun 2023 20:09:27 +0200 Subject: [PATCH 021/216] refactor: convert closure into function (#3144) --- crates/stages/src/stages/tx_lookup.rs | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index 11757dc40d1d..6b92d51c42ac 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -79,16 +79,6 @@ impl Stage for TransactionLookupStage { let chunk: Vec<_> = chunk.collect(); transaction_count += chunk.len(); - // closure that will calculate the TxHash - let calculate_hash = - |entry: Result<(TxNumber, TransactionSignedNoHash), reth_db::DatabaseError>, - rlp_buf: &mut Vec| - -> Result<(H256, u64), Box> { - let (tx_id, tx) = entry.map_err(|e| Box::new(e.into()))?; - tx.transaction.encode_with_signature(&tx.signature, rlp_buf, false); - Ok((H256(keccak256(rlp_buf)), tx_id)) - }; - // Spawn the task onto the global rayon pool // This task will send the results through the channel after it has calculated the hash. rayon::spawn(move || { @@ -179,6 +169,17 @@ impl Stage for TransactionLookupStage { } } +/// Calculates the hash of the given transaction +#[inline] +fn calculate_hash( + entry: Result<(TxNumber, TransactionSignedNoHash), DatabaseError>, + rlp_buf: &mut Vec, +) -> Result<(H256, TxNumber), Box> { + let (tx_id, tx) = entry.map_err(|e| Box::new(e.into()))?; + tx.transaction.encode_with_signature(&tx.signature, rlp_buf, false); + Ok((H256(keccak256(rlp_buf)), tx_id)) +} + fn stage_checkpoint( provider: &DatabaseProviderRW<'_, &DB>, ) -> Result { From 55b6081bd6b3c88319c47a496eb29256192e2140 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 14 Jun 2023 21:55:47 +0200 Subject: [PATCH 022/216] feat(rpc): add helper function to also return the used state (#3147) --- crates/rpc/rpc/src/eth/api/transactions.rs | 47 ++++++++++++++++++---- crates/rpc/rpc/src/eth/revm_utils.rs | 29 +++++++++++-- 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 266906679739..0314a6d7c708 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -2,14 +2,15 @@ use crate::{ eth::{ error::{EthApiError, EthResult, SignError}, - revm_utils::{inspect, prepare_call_env, replay_transactions_until, transact}, + revm_utils::{ + inspect, inspect_and_return_db, prepare_call_env, replay_transactions_until, transact, + EvmOverrides, + }, utils::recover_raw_transaction, }, EthApi, EthApiSpec, }; use async_trait::async_trait; - -use crate::eth::revm_utils::EvmOverrides; use reth_network_api::NetworkInfo; use reth_primitives::{ Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredTransaction, Header, @@ -35,6 +36,9 @@ use revm::{ }; use revm_primitives::{utilities::create_address, Env, ResultAndState, SpecId}; +/// Helper alias type for the state's [CacheDB] +pub(crate) type StateCacheDB<'r> = CacheDB>>; + /// Commonly used transaction related functions for the [EthApi] type in the `eth_` namespace #[async_trait::async_trait] pub trait EthTransactions: Send + Sync { @@ -122,7 +126,7 @@ pub trait EthTransactions: Send + Sync { f: F, ) -> EthResult where - F: for<'r> FnOnce(CacheDB>>, Env) -> EthResult + Send; + F: for<'r> FnOnce(StateCacheDB<'r>, Env) -> EthResult + Send; /// Executes the call request at the given [BlockId]. async fn transact_call_at( @@ -141,7 +145,18 @@ pub trait EthTransactions: Send + Sync { inspector: I, ) -> EthResult<(ResultAndState, Env)> where - I: for<'r> Inspector>>> + Send; + I: for<'r> Inspector> + Send; + + /// Executes the call request at the given [BlockId] + async fn inspect_call_at_and_return_state<'a, I>( + &'a self, + request: CallRequest, + at: BlockId, + overrides: EvmOverrides, + inspector: I, + ) -> EthResult<(ResultAndState, Env, StateCacheDB<'a>)> + where + I: Inspector> + Send; /// Executes the transaction on top of the given [BlockId] with a tracer configured by the /// config. @@ -456,7 +471,7 @@ where f: F, ) -> EthResult where - F: for<'r> FnOnce(CacheDB>>, Env) -> EthResult + Send, + F: for<'r> FnOnce(StateCacheDB<'r>, Env) -> EthResult + Send, { let (cfg, block_env, at) = self.evm_env_at(at).await?; let state = self.state_at(at)?; @@ -483,11 +498,29 @@ where inspector: I, ) -> EthResult<(ResultAndState, Env)> where - I: for<'r> Inspector>>> + Send, + I: for<'r> Inspector> + Send, { self.with_call_at(request, at, overrides, |db, env| inspect(db, env, inspector)).await } + async fn inspect_call_at_and_return_state<'a, I>( + &'a self, + request: CallRequest, + at: BlockId, + overrides: EvmOverrides, + inspector: I, + ) -> EthResult<(ResultAndState, Env, StateCacheDB<'a>)> + where + I: Inspector> + Send, + { + let (cfg, block_env, at) = self.evm_env_at(at).await?; + let state = self.state_at(at)?; + let mut db = SubState::new(State::new(state)); + + let env = prepare_call_env(cfg, block_env, request, &mut db, overrides)?; + inspect_and_return_db(db, env, inspector) + } + fn trace_at( &self, env: Env, diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 2b0a3cf847c1..9b14e0b0db98 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -122,11 +122,11 @@ where } /// Executes the [Env] against the given [Database] without committing state changes. -pub(crate) fn inspect(db: S, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)> +pub(crate) fn inspect(db: DB, env: Env, inspector: I) -> EthResult<(ResultAndState, Env)> where - S: Database, - ::Error: Into, - I: Inspector, + DB: Database, + ::Error: Into, + I: Inspector, { let mut evm = revm::EVM::with_env(env); evm.database(db); @@ -134,6 +134,27 @@ where Ok((res, evm.env)) } +/// Same as [inspect] but also returns the database again. +/// +/// Even though [Database] is also implemented on `&mut` +/// this is still useful if there are certain trait bounds on the Inspector's database generic type +pub(crate) fn inspect_and_return_db( + db: DB, + env: Env, + inspector: I, +) -> EthResult<(ResultAndState, Env, DB)> +where + DB: Database, + ::Error: Into, + I: Inspector, +{ + let mut evm = revm::EVM::with_env(env); + evm.database(db); + let res = evm.inspect(inspector)?; + let db = evm.take_db(); + Ok((res, evm.env, db)) +} + /// Replays all the transactions until the target transaction is found. /// /// All transactions before the target transaction are executed and their changes are written to the From a7c7b48feb857ae3b8a318b924f124e844e35a7c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 14 Jun 2023 21:57:24 +0200 Subject: [PATCH 023/216] chore: update ef-tests crate description (#3151) --- testing/ef-tests/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index b6dae6194823..4cabdf2edd3a 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ef-tests" version = "0.1.0" -description = "Staged syncing primitives used in reth." +description = "EF testing support for reth." edition.workspace = true rust-version.workspace = true license.workspace = true From 9b81043a5322cf7653e27f7fdafc368c516d9b6a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 14 Jun 2023 21:58:39 +0200 Subject: [PATCH 024/216] chore(deps): move ethers to workspace deps (#3145) --- Cargo.toml | 8 +++++++- crates/net/eth-wire/Cargo.toml | 4 ++-- crates/net/network/Cargo.toml | 10 +++++----- crates/primitives/Cargo.toml | 2 +- crates/rpc/rpc/Cargo.toml | 2 +- crates/staged-sync/Cargo.toml | 8 ++++---- 6 files changed, 20 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c38e1da55ad..28657600630f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,7 @@ revm = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" } revm-primitives = { git = "https://github.com/bluealloy/revm/", branch = "release/v25" } [workspace.dependencies] -## eth +## reth revm = { version = "3" } revm-primitives = "1.1" reth-primitives = { path = "./crates/primitives" } @@ -92,6 +92,12 @@ reth-transaction-pool = { path = "./crates/transaction-pool" } reth-tasks = { path = "./crates/tasks" } reth-network-api = { path = "./crates/net/network-api" } +## eth +ethers-core = { version = "2.0.7", default-features = false } +ethers-providers = { version = "2.0.7", default-features = false } +ethers-signers = { version = "2.0.7", default-features = false } +ethers-middleware = { version = "2.0.7", default-features = false } + ## misc tracing = "^0.1.0" thiserror = "1.0.37" diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index bdb0ed5c6317..f9acb39bacff 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -27,7 +27,7 @@ reth-rlp = { workspace = true, features = [ reth-metrics = { workspace = true } # used for Chain and builders -ethers-core = { version = "2.0.7", default-features = false } +ethers-core = { workspace = true, default-features = false } tokio = { workspace = true, features = ["full"] } tokio-util = { workspace = true, features = ["io", "codec"] } @@ -47,7 +47,7 @@ proptest-derive = { version = "0.3", optional = true } [dev-dependencies] reth-primitives = { workspace = true, features = ["arbitrary"] } reth-tracing = { path = "../../tracing" } -ethers-core = { version = "2.0.7", default-features = false } +ethers-core = { workspace = true, default-features = false } test-fuzz = "3.0.4" tokio-util = { workspace = true, features = ["io", "codec"] } diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index cb1d90983075..6b90bb35b050 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -60,7 +60,7 @@ rand = { workspace = true } secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } enr = { version = "0.8.1", features = ["rust-secp256k1"], optional = true } -ethers-core = { version = "2.0.7", default-features = false, optional = true } +ethers-core = { workspace = true, default-features = false, optional = true } tempfile = { version = "3.3", optional = true } [dev-dependencies] @@ -76,10 +76,10 @@ reth-provider = { workspace = true, features = ["test-utils"] } reth-tracing = { path = "../../tracing" } reth-transaction-pool = { workspace = true, features = ["test-utils"] } -ethers-core = { version = "2.0.7", default-features = false } -ethers-providers = { version = "2.0.7", default-features = false } -ethers-signers = { version = "2.0.7", default-features = false } -ethers-middleware = { version = "2.0.7", default-features = false } +ethers-core = { workspace = true, default-features = false } +ethers-providers = { workspace = true, default-features = false } +ethers-signers = { workspace = true, default-features = false } +ethers-middleware = { workspace = true, default-features = false } enr = { version = "0.8.1", features = ["serde", "rust-secp256k1"] } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index a47af1c2010d..0efed1faa76a 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -17,7 +17,7 @@ reth-codecs = { version = "0.1.0", path = "../storage/codecs" } revm-primitives = { workspace = true, features = ["serde"] } # ethereum -ethers-core = { version = "2.0.7", default-features = false } +ethers-core = { workspace = true, default-features = false } tiny-keccak = { version = "2.0", features = ["keccak"] } crunchy = { version = "0.2.2", default-features = false, features = ["limit_256"] } ruint = { version = "1.7.0", features = ["primitive-types", "rlp"] } diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index b39f3e6a6d16..0cf7a4f83397 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -29,7 +29,7 @@ revm = { workspace = true, features = [ "optional_eip3607", "optional_no_base_fee", ] } -ethers-core = { version = "2.0.7", features = ["eip712"] } +ethers-core = { workspace = true, features = ["eip712"] } revm-primitives = { workspace = true, features = ["serde"] } # rpc diff --git a/crates/staged-sync/Cargo.toml b/crates/staged-sync/Cargo.toml index f0e98d825721..ebdbed6eb77f 100644 --- a/crates/staged-sync/Cargo.toml +++ b/crates/staged-sync/Cargo.toml @@ -42,12 +42,12 @@ thiserror = { workspace = true } enr = { version = "0.8.1", features = ["serde", "rust-secp256k1"], optional = true } # ethers -ethers-core = { version = "2.0.7", default-features = false, optional = true } -ethers-providers = { version = "2.0.7", features = [ +ethers-core = { workspace = true, default-features = false, optional = true } +ethers-providers = { workspace = true, features = [ "ws", ], default-features = false, optional = true } -ethers-middleware = { version = "2.0.7", default-features = false, optional = true } -ethers-signers = { version = "2.0.7", default-features = false, optional = true } +ethers-middleware = { workspace = true, default-features = false, optional = true } +ethers-signers = { workspace = true, default-features = false, optional = true } # async / futures async-trait = { workspace = true, optional = true } From ce4600b5b4da852db30231fd6ce0dc3758d996a3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 14 Jun 2023 22:17:01 +0200 Subject: [PATCH 025/216] fix: use serde json value for tracer config (#3154) --- .../rpc/rpc-types/src/eth/trace/geth/call.rs | 6 +- .../rpc/rpc-types/src/eth/trace/geth/mod.rs | 106 ++++++------------ .../rpc-types/src/eth/trace/geth/pre_state.rs | 5 +- crates/rpc/rpc/src/debug.rs | 30 ++--- 4 files changed, 49 insertions(+), 98 deletions(-) diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/call.rs b/crates/rpc/rpc-types/src/eth/trace/geth/call.rs index b38737b145b4..8af626d11541 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/call.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/call.rs @@ -68,9 +68,9 @@ mod tests { opts.tracing_options.tracer = Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer)); opts.tracing_options.tracer_config = - Some(GethDebugTracerConfig::BuiltInTracer(GethDebugBuiltInTracerConfig::CallTracer( - CallConfig { only_top_call: Some(true), with_log: Some(true) }, - ))); + serde_json::to_value(CallConfig { only_top_call: Some(true), with_log: Some(true) }) + .unwrap() + .into(); assert_eq!( serde_json::to_string(&opts).unwrap(), diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs index 05c6e1d9f913..526c9dc1fce9 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs @@ -3,7 +3,7 @@ use crate::{state::StateOverride, BlockOverrides}; use reth_primitives::{Bytes, H256, U256}; -use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; +use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Serialize, Serializer}; use std::collections::BTreeMap; // re-exports @@ -183,30 +183,6 @@ pub enum GethDebugBuiltInTracerType { NoopTracer, } -/// Configuration for the builtin tracer -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[serde(untagged)] -pub enum GethDebugBuiltInTracerConfig { - CallTracer(CallConfig), - PreStateTracer(PreStateConfig), -} - -// === impl GethDebugBuiltInTracerConfig === - -impl GethDebugBuiltInTracerConfig { - /// Returns true if the config matches the given tracer - pub fn matches_tracer(&self, tracer: &GethDebugBuiltInTracerType) -> bool { - matches!( - (self, tracer), - (GethDebugBuiltInTracerConfig::CallTracer(_), GethDebugBuiltInTracerType::CallTracer,) | - ( - GethDebugBuiltInTracerConfig::PreStateTracer(_), - GethDebugBuiltInTracerType::PreStateTracer, - ) - ) - } -} - /// Available tracers /// /// See and @@ -220,61 +196,45 @@ pub enum GethDebugTracerType { } /// Configuration of the tracer -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[serde(untagged)] -pub enum GethDebugTracerConfig { - /// built-in tracer - BuiltInTracer(GethDebugBuiltInTracerConfig), - /// custom JS tracer - JsTracer(serde_json::Value), -} +/// +/// This is a simple wrapper around serde_json::Value. +/// with helpers for deserializing tracer configs. +#[derive(Debug, PartialEq, Eq, Clone, Default, Deserialize, Serialize)] +#[serde(transparent)] +pub struct GethDebugTracerConfig(pub serde_json::Value); // === impl GethDebugTracerConfig === impl GethDebugTracerConfig { - /// Returns the [CallConfig] if it is a call config. - pub fn into_call_config(self) -> Option { - match self { - GethDebugTracerConfig::BuiltInTracer(GethDebugBuiltInTracerConfig::CallTracer(cfg)) => { - Some(cfg) - } - _ => None, - } + /// Returns if this is a null object + pub fn is_null(&self) -> bool { + self.0.is_null() } - /// Returns the json config if this config is a JS tracer. - pub fn into_js_config(self) -> Option { - match self { - GethDebugTracerConfig::JsTracer(cfg) => Some(cfg), - _ => None, - } + /// Consumes the config and tries to deserialize it into the given type. + pub fn from_value(self) -> Result { + serde_json::from_value(self.0) } - /// Returns the [PreStateConfig] if it is a call config. - pub fn into_pre_state_config(self) -> Option { - match self { - GethDebugTracerConfig::BuiltInTracer(GethDebugBuiltInTracerConfig::PreStateTracer( - cfg, - )) => Some(cfg), - _ => None, - } + /// Returns the [CallConfig] if it is a call config. + pub fn into_call_config(self) -> Result { + self.from_value() } - /// Returns true if the config matches the given tracer - pub fn matches_tracer(&self, tracer: &GethDebugTracerType) -> bool { - match (self, tracer) { - (_, GethDebugTracerType::BuiltInTracer(tracer)) => self.matches_builtin_tracer(tracer), - (GethDebugTracerConfig::JsTracer(_), GethDebugTracerType::JsTracer(_)) => true, - _ => false, - } + /// Returns the raw json value + pub fn into_json(self) -> serde_json::Value { + self.0 } - /// Returns true if the config matches the given tracer - pub fn matches_builtin_tracer(&self, tracer: &GethDebugBuiltInTracerType) -> bool { - match (self, tracer) { - (GethDebugTracerConfig::BuiltInTracer(config), tracer) => config.matches_tracer(tracer), - (GethDebugTracerConfig::JsTracer(_), _) => false, - } + /// Returns the [PreStateConfig] if it is a call config. + pub fn into_pre_state_config(self) -> Result { + self.from_value() + } +} + +impl From for GethDebugTracerConfig { + fn from(value: serde_json::Value) -> Self { + GethDebugTracerConfig(value) } } @@ -291,10 +251,16 @@ pub struct GethDebugTracingOptions { /// If `None` then the default structlog tracer is used. #[serde(default, skip_serializing_if = "Option::is_none")] pub tracer: Option, + /// Config specific to given `tracer`. + /// + /// Note default struct logger config are historically embedded in main object. + /// /// tracerConfig is slated for Geth v1.11.0 /// See - #[serde(default, skip_serializing_if = "Option::is_none")] - pub tracer_config: Option, + /// + /// This could be [CallConfig] or [PreStateConfig] depending on the tracer. + #[serde(default, skip_serializing_if = "GethDebugTracerConfig::is_null")] + pub tracer_config: GethDebugTracerConfig, /// A string of decimal integers that overrides the JavaScript-based tracing calls default /// timeout of 5 seconds. #[serde(default, skip_serializing_if = "Option::is_none")] diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs index e8bde9fbd835..fc53ecfa7b0b 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs @@ -62,9 +62,8 @@ mod tests { opts.tracing_options.config.disable_storage = Some(false); opts.tracing_options.tracer = Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::PreStateTracer)); - opts.tracing_options.tracer_config = Some(GethDebugTracerConfig::BuiltInTracer( - GethDebugBuiltInTracerConfig::PreStateTracer(PreStateConfig { diff_mode: Some(true) }), - )); + opts.tracing_options.tracer_config = + serde_json::to_value(PreStateConfig { diff_mode: Some(true) }).unwrap().into(); assert_eq!( serde_json::to_string(&opts).unwrap(), diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 76911cb6f748..8a44d16984fe 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -268,13 +268,6 @@ where let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; if let Some(tracer) = tracer { - // valid matching config - if let Some(ref config) = tracer_config { - if !config.matches_tracer(&tracer) { - return Err(EthApiError::InvalidTracerConfig) - } - } - return match tracer { GethDebugTracerType::BuiltInTracer(tracer) => match tracer { GethDebugBuiltInTracerType::FourByteTracer => { @@ -287,9 +280,9 @@ where return Ok(FourByteFrame::from(inspector).into()) } GethDebugBuiltInTracerType::CallTracer => { - // we validated the config above - let call_config = - tracer_config.and_then(|c| c.into_call_config()).unwrap_or_default(); + let call_config = tracer_config + .into_call_config() + .map_err(|_| EthApiError::InvalidTracerConfig)?; let mut inspector = TracingInspector::new( TracingInspectorConfig::from_geth_config(&config), @@ -311,7 +304,7 @@ where GethDebugBuiltInTracerType::NoopTracer => Ok(NoopFrame::default().into()), }, GethDebugTracerType::JsTracer(code) => { - let config = tracer_config.and_then(|c| c.into_js_config()).unwrap_or_default(); + let config = tracer_config.into_json(); // for JS tracing we need to setup all async work before we can start tracing // because JSTracer and all JS types are not Send @@ -360,13 +353,6 @@ where let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; if let Some(tracer) = tracer { - // valid matching config - if let Some(ref config) = tracer_config { - if !config.matches_tracer(&tracer) { - return Err(EthApiError::InvalidTracerConfig) - } - } - return match tracer { GethDebugTracerType::BuiltInTracer(tracer) => match tracer { GethDebugBuiltInTracerType::FourByteTracer => { @@ -375,9 +361,9 @@ where return Ok((FourByteFrame::from(inspector).into(), res.state)) } GethDebugBuiltInTracerType::CallTracer => { - // we validated the config above - let call_config = - tracer_config.and_then(|c| c.into_call_config()).unwrap_or_default(); + let call_config = tracer_config + .into_call_config() + .map_err(|_| EthApiError::InvalidTracerConfig)?; let mut inspector = TracingInspector::new( TracingInspectorConfig::from_geth_config(&config), @@ -397,7 +383,7 @@ where } }, GethDebugTracerType::JsTracer(code) => { - let config = tracer_config.and_then(|c| c.into_js_config()).unwrap_or_default(); + let config = tracer_config.into_json(); // we spawn the database service that will be used by the JS tracer // TODO(mattsse) this is not quite accurate when tracing a block inside a From 4d3d4974a48b4e838ccda50c3171da11c4885a13 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:03:05 -0400 Subject: [PATCH 026/216] feat: add downloader buffer graph (#3165) --- etc/grafana/dashboards/overview.json | 106 +++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 18f64c0fb5af..2fb19809f3e4 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -2477,6 +2477,112 @@ "title": "Requests", "type": "timeseries" }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "The number of blocks and size in bytes of those blocks", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 93 + }, + "id": 73, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_downloaders_bodies_buffered_blocks_size_bytes", + "hide": false, + "legendFormat": "Buffered blocks size (bytes)", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_downloaders_bodies_buffered_blocks", + "hide": false, + "legendFormat": "Buffered blocks", + "range": true, + "refId": "B" + } + ], + "title": "Downloader buffer", + "type": "timeseries" + }, { "collapsed": true, "gridPos": { From a96799614454a388db094a891f8c7edd8dc14a1a Mon Sep 17 00:00:00 2001 From: Rodrigo Herrera Itie <80117772+rodrigoherrerai@users.noreply.github.com> Date: Wed, 14 Jun 2023 18:23:10 -0600 Subject: [PATCH 027/216] Update installation.md (#3167) --- book/installation/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/installation/installation.md b/book/installation/installation.md index e3ce069af458..d4653b97f91c 100644 --- a/book/installation/installation.md +++ b/book/installation/installation.md @@ -52,4 +52,4 @@ A stable and dependable internet connection is crucial for both syncing a node f Note that due to Reth's staged sync, you only need an internet connection for the Headers and Bodies stages. This means that the first 1-3 hours (depending on your internet connection) will be online, downloading all necessary data, and the rest will be done offline and does not require an internet connection. -Once you're synced to the tip you will need a reliable connection, especially if you're operating a validator. A 24MBps connection is recommended, but you can probably get away with less. Make sure your ISP does not cap your bandwidth. \ No newline at end of file +Once you're synced to the tip you will need a reliable connection, especially if you're operating a validator. A 24Mbps connection is recommended, but you can probably get away with less. Make sure your ISP does not cap your bandwidth. From 4466fad370b92d8084f7a4a1808a833060d50a15 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 15 Jun 2023 03:04:09 -0400 Subject: [PATCH 028/216] feat: put blocks on right side with separate axis placement (#3170) --- etc/grafana/dashboards/overview.json | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 2fb19809f3e4..271d2d984be7 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -2533,7 +2533,20 @@ ] } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "B" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + } + ] + } + ] }, "gridPos": { "h": 8, From 3a90eac8b101ff5a5f459595d7a716ba6522d695 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 13:13:51 +0200 Subject: [PATCH 029/216] fix: exclude calls to precompiles in call graphs (#3141) --- .../revm/revm-inspectors/src/tracing/arena.rs | 38 ++++++++++++++++--- .../revm/revm-inspectors/src/tracing/mod.rs | 15 +++++++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/arena.rs b/crates/revm/revm-inspectors/src/tracing/arena.rs index 9e8ffa02ef6f..fc7d12896ff6 100644 --- a/crates/revm/revm-inspectors/src/tracing/arena.rs +++ b/crates/revm/revm-inspectors/src/tracing/arena.rs @@ -11,7 +11,16 @@ pub struct CallTraceArena { impl CallTraceArena { /// Pushes a new trace into the arena, returning the trace ID - pub(crate) fn push_trace(&mut self, mut entry: usize, new_trace: CallTrace) -> usize { + /// + /// This appends a new trace to the arena, and also inserts a new entry in the node's parent + /// node children set if `attach_to_parent` is `true`. E.g. if calls to precompiles should + /// not be included in the call graph this should be called with [PushTraceKind::PushOnly]. + pub(crate) fn push_trace( + &mut self, + mut entry: usize, + kind: PushTraceKind, + new_trace: CallTrace, + ) -> usize { loop { match new_trace.depth { // The entry node, just update it @@ -22,9 +31,6 @@ impl CallTraceArena { // We found the parent node, add the new trace as a child _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { let id = self.arena.len(); - - let trace_location = self.arena[entry].children.len(); - self.arena[entry].ordering.push(LogCallOrder::Call(trace_location)); let node = CallTraceNode { parent: Some(entry), trace: new_trace, @@ -32,7 +38,14 @@ impl CallTraceArena { ..Default::default() }; self.arena.push(node); - self.arena[entry].children.push(id); + + // also track the child in the parent node + if kind.is_attach_to_parent() { + let parent = &mut self.arena[entry]; + let trace_location = parent.children.len(); + parent.ordering.push(LogCallOrder::Call(trace_location)); + parent.children.push(id); + } return id } @@ -45,6 +58,21 @@ impl CallTraceArena { } } +/// How to push a trace into the arena +pub(crate) enum PushTraceKind { + /// This will _only_ push the trace into the arena. + PushOnly, + /// This will push the trace into the arena, and also insert a new entry in the node's parent + /// node children set. + PushAndAttachToParent, +} + +impl PushTraceKind { + fn is_attach_to_parent(&self) -> bool { + matches!(self, PushTraceKind::PushAndAttachToParent) + } +} + impl Default for CallTraceArena { fn default() -> Self { // The first node is the root node diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index 26504a71d6eb..0ea99f90dd35 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -21,7 +21,10 @@ mod fourbyte; mod opcount; mod types; mod utils; -use crate::tracing::types::{CallTraceNode, StorageChange}; +use crate::tracing::{ + arena::PushTraceKind, + types::{CallTraceNode, StorageChange}, +}; pub use builder::{geth::GethTraceBuilder, parity::ParityTraceBuilder}; pub use config::TracingInspectorConfig; pub use fourbyte::FourByteInspector; @@ -126,8 +129,18 @@ impl TracingInspector { caller: Address, maybe_precompile: Option, ) { + // This will only be true if the inspector is configured to exclude precompiles and the call + // is to a precompile + let push_kind = if maybe_precompile.unwrap_or(false) { + // We don't want to track precompiles + PushTraceKind::PushOnly + } else { + PushTraceKind::PushAndAttachToParent + }; + self.trace_stack.push(self.traces.push_trace( 0, + push_kind, CallTrace { depth, address, From 5bb090cd9f8ed63f55d888a402d233dd1eced9e7 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 13:53:11 +0200 Subject: [PATCH 030/216] refactor: trigger pipeline with finalized hash (#3142) Co-authored-by: Bjerg --- crates/consensus/beacon/src/engine/mod.rs | 51 +++++++++++++---------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 5dbc09124ad8..fcaca8dce089 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -956,27 +956,26 @@ where } } - /// Attempt to restore the tree with the finalized block number. - /// If the finalized block is missing from the database, trigger the pipeline run. - fn restore_tree_if_possible( + /// Attempt to restore the tree with the given block hash. + /// + /// This is invoked after a full pipeline to update the tree with the most recent canonical + /// hashes. + /// + /// If the given block is missing from the database, this will return `false`. Otherwise, `true` + /// is returned: the database contains the hash and the tree was updated. + fn update_tree_on_finished_pipeline( &mut self, - state: ForkchoiceState, - ) -> Result<(), reth_interfaces::Error> { - let needs_pipeline_run = match self.blockchain.block_number(state.finalized_block_hash)? { + block_hash: H256, + ) -> Result { + let synced_to_finalized = match self.blockchain.block_number(block_hash)? { Some(number) => { // Attempt to restore the tree. self.blockchain.restore_canonical_hashes(number)?; - - // After restoring the tree, check if the head block is missing. - self.blockchain.header(&state.head_block_hash)?.is_none() + true } - None => true, + None => false, }; - - if needs_pipeline_run { - self.sync.set_pipeline_sync_target(state.head_block_hash); - } - Ok(()) + Ok(synced_to_finalized) } /// Invoked if we successfully downloaded a new block from the network. @@ -1141,10 +1140,10 @@ where } }; - // TODO: figure out how to make this less complex: - // restore_tree_if_possible will run the pipeline if the current_state head - // hash is missing. This can arise if we buffer the forkchoice head, and if - // the head is an ancestor of an invalid block. + // Next, we check if we need to schedule another pipeline run or transition + // to live sync via tree. + // This can arise if we buffer the forkchoice head, and if the head is an + // ancestor of an invalid block. // // * The forkchoice head could be buffered if it were first sent as a // `newPayload` request. @@ -1165,8 +1164,18 @@ where .is_none() { // Update the state and hashes of the blockchain tree if possible. - match self.restore_tree_if_possible(sync_target_state) { - Ok(_) => self.sync_state_updater.update_sync_state(SyncState::Idle), + match self.update_tree_on_finished_pipeline( + sync_target_state.finalized_block_hash, + ) { + Ok(synced) => { + if !synced { + // We don't have the finalized block in the database, so + // we need to run another pipeline. + self.sync.set_pipeline_sync_target( + sync_target_state.finalized_block_hash, + ); + } + } Err(error) => { error!(target: "consensus::engine", ?error, "Error restoring blockchain tree state"); return Some(Err(error.into())) From 64392209e95189a98181fab68c86b241c63708ca Mon Sep 17 00:00:00 2001 From: Bjerg Date: Thu, 15 Jun 2023 13:57:46 +0200 Subject: [PATCH 031/216] perf: reduce allocations in changeset helpers (#3169) --- .../src/providers/database/provider.rs | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index a355a09e6394..5a1cd977a182 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -265,23 +265,22 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { &self, range: RangeInclusive, ) -> std::result::Result>, TransactionError> { - let storage_changeset = self - .tx - .cursor_read::()? - .walk_range(BlockNumberAddress::range(range))? - .collect::, _>>()?; + let mut changeset_cursor = self.tx.cursor_read::()?; - // fold all storages to one set of changes - let storage_changeset_lists = storage_changeset.into_iter().fold( - BTreeMap::new(), - |mut storages: BTreeMap<(Address, H256), Vec>, (index, storage)| { - storages - .entry((index.address(), storage.key)) - .or_default() - .push(index.block_number()); - storages - }, - ); + let storage_changeset_lists = + changeset_cursor.walk_range(BlockNumberAddress::range(range))?.try_fold( + BTreeMap::new(), + |mut storages: BTreeMap<(Address, H256), Vec>, + entry| + -> std::result::Result<_, TransactionError> { + let (index, storage) = entry?; + storages + .entry((index.address(), storage.key)) + .or_default() + .push(index.block_number()); + Ok(storages) + }, + )?; Ok(storage_changeset_lists) } @@ -293,22 +292,18 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { &self, range: RangeInclusive, ) -> std::result::Result>, TransactionError> { - let account_changesets = self - .tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; + let mut changeset_cursor = self.tx.cursor_read::()?; - let account_transtions = account_changesets - .into_iter() - // fold all account to one set of changed accounts - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, (index, account)| { - accounts.entry(account.address).or_default().push(index); - accounts - }, - ); + let account_transtions = changeset_cursor.walk_range(range)?.try_fold( + BTreeMap::new(), + |mut accounts: BTreeMap>, + entry| + -> std::result::Result<_, TransactionError> { + let (index, account) = entry?; + accounts.entry(account.address).or_default().push(index); + Ok(accounts) + }, + )?; Ok(account_transtions) } From ff36f78c2b487b5a44288a85973feca8441bb329 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Thu, 15 Jun 2023 13:58:35 +0200 Subject: [PATCH 032/216] feat: stage eta (#3135) --- Cargo.lock | 1 + bin/reth/Cargo.toml | 1 + bin/reth/src/node/events.rs | 102 +++++++++++++++------ crates/primitives/src/stage/checkpoints.rs | 23 +++++ crates/stages/src/pipeline/mod.rs | 2 +- 5 files changed, 100 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eca32685989b..93fdf91ddb01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4925,6 +4925,7 @@ dependencies = [ "futures", "hex", "human_bytes", + "humantime", "hyper", "jemallocator", "metrics-exporter-prometheus", diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 3f96e825b903..ddaa59a9a933 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -83,6 +83,7 @@ backon = "0.4" hex = "0.4" thiserror = { workspace = true } pretty_assertions = "1.3.0" +humantime = "2.1.0" [features] jemalloc = ["dep:jemallocator"] diff --git a/bin/reth/src/node/events.rs b/bin/reth/src/node/events.rs index 04b36b59cc65..9ab5a326a6d7 100644 --- a/bin/reth/src/node/events.rs +++ b/bin/reth/src/node/events.rs @@ -7,7 +7,7 @@ use reth_interfaces::consensus::ForkchoiceState; use reth_network::{NetworkEvent, NetworkHandle}; use reth_network_api::PeersInfo; use reth_primitives::{ - stage::{StageCheckpoint, StageId}, + stage::{EntitiesCheckpoint, StageCheckpoint, StageId}, BlockNumber, }; use reth_stages::{ExecOutput, PipelineEvent}; @@ -15,10 +15,10 @@ use std::{ future::Future, pin::Pin, task::{Context, Poll}, - time::Duration, + time::{Duration, Instant}, }; use tokio::time::Interval; -use tracing::{debug, info, warn}; +use tracing::{info, warn}; /// Interval of reporting node state. const INFO_MESSAGE_INTERVAL: Duration = Duration::from_secs(30); @@ -29,6 +29,8 @@ struct NodeState { network: Option, /// The stage currently being executed. current_stage: Option, + /// The ETA for the current stage. + eta: Eta, /// The current checkpoint of the executing stage. current_checkpoint: StageCheckpoint, /// The latest canonical block added in the consensus engine. @@ -40,6 +42,7 @@ impl NodeState { Self { network, current_stage: None, + eta: Eta::default(), current_checkpoint: StageCheckpoint::new(0), latest_canonical_engine_block: None, } @@ -59,11 +62,11 @@ impl NodeState { if notable { info!( - target: "reth::cli", pipeline_stages = %format!("{pipeline_position}/{pipeline_total}"), stage = %stage_id, from = self.current_checkpoint.block_number, checkpoint = %self.current_checkpoint, + eta = %self.eta, "Executing stage", ); } @@ -75,17 +78,14 @@ impl NodeState { result: ExecOutput { checkpoint, done }, } => { self.current_checkpoint = checkpoint; - - if done { - self.current_stage = None; - } + self.eta.update(self.current_checkpoint); info!( - target: "reth::cli", pipeline_stages = %format!("{pipeline_position}/{pipeline_total}"), stage = %stage_id, - progress = checkpoint.block_number, + block = checkpoint.block_number, %checkpoint, + eta = %self.eta, "{}", if done { "Stage finished executing" @@ -93,22 +93,20 @@ impl NodeState { "Stage committed progress" } ); + + if done { + self.current_stage = None; + self.eta = Eta::default(); + } } _ => (), } } - fn handle_network_event(&mut self, event: NetworkEvent) { - match event { - NetworkEvent::SessionEstablished { peer_id, status, .. } => { - info!(target: "reth::cli", connected_peers = self.num_connected_peers(), peer_id = %peer_id, best_block = %status.blockhash, "Peer connected"); - } - NetworkEvent::SessionClosed { peer_id, reason } => { - let reason = reason.map(|s| s.to_string()).unwrap_or_else(|| "None".to_string()); - debug!(target: "reth::cli", connected_peers = self.num_connected_peers(), peer_id = %peer_id, %reason, "Peer disconnected."); - } - _ => (), - } + fn handle_network_event(&mut self, _: NetworkEvent) { + // NOTE(onbjerg): This used to log established/disconnecting sessions, but this is already + // logged in the networking component. I kept this stub in case we want to catch other + // networking events later on. } fn handle_consensus_engine_event(&mut self, event: BeaconConsensusEngineEvent) { @@ -117,7 +115,6 @@ impl NodeState { let ForkchoiceState { head_block_hash, safe_block_hash, finalized_block_hash } = state; info!( - target: "reth::cli", ?head_block_hash, ?safe_block_hash, ?finalized_block_hash, @@ -128,10 +125,10 @@ impl NodeState { BeaconConsensusEngineEvent::CanonicalBlockAdded(block) => { self.latest_canonical_engine_block = Some(block.number); - info!(target: "reth::cli", number=block.number, hash=?block.hash, "Block added to canonical chain"); + info!(number=block.number, hash=?block.hash, "Block added to canonical chain"); } BeaconConsensusEngineEvent::ForkBlockAdded(block) => { - info!(target: "reth::cli", number=block.number, hash=?block.hash, "Block added to fork chain"); + info!(number=block.number, hash=?block.hash, "Block added to fork chain"); } } } @@ -139,16 +136,16 @@ impl NodeState { fn handle_consensus_layer_health_event(&self, event: ConsensusLayerHealthEvent) { match event { ConsensusLayerHealthEvent::NeverSeen => { - warn!(target: "reth::cli", "Post-merge network, but never seen beacon client. Please launch one to follow the chain!") + warn!("Post-merge network, but never seen beacon client. Please launch one to follow the chain!") } ConsensusLayerHealthEvent::HasNotBeenSeenForAWhile(period) => { - warn!(target: "reth::cli", ?period, "Post-merge network, but no beacon client seen for a while. Please launch one to follow the chain!") + warn!(?period, "Post-merge network, but no beacon client seen for a while. Please launch one to follow the chain!") } ConsensusLayerHealthEvent::NeverReceivedUpdates => { - warn!(target: "reth::cli", "Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!") + warn!("Beacon client online, but never received consensus updates. Please ensure your beacon client is operational to follow the chain!") } ConsensusLayerHealthEvent::HaveNotReceivedUpdatesForAWhile(period) => { - warn!(target: "reth::cli", ?period, "Beacon client online, but no consensus updates received for a while. Please fix your beacon client to follow the chain!") + warn!(?period, "Beacon client online, but no consensus updates received for a while. Please fix your beacon client to follow the chain!") } } } @@ -232,6 +229,7 @@ where connected_peers = this.state.num_connected_peers(), %stage, checkpoint = %this.state.current_checkpoint, + eta = %this.state.eta, "Status" ); } else { @@ -264,3 +262,51 @@ where Poll::Pending } } + +/// A container calculating the estimated time that a stage will complete in, based on stage +/// checkpoints reported by the pipeline. +/// +/// One `Eta` is only valid for a single stage. +#[derive(Default)] +struct Eta { + /// The last stage checkpoint + last_checkpoint: EntitiesCheckpoint, + /// The last time the stage reported its checkpoint + last_checkpoint_time: Option, + /// The current ETA + eta: Option, +} + +impl Eta { + /// Update the ETA given the checkpoint. + fn update(&mut self, checkpoint: StageCheckpoint) { + let current = checkpoint.entities(); + + if let Some(last_checkpoint_time) = &self.last_checkpoint_time { + let processed_since_last = current.processed - self.last_checkpoint.processed; + let elapsed = last_checkpoint_time.elapsed(); + let per_second = processed_since_last as f64 / elapsed.as_secs_f64(); + + self.eta = Some(Duration::from_secs_f64( + (current.total - current.processed) as f64 / per_second, + )); + } + + self.last_checkpoint = current; + self.last_checkpoint_time = Some(Instant::now()); + } +} + +impl std::fmt::Display for Eta { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some((eta, last_checkpoint_time)) = self.eta.zip(self.last_checkpoint_time) { + let remaining = eta.checked_sub(last_checkpoint_time.elapsed()); + + if let Some(remaining) = remaining { + return write!(f, "{}", humantime::format_duration(remaining)) + } + } + + write!(f, "unknown") + } +} diff --git a/crates/primitives/src/stage/checkpoints.rs b/crates/primitives/src/stage/checkpoints.rs index daa98f20b082..f6ee35c90198 100644 --- a/crates/primitives/src/stage/checkpoints.rs +++ b/crates/primitives/src/stage/checkpoints.rs @@ -216,6 +216,29 @@ impl StageCheckpoint { self.block_number = block_number; self } + + /// Get the underlying [`EntitiesCheckpoint`] to determine the number of entities processed, and + /// the number of total entities to process. + pub fn entities(&self) -> EntitiesCheckpoint { + match self.stage_checkpoint { + Some( + StageUnitCheckpoint::Account(AccountHashingCheckpoint { + progress: entities, .. + }) | + StageUnitCheckpoint::Storage(StorageHashingCheckpoint { + progress: entities, .. + }) | + StageUnitCheckpoint::Entities(entities) | + StageUnitCheckpoint::Execution(ExecutionCheckpoint { progress: entities, .. }) | + StageUnitCheckpoint::Headers(HeadersCheckpoint { progress: entities, .. }) | + StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint { + progress: entities, + .. + }), + ) => entities, + None => EntitiesCheckpoint::default(), + } + } } impl Display for StageCheckpoint { diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index e88b23773514..e8ceaf874b5c 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -355,7 +355,7 @@ where Ok(out @ ExecOutput { checkpoint, done }) => { made_progress |= checkpoint.block_number != prev_checkpoint.unwrap_or_default().block_number; - info!( + debug!( target: "sync::pipeline", stage = %stage_id, progress = checkpoint.block_number, From 49adb8de0f4bd49523fc7ae6bdb0648030cfba3e Mon Sep 17 00:00:00 2001 From: Bjerg Date: Thu, 15 Jun 2023 15:30:09 +0200 Subject: [PATCH 033/216] docs: various book chores (#3172) --- book/SUMMARY.md | 4 +--- book/run/observability.md | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 8acb29ac6ebe..cabe3f097f1a 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -20,12 +20,10 @@ 1. [admin](./jsonrpc/admin.md) 1. [rpc](./jsonrpc/rpc.md) 1. [CLI Reference](./cli/cli.md) - 1. [reth node](./cli/node.md) --> + 1. [reth node](./cli/node.md) 1. [reth db](./cli/db.md) 1. [reth p2p](./cli/p2p.md) 1. [reth stage](./cli/stage.md) - 1. [reth dump-stage](./cli/dump-stage.md) - 1. [reth drop-stage](./cli/drop-stage.md) 1. [Developers](./developers/developers.md) 1. [Contribute](./developers/contribute.md) 1. [Architecture](./developers/architecture.md) \ No newline at end of file diff --git a/book/run/observability.md b/book/run/observability.md index 0746085d2e0d..0b24f139a71d 100644 --- a/book/run/observability.md +++ b/book/run/observability.md @@ -22,7 +22,7 @@ while true; do date; curl -s localhost:9000 | grep -Ev '^(#|$)' | sort; echo; sl We're finally getting somewhere! As a final step, though, wouldn't it be great to see how these metrics progress over time (and generally, in a GUI)? -### Prometheus & Grafana +## Prometheus & Grafana We're going to be using Prometheus to collect metrics off of the endpoint we set up, and use Grafana to scrape the metrics from Prometheus and define a dashboard with them. @@ -55,7 +55,7 @@ To configure the dashboard in Grafana, click on the squares icon in the upper le And voilá, you should see your dashboard! If you're not yet connected to any peers, the dashboard will look like it's in an empty state, but once you are, you should see it start populating with data. -### Conclusion +## Conclusion In this runbook, we took you through starting the node, exposing different log levels, exporting metrics, and finally viewing those metrics in a Grafana dashboard. From e8a5ba1d5fbbfcab96cd8a0c279990bd8ddf1cd7 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Thu, 15 Jun 2023 15:43:34 +0200 Subject: [PATCH 034/216] perf: reduce more allocs (#3176) --- .../src/providers/database/provider.rs | 74 +++++++------------ 1 file changed, 28 insertions(+), 46 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 5a1cd977a182..77de5ac9f646 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -215,22 +215,16 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { &self, range: RangeInclusive, ) -> std::result::Result>, TransactionError> { - Ok(self - .tx + self.tx .cursor_read::()? .walk_range(BlockNumberAddress::range(range))? - .collect::, _>>()? - .into_iter() // fold all storages and save its old state so we can remove it from HashedStorage // it is needed as it is dup table. - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, - (BlockNumberAddress((_, address)), storage_entry)| { - accounts.entry(address).or_default().insert(storage_entry.key); - accounts - }, - )) + .try_fold(BTreeMap::new(), |mut accounts: BTreeMap>, entry| { + let (BlockNumberAddress((_, address)), storage_entry) = entry?; + accounts.entry(address).or_default().insert(storage_entry.key); + Ok(accounts) + }) } /// Get plainstate storages @@ -313,17 +307,14 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { &self, range: RangeInclusive, ) -> std::result::Result, TransactionError> { - Ok(self - .tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()? - .into_iter() - // fold all account to one set of changed accounts - .fold(BTreeSet::new(), |mut accounts: BTreeSet
, (_, account_before)| { + self.tx.cursor_read::()?.walk_range(range)?.try_fold( + BTreeSet::new(), + |mut accounts: BTreeSet
, entry| { + let (_, account_before) = entry?; accounts.insert(account_before.address); - accounts - })) + Ok(accounts) + }, + ) } /// Get plainstate account from iterator @@ -1099,8 +1090,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { id: StageId, checkpoint: StageCheckpoint, ) -> std::result::Result<(), DatabaseError> { - self.tx.put::(id.to_string(), checkpoint)?; - Ok(()) + self.tx.put::(id.to_string(), checkpoint) } /// Get stage checkpoint progress. @@ -1117,8 +1107,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { id: StageId, checkpoint: Vec, ) -> std::result::Result<(), DatabaseError> { - self.tx.put::(id.to_string(), checkpoint)?; - Ok(()) + self.tx.put::(id.to_string(), checkpoint) } /// Get lastest block number. @@ -1452,18 +1441,15 @@ impl<'this, TX: DbTx<'this>> AccountExtProvider for DatabaseProvider<'this, TX> &self, range: impl RangeBounds, ) -> Result> { - Ok(self - .tx + self.tx .cursor_read::()? .walk_range(range)? - .collect::, _>>()? - .into_iter() - // fold all account to one set of changed accounts - .fold(BTreeSet::new(), |mut accounts: BTreeSet
, (_, account_before)| { - accounts.insert(account_before.address); - accounts - })) + .map(|entry| { + entry.map(|(_, account_before)| account_before.address).map_err(Into::into) + }) + .collect() } + fn basic_accounts( &self, iter: impl IntoIterator, @@ -1647,9 +1633,9 @@ impl<'this, TX: DbTx<'this>> TransactionsProvider for DatabaseProvider<'this, TX &self, tx_hash: TxHash, ) -> Result> { + let mut transaction_cursor = self.tx.cursor_read::()?; if let Some(transaction_id) = self.transaction_id(tx_hash)? { if let Some(transaction) = self.transaction_by_id(transaction_id)? { - let mut transaction_cursor = self.tx.cursor_read::()?; if let Some(block_number) = transaction_cursor.seek(transaction_id).map(|b| b.map(|(_, bn)| bn))? { @@ -1689,13 +1675,13 @@ impl<'this, TX: DbTx<'this>> TransactionsProvider for DatabaseProvider<'this, TX &self, id: BlockHashOrNumber, ) -> Result>> { + let mut tx_cursor = self.tx.cursor_read::()?; if let Some(block_number) = self.convert_hash_or_number(id)? { if let Some(body) = self.block_body_indices(block_number)? { let tx_range = body.tx_num_range(); return if tx_range.is_empty() { Ok(Some(Vec::new())) } else { - let mut tx_cursor = self.tx.cursor_read::()?; let transactions = tx_cursor .walk_range(tx_range)? .map(|result| result.map(|(_, tx)| tx.into())) @@ -1711,14 +1697,14 @@ impl<'this, TX: DbTx<'this>> TransactionsProvider for DatabaseProvider<'this, TX &self, range: impl RangeBounds, ) -> Result>> { - let mut results = Vec::default(); + let mut results = Vec::new(); let mut body_cursor = self.tx.cursor_read::()?; let mut tx_cursor = self.tx.cursor_read::()?; for entry in body_cursor.walk_range(range)? { let (_, body) = entry?; let tx_num_range = body.tx_num_range(); if tx_num_range.is_empty() { - results.push(Vec::default()); + results.push(Vec::new()); } else { results.push( tx_cursor @@ -1787,13 +1773,9 @@ impl<'this, TX: DbTx<'this>> WithdrawalsProvider for DatabaseProvider<'this, TX> } fn latest_withdrawal(&self) -> Result> { - let latest_block_withdrawal = self.tx.cursor_read::()?.last(); - latest_block_withdrawal - .map(|block_withdrawal_pair| { - block_withdrawal_pair - .and_then(|(_, block_withdrawal)| block_withdrawal.withdrawals.last().cloned()) - }) - .map_err(Into::into) + let latest_block_withdrawal = self.tx.cursor_read::()?.last()?; + Ok(latest_block_withdrawal + .and_then(|(_, mut block_withdrawal)| block_withdrawal.withdrawals.pop())) } } From 01b0143ecb869f1af5d9207faacd6b458e70de3e Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 15 Jun 2023 16:47:19 +0300 Subject: [PATCH 035/216] chore(sync): remove progress terminology from test macros (#3177) --- crates/stages/src/test_utils/macros.rs | 66 ++++++++++++++++++-------- 1 file changed, 45 insertions(+), 21 deletions(-) diff --git a/crates/stages/src/test_utils/macros.rs b/crates/stages/src/test_utils/macros.rs index f691d13711b1..8cc3e9cda1b1 100644 --- a/crates/stages/src/test_utils/macros.rs +++ b/crates/stages/src/test_utils/macros.rs @@ -18,19 +18,23 @@ macro_rules! stage_test_suite { assert_matches::assert_matches!(result, Ok(_)); // Validate the stage execution - assert_matches::assert_matches!(runner.validate_execution(input, result.unwrap().ok()),Ok(_), "execution validation"); + assert_matches::assert_matches!( + runner.validate_execution(input, result.unwrap().ok()), + Ok(_), + "execution validation" + ); } // Run the complete stage execution flow. #[tokio::test] async fn [< execute_ $name>] () { - let (previous_stage, stage_progress) = (500, 100); + let (target, current_checkpoint) = (500, 100); // Set up the runner let mut runner = $runner::default(); let input = crate::stage::ExecInput { - target: Some(previous_stage), - checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(stage_progress)), + target: Some(target), + checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(current_checkpoint)), }; let seed = runner.seed_execution(input).expect("failed to seed"); let rx = runner.execute(input); @@ -43,11 +47,15 @@ macro_rules! stage_test_suite { assert_matches::assert_matches!( result, Ok(ExecOutput { done, checkpoint }) - if done && checkpoint.block_number == previous_stage + if done && checkpoint.block_number == target ); // Validate the stage execution - assert_matches::assert_matches!(runner.validate_execution(input, result.ok()),Ok(_), "execution validation"); + assert_matches::assert_matches!( + runner.validate_execution(input, result.ok()), + Ok(_), + "execution validation" + ); } // Check that unwind does not panic on no new entries within the input range. @@ -70,19 +78,23 @@ macro_rules! stage_test_suite { ); // Validate the stage unwind - assert_matches::assert_matches!(runner.validate_unwind(input),Ok(_), "unwind validation"); + assert_matches::assert_matches!( + runner.validate_unwind(input), + Ok(_), + "unwind validation" + ); } // Run complete execute and unwind flow. #[tokio::test] async fn [< unwind_ $name>] () { - let (previous_stage, stage_progress) = (500, 100); + let (target, current_checkpoint) = (500, 100); // Set up the runner let mut runner = $runner::default(); let execute_input = crate::stage::ExecInput { - target: Some(previous_stage), - checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(stage_progress)), + target: Some(target), + checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(current_checkpoint)), }; let seed = runner.seed_execution(execute_input).expect("failed to seed"); @@ -95,15 +107,19 @@ macro_rules! stage_test_suite { assert_matches::assert_matches!( result, Ok(ExecOutput { done, checkpoint }) - if done && checkpoint.block_number == previous_stage + if done && checkpoint.block_number == target + ); + assert_matches::assert_matches!( + runner.validate_execution(execute_input, result.ok()), + Ok(_), + "execution validation" ); - assert_matches::assert_matches!(runner.validate_execution(execute_input, result.ok()),Ok(_), "execution validation"); // Run stage unwind let unwind_input = crate::stage::UnwindInput { - unwind_to: stage_progress, - checkpoint: reth_primitives::stage::StageCheckpoint::new(previous_stage), + unwind_to: current_checkpoint, + checkpoint: reth_primitives::stage::StageCheckpoint::new(target), bad_block: None, }; @@ -117,7 +133,11 @@ macro_rules! stage_test_suite { ); // Validate the stage unwind - assert_matches::assert_matches!(runner.validate_unwind(unwind_input),Ok(_), "unwind validation"); + assert_matches::assert_matches!( + runner.validate_unwind(unwind_input), + Ok(_), + "unwind validation" + ); } } }; @@ -129,17 +149,17 @@ macro_rules! stage_test_suite_ext { ($runner:ident, $name:ident) => { crate::test_utils::stage_test_suite!($runner, $name); - paste::item! { + paste::item! { /// Check that the execution is short-circuited if the target was already reached. #[tokio::test] async fn [< execute_already_reached_target_ $name>] () { - let stage_progress = 1000; + let current_checkpoint = 1000; // Set up the runner let mut runner = $runner::default(); let input = crate::stage::ExecInput { - target: Some(stage_progress), - checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(stage_progress)), + target: Some(current_checkpoint), + checkpoint: Some(reth_primitives::stage::StageCheckpoint::new(current_checkpoint)), }; let seed = runner.seed_execution(input).expect("failed to seed"); @@ -154,11 +174,15 @@ macro_rules! stage_test_suite_ext { assert_matches::assert_matches!( result, Ok(ExecOutput { done, checkpoint }) - if done && checkpoint.block_number == stage_progress + if done && checkpoint.block_number == current_checkpoint ); // Validate the stage execution - assert_matches::assert_matches!(runner.validate_execution(input, result.ok()),Ok(_), "execution validation"); + assert_matches::assert_matches!( + runner.validate_execution(input, result.ok()), + Ok(_), + "execution validation" + ); } } }; From 83b4c5517517115b786cb7084d2121c13d90b05e Mon Sep 17 00:00:00 2001 From: Bjerg Date: Thu, 15 Jun 2023 16:07:38 +0200 Subject: [PATCH 036/216] docs: readme pass (#3174) --- README.md | 71 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 8471788541d0..657ba02d9ee9 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,31 @@ -#

reth 🏗️🚧

+# reth + +[![CI status](https://github.com/paradigmxyz/reth/workflows/ci/badge.svg)][gh-ci] +[![cargo-deny status](https://github.com/paradigmxyz/reth/workflows/deny/badge.svg)][gh-deny] +[![Codecov](https://img.shields.io/codecov/c/github/paradigmxyz/reth?token=c24SDcMImE)][codecov] +[![Telegram Chat][tg-badge]][tg-url] **Modular, contributor-friendly and blazing-fast implementation of the Ethereum protocol** ![](./assets/reth.jpg) -*The project is still work in progress, see the [disclaimer below](#-warning-under-construction-).* +**[User Book](https://paradigmxyz.github.io/reth)** +| **[Developer Docs](./docs)** +| **[Crate Docs](https://paradigmxyz.github.io/reth/docs)** -[![CI status](https://github.com/paradigmxyz/reth/workflows/ci/badge.svg)][gh-ci] -[![cargo-deny status](https://github.com/paradigmxyz/reth/workflows/deny/badge.svg)][gh-deny] -[![Codecov](https://img.shields.io/codecov/c/github/paradigmxyz/reth?token=c24SDcMImE)][codecov] -[![Telegram Chat][tg-badge]][tg-url] +*The project is still work in progress, see the [disclaimer below](#status).* [codecov]: https://app.codecov.io/gh/paradigmxyz/reth [gh-ci]: https://github.com/paradigmxyz/reth/actions/workflows/ci.yml [gh-deny]: https://github.com/paradigmxyz/reth/actions/workflows/deny.yml [tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fparadigm%5Freth -## What is Reth? What are its goals? +## What is Reth? Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is a new Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient. Reth is an Execution Layer (EL) and is compatible with all Ethereum Consensus Layer (CL) implementations that support the [Engine API](https://github.com/ethereum/execution-apis/tree/59e3a719021f48c1ef5653840e3ea5750e6af693/src/engine). It is originally built and driven forward by [Paradigm](https://paradigm.xyz/), and is licensed under the Apache and MIT licenses. +## Goals + As a full Ethereum node, Reth allows users to connect to the Ethereum network and interact with the Ethereum blockchain. This includes sending and receiving transactions/logs/traces, as well as accessing and interacting with smart contracts. Building a successful Ethereum node requires creating a high-quality implementation that is both secure and efficient, as well as being easy to use on consumer hardware. It also requires building a strong community of contributors who can help support and improve the software. More concretely, our goals are: @@ -33,15 +39,38 @@ More concretely, our goals are: ## Status -The project is not ready for use. We hope to have full sync implemented sometime in Q1 2023, followed by optimizations. In the meantime, we're working on making sure every crate of the repository is well documented, abstracted and tested. +The project is **not ready for production use**. + +Reth is fully capable of syncing, however, there are still some missing features, and we are still working on performance and stability. Because of this, we are still introducing breaking changes. + +It has **not been audited for security purposes** and should not be used in production yet. + +We will be updating the documentation with the completion status of each component, as well as include more contributing guidelines (design docs, architecture diagrams, repository layouts) and "good first issues". + +We appreciate your patience until we get there. Until then, we are happy to answer all questions in the Telegram link above. + +## For Users + +See the [Reth Book](https://paradigmxyz.github.io/reth) for instructions on how to install and run Reth. ## For Developers -### Running Reth +### Using reth as a library + +You can use individual crates of reth in your project. -See the [Reth Book](https://paradigmxyz.github.io/reth) for instructions on how to run Reth. +The crate docs can be found [here](https://paradigmxyz.github.io/reth/docs). -### Build & Test +For a general overview of the crates, see [Project Layout](./docs/repo/layout.md). + +### Contributing + +If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/paradigm_reth) to chat with us about the development of Reth! + +- Our contributor guidelines can be found in [`CONTRIBUTING.md`](./CONTRIBUTING.md). +- See our [contributor docs](./docs) for more information on the project. A good starting point is [Project Layout](./docs/repo/layout.md). + +### Building and testing The Minimum Supported Rust Version (MSRV) of this project is [1.70.0](https://blog.rust-lang.org/2023/06/01/Rust-1.70.0.html). @@ -68,16 +97,6 @@ cargo test --workspace --features geth-tests We recommend using [`cargo nextest`](https://nexte.st/) to speed up testing. With nextest installed, simply substitute `cargo test` with `cargo nextest run`. -## Contributing - -If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/paradigm_reth) to chat with us about the development of Reth! - -See our [contributor docs](./docs) for more information on the project. - -A good starting point is [Project Layout](./docs/repo/layout.md) which gives an overview of the repository's structure, and descriptions for each package. - -Our contributor guidelines can be found in [`CONTRIBUTING.md`](./CONTRIBUTING.md). - ## Getting Help If you have any questions, first see if the answer to your question can be found in the [book][book]. @@ -101,15 +120,5 @@ None of this would have been possible without them, so big shoutout to the teams * [Erigon](https://github.com/ledgerwatch/erigon) (fka Turbo-Geth): Erigon pioneered the ["Staged Sync" architecture](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) that Reth is using, as well as [introduced MDBX](https://github.com/ledgerwatch/erigon/wiki/Choice-of-storage-engine) as the database of choice. We thank Erigon for pushing the state of the art research on the performance limits of Ethereum nodes. * [Akula](https://github.com/akula-bft/akula/): Reth uses forks of the Apache versions of Akula's [MDBX Bindings](https://github.com/paradigmxyz/reth/pull/132), [FastRLP](https://github.com/paradigmxyz/reth/pull/63) and [ECIES](https://github.com/paradigmxyz/reth/pull/80) . Given that these packages were already released under the Apache License, and they implement standardized solutions, we decided not to reimplement them to iterate faster. We thank the Akula team for their contributions to the Rust Ethereum ecosystem and for publishing these packages. -## 🚧 WARNING: UNDER CONSTRUCTION 🚧 - -This project is work in progress and subject to frequent changes as we are still working on wiring up each individual node component into a full syncing pipeline. - -It has not been audited for security purposes and should not be used in production yet. - -We will be updating the documentation with the completion status of each component, as well as include more contributing guidelines (design docs, architecture diagrams, repository layouts) and "good first issues". See the "Contributing and Getting Help" section above for more. - -We appreciate your patience until we get there. Until then, we are happy to answer all questions in the Telegram link above. - [book]: https://paradigmxyz.github.io/reth/ [tg-url]: https://t.me/paradigm_reth From 600f3eac8c494dfc10b628527ff87513e2381b7f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 16:29:37 +0200 Subject: [PATCH 037/216] chore(rpc): simplify GethTrace type (#3180) --- crates/rpc/rpc-api/src/debug.rs | 6 +- .../rpc/rpc-types/src/eth/trace/geth/mod.rs | 61 ++++++++----------- .../rpc/rpc-types/src/eth/trace/geth/noop.rs | 2 + .../rpc-types/src/eth/trace/geth/pre_state.rs | 1 + crates/rpc/rpc/src/debug.rs | 19 +++--- 5 files changed, 41 insertions(+), 48 deletions(-) diff --git a/crates/rpc/rpc-api/src/debug.rs b/crates/rpc/rpc-api/src/debug.rs index 32c776338583..0d29042b5efd 100644 --- a/crates/rpc/rpc-api/src/debug.rs +++ b/crates/rpc/rpc-api/src/debug.rs @@ -2,7 +2,7 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256}; use reth_rpc_types::{ trace::geth::{ - BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTraceFrame, + BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult, }, CallRequest, RichBlock, @@ -84,7 +84,7 @@ pub trait DebugApi { &self, tx_hash: H256, opts: Option, - ) -> RpcResult; + ) -> RpcResult; /// The `debug_traceCall` method lets you run an `eth_call` within the context of the given /// block execution using the final state of parent block as the base. @@ -101,5 +101,5 @@ pub trait DebugApi { request: CallRequest, block_number: Option, opts: Option, - ) -> RpcResult; + ) -> RpcResult; } diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs index 526c9dc1fce9..092efa438559 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/mod.rs @@ -20,7 +20,7 @@ mod noop; mod pre_state; /// Result type for geth style transaction trace -pub type TraceResult = crate::trace::common::TraceResult; +pub type TraceResult = crate::trace::common::TraceResult; /// blockTraceResult represents the results of tracing a single block when an entire chain is being /// traced. ref @@ -34,7 +34,7 @@ pub struct BlockTraceResult { pub traces: Vec, } -/// Geth Default trace frame +/// Geth Default struct log trace frame /// /// #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -91,64 +91,55 @@ pub struct StructLog { pub error: Option, } -/// Tracing response +/// Tracing response objects +/// +/// Note: This deserializes untagged, so it's possible that a custom javascript tracer response +/// matches another variant, for example a js tracer that returns `{}` would be deserialized as +/// [GethTrace::NoopTracer] #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] #[serde(untagged)] -pub enum GethTraceFrame { +pub enum GethTrace { + /// The response for the default struct log tracer Default(DefaultFrame), - NoopTracer(NoopFrame), - FourByteTracer(FourByteFrame), + /// The response for call tracer CallTracer(CallFrame), + /// The response for four byte tracer + FourByteTracer(FourByteFrame), + /// The response for pre-state byte tracer PreStateTracer(PreStateFrame), + /// An empty json response + NoopTracer(NoopFrame), + /// Any other trace response, such as custom javascript response objects JS(serde_json::Value), } -impl From for GethTraceFrame { +impl From for GethTrace { fn from(value: DefaultFrame) -> Self { - GethTraceFrame::Default(value) + GethTrace::Default(value) } } -impl From for GethTraceFrame { +impl From for GethTrace { fn from(value: FourByteFrame) -> Self { - GethTraceFrame::FourByteTracer(value) + GethTrace::FourByteTracer(value) } } -impl From for GethTraceFrame { +impl From for GethTrace { fn from(value: CallFrame) -> Self { - GethTraceFrame::CallTracer(value) + GethTrace::CallTracer(value) } } -impl From for GethTraceFrame { +impl From for GethTrace { fn from(value: PreStateFrame) -> Self { - GethTraceFrame::PreStateTracer(value) + GethTrace::PreStateTracer(value) } } -impl From for GethTraceFrame { +impl From for GethTrace { fn from(value: NoopFrame) -> Self { - GethTraceFrame::NoopTracer(value) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] -#[serde(untagged)] -pub enum GethTrace { - Known(GethTraceFrame), - Unknown(serde_json::Value), -} - -impl From for GethTrace { - fn from(value: GethTraceFrame) -> Self { - GethTrace::Known(value) - } -} - -impl From for GethTrace { - fn from(value: serde_json::Value) -> Self { - GethTrace::Unknown(value) + GethTrace::NoopTracer(value) } } diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/noop.rs b/crates/rpc/rpc-types/src/eth/trace/geth/noop.rs index 003b1dcc99c1..a3b3f726d8ba 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/noop.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/noop.rs @@ -1,9 +1,11 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +/// An empty frame response that's only an empty json object `{}` /// #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct NoopFrame(BTreeMap); + #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)] struct Null; diff --git a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs index fc53ecfa7b0b..b6301da3f2e1 100644 --- a/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs +++ b/crates/rpc/rpc-types/src/eth/trace/geth/pre_state.rs @@ -14,6 +14,7 @@ pub enum PreStateFrame { pub struct PreStateMode(pub BTreeMap); #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] pub struct DiffMode { pub pre: BTreeMap, pub post: BTreeMap, diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 8a44d16984fe..dc5a423cec26 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -26,8 +26,7 @@ use reth_rpc_api::DebugApiServer; use reth_rpc_types::{ trace::geth::{ BlockTraceResult, FourByteFrame, GethDebugBuiltInTracerType, GethDebugTracerType, - GethDebugTracingCallOptions, GethDebugTracingOptions, GethTraceFrame, NoopFrame, - TraceResult, + GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, NoopFrame, TraceResult, }, BlockError, CallRequest, RichBlock, }; @@ -203,7 +202,7 @@ where &self, tx_hash: H256, opts: GethDebugTracingOptions, - ) -> EthResult { + ) -> EthResult { let (transaction, block) = match self.inner.eth_api.transaction_and_block(tx_hash).await? { None => return Err(EthApiError::TransactionNotFound), Some(res) => res, @@ -244,7 +243,7 @@ where call: CallRequest, block_id: Option, opts: GethDebugTracingCallOptions, - ) -> EthResult { + ) -> EthResult { self.on_blocking_task(|this| async move { this.try_debug_trace_call(call, block_id, opts).await }) @@ -260,7 +259,7 @@ where call: CallRequest, block_id: Option, opts: GethDebugTracingCallOptions, - ) -> EthResult { + ) -> EthResult { let at = block_id.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest)); let GethDebugTracingCallOptions { tracing_options, state_overrides, block_overrides } = opts; @@ -319,7 +318,7 @@ where let (res, env) = inspect(db, env, &mut inspector)?; let result = inspector.json_result(res, &env)?; - Ok(GethTraceFrame::JS(result)) + Ok(GethTrace::JS(result)) } } } @@ -349,7 +348,7 @@ where env: Env, at: BlockId, db: &mut SubState>, - ) -> EthResult<(GethTraceFrame, revm_primitives::State)> { + ) -> EthResult<(GethTrace, revm_primitives::State)> { let GethDebugTracingOptions { config, tracer, tracer_config, .. } = opts; if let Some(tracer) = tracer { @@ -395,7 +394,7 @@ where let state = res.state.clone(); let result = inspector.json_result(res, &env)?; - Ok((GethTraceFrame::JS(result), state)) + Ok((GethTrace::JS(result), state)) } } } @@ -598,7 +597,7 @@ where &self, tx_hash: H256, opts: Option, - ) -> RpcResult { + ) -> RpcResult { let _permit = self.acquire_trace_permit().await; Ok(DebugApi::debug_trace_transaction(self, tx_hash, opts.unwrap_or_default()).await?) } @@ -609,7 +608,7 @@ where request: CallRequest, block_number: Option, opts: Option, - ) -> RpcResult { + ) -> RpcResult { let _permit = self.acquire_trace_permit().await; Ok(DebugApi::debug_trace_call(self, request, block_number, opts.unwrap_or_default()) .await?) From 081df84f990b52b6ef5aa57a17d8a9174c54f012 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 17:12:10 +0200 Subject: [PATCH 038/216] chore: use std HashMap (#3182) --- crates/payload/builder/Cargo.toml | 1 - crates/payload/builder/src/database.rs | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index f180c6cdaaed..70e4954faa3c 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -29,7 +29,6 @@ futures-util = { workspace = true } thiserror = { workspace = true } sha2 = { version = "0.10", default-features = false } tracing = { workspace = true } -hashbrown = "0.13" [features] test-utils = [] diff --git a/crates/payload/builder/src/database.rs b/crates/payload/builder/src/database.rs index 9a7263ec50b0..78a25a1e07d5 100644 --- a/crates/payload/builder/src/database.rs +++ b/crates/payload/builder/src/database.rs @@ -1,12 +1,14 @@ //! Database adapters for payload building. -use hashbrown::{hash_map::Entry, HashMap}; use reth_primitives::U256; use revm_primitives::{ db::{Database, DatabaseRef}, AccountInfo, Address, Bytecode, B256, }; -use std::cell::RefCell; +use std::{ + cell::RefCell, + collections::{hash_map::Entry, HashMap}, +}; /// A container type that caches all [DatabaseRef] reads from an underlying [DatabaseRef]. /// From e6dd5766395ec0af1318d482a4986b22633142c3 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 18:41:07 +0200 Subject: [PATCH 039/216] fix: use eth namespace for filter trait (#3184) --- crates/rpc/rpc-api/src/eth_filter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-api/src/eth_filter.rs b/crates/rpc/rpc-api/src/eth_filter.rs index 6b6dba4de0c3..9e313c75f70b 100644 --- a/crates/rpc/rpc-api/src/eth_filter.rs +++ b/crates/rpc/rpc-api/src/eth_filter.rs @@ -2,8 +2,8 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_rpc_types::{Filter, FilterChanges, FilterId, Log}; /// Rpc Interface for poll-based ethereum filter API. -#[cfg_attr(not(feature = "client"), rpc(server, namespace = "net"))] -#[cfg_attr(feature = "client", rpc(server, client, namespace = "net"))] +#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] +#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] pub trait EthFilterApi { /// Creates anew filter and returns its id. #[method(name = "newFilter")] From f25fcca33ec2fa90fac79771d8d94529bcbb38c2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 18:50:04 +0200 Subject: [PATCH 040/216] chore: Cargo.lock out of sync (#3185) --- Cargo.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 93fdf91ddb01..1130edd46f93 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5455,7 +5455,6 @@ name = "reth-payload-builder" version = "0.1.0" dependencies = [ "futures-util", - "hashbrown 0.13.2", "reth-interfaces", "reth-metrics", "reth-primitives", From 32e642d6b0af5a7a10ad485685373508968a04bb Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 15 Jun 2023 18:06:45 +0100 Subject: [PATCH 041/216] fix(stages): disable index history stages checkpoints (#3178) --- .../src/stages/index_account_history.rs | 213 +--------------- .../src/stages/index_storage_history.rs | 228 +----------------- 2 files changed, 14 insertions(+), 427 deletions(-) diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index 2261da83a842..f20b9c5bea6e 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -1,13 +1,8 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; -use reth_db::{cursor::DbCursorRO, database::Database, tables, transaction::DbTx, DatabaseError}; -use reth_primitives::{ - stage::{ - CheckpointBlockRange, EntitiesCheckpoint, IndexHistoryCheckpoint, StageCheckpoint, StageId, - }, - BlockNumber, -}; +use reth_db::database::Database; +use reth_primitives::stage::{StageCheckpoint, StageId}; use reth_provider::DatabaseProviderRW; -use std::{fmt::Debug, ops::RangeInclusive}; +use std::fmt::Debug; /// Stage is indexing history the account changesets generated in /// [`ExecutionStage`][crate::stages::ExecutionStage]. For more information @@ -44,27 +39,11 @@ impl Stage for IndexAccountHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); - let mut stage_checkpoint = stage_checkpoint( - provider, - input.checkpoint(), - // It is important to provide the full block range into the checkpoint, - // not the one accounting for commit threshold, to get the correct range end. - &input.next_block_range(), - )?; - let indices = provider.get_account_transition_ids_from_changeset(range.clone())?; - let changesets = indices.values().map(|blocks| blocks.len() as u64).sum::(); - // Insert changeset to history index provider.insert_account_history_index(indices)?; - stage_checkpoint.progress.processed += changesets; - - Ok(ExecOutput { - checkpoint: StageCheckpoint::new(*range.end()) - .with_index_history_stage_checkpoint(stage_checkpoint), - done: is_final_range, - }) + Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()), done: is_final_range }) } /// Unwind the stage. @@ -76,70 +55,15 @@ impl Stage for IndexAccountHistoryStage { let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); - let changesets = provider.unwind_account_history_indices(range)?; - - let checkpoint = - if let Some(mut stage_checkpoint) = input.checkpoint.index_history_stage_checkpoint() { - stage_checkpoint.progress.processed -= changesets as u64; - StageCheckpoint::new(unwind_progress) - .with_index_history_stage_checkpoint(stage_checkpoint) - } else { - StageCheckpoint::new(unwind_progress) - }; + provider.unwind_account_history_indices(range)?; // from HistoryIndex higher than that number. - Ok(UnwindOutput { checkpoint }) + Ok(UnwindOutput { checkpoint: StageCheckpoint::new(unwind_progress) }) } } -/// The function proceeds as follows: -/// 1. It first checks if the checkpoint has an [IndexHistoryCheckpoint] that matches the given -/// block range. If it does, the function returns that checkpoint. -/// 2. If the checkpoint's block range end matches the current checkpoint's block number, it creates -/// a new [IndexHistoryCheckpoint] with the given block range and updates the progress with the -/// current progress. -/// 3. If none of the above conditions are met, it creates a new [IndexHistoryCheckpoint] with the -/// given block range and calculates the progress by counting the number of processed entries in the -/// [tables::AccountChangeSet] table within the given block range. -fn stage_checkpoint( - provider: &DatabaseProviderRW<'_, &DB>, - checkpoint: StageCheckpoint, - range: &RangeInclusive, -) -> Result { - Ok(match checkpoint.index_history_stage_checkpoint() { - Some(stage_checkpoint @ IndexHistoryCheckpoint { block_range, .. }) - if block_range == CheckpointBlockRange::from(range) => - { - stage_checkpoint - } - Some(IndexHistoryCheckpoint { block_range, progress }) - if block_range.to == checkpoint.block_number => - { - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange::from(range), - progress: EntitiesCheckpoint { - processed: progress.processed, - total: provider.tx_ref().entries::()? as u64, - }, - } - } - _ => IndexHistoryCheckpoint { - block_range: CheckpointBlockRange::from(range), - progress: EntitiesCheckpoint { - processed: provider - .tx_ref() - .cursor_read::()? - .walk_range(0..=checkpoint.block_number)? - .count() as u64, - total: provider.tx_ref().entries::()? as u64, - }, - }, - }) -} - #[cfg(test)] mod tests { - use assert_matches::assert_matches; use reth_provider::ProviderFactory; use std::collections::BTreeMap; @@ -213,18 +137,7 @@ mod tests { let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let out = stage.execute(&mut provider, input).await.unwrap(); - assert_eq!( - out, - ExecOutput { - checkpoint: StageCheckpoint::new(5).with_index_history_stage_checkpoint( - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: input.next_block(), to: run_to }, - progress: EntitiesCheckpoint { processed: 2, total: 2 } - } - ), - done: true - } - ); + assert_eq!(out, ExecOutput { checkpoint: StageCheckpoint::new(5), done: true }); provider.commit().unwrap(); } @@ -437,116 +350,4 @@ mod tests { ]) ); } - - #[tokio::test] - async fn stage_checkpoint_range() { - // init - let test_tx = TestTransaction::default(); - - // setup - partial_setup(&test_tx); - - // run - { - let mut stage = IndexAccountHistoryStage { commit_threshold: 4 }; // Two runs required - let factory = ProviderFactory::new(&test_tx.tx, MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); - - let mut input = ExecInput { target: Some(5), ..Default::default() }; - let out = stage.execute(&mut provider, input).await.unwrap(); - assert_eq!( - out, - ExecOutput { - checkpoint: StageCheckpoint::new(4).with_index_history_stage_checkpoint( - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: 1, to: 5 }, - progress: EntitiesCheckpoint { processed: 1, total: 2 } - } - ), - done: false - } - ); - input.checkpoint = Some(out.checkpoint); - - let out = stage.execute(&mut provider, input).await.unwrap(); - assert_eq!( - out, - ExecOutput { - checkpoint: StageCheckpoint::new(5).with_index_history_stage_checkpoint( - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: 5, to: 5 }, - progress: EntitiesCheckpoint { processed: 2, total: 2 } - } - ), - done: true - } - ); - - provider.commit().unwrap(); - } - - // verify - let table = cast(test_tx.table::().unwrap()); - assert_eq!(table, BTreeMap::from([(shard(u64::MAX), vec![4, 5])])); - - // unwind - unwind(&test_tx, 5, 0).await; - - // verify initial state - let table = test_tx.table::().unwrap(); - assert!(table.is_empty()); - } - - #[test] - fn stage_checkpoint_recalculation() { - let tx = TestTransaction::default(); - - tx.commit(|tx| { - tx.put::( - 1, - AccountBeforeTx { - address: H160(hex!("0000000000000000000000000000000000000001")), - info: None, - }, - ) - .unwrap(); - tx.put::( - 1, - AccountBeforeTx { - address: H160(hex!("0000000000000000000000000000000000000002")), - info: None, - }, - ) - .unwrap(); - tx.put::( - 2, - AccountBeforeTx { - address: H160(hex!("0000000000000000000000000000000000000001")), - info: None, - }, - ) - .unwrap(); - tx.put::( - 2, - AccountBeforeTx { - address: H160(hex!("0000000000000000000000000000000000000002")), - info: None, - }, - ) - .unwrap(); - Ok(()) - }) - .unwrap(); - - let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let provider = factory.provider_rw().unwrap(); - - assert_matches!( - stage_checkpoint(&provider, StageCheckpoint::new(1), &(1..=2)).unwrap(), - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: 1, to: 2 }, - progress: EntitiesCheckpoint { processed: 2, total: 4 } - } - ); - } } diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index e54d080b66a1..2566861de9bd 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -1,16 +1,8 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; -use reth_db::{ - cursor::DbCursorRO, database::Database, models::BlockNumberAddress, tables, transaction::DbTx, - DatabaseError, -}; -use reth_primitives::{ - stage::{ - CheckpointBlockRange, EntitiesCheckpoint, IndexHistoryCheckpoint, StageCheckpoint, StageId, - }, - BlockNumber, -}; +use reth_db::{database::Database, models::BlockNumberAddress}; +use reth_primitives::stage::{StageCheckpoint, StageId}; use reth_provider::DatabaseProviderRW; -use std::{fmt::Debug, ops::RangeInclusive}; +use std::fmt::Debug; /// Stage is indexing history the account changesets generated in /// [`ExecutionStage`][crate::stages::ExecutionStage]. For more information @@ -47,26 +39,10 @@ impl Stage for IndexStorageHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); - let mut stage_checkpoint = stage_checkpoint( - provider, - input.checkpoint(), - // It is important to provide the full block range into the checkpoint, - // not the one accounting for commit threshold, to get the correct range end. - &input.next_block_range(), - )?; - let indices = provider.get_storage_transition_ids_from_changeset(range.clone())?; - let changesets = indices.values().map(|blocks| blocks.len() as u64).sum::(); - provider.insert_storage_history_index(indices)?; - stage_checkpoint.progress.processed += changesets; - - Ok(ExecOutput { - checkpoint: StageCheckpoint::new(*range.end()) - .with_index_history_stage_checkpoint(stage_checkpoint), - done: is_final_range, - }) + Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()), done: is_final_range }) } /// Unwind the stage. @@ -78,71 +54,14 @@ impl Stage for IndexStorageHistoryStage { let (range, unwind_progress, _) = input.unwind_block_range_with_threshold(self.commit_threshold); - let changesets = - provider.unwind_storage_history_indices(BlockNumberAddress::range(range))?; + provider.unwind_storage_history_indices(BlockNumberAddress::range(range))?; - let checkpoint = - if let Some(mut stage_checkpoint) = input.checkpoint.index_history_stage_checkpoint() { - stage_checkpoint.progress.processed -= changesets as u64; - StageCheckpoint::new(unwind_progress) - .with_index_history_stage_checkpoint(stage_checkpoint) - } else { - StageCheckpoint::new(unwind_progress) - }; - - Ok(UnwindOutput { checkpoint }) + Ok(UnwindOutput { checkpoint: StageCheckpoint::new(unwind_progress) }) } } -/// The function proceeds as follows: -/// 1. It first checks if the checkpoint has an [IndexHistoryCheckpoint] that matches the given -/// block range. If it does, the function returns that checkpoint. -/// 2. If the checkpoint's block range end matches the current checkpoint's block number, it creates -/// a new [IndexHistoryCheckpoint] with the given block range and updates the progress with the -/// current progress. -/// 3. If none of the above conditions are met, it creates a new [IndexHistoryCheckpoint] with the -/// given block range and calculates the progress by counting the number of processed entries in the -/// [tables::StorageChangeSet] table within the given block range. -fn stage_checkpoint( - provider: &DatabaseProviderRW<'_, &DB>, - checkpoint: StageCheckpoint, - range: &RangeInclusive, -) -> Result { - Ok(match checkpoint.index_history_stage_checkpoint() { - Some(stage_checkpoint @ IndexHistoryCheckpoint { block_range, .. }) - if block_range == CheckpointBlockRange::from(range) => - { - stage_checkpoint - } - Some(IndexHistoryCheckpoint { block_range, progress }) - if block_range.to == checkpoint.block_number => - { - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange::from(range), - progress: EntitiesCheckpoint { - processed: progress.processed, - total: provider.tx_ref().entries::()? as u64, - }, - } - } - _ => IndexHistoryCheckpoint { - block_range: CheckpointBlockRange::from(range), - progress: EntitiesCheckpoint { - processed: provider - .tx_ref() - .cursor_read::()? - .walk_range(BlockNumberAddress::range(0..=checkpoint.block_number))? - .count() as u64, - total: provider.tx_ref().entries::()? as u64, - }, - }, - }) -} - #[cfg(test)] mod tests { - - use assert_matches::assert_matches; use reth_provider::ProviderFactory; use std::collections::BTreeMap; @@ -226,18 +145,7 @@ mod tests { let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); let mut provider = factory.provider_rw().unwrap(); let out = stage.execute(&mut provider, input).await.unwrap(); - assert_eq!( - out, - ExecOutput { - checkpoint: StageCheckpoint::new(5).with_index_history_stage_checkpoint( - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: input.next_block(), to: run_to }, - progress: EntitiesCheckpoint { processed: 2, total: 2 } - } - ), - done: true - } - ); + assert_eq!(out, ExecOutput { checkpoint: StageCheckpoint::new(5), done: true }); provider.commit().unwrap(); } @@ -453,126 +361,4 @@ mod tests { ]) ); } - - #[tokio::test] - async fn stage_checkpoint_range() { - // init - let test_tx = TestTransaction::default(); - - // setup - partial_setup(&test_tx); - - // run - { - let mut stage = IndexStorageHistoryStage { commit_threshold: 4 }; // Two runs required - let factory = ProviderFactory::new(&test_tx.tx, MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); - - let mut input = ExecInput { target: Some(5), ..Default::default() }; - let out = stage.execute(&mut provider, input).await.unwrap(); - assert_eq!( - out, - ExecOutput { - checkpoint: StageCheckpoint::new(4).with_index_history_stage_checkpoint( - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: 1, to: 5 }, - progress: EntitiesCheckpoint { processed: 1, total: 2 } - } - ), - done: false - } - ); - input.checkpoint = Some(out.checkpoint); - - let out = stage.execute(&mut provider, input).await.unwrap(); - assert_eq!( - out, - ExecOutput { - checkpoint: StageCheckpoint::new(5).with_index_history_stage_checkpoint( - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: 5, to: 5 }, - progress: EntitiesCheckpoint { processed: 2, total: 2 } - } - ), - done: true - } - ); - - provider.commit().unwrap(); - } - - // verify - let table = cast(test_tx.table::().unwrap()); - assert_eq!(table, BTreeMap::from([(shard(u64::MAX), vec![4, 5])])); - - // unwind - unwind(&test_tx, 5, 0).await; - - // verify initial state - let table = test_tx.table::().unwrap(); - assert!(table.is_empty()); - } - - #[test] - fn stage_checkpoint_recalculation() { - let tx = TestTransaction::default(); - - tx.commit(|tx| { - tx.put::( - BlockNumberAddress((1, H160(hex!("0000000000000000000000000000000000000001")))), - storage(H256(hex!( - "0000000000000000000000000000000000000000000000000000000000000001" - ))), - ) - .unwrap(); - tx.put::( - BlockNumberAddress((1, H160(hex!("0000000000000000000000000000000000000001")))), - storage(H256(hex!( - "0000000000000000000000000000000000000000000000000000000000000002" - ))), - ) - .unwrap(); - tx.put::( - BlockNumberAddress((1, H160(hex!("0000000000000000000000000000000000000002")))), - storage(H256(hex!( - "0000000000000000000000000000000000000000000000000000000000000001" - ))), - ) - .unwrap(); - tx.put::( - BlockNumberAddress((2, H160(hex!("0000000000000000000000000000000000000001")))), - storage(H256(hex!( - "0000000000000000000000000000000000000000000000000000000000000001" - ))), - ) - .unwrap(); - tx.put::( - BlockNumberAddress((2, H160(hex!("0000000000000000000000000000000000000001")))), - storage(H256(hex!( - "0000000000000000000000000000000000000000000000000000000000000002" - ))), - ) - .unwrap(); - tx.put::( - BlockNumberAddress((2, H160(hex!("0000000000000000000000000000000000000002")))), - storage(H256(hex!( - "0000000000000000000000000000000000000000000000000000000000000001" - ))), - ) - .unwrap(); - Ok(()) - }) - .unwrap(); - - let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let provider = factory.provider_rw().unwrap(); - - assert_matches!( - stage_checkpoint(&provider, StageCheckpoint::new(1), &(1..=2)).unwrap(), - IndexHistoryCheckpoint { - block_range: CheckpointBlockRange { from: 1, to: 2 }, - progress: EntitiesCheckpoint { processed: 3, total: 6 } - } - ); - } } From 93f9433bdff5503b4bed6605fd875a78ad634b3e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 15 Jun 2023 18:17:34 +0100 Subject: [PATCH 042/216] feat(bin): display ETA with second precision (#3186) --- bin/reth/src/node/events.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/bin/reth/src/node/events.rs b/bin/reth/src/node/events.rs index 9ab5a326a6d7..f2e6a53a90b0 100644 --- a/bin/reth/src/node/events.rs +++ b/bin/reth/src/node/events.rs @@ -303,10 +303,36 @@ impl std::fmt::Display for Eta { let remaining = eta.checked_sub(last_checkpoint_time.elapsed()); if let Some(remaining) = remaining { - return write!(f, "{}", humantime::format_duration(remaining)) + return write!( + f, + "{}", + humantime::format_duration(Duration::from_secs(remaining.as_secs())) + ) } } write!(f, "unknown") } } + +#[cfg(test)] +mod tests { + use crate::node::events::Eta; + use std::time::{Duration, Instant}; + + #[test] + fn eta_display_no_milliseconds() { + let eta = Eta { + last_checkpoint_time: Some(Instant::now()), + eta: Some(Duration::from_millis( + 13 * 60 * 1000 + // Minutes + 37 * 1000 + // Seconds + 999, // Milliseconds + )), + ..Default::default() + } + .to_string(); + + assert_eq!(eta, "13m 37s"); + } +} From 2588d9282fa756fd6db0d47589fad38b38945614 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 19:53:04 +0200 Subject: [PATCH 043/216] feat: try to canonicalize safe and finalized (#2971) --- .../consensus/beacon/src/engine/forkchoice.rs | 38 +++++++++++ crates/consensus/beacon/src/engine/mod.rs | 63 +++++++++++++------ 2 files changed, 82 insertions(+), 19 deletions(-) diff --git a/crates/consensus/beacon/src/engine/forkchoice.rs b/crates/consensus/beacon/src/engine/forkchoice.rs index fb29471e3b51..8332ea030841 100644 --- a/crates/consensus/beacon/src/engine/forkchoice.rs +++ b/crates/consensus/beacon/src/engine/forkchoice.rs @@ -118,3 +118,41 @@ impl From for ForkchoiceStatus { ForkchoiceStatus::from_payload_status(&status) } } + +/// A helper type to check represent hashes of a [ForkchoiceState] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum ForkchoiceStateHash { + Head(H256), + Safe(H256), + Finalized(H256), +} + +impl ForkchoiceStateHash { + /// Tries to find a matching hash in the given [ForkchoiceState]. + pub(crate) fn find(state: &ForkchoiceState, hash: H256) -> Option { + if state.head_block_hash == hash { + Some(ForkchoiceStateHash::Head(hash)) + } else if state.safe_block_hash == hash { + Some(ForkchoiceStateHash::Safe(hash)) + } else if state.finalized_block_hash == hash { + Some(ForkchoiceStateHash::Finalized(hash)) + } else { + None + } + } + + /// Returns true if this is the head hash of the [ForkchoiceState] + pub(crate) fn is_head(&self) -> bool { + matches!(self, ForkchoiceStateHash::Head(_)) + } +} + +impl AsRef for ForkchoiceStateHash { + fn as_ref(&self) -> &H256 { + match self { + ForkchoiceStateHash::Head(h) => h, + ForkchoiceStateHash::Safe(h) => h, + ForkchoiceStateHash::Finalized(h) => h, + } + } +} diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index fcaca8dce089..5bb7f5ef2f85 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -17,8 +17,8 @@ use reth_interfaces::{ }; use reth_payload_builder::{PayloadBuilderAttributes, PayloadBuilderHandle}; use reth_primitives::{ - listener::EventListeners, stage::StageId, BlockNumber, Head, Header, SealedBlock, SealedHeader, - H256, U256, + listener::EventListeners, stage::StageId, BlockNumHash, BlockNumber, Head, Header, SealedBlock, + SealedHeader, H256, U256, }; use reth_provider::{ BlockProvider, BlockSource, CanonChainTracker, ProviderError, StageCheckpointProvider, @@ -58,7 +58,7 @@ mod event; mod forkchoice; pub(crate) mod sync; -use crate::engine::forkchoice::ForkchoiceStateTracker; +use crate::engine::forkchoice::{ForkchoiceStateHash, ForkchoiceStateTracker}; pub use event::BeaconConsensusEngineEvent; /// The maximum number of invalid headers that can be tracked by the engine. @@ -1007,6 +1007,7 @@ where /// chain is invalid, which means the FCU that triggered the download is invalid. Here we can /// stop because there's nothing to do here and the engine needs to wait for another FCU. fn on_downloaded_block(&mut self, block: SealedBlock) { + let num_hash = block.num_hash(); trace!(target: "consensus::engine", hash=?block.hash, number=%block.number, "Downloaded full block"); // check if the block's parent is already marked as invalid if self.check_invalid_ancestor_with_head(block.parent_hash, block.hash).is_some() { @@ -1019,11 +1020,11 @@ where match status { BlockStatus::Valid => { // block is connected to the current canonical head and is valid. - self.try_make_sync_target_canonical(); + self.try_make_sync_target_canonical(num_hash); } BlockStatus::Accepted => { // block is connected to the canonical chain, but not the current head - self.try_make_sync_target_canonical(); + self.try_make_sync_target_canonical(num_hash); } BlockStatus::Disconnected { missing_parent } => { // continue downloading the missing parent @@ -1049,21 +1050,45 @@ where /// Note: This will not succeed if the sync target has changed since the block download request /// was issued and the new target is still disconnected and additional missing blocks are /// downloaded - fn try_make_sync_target_canonical(&mut self) { + fn try_make_sync_target_canonical(&mut self, inserted: BlockNumHash) { if let Some(target) = self.forkchoice_state_tracker.sync_target_state() { - // optimistically try to make the chain canonical, the sync target might have changed - // since the block download request was issued (new FCU received) - if let Ok(outcome) = self.blockchain.make_canonical(&target.head_block_hash) { - let new_head = outcome.into_header(); - debug!(target: "consensus::engine", hash=?new_head.hash, number=new_head.number, "canonicalized new head"); - - // we're no longer syncing - self.sync_state_updater.update_sync_state(SyncState::Idle); - // clear any active block requests - self.sync.clear_full_block_requests(); - - // we can update the FCU blocks - let _ = self.update_canon_chain(&target); + // optimistically try to make the head of the current FCU target canonical, the sync + // target might have changed since the block download request was issued + // (new FCU received) + match self.blockchain.make_canonical(&target.head_block_hash) { + Ok(outcome) => { + let new_head = outcome.into_header(); + debug!(target: "consensus::engine", hash=?new_head.hash, number=new_head.number, "canonicalized new head"); + + // we're no longer syncing + self.sync_state_updater.update_sync_state(SyncState::Idle); + // clear any active block requests + self.sync.clear_full_block_requests(); + + // we can update the FCU blocks + let _ = self.update_canon_chain(&target); + } + Err(err) => { + // if we failed to make the FCU's head canonical, because we don't have that + // block yet, then we can try to make the inserted block canonical if we know + // it's part of the canonical chain: if it's the safe or the finalized block + if matches!( + err, + reth_interfaces::Error::Execution( + BlockExecutionError::BlockHashNotFoundInChain { .. } + ) + ) { + // if the inserted block is the currently targeted `finalized` or `safe` + // block, we will attempt to make them canonical, + // because they are also part of the canonical chain and + // their missing block range might already be downloaded (buffered). + if let Some(target_hash) = ForkchoiceStateHash::find(&target, inserted.hash) + .filter(|h| !h.is_head()) + { + let _ = self.blockchain.make_canonical(target_hash.as_ref()); + } + } + } } } } From 0d691fee1697d18155cd23ccde0f1949ee90a289 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 19:53:22 +0200 Subject: [PATCH 044/216] fix: mark state as synced when transition to live sync (#3187) --- crates/consensus/beacon/src/engine/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 5bb7f5ef2f85..43175b86f89c 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1193,7 +1193,10 @@ where sync_target_state.finalized_block_hash, ) { Ok(synced) => { - if !synced { + if synced { + // we're consider this synced and transition to live sync + self.sync_state_updater.update_sync_state(SyncState::Idle); + } else { // We don't have the finalized block in the database, so // we need to run another pipeline. self.sync.set_pipeline_sync_target( From 0b876de27ecde5bf3dd6cf60c1631b2622714db6 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Thu, 15 Jun 2023 20:48:10 +0200 Subject: [PATCH 045/216] docs: add some content to dev section of book (#3183) --- book/SUMMARY.md | 3 +-- book/developers/architecture.md | 1 - book/developers/contribute.md | 8 +++++++- book/developers/developers.md | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) delete mode 100644 book/developers/architecture.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index cabe3f097f1a..27cbb66eef2c 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -25,5 +25,4 @@ 1. [reth p2p](./cli/p2p.md) 1. [reth stage](./cli/stage.md) 1. [Developers](./developers/developers.md) - 1. [Contribute](./developers/contribute.md) - 1. [Architecture](./developers/architecture.md) \ No newline at end of file + 1. [Contribute](./developers/contribute.md) \ No newline at end of file diff --git a/book/developers/architecture.md b/book/developers/architecture.md deleted file mode 100644 index c79bec1ac69a..000000000000 --- a/book/developers/architecture.md +++ /dev/null @@ -1 +0,0 @@ -# Architecture diff --git a/book/developers/contribute.md b/book/developers/contribute.md index a6fbbc1354a4..74f00e69a1a3 100644 --- a/book/developers/contribute.md +++ b/book/developers/contribute.md @@ -1,3 +1,9 @@ # Contribute - \ No newline at end of file + + +Reth has docs specifically geared for developers and contributors, including documentation on the structure and architecture of reth, the general workflow we employ, and other useful tips. + +You can find these docs [here](https://github.com/paradigmxyz/reth/tree/main/docs). + +Check out our contributing guidelines [here](https://github.com/paradigmxyz/reth/blob/main/CONTRIBUTING.md). diff --git a/book/developers/developers.md b/book/developers/developers.md index 73437b84b837..e5bf7cde90de 100644 --- a/book/developers/developers.md +++ b/book/developers/developers.md @@ -1 +1,3 @@ # Developers + +Reth is composed of several crates that can be used in standalone projects. If you are interested in using one or more of the crates, you can get an overview of them in the [developer docs](https://github.com/paradigmxyz/reth/tree/main/docs), or take a look at the [crate docs](https://paradigmxyz.github.io/reth/docs). \ No newline at end of file From 67593ca74924aceff946b5eccfccdf2edbda8ec9 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 15 Jun 2023 20:36:51 +0200 Subject: [PATCH 046/216] feat(metrics): split `DownloaderMetrics` into body and headers downloaders (#3171) --- crates/net/downloaders/src/bodies/bodies.rs | 6 +- crates/net/downloaders/src/bodies/queue.rs | 6 +- crates/net/downloaders/src/bodies/request.rs | 12 +-- .../src/headers/reverse_headers.rs | 9 +- crates/net/downloaders/src/metrics.rs | 86 +++++++++++++++---- 5 files changed, 84 insertions(+), 35 deletions(-) diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index 808ad130cd9b..91d0fb8e7c5f 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -1,5 +1,5 @@ use super::queue::BodiesRequestQueue; -use crate::{bodies::task::TaskDownloader, metrics::DownloaderMetrics}; +use crate::{bodies::task::TaskDownloader, metrics::BodyDownloaderMetrics}; use futures::Stream; use futures_util::StreamExt; use reth_db::{cursor::DbCursorRO, database::Database, tables, transaction::DbTx}; @@ -63,7 +63,7 @@ pub struct BodiesDownloader { /// Queued body responses that can be returned for insertion into the database. queued_bodies: Vec, /// The bodies downloader metrics. - metrics: DownloaderMetrics, + metrics: BodyDownloaderMetrics, } impl BodiesDownloader @@ -559,7 +559,7 @@ impl BodiesDownloaderBuilder { concurrent_requests_range, max_buffered_blocks: max_buffered_responses, } = self; - let metrics = DownloaderMetrics::new(BODIES_DOWNLOADER_SCOPE); + let metrics = BodyDownloaderMetrics::default(); let in_progress_queue = BodiesRequestQueue::new(metrics.clone()); BodiesDownloader { client: Arc::new(client), diff --git a/crates/net/downloaders/src/bodies/queue.rs b/crates/net/downloaders/src/bodies/queue.rs index 2483eb25fe77..0fc9635df3fd 100644 --- a/crates/net/downloaders/src/bodies/queue.rs +++ b/crates/net/downloaders/src/bodies/queue.rs @@ -1,5 +1,5 @@ use super::request::BodiesRequestFuture; -use crate::metrics::DownloaderMetrics; +use crate::metrics::BodyDownloaderMetrics; use futures::{stream::FuturesUnordered, Stream}; use futures_util::StreamExt; use reth_interfaces::{ @@ -23,7 +23,7 @@ pub(crate) struct BodiesRequestQueue { /// Inner body request queue. inner: FuturesUnordered>, /// The downloader metrics. - metrics: DownloaderMetrics, + metrics: BodyDownloaderMetrics, /// Last requested block number. pub(crate) last_requested_block_number: Option, } @@ -33,7 +33,7 @@ where B: BodiesClient + 'static, { /// Create new instance of request queue. - pub(crate) fn new(metrics: DownloaderMetrics) -> Self { + pub(crate) fn new(metrics: BodyDownloaderMetrics) -> Self { Self { metrics, inner: Default::default(), last_requested_block_number: None } } diff --git a/crates/net/downloaders/src/bodies/request.rs b/crates/net/downloaders/src/bodies/request.rs index e4f883bbce46..e73836a4372b 100644 --- a/crates/net/downloaders/src/bodies/request.rs +++ b/crates/net/downloaders/src/bodies/request.rs @@ -1,4 +1,4 @@ -use crate::metrics::DownloaderMetrics; +use crate::metrics::BodyDownloaderMetrics; use futures::{Future, FutureExt}; use reth_interfaces::{ consensus::{Consensus as ConsensusTrait, Consensus}, @@ -38,7 +38,7 @@ use std::{ pub(crate) struct BodiesRequestFuture { client: Arc, consensus: Arc, - metrics: DownloaderMetrics, + metrics: BodyDownloaderMetrics, // Headers to download. The collection is shrunk as responses are buffered. pending_headers: VecDeque, /// Internal buffer for all blocks @@ -56,7 +56,7 @@ where pub(crate) fn new( client: Arc, consensus: Arc, - metrics: DownloaderMetrics, + metrics: BodyDownloaderMetrics, ) -> Self { Self { client, @@ -235,7 +235,7 @@ mod tests { use super::*; use crate::{ bodies::test_utils::zip_blocks, - test_utils::{generate_bodies, TestBodiesClient, TEST_SCOPE}, + test_utils::{generate_bodies, TestBodiesClient}, }; use reth_interfaces::{ p2p::bodies::response::BlockResponse, @@ -253,7 +253,7 @@ mod tests { let fut = BodiesRequestFuture::new( client.clone(), Arc::new(TestConsensus::default()), - DownloaderMetrics::new(TEST_SCOPE), + BodyDownloaderMetrics::default(), ) .with_headers(headers.clone()); @@ -277,7 +277,7 @@ mod tests { let fut = BodiesRequestFuture::new( client.clone(), Arc::new(TestConsensus::default()), - DownloaderMetrics::new(TEST_SCOPE), + BodyDownloaderMetrics::default(), ) .with_headers(headers.clone()); diff --git a/crates/net/downloaders/src/headers/reverse_headers.rs b/crates/net/downloaders/src/headers/reverse_headers.rs index 6ed78ccc607e..9a68c977179e 100644 --- a/crates/net/downloaders/src/headers/reverse_headers.rs +++ b/crates/net/downloaders/src/headers/reverse_headers.rs @@ -1,7 +1,7 @@ //! A headers downloader that can handle multiple requests concurrently. use super::task::TaskDownloader; -use crate::metrics::DownloaderMetrics; +use crate::metrics::HeaderDownloaderMetrics; use futures::{stream::Stream, FutureExt}; use futures_util::{stream::FuturesUnordered, StreamExt}; use rayon::prelude::*; @@ -37,9 +37,6 @@ use tracing::{error, trace}; /// downloader is yielding a next batch of headers that is being committed to the database. const REQUESTS_PER_PEER_MULTIPLIER: usize = 5; -/// The scope for headers downloader metrics. -pub const HEADERS_DOWNLOADER_SCOPE: &str = "downloaders.headers"; - /// Wrapper for internal downloader errors. #[allow(clippy::large_enum_variant)] #[derive(Error, Debug)] @@ -100,7 +97,7 @@ pub struct ReverseHeadersDownloader { /// Note: headers are sorted from high to low queued_validated_headers: Vec, /// Header downloader metrics. - metrics: DownloaderMetrics, + metrics: HeaderDownloaderMetrics, } // === impl ReverseHeadersDownloader === @@ -1163,7 +1160,7 @@ impl ReverseHeadersDownloaderBuilder { in_progress_queue: Default::default(), buffered_responses: Default::default(), queued_validated_headers: Default::default(), - metrics: DownloaderMetrics::new(HEADERS_DOWNLOADER_SCOPE), + metrics: Default::default(), } } } diff --git a/crates/net/downloaders/src/metrics.rs b/crates/net/downloaders/src/metrics.rs index 59476ca0c427..38fc642eb318 100644 --- a/crates/net/downloaders/src/metrics.rs +++ b/crates/net/downloaders/src/metrics.rs @@ -4,22 +4,79 @@ use reth_metrics::{ Metrics, }; -/// Common downloader metrics. +/// Common body downloader metrics. /// -/// These metrics will be dynamically initialized with the provided scope -/// by corresponding downloaders. +/// These metrics will be initialized with the `downloaders.bodies` scope. /// ``` -/// use reth_downloaders::metrics::DownloaderMetrics; +/// use reth_downloaders::metrics::BodyDownloaderMetrics; /// use reth_interfaces::p2p::error::DownloadError; /// /// // Initialize metrics. -/// let metrics = DownloaderMetrics::new("downloaders.headers"); +/// let metrics = BodyDownloaderMetrics::default(); +/// // Increment `downloaders.bodies.timeout_errors` counter by 1. +/// metrics.increment_errors(&DownloadError::Timeout); +/// ``` +#[derive(Clone, Metrics)] +#[metrics(scope = "downloaders.bodies")] +pub struct BodyDownloaderMetrics { + /// The number of items that were successfully sent to the poller (stage) + pub total_flushed: Counter, + /// Number of items that were successfully downloaded + pub total_downloaded: Counter, + /// The number of requests (can contain more than 1 item) currently in-flight. + pub in_flight_requests: Gauge, + /// The number of responses (can contain more than 1 item) in the internal buffer of the + /// downloader. + pub buffered_responses: Gauge, + /// The number of blocks the internal buffer of the + /// downloader. + /// These are bodies that have been received, but not cannot be committed yet because they're + /// not contiguous + pub buffered_blocks: Gauge, + /// Total amount of memory used by the buffered blocks in bytes + pub buffered_blocks_size_bytes: Gauge, + /// The number blocks that are contiguous and are queued for insertion into the db. + pub queued_blocks: Gauge, + /// The number of out-of-order requests sent by the downloader. + /// The consumer of the download stream is able to re-request data (bodies) in case + /// it encountered a recoverable error (e.g. during insertion). + /// Out-of-order request happen when the new download range start for bodies downloader + /// is less than the last block number returned from the stream. + pub out_of_order_requests: Counter, + /// Number of timeout errors while requesting items + pub timeout_errors: Counter, + /// Number of validation errors while requesting items + pub validation_errors: Counter, + /// Number of unexpected errors while requesting items + pub unexpected_errors: Counter, +} + +impl BodyDownloaderMetrics { + /// Increment errors counter. + pub fn increment_errors(&self, error: &DownloadError) { + match error { + DownloadError::Timeout => self.timeout_errors.increment(1), + DownloadError::BodyValidation { .. } => self.validation_errors.increment(1), + _error => self.unexpected_errors.increment(1), + } + } +} + +/// Common header downloader metrics. +/// +/// These metrics will be initialized with the `downloaders.headers` scope. +/// ``` +/// use reth_downloaders::metrics::HeaderDownloaderMetrics; +/// use reth_interfaces::p2p::error::DownloadError; +/// +/// // Initialize metrics. +/// let metrics = HeaderDownloaderMetrics::default(); /// // Increment `downloaders.headers.timeout_errors` counter by 1. /// metrics.increment_errors(&DownloadError::Timeout); /// ``` #[derive(Clone, Metrics)] -#[metrics(dynamic = true)] -pub struct DownloaderMetrics { +#[metrics(scope = "downloaders.headers")] +pub struct HeaderDownloaderMetrics { /// The number of items that were successfully sent to the poller (stage) pub total_flushed: Counter, /// Number of items that were successfully downloaded @@ -39,13 +96,10 @@ pub struct DownloaderMetrics { /// The number blocks that are contiguous and are queued for insertion into the db. pub queued_blocks: Gauge, /// The number of out-of-order requests sent by the downloader. - /// The consumer of the download stream is able to re-request data (headers or bodies) in case + /// The consumer of the download stream is able to re-request data (headers) in case /// it encountered a recoverable error (e.g. during insertion). - /// Out-of-order request happen when: - /// - the headers downloader `SyncTarget::Tip` hash is different from the previous sync - /// target hash. - /// - the new download range start for bodies donwloader is less than the last block number - /// returned from the stream. + /// Out-of-order request happen when the headers downloader `SyncTarget::Tip` + /// hash is different from the previous sync target hash. pub out_of_order_requests: Counter, /// Number of timeout errors while requesting items pub timeout_errors: Counter, @@ -55,14 +109,12 @@ pub struct DownloaderMetrics { pub unexpected_errors: Counter, } -impl DownloaderMetrics { +impl HeaderDownloaderMetrics { /// Increment errors counter. pub fn increment_errors(&self, error: &DownloadError) { match error { DownloadError::Timeout => self.timeout_errors.increment(1), - DownloadError::HeaderValidation { .. } | DownloadError::BodyValidation { .. } => { - self.validation_errors.increment(1) - } + DownloadError::HeaderValidation { .. } => self.validation_errors.increment(1), _error => self.unexpected_errors.increment(1), } } From ab2a38a549f7bc8914764f1947e48e872394e5eb Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 15 Jun 2023 19:46:50 +0100 Subject: [PATCH 047/216] chore: replace `ExecutionStage::read_block_with_senders` with `BlockProvider::block_with_senders` (#3168) --- crates/interfaces/src/provider.rs | 3 + crates/stages/src/stages/execution.rs | 64 +++-------------- .../provider/src/providers/database/mod.rs | 25 +++++-- .../src/providers/database/provider.rs | 71 +++++++++++++++++-- crates/storage/provider/src/providers/mod.rs | 32 +++++++-- .../storage/provider/src/test_utils/mock.rs | 33 ++++++++- .../storage/provider/src/test_utils/noop.rs | 32 +++++++++ crates/storage/provider/src/traits/block.rs | 17 ++++- .../provider/src/traits/transactions.rs | 12 +++- .../provider/src/traits/withdrawals.rs | 1 + 10 files changed, 215 insertions(+), 75 deletions(-) diff --git a/crates/interfaces/src/provider.rs b/crates/interfaces/src/provider.rs index c8ca9f1d86d7..6ef7517d002f 100644 --- a/crates/interfaces/src/provider.rs +++ b/crates/interfaces/src/provider.rs @@ -35,6 +35,9 @@ pub enum ProviderError { /// Thrown when required header related data was not found but was required. #[error("No header found for {0:?}")] HeaderNotFound(BlockHashOrNumber), + /// Thrown we were unable to find a specific block + #[error("Block does not exist {0:?}")] + BlockNotFound(BlockHashOrNumber), /// Thrown we were unable to find the best block #[error("Best block does not exist")] BestBlockNotFound, diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 1759da6997a0..e57c944aea92 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -16,11 +16,11 @@ use reth_primitives::{ stage::{ CheckpointBlockRange, EntitiesCheckpoint, ExecutionCheckpoint, StageCheckpoint, StageId, }, - Block, BlockNumber, BlockWithSenders, Header, TransactionSigned, U256, + BlockNumber, Header, U256, }; use reth_provider::{ post_state::PostState, BlockExecutor, BlockProvider, DatabaseProviderRW, ExecutorFactory, - HeaderProvider, LatestStateProviderRef, ProviderError, WithdrawalsProvider, + HeaderProvider, LatestStateProviderRef, ProviderError, }; use std::{ops::RangeInclusive, time::Instant}; use tracing::*; @@ -84,59 +84,6 @@ impl ExecutionStage { Self::new(executor_factory, ExecutionStageThresholds::default()) } - // TODO(joshie): This should be in the block provider trait once we consolidate - fn read_block_with_senders( - provider: &DatabaseProviderRW<'_, &DB>, - block_number: BlockNumber, - ) -> Result<(BlockWithSenders, U256), StageError> { - let header = provider - .header_by_number(block_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; - let td = provider - .header_td_by_number(block_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; - let ommers = provider.ommers(block_number.into())?.unwrap_or_default(); - let withdrawals = provider.withdrawals_by_block(block_number.into(), header.timestamp)?; - - // Get the block body - let body = provider.block_body_indices(block_number)?; - let tx_range = body.tx_num_range(); - - // Get the transactions in the body - let tx = provider.tx_ref(); - let (transactions, senders) = if tx_range.is_empty() { - (Vec::new(), Vec::new()) - } else { - let transactions = tx - .cursor_read::()? - .walk_range(tx_range.clone())? - .map(|entry| entry.map(|tx| tx.1)) - .collect::, _>>()?; - - let senders = tx - .cursor_read::()? - .walk_range(tx_range)? - .map(|entry| entry.map(|sender| sender.1)) - .collect::, _>>()?; - - (transactions, senders) - }; - - let body = transactions - .into_iter() - .map(|tx| { - TransactionSigned { - // TODO: This is the fastest way right now to make everything just work with - // a dummy transaction hash. - hash: Default::default(), - signature: tx.signature, - transaction: tx.transaction, - } - }) - .collect(); - Ok((Block { header, body, ommers, withdrawals }.with_senders(senders), td)) - } - /// Execute the stage. pub fn execute_inner( &self, @@ -162,7 +109,12 @@ impl ExecutionStage { // Execute block range let mut state = PostState::default(); for block_number in start_block..=max_block { - let (block, td) = Self::read_block_with_senders(provider, block_number)?; + let td = provider + .header_td_by_number(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + let block = provider + .block_with_senders(block_number)? + .ok_or_else(|| ProviderError::BlockNotFound(block_number.into()))?; // Configure the executor to use the current state. trace!(target: "sync::stages::execution", number = block_number, txs = block.body.len(), "Executing block"); diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 30aa60298634..c4370e79111a 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -9,9 +9,9 @@ use reth_db::{database::Database, models::StoredBlockBodyIndices, tables, transa use reth_interfaces::Result; use reth_primitives::{ stage::{StageCheckpoint, StageId}, - Block, BlockHash, BlockHashOrNumber, BlockNumber, ChainInfo, ChainSpec, Header, Receipt, - SealedBlock, SealedHeader, TransactionMeta, TransactionSigned, TxHash, TxNumber, Withdrawal, - H256, U256, + Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, ChainInfo, + ChainSpec, Header, Receipt, SealedBlock, SealedHeader, TransactionMeta, TransactionSigned, + TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, H256, U256, }; use reth_revm_primitives::primitives::{BlockEnv, CfgEnv}; use std::{ops::RangeBounds, sync::Arc}; @@ -189,8 +189,12 @@ impl BlockProvider for ProviderFactory { self.provider()?.ommers(id) } - fn block_body_indices(&self, num: u64) -> Result> { - self.provider()?.block_body_indices(num) + fn block_body_indices(&self, number: BlockNumber) -> Result> { + self.provider()?.block_body_indices(number) + } + + fn block_with_senders(&self, number: BlockNumber) -> Result> { + self.provider()?.block_with_senders(number) } } @@ -231,6 +235,17 @@ impl TransactionsProvider for ProviderFactory { ) -> Result>> { self.provider()?.transactions_by_block_range(range) } + + fn transactions_by_tx_range( + &self, + range: impl RangeBounds, + ) -> Result> { + self.provider()?.transactions_by_tx_range(range) + } + + fn senders_by_tx_range(&self, range: impl RangeBounds) -> Result> { + self.provider()?.senders_by_tx_range(range) + } } impl ReceiptProvider for ProviderFactory { diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 77de5ac9f646..dec82dd68bfc 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -25,10 +25,10 @@ use reth_interfaces::Result; use reth_primitives::{ keccak256, stage::{StageCheckpoint, StageId}, - Account, Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, ChainInfo, ChainSpec, - Hardfork, Head, Header, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, - StorageEntry, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, TxHash, - TxNumber, Withdrawal, H256, U256, + Account, Address, Block, BlockHash, BlockHashOrNumber, BlockNumber, BlockWithSenders, + ChainInfo, ChainSpec, Hardfork, Head, Header, Receipt, SealedBlock, SealedBlockWithSenders, + SealedHeader, StorageEntry, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, + TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, H256, U256, }; use reth_revm_primitives::{ config::revm_spec, @@ -1609,6 +1609,48 @@ impl<'this, TX: DbTx<'this>> BlockProvider for DatabaseProvider<'this, TX> { fn block_body_indices(&self, num: u64) -> Result> { Ok(self.tx.get::(num)?) } + + /// Returns the block with senders with matching number from database. + /// + /// **NOTE: The transactions have invalid hashes, since they would need to be calculated on the + /// spot, and we want fast querying.** + /// + /// Returns `None` if block is not found. + fn block_with_senders(&self, block_number: BlockNumber) -> Result> { + let header = self + .header_by_number(block_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; + + let ommers = self.ommers(block_number.into())?.unwrap_or_default(); + let withdrawals = self.withdrawals_by_block(block_number.into(), header.timestamp)?; + + // Get the block body + let body = self + .block_body_indices(block_number)? + .ok_or(ProviderError::BlockBodyIndicesNotFound(block_number))?; + let tx_range = body.tx_num_range(); + + let (transactions, senders) = if tx_range.is_empty() { + (vec![], vec![]) + } else { + (self.transactions_by_tx_range(tx_range.clone())?, self.senders_by_tx_range(tx_range)?) + }; + + let body = transactions + .into_iter() + .map(|tx| { + TransactionSigned { + // TODO: This is the fastest way right now to make everything just work with + // a dummy transaction hash. + hash: Default::default(), + signature: tx.signature, + transaction: tx.transaction, + } + }) + .collect(); + + Ok(Some(Block { header, body, ommers, withdrawals }.with_senders(senders))) + } } impl<'this, TX: DbTx<'this>> TransactionsProvider for DatabaseProvider<'this, TX> { @@ -1716,6 +1758,27 @@ impl<'this, TX: DbTx<'this>> TransactionsProvider for DatabaseProvider<'this, TX } Ok(results) } + + fn transactions_by_tx_range( + &self, + range: impl RangeBounds, + ) -> Result> { + Ok(self + .tx + .cursor_read::()? + .walk_range(range)? + .map(|entry| entry.map(|tx| tx.1)) + .collect::, _>>()?) + } + + fn senders_by_tx_range(&self, range: impl RangeBounds) -> Result> { + Ok(self + .tx + .cursor_read::()? + .walk_range(range)? + .map(|entry| entry.map(|sender| sender.1)) + .collect::, _>>()?) + } } impl<'this, TX: DbTx<'this>> ReceiptProvider for DatabaseProvider<'this, TX> { diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 7bb70825e431..9594f817363d 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -13,9 +13,10 @@ use reth_interfaces::{ }; use reth_primitives::{ stage::{StageCheckpoint, StageId}, - Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumHash, BlockNumber, BlockNumberOrTag, - ChainInfo, Header, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, TransactionMeta, - TransactionSigned, TxHash, TxNumber, Withdrawal, H256, U256, + Address, Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumHash, BlockNumber, + BlockNumberOrTag, BlockWithSenders, ChainInfo, Header, Receipt, SealedBlock, + SealedBlockWithSenders, SealedHeader, TransactionMeta, TransactionSigned, + TransactionSignedNoHash, TxHash, TxNumber, Withdrawal, H256, U256, }; use reth_revm_primitives::primitives::{BlockEnv, CfgEnv}; pub use state::{ @@ -234,8 +235,18 @@ where self.database.provider()?.ommers(id) } - fn block_body_indices(&self, num: u64) -> Result> { - self.database.provider()?.block_body_indices(num) + fn block_body_indices(&self, number: BlockNumber) -> Result> { + self.database.provider()?.block_body_indices(number) + } + + /// Returns the block with senders with matching number from database. + /// + /// **NOTE: The transactions have invalid hashes, since they would need to be calculated on the + /// spot, and we want fast querying.** + /// + /// Returns `None` if block is not found. + fn block_with_senders(&self, number: BlockNumber) -> Result> { + self.database.provider()?.block_with_senders(number) } } @@ -280,6 +291,17 @@ where ) -> Result>> { self.database.provider()?.transactions_by_block_range(range) } + + fn transactions_by_tx_range( + &self, + range: impl RangeBounds, + ) -> Result> { + self.database.provider()?.transactions_by_tx_range(range) + } + + fn senders_by_tx_range(&self, range: impl RangeBounds) -> Result> { + self.database.provider()?.senders_by_tx_range(range) + } } impl ReceiptProvider for BlockchainProvider diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index a94e26a4f180..9c029cd11217 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -3,14 +3,15 @@ use crate::{ AccountProvider, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, PostStateDataProvider, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, + WithdrawalsProvider, }; use parking_lot::Mutex; use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::{provider::ProviderError, Result}; use reth_primitives::{ keccak256, Account, Address, Block, BlockHash, BlockHashOrNumber, BlockId, BlockNumber, - Bytecode, Bytes, ChainInfo, Header, Receipt, SealedBlock, SealedHeader, StorageKey, - StorageValue, TransactionMeta, TransactionSigned, TxHash, TxNumber, H256, U256, + BlockWithSenders, Bytecode, Bytes, ChainInfo, Header, Receipt, SealedBlock, SealedHeader, + StorageKey, StorageValue, TransactionMeta, TransactionSigned, TxHash, TxNumber, H256, U256, }; use reth_revm_primitives::primitives::{BlockEnv, CfgEnv}; use std::{ @@ -207,6 +208,17 @@ impl TransactionsProvider for MockEthProvider { Ok(map.into_values().collect()) } + + fn senders_by_tx_range(&self, _range: impl RangeBounds) -> Result> { + unimplemented!() + } + + fn transactions_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result> { + unimplemented!() + } } impl ReceiptProvider for MockEthProvider { @@ -313,6 +325,10 @@ impl BlockProvider for MockEthProvider { fn block_body_indices(&self, _num: u64) -> Result> { Ok(None) } + + fn block_with_senders(&self, _number: BlockNumber) -> Result> { + Ok(None) + } } impl BlockProviderIdExt for MockEthProvider { @@ -478,3 +494,16 @@ impl StateProviderFactory for Arc { todo!() } } + +impl WithdrawalsProvider for MockEthProvider { + fn latest_withdrawal(&self) -> Result> { + unimplemented!() + } + fn withdrawals_by_block( + &self, + _id: BlockHashOrNumber, + _timestamp: u64, + ) -> Result>> { + unimplemented!() + } +} diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 24310c620adc..97d6933b2df6 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -3,6 +3,7 @@ use crate::{ AccountProvider, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, StageCheckpointProvider, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, + WithdrawalsProvider, }; use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::Result; @@ -69,6 +70,13 @@ impl BlockProvider for NoopProvider { fn block_body_indices(&self, _num: u64) -> Result> { Ok(None) } + + fn block_with_senders( + &self, + _number: BlockNumber, + ) -> Result> { + Ok(None) + } } impl BlockProviderIdExt for NoopProvider { @@ -140,6 +148,17 @@ impl TransactionsProvider for NoopProvider { ) -> Result>> { Ok(Vec::default()) } + + fn senders_by_tx_range(&self, _range: impl RangeBounds) -> Result> { + Ok(Vec::default()) + } + + fn transactions_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result> { + Ok(Vec::default()) + } } impl ReceiptProvider for NoopProvider { @@ -293,3 +312,16 @@ impl StageCheckpointProvider for NoopProvider { Ok(None) } } + +impl WithdrawalsProvider for NoopProvider { + fn latest_withdrawal(&self) -> Result> { + Ok(None) + } + fn withdrawals_by_block( + &self, + _id: BlockHashOrNumber, + _timestamp: u64, + ) -> Result>> { + Ok(None) + } +} diff --git a/crates/storage/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index 52c4de5d368e..f2ffe8767585 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -1,10 +1,12 @@ use crate::{ BlockIdProvider, BlockNumProvider, HeaderProvider, ReceiptProvider, TransactionsProvider, + WithdrawalsProvider, }; use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::Result; use reth_primitives::{ - Block, BlockHashOrNumber, BlockId, BlockNumberOrTag, Header, SealedBlock, SealedHeader, H256, + Block, BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, BlockWithSenders, Header, + SealedBlock, SealedHeader, H256, }; /// A helper enum that represents the origin of the requested block. @@ -44,7 +46,13 @@ impl BlockSource { /// the database. #[auto_impl::auto_impl(&, Arc)] pub trait BlockProvider: - BlockNumProvider + HeaderProvider + TransactionsProvider + ReceiptProvider + Send + Sync + BlockNumProvider + + HeaderProvider + + TransactionsProvider + + ReceiptProvider + + WithdrawalsProvider + + Send + + Sync { /// Tries to find in the given block source. /// @@ -87,6 +95,11 @@ pub trait BlockProvider: /// /// Returns `None` if block is not found. fn block_body_indices(&self, num: u64) -> Result>; + + /// Returns the block with senders with matching number from database. + /// + /// Returns `None` if block is not found. + fn block_with_senders(&self, number: BlockNumber) -> Result>; } /// Trait extension for `BlockProvider`, for types that implement `BlockId` conversion. diff --git a/crates/storage/provider/src/traits/transactions.rs b/crates/storage/provider/src/traits/transactions.rs index f80bf9db5bff..ca5e15a88c00 100644 --- a/crates/storage/provider/src/traits/transactions.rs +++ b/crates/storage/provider/src/traits/transactions.rs @@ -1,7 +1,8 @@ use crate::BlockNumProvider; use reth_interfaces::Result; use reth_primitives::{ - BlockHashOrNumber, BlockNumber, TransactionMeta, TransactionSigned, TxHash, TxNumber, + Address, BlockHashOrNumber, BlockNumber, TransactionMeta, TransactionSigned, + TransactionSignedNoHash, TxHash, TxNumber, }; use std::ops::RangeBounds; @@ -41,4 +42,13 @@ pub trait TransactionsProvider: BlockNumProvider + Send + Sync { &self, range: impl RangeBounds, ) -> Result>>; + + /// Get transactions by tx range. + fn transactions_by_tx_range( + &self, + range: impl RangeBounds, + ) -> Result>; + + /// Get Senders from a tx range. + fn senders_by_tx_range(&self, range: impl RangeBounds) -> Result>; } diff --git a/crates/storage/provider/src/traits/withdrawals.rs b/crates/storage/provider/src/traits/withdrawals.rs index ffa33febab40..d5d65808f521 100644 --- a/crates/storage/provider/src/traits/withdrawals.rs +++ b/crates/storage/provider/src/traits/withdrawals.rs @@ -2,6 +2,7 @@ use reth_interfaces::Result; use reth_primitives::{BlockHashOrNumber, Withdrawal}; /// Client trait for fetching [Withdrawal] related data. +#[auto_impl::auto_impl(&, Arc)] pub trait WithdrawalsProvider: Send + Sync { /// Get withdrawals by block id. fn withdrawals_by_block( From 691d0f91a134026afcea969f7473dfa86fa354d5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 20:51:24 +0200 Subject: [PATCH 048/216] fix: poll sync controller once if pipeline sync is pending (#3179) --- crates/consensus/beacon/src/engine/mod.rs | 56 +++++++++++++++------- crates/consensus/beacon/src/engine/sync.rs | 5 ++ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 43175b86f89c..ee784a0500c4 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1244,29 +1244,53 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - // Process all incoming messages first. - while let Poll::Ready(Some(msg)) = this.engine_message_rx.poll_next_unpin(cx) { - match msg { - BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => { - if this.on_forkchoice_updated(state, payload_attrs, tx) { - return Poll::Ready(Ok(())) + // Process all incoming messages from the CL, these can affect the state of the + // SyncController, hence they are polled first, and they're also time sensitive. + loop { + // If a new pipeline run is pending we poll the sync controller first so that it takes + // precedence over any FCU messages. This ensures that a queued pipeline run via + // [EngineSyncController::set_pipeline_sync_target] are processed before any forkchoice + // updates. + if this.sync.is_pipeline_sync_pending() { + // the next event is guaranteed to be a [EngineSyncEvent::PipelineStarted] + if let Poll::Ready(sync_event) = this.sync.poll(cx) { + if let Some(res) = this.on_sync_event(sync_event) { + return Poll::Ready(res) } } - BeaconEngineMessage::NewPayload { payload, tx } => { - this.metrics.new_payload_messages.increment(1); - let res = this.on_new_payload(payload); - let _ = tx.send(res); - } - BeaconEngineMessage::TransitionConfigurationExchanged => { - this.blockchain.on_transition_configuration_exchanged(); + } + + // handle next engine message, else exit the loop + match this.engine_message_rx.poll_next_unpin(cx) { + Poll::Ready(Some(msg)) => match msg { + BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => { + if this.on_forkchoice_updated(state, payload_attrs, tx) { + return Poll::Ready(Ok(())) + } + } + BeaconEngineMessage::NewPayload { payload, tx } => { + this.metrics.new_payload_messages.increment(1); + let res = this.on_new_payload(payload); + let _ = tx.send(res); + } + BeaconEngineMessage::TransitionConfigurationExchanged => { + this.blockchain.on_transition_configuration_exchanged(); + } + BeaconEngineMessage::EventListener(tx) => { + this.listeners.push_listener(tx); + } + }, + Poll::Ready(None) => { + unreachable!("Engine holds the a sender to the message channel") } - BeaconEngineMessage::EventListener(tx) => { - this.listeners.push_listener(tx); + Poll::Pending => { + // no more CL messages to process + break } } } - // poll sync controller + // drain the sync controller while let Poll::Ready(sync_event) = this.sync.poll(cx) { if let Some(res) = this.on_sync_event(sync_event) { return Poll::Ready(res) diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index a093b57bae8c..70e99657c065 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -106,6 +106,11 @@ where self.run_pipeline_continuously } + /// Returns `true` if a pipeline target is queued and will be triggered on the next `poll`. + pub(crate) fn is_pipeline_sync_pending(&self) -> bool { + self.pending_pipeline_target.is_some() && self.pipeline_state.is_idle() + } + /// Returns `true` if the pipeline is idle. pub(crate) fn is_pipeline_idle(&self) -> bool { self.pipeline_state.is_idle() From 144f4bdcee32b158af88ac07ff08f71bcf9036e7 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Thu, 15 Jun 2023 21:58:49 +0200 Subject: [PATCH 049/216] ci: fix required checks (#3192) --- .github/workflows/bench.yml | 7 ++++++- .github/workflows/ci.yml | 6 +++++- .github/workflows/fuzz.yml | 8 +++++++- .github/workflows/integration.yml | 6 +++++- .github/workflows/unit.yml | 6 +++++- 5 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 5393f27f058c..2a1ad02ea9ee 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -67,9 +67,14 @@ jobs: - name: Check if benchmarks build run: cargo bench --all --all-features --all-targets --no-run + bench-success: + if: always() name: bench success needs: bench-check runs-on: ubuntu-latest steps: - - run: echo Success! + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89e16db168b7..4dc36d318c45 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,8 +53,12 @@ jobs: run: RUSTDOCFLAGS="-D warnings" cargo doc --all --no-deps --all-features --document-private-items lint-success: + if: always() name: lint success runs-on: ubuntu-latest needs: [lint, doc-lint] steps: - - run: echo Success! + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} \ No newline at end of file diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index b69d7b92b3b7..9dbc7568ca64 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -63,8 +63,14 @@ jobs: flags: fuzz-tests fuzz-success: + if: always() name: fuzz success runs-on: ubuntu-latest needs: all steps: - - run: echo Success! + # Note: This check is a dummy because we currently have fuzz tests disabled. + - run: echo OK. + #- name: Decide whether the needed jobs succeeded or failed + # uses: re-actors/alls-green@release/v1 + # with: + # jobs: ${{ toJSON(needs) }} \ No newline at end of file diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 11a1919239d4..992c447462ec 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -91,8 +91,12 @@ jobs: --debug.terminate integration-success: + if: always() name: integration success runs-on: ubuntu-latest needs: [test, sync] steps: - - run: echo Success! + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} \ No newline at end of file diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 46bd573ed554..2e495074ac3e 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -95,8 +95,12 @@ jobs: run: cargo test --doc --all --all-features unit-success: + if: always() name: unit success runs-on: ubuntu-latest needs: [test, eth-blockchain, doc-test] steps: - - run: echo Success! + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} \ No newline at end of file From 85085896a9ae2457e98f3d869efee77256f3c4af Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 15 Jun 2023 16:04:23 -0400 Subject: [PATCH 050/216] fix: encode forkid entry in struct, accept trailing fields (#3193) --- crates/net/discv4/src/lib.rs | 43 +++++++++++++++++++++++++++++ crates/net/discv4/src/proto.rs | 4 +-- crates/net/network/src/discovery.rs | 5 ++-- 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 0c9334480893..e4ccc65e59cb 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -35,6 +35,7 @@ use reth_primitives::{ bytes::{Bytes, BytesMut}, ForkId, PeerId, H256, }; +use reth_rlp::{RlpDecodable, RlpEncodable}; use secp256k1::SecretKey; use std::{ cell::RefCell, @@ -1967,14 +1968,56 @@ pub enum DiscoveryUpdate { Batch(Vec), } +/// Represents a forward-compatible ENR entry for including the forkid in a node record via +/// EIP-868. Forward compatibility is achieved by allowing trailing fields. +/// +/// See: +/// +/// +/// for how geth implements ForkId values and forward compatibility. +#[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] +#[rlp(trailing)] +pub struct EnrForkIdEntry { + /// The inner forkid + pub fork_id: ForkId, +} + +impl From for EnrForkIdEntry { + fn from(fork_id: ForkId) -> Self { + Self { fork_id } + } +} + #[cfg(test)] mod tests { use super::*; use crate::test_utils::{create_discv4, create_discv4_with_config, rng_endpoint, rng_record}; use rand::{thread_rng, Rng}; use reth_primitives::{hex_literal::hex, mainnet_nodes, ForkHash}; + use reth_rlp::{Decodable, Encodable}; use std::{future::poll_fn, net::Ipv4Addr}; + #[test] + fn test_enr_forkid_entry_decode() { + let raw: [u8; 8] = [0xc7, 0xc6, 0x84, 0xdc, 0xe9, 0x6c, 0x2d, 0x80]; + let decoded = EnrForkIdEntry::decode(&mut &raw[..]).unwrap(); + let expected = EnrForkIdEntry { + fork_id: ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 0 }, + }; + assert_eq!(expected, decoded); + } + + #[test] + fn test_enr_forkid_entry_encode() { + let original = EnrForkIdEntry { + fork_id: ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 0 }, + }; + let mut encoded = Vec::new(); + original.encode(&mut encoded); + let expected: [u8; 8] = [0xc7, 0xc6, 0x84, 0xdc, 0xe9, 0x6c, 0x2d, 0x80]; + assert_eq!(&expected[..], encoded.as_slice()); + } + #[test] fn test_local_rotator() { let id = PeerId::random(); diff --git a/crates/net/discv4/src/proto.rs b/crates/net/discv4/src/proto.rs index bf989ffccd24..e22cd4c028ce 100644 --- a/crates/net/discv4/src/proto.rs +++ b/crates/net/discv4/src/proto.rs @@ -1,6 +1,6 @@ #![allow(missing_docs)] -use crate::{error::DecodePacketError, PeerId, MAX_PACKET_SIZE, MIN_PACKET_SIZE}; +use crate::{error::DecodePacketError, EnrForkIdEntry, PeerId, MAX_PACKET_SIZE, MIN_PACKET_SIZE}; use enr::{Enr, EnrKey}; use reth_primitives::{ bytes::{Buf, BufMut, Bytes, BytesMut}, @@ -306,7 +306,7 @@ impl EnrResponse { /// See also pub fn eth_fork_id(&self) -> Option { let mut maybe_fork_id = self.enr.0.get(b"eth")?; - ForkId::decode(&mut maybe_fork_id).ok() + EnrForkIdEntry::decode(&mut maybe_fork_id).ok().map(|entry| entry.fork_id) } } diff --git a/crates/net/network/src/discovery.rs b/crates/net/network/src/discovery.rs index 69f1e4202d13..0bf64e7c028c 100644 --- a/crates/net/network/src/discovery.rs +++ b/crates/net/network/src/discovery.rs @@ -2,7 +2,7 @@ use crate::error::{NetworkError, ServiceKind}; use futures::StreamExt; -use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config}; +use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config, EnrForkIdEntry}; use reth_dns_discovery::{ DnsDiscoveryConfig, DnsDiscoveryHandle, DnsDiscoveryService, DnsNodeRecordUpdate, DnsResolver, }; @@ -100,7 +100,8 @@ impl Discovery { #[allow(unused)] pub(crate) fn update_fork_id(&self, fork_id: ForkId) { if let Some(discv4) = &self.discv4 { - discv4.set_eip868_rlp("eth".as_bytes().to_vec(), fork_id) + // use forward-compatible forkid entry + discv4.set_eip868_rlp("eth".as_bytes().to_vec(), EnrForkIdEntry::from(fork_id)) } } From 2178035d207a77af52c876e1d1277250e610f711 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 15 Jun 2023 22:47:17 +0200 Subject: [PATCH 051/216] feat: add js debug test utils (#3153) --- Cargo.lock | 1 + crates/rpc/rpc-testing-util/Cargo.toml | 1 + .../rpc-testing-util/assets/noop-tracer.js | 8 ++ crates/rpc/rpc-testing-util/src/debug.rs | 79 +++++++++++++++++++ crates/rpc/rpc-testing-util/src/lib.rs | 1 + 5 files changed, 90 insertions(+) create mode 100644 crates/rpc/rpc-testing-util/assets/noop-tracer.js create mode 100644 crates/rpc/rpc-testing-util/src/debug.rs diff --git a/Cargo.lock b/Cargo.lock index 1130edd46f93..cfa24a16fdd1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5664,6 +5664,7 @@ dependencies = [ "reth-primitives", "reth-rpc-api", "reth-rpc-types", + "serde_json", "tokio", ] diff --git a/crates/rpc/rpc-testing-util/Cargo.toml b/crates/rpc/rpc-testing-util/Cargo.toml index e9c7b8aec1b2..c3e5cafd49c4 100644 --- a/crates/rpc/rpc-testing-util/Cargo.toml +++ b/crates/rpc/rpc-testing-util/Cargo.toml @@ -22,6 +22,7 @@ futures = { workspace = true } # misc jsonrpsee = { version = "0.18", features = ["client", "async-client"] } +serde_json.workspace = true [dev-dependencies] tokio = { workspace = true, features = ["rt-multi-thread", "macros", "rt"] } diff --git a/crates/rpc/rpc-testing-util/assets/noop-tracer.js b/crates/rpc/rpc-testing-util/assets/noop-tracer.js new file mode 100644 index 000000000000..6906e3ee7b85 --- /dev/null +++ b/crates/rpc/rpc-testing-util/assets/noop-tracer.js @@ -0,0 +1,8 @@ +{ + // required function that is invoked when a step fails + fault: function(log, db) { }, + // required function that returns the result of the tracer: empty object + result: function(ctx, db) { return {}; }, + // optional function that is invoked for every opcode + step: function(log, db) { } +} \ No newline at end of file diff --git a/crates/rpc/rpc-testing-util/src/debug.rs b/crates/rpc/rpc-testing-util/src/debug.rs new file mode 100644 index 000000000000..da94fb3eead3 --- /dev/null +++ b/crates/rpc/rpc-testing-util/src/debug.rs @@ -0,0 +1,79 @@ +//! Helpers for testing debug trace calls. + +use reth_primitives::H256; +use reth_rpc_api::clients::DebugApiClient; +use reth_rpc_types::trace::geth::{GethDebugTracerType, GethDebugTracingOptions}; + +const NOOP_TRACER: &str = include_str!("../assets/noop-tracer.js"); + +/// An extension trait for the Trace API. +#[async_trait::async_trait] +pub trait DebugApiExt { + /// The provider type that is used to make the requests. + type Provider; + + /// Same as [DebugApiClient::debug_trace_transaction] but returns the result as json. + async fn debug_trace_transaction_json( + &self, + hash: H256, + opts: GethDebugTracingOptions, + ) -> Result; +} + +#[async_trait::async_trait] +impl DebugApiExt for T { + type Provider = T; + + async fn debug_trace_transaction_json( + &self, + hash: H256, + opts: GethDebugTracingOptions, + ) -> Result { + let mut params = jsonrpsee::core::params::ArrayParams::new(); + params.insert(hash).unwrap(); + params.insert(opts).unwrap(); + self.request("debug_traceTransaction", params).await + } +} + +/// A javascript tracer that does nothing +#[derive(Debug, Clone, Copy, Default)] +#[non_exhaustive] +pub struct NoopJsTracer; + +impl From for GethDebugTracingOptions { + fn from(_: NoopJsTracer) -> Self { + GethDebugTracingOptions { + tracer: Some(GethDebugTracerType::JsTracer(NOOP_TRACER.to_string())), + tracer_config: serde_json::Value::Object(Default::default()).into(), + ..Default::default() + } + } +} +impl From for Option { + fn from(_: NoopJsTracer) -> Self { + Some(NoopJsTracer.into()) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + debug::{DebugApiExt, NoopJsTracer}, + utils::parse_env_url, + }; + use jsonrpsee::http_client::HttpClientBuilder; + + #[tokio::test] + #[ignore] + async fn can_trace_noop_sepolia() { + // random tx + let tx = + "0x5525c63a805df2b83c113ebcc8c7672a3b290673c4e81335b410cd9ebc64e085".parse().unwrap(); + let url = parse_env_url("RETH_RPC_TEST_NODE_URL").unwrap(); + let client = HttpClientBuilder::default().build(url).unwrap(); + let res = + client.debug_trace_transaction_json(tx, NoopJsTracer::default().into()).await.unwrap(); + assert_eq!(res, serde_json::Value::Object(Default::default())); + } +} diff --git a/crates/rpc/rpc-testing-util/src/lib.rs b/crates/rpc/rpc-testing-util/src/lib.rs index a3e2b6af171b..67f20bafff0d 100644 --- a/crates/rpc/rpc-testing-util/src/lib.rs +++ b/crates/rpc/rpc-testing-util/src/lib.rs @@ -7,6 +7,7 @@ //! Reth RPC testing utilities. +pub mod debug; pub mod trace; pub mod utils; From 5375a2dd6a491e28540e648c4ce986c99654f06e Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Thu, 15 Jun 2023 23:06:07 +0200 Subject: [PATCH 052/216] feat(consensus): adding `warn!` for insertion in `invalid_headers` (#3194) --- crates/consensus/beacon/src/engine/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index ee784a0500c4..5082a608f606 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -467,7 +467,7 @@ where header.parent_hash }; - // popualte the latest valid hash field + // populate the latest valid hash field let status = self.prepare_invalid_response(parent_hash); Some(status) @@ -1035,7 +1035,7 @@ where Err(err) => { debug!(target: "consensus::engine", ?err, "Failed to insert downloaded block"); if !matches!(err.kind(), InsertBlockErrorKind::Internal(_)) { - // non-internal error kinds occurr if the payload is invalid + // non-internal error kinds occur if the payload is invalid self.invalid_headers.insert(err.into_block().header); } } @@ -1319,6 +1319,8 @@ impl InvalidHeaderCache { /// Inserts an invalid block into the cache, with a given invalid ancestor. fn insert_with_invalid_ancestor(&mut self, header_hash: H256, invalid_ancestor: Arc
) { + warn!(target: "consensus::engine", "Bad block with header hash: {:?}, invalid ancestor: {:?}", + header_hash, invalid_ancestor); self.headers.insert(header_hash, invalid_ancestor); } @@ -1326,6 +1328,8 @@ impl InvalidHeaderCache { fn insert(&mut self, invalid_ancestor: SealedHeader) { let hash = invalid_ancestor.hash; let header = invalid_ancestor.unseal(); + warn!(target: "consensus::engine", "Bad block with header hash: {:?}, invalid ancestor: {:?}", + hash, header); self.headers.insert(hash, Arc::new(header)); } } From 5e0508b973458e3ff0cfcf28f608d76c0b37e712 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 15 Jun 2023 18:35:21 -0400 Subject: [PATCH 053/216] fix: shrink queued bodies and headers when drained or split (#3191) --- crates/net/downloaders/src/bodies/bodies.rs | 9 ++- .../src/headers/reverse_headers.rs | 77 ++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index 91d0fb8e7c5f..9d8287674faa 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -200,8 +200,8 @@ where self.download_range = RangeInclusive::new(1, 0); self.latest_queued_block_number.take(); self.in_progress_queue.clear(); - self.queued_bodies.clear(); - self.buffered_responses.clear(); + self.queued_bodies = Vec::new(); + self.buffered_responses = BinaryHeap::new(); self.num_buffered_blocks = 0; // reset metrics @@ -270,6 +270,7 @@ where fn try_split_next_batch(&mut self) -> Option> { if self.queued_bodies.len() >= self.stream_batch_size { let next_batch = self.queued_bodies.drain(..self.stream_batch_size).collect::>(); + self.queued_bodies.shrink_to_fit(); self.metrics.total_flushed.increment(next_batch.len() as u64); self.metrics.queued_blocks.set(self.queued_bodies.len() as f64); return Some(next_batch) @@ -410,6 +411,9 @@ where this.queue_bodies(buf_response); } + // shrink the buffer so that it doesn't grow indefinitely + this.buffered_responses.shrink_to_fit(); + if !new_request_submitted { break } @@ -422,6 +426,7 @@ where } let batch_size = this.stream_batch_size.min(this.queued_bodies.len()); let next_batch = this.queued_bodies.drain(..batch_size).collect::>(); + this.queued_bodies.shrink_to_fit(); this.metrics.total_flushed.increment(next_batch.len() as u64); this.metrics.queued_blocks.set(this.queued_bodies.len() as f64); return Poll::Ready(Some(Ok(next_batch))) diff --git a/crates/net/downloaders/src/headers/reverse_headers.rs b/crates/net/downloaders/src/headers/reverse_headers.rs index 9a68c977179e..17a9232278c4 100644 --- a/crates/net/downloaders/src/headers/reverse_headers.rs +++ b/crates/net/downloaders/src/headers/reverse_headers.rs @@ -600,8 +600,8 @@ where /// Clears all requests/responses. fn clear(&mut self) { self.lowest_validated_header.take(); - self.queued_validated_headers.clear(); - self.buffered_responses.clear(); + self.queued_validated_headers = Vec::new(); + self.buffered_responses = BinaryHeap::new(); self.in_progress_queue.clear(); self.metrics.in_flight_requests.set(0.); @@ -613,6 +613,22 @@ where let batch_size = self.stream_batch_size.min(self.queued_validated_headers.len()); let mut rem = self.queued_validated_headers.split_off(batch_size); std::mem::swap(&mut rem, &mut self.queued_validated_headers); + // If the downloader consumer does not flush headers at the same rate that the downloader + // queues them, then the `queued_validated_headers` buffer can grow unbounded. + // + // The semantics of `split_off` state that the capacity of the original buffer is + // unchanged, so queued_validated_headers will then have only `batch_size` elements, and + // its original capacity. Because `rem` is initially populated with elements `[batch_size, + // len)` of `queued_validated_headers`, it will have a capacity of at least `len - + // batch_size`, and the total memory allocated by the two buffers will be around double the + // original size of `queued_validated_headers`. + // + // These are then mem::swapped, leaving `rem` with a large capacity, but small length. + // + // To prevent these allocations from leaking to the consumer, we shrink the capacity of the + // new buffer. The total memory allocated should then be not much more than the original + // size of `queued_validated_headers`. + rem.shrink_to_fit(); rem } } @@ -776,6 +792,9 @@ where } } + // shrink the buffer after handling sync target outcomes + this.buffered_responses.shrink_to_fit(); + // this loop will submit new requests and poll them, if a new batch is ready it is returned // The actual work is done by the receiver of the request channel, this means, polling the // request future is just reading from a `oneshot::Receiver`. Hence, this loop tries to keep @@ -805,6 +824,9 @@ where }; } + // shrink the buffer after handling headers outcomes + this.buffered_responses.shrink_to_fit(); + // marks the loop's exit condition: exit if no requests submitted let mut progress = false; @@ -1426,11 +1448,62 @@ mod tests { let headers = downloader.next().await.unwrap(); assert_eq!(headers, Ok(vec![p0])); + let headers = headers.unwrap(); + assert_eq!(headers.capacity(), headers.len()); + + let headers = downloader.next().await.unwrap(); + assert_eq!(headers, Ok(vec![p1])); + let headers = headers.unwrap(); + assert_eq!(headers.capacity(), headers.len()); + + let headers = downloader.next().await.unwrap(); + assert_eq!(headers, Ok(vec![p2])); + let headers = headers.unwrap(); + assert_eq!(headers.capacity(), headers.len()); + + assert!(downloader.next().await.is_none()); + } + + #[tokio::test] + async fn download_one_by_one_larger_request_limit() { + reth_tracing::init_test_tracing(); + let p3 = SealedHeader::default(); + let p2 = child_header(&p3); + let p1 = child_header(&p2); + let p0 = child_header(&p1); + + let client = Arc::new(TestHeadersClient::default()); + let mut downloader = ReverseHeadersDownloaderBuilder::default() + .stream_batch_size(1) + .request_limit(3) + .build(Arc::clone(&client), Arc::new(TestConsensus::default())); + downloader.update_local_head(p3.clone()); + downloader.update_sync_target(SyncTarget::Tip(p0.hash())); + + client + .extend(vec![ + p0.as_ref().clone(), + p1.as_ref().clone(), + p2.as_ref().clone(), + p3.as_ref().clone(), + ]) + .await; + + let headers = downloader.next().await.unwrap(); + assert_eq!(headers, Ok(vec![p0])); + let headers = headers.unwrap(); + assert_eq!(headers.capacity(), headers.len()); let headers = downloader.next().await.unwrap(); assert_eq!(headers, Ok(vec![p1])); + let headers = headers.unwrap(); + assert_eq!(headers.capacity(), headers.len()); + let headers = downloader.next().await.unwrap(); assert_eq!(headers, Ok(vec![p2])); + let headers = headers.unwrap(); + assert_eq!(headers.capacity(), headers.len()); + assert!(downloader.next().await.is_none()); } } From bded94ef94890153945bfb2763c54258ff24fefb Mon Sep 17 00:00:00 2001 From: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:59:22 +0200 Subject: [PATCH 054/216] perf(rlp): improve uint encoding performance (#3197) --- crates/rlp/src/encode.rs | 58 ++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/crates/rlp/src/encode.rs b/crates/rlp/src/encode.rs index 08e31acd3544..759165a081a4 100644 --- a/crates/rlp/src/encode.rs +++ b/crates/rlp/src/encode.rs @@ -4,9 +4,11 @@ use auto_impl::auto_impl; use bytes::{BufMut, Bytes, BytesMut}; use core::borrow::Borrow; -fn zeroless_view(v: &impl AsRef<[u8]>) -> &[u8] { - let v = v.as_ref(); - &v[v.iter().take_while(|&&b| b == 0).count()..] +macro_rules! to_be_bytes_trimmed { + ($be:ident, $x:expr) => {{ + $be = $x.to_be_bytes(); + &$be[($x.leading_zeros() / 8) as usize..] + }}; } impl Header { @@ -16,8 +18,8 @@ impl Header { let code = if self.list { EMPTY_LIST_CODE } else { EMPTY_STRING_CODE }; out.put_u8(code + self.payload_length as u8); } else { - let len_be = self.payload_length.to_be_bytes(); - let len_be = zeroless_view(&len_be); + let len_be; + let len_be = to_be_bytes_trimmed!(len_be, self.payload_length); let code = if self.list { 0xF7 } else { 0xB7 }; out.put_u8(code + len_be.len() as u8); out.put_slice(len_be); @@ -132,8 +134,8 @@ macro_rules! encodable_uint { } else if *self < <$t>::from(EMPTY_STRING_CODE) { out.put_u8(u8::try_from(*self).unwrap()); } else { - let be = self.to_be_bytes(); - let be = zeroless_view(&be); + let be; + let be = to_be_bytes_trimmed!(be, *self); out.put_u8(EMPTY_STRING_CODE + be.len() as u8); out.put_slice(be); } @@ -278,7 +280,8 @@ mod ethereum_types_support { } fn encode(&self, out: &mut dyn bytes::BufMut) { - self.to_be_bytes_trimmed_vec().as_slice().encode(out) + let be = self.to_be_bytes::<$n_bytes>(); + (&be[self.leading_zeros() / 8..]).encode(out); } } }; @@ -622,4 +625,43 @@ mod tests { "abcdefgh".to_string().encode(&mut b); assert_eq!(&encoded(SmolStr::new("abcdefgh"))[..], b.as_ref()); } + + #[test] + fn to_be_bytes_trimmed() { + macro_rules! test_to_be_bytes_trimmed { + ($($x:expr => $expected:expr),+ $(,)?) => {$( + let be; + assert_eq!(to_be_bytes_trimmed!(be, $x), $expected); + )+}; + } + + test_to_be_bytes_trimmed! { + 0u8 => [], + 0u16 => [], + 0u32 => [], + 0u64 => [], + 0usize => [], + 0u128 => [], + + 1u8 => [1], + 1u16 => [1], + 1u32 => [1], + 1u64 => [1], + 1usize => [1], + 1u128 => [1], + + u8::MAX => [0xff], + u16::MAX => [0xff, 0xff], + u32::MAX => [0xff, 0xff, 0xff, 0xff], + u64::MAX => [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + u128::MAX => [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff], + + 1u8 => [1], + 255u8 => [255], + 256u16 => [1, 0], + 65535u16 => [255, 255], + 65536u32 => [1, 0, 0], + 65536u64 => [1, 0, 0], + } + } } From 81963a17cde548de5457f9f4c765d2ea9a456afd Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 16 Jun 2023 11:51:14 +0100 Subject: [PATCH 055/216] chore: remove unused methods on `DatabaseProvider` (#3196) --- .../src/providers/database/provider.rs | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index dec82dd68bfc..cb0ddd2d3acc 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1110,23 +1110,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { self.tx.put::(id.to_string(), checkpoint) } - /// Get lastest block number. - pub fn tip_number(&self) -> std::result::Result { - Ok(self.tx.cursor_read::()?.last()?.unwrap_or_default().0) - } - - /// Query [tables::CanonicalHeaders] table for block hash by block number - pub fn get_block_hash( - &self, - block_number: BlockNumber, - ) -> std::result::Result { - let hash = self - .tx - .get::(block_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))?; - Ok(hash) - } - /// Query the block body by number. pub fn block_body_indices( &self, @@ -1139,24 +1122,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(body) } - /// Query the block header by number - pub fn get_header(&self, number: BlockNumber) -> std::result::Result { - let header = self - .tx - .get::(number)? - .ok_or_else(|| ProviderError::HeaderNotFound(number.into()))?; - Ok(header) - } - - /// Get the total difficulty for a block. - pub fn get_td(&self, block: BlockNumber) -> std::result::Result { - let td = self - .tx - .get::(block)? - .ok_or(ProviderError::TotalDifficultyNotFound { number: block })?; - Ok(td.into()) - } - /// Unwind table by some number key. /// Returns number of rows unwound. /// From b90d0b41069015d5746b08b3e408e738bc65a6a7 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 16 Jun 2023 14:20:37 +0300 Subject: [PATCH 056/216] chore(provider): remove transition terminology (#3198) --- .../src/stages/index_account_history.rs | 2 +- .../src/stages/index_storage_history.rs | 2 +- .../src/providers/database/provider.rs | 39 +++++++++---------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index f20b9c5bea6e..b3adc01a9483 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -39,7 +39,7 @@ impl Stage for IndexAccountHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); - let indices = provider.get_account_transition_ids_from_changeset(range.clone())?; + let indices = provider.get_account_block_numbers_from_changesets(range.clone())?; // Insert changeset to history index provider.insert_account_history_index(indices)?; diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index 2566861de9bd..0f6e3fba262a 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -39,7 +39,7 @@ impl Stage for IndexStorageHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); - let indices = provider.get_storage_transition_ids_from_changeset(range.clone())?; + let indices = provider.get_storage_block_numbers_from_changesets(range.clone())?; provider.insert_storage_history_index(indices)?; Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()), done: is_final_range }) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index cb0ddd2d3acc..489bb647dbe4 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -120,7 +120,7 @@ fn unwind_account_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( break } cursor.delete_current()?; - // check first item and if it is more and eq than `transition_id` delete current + // check first item and if it is more and eq than `block_number` delete current // item. let first = list.iter(0).next().expect("List can't empty"); if first >= block_number as usize { @@ -159,7 +159,7 @@ fn unwind_storage_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( break } cursor.delete_current()?; - // check first item and if it is more and eq than `transition_id` delete current + // check first item and if it is more and eq than `block_number` delete current // item. let first = list.iter(0).next().expect("List can't empty"); if first >= block_number as usize { @@ -252,10 +252,10 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { .collect::, _>>() } - /// Get all transaction ids where account got changed. + /// Get all block numbers where account got changed. /// /// NOTE: Get inclusive range of blocks. - pub fn get_storage_transition_ids_from_changeset( + pub fn get_storage_block_numbers_from_changesets( &self, range: RangeInclusive, ) -> std::result::Result>, TransactionError> { @@ -279,10 +279,10 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { Ok(storage_changeset_lists) } - /// Get all transaction ids where account got changed. + /// Get all block numbers where account got changed. /// /// NOTE: Get inclusive range of blocks. - pub fn get_account_transition_ids_from_changeset( + pub fn get_account_block_numbers_from_changesets( &self, range: RangeInclusive, ) -> std::result::Result>, TransactionError> { @@ -363,7 +363,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { ) -> std::result::Result<(), TransactionError> { let mut hashed_accounts = self.tx.cursor_write::()?; - // Aggregate all transition changesets and make a list of accounts that have been changed. + // Aggregate all block changesets and make a list of accounts that have been changed. self.tx .cursor_read::()? .walk_range(range)? @@ -406,7 +406,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { ) -> std::result::Result<(), TransactionError> { let mut hashed_storage = self.tx.cursor_dup_write::()?; - // Aggregate all transition changesets and make list of accounts that have been changed. + // Aggregate all block changesets and make list of accounts that have been changed. self.tx .cursor_read::()? .walk_range(range)? @@ -465,11 +465,11 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { let last_indices = account_changeset .into_iter() - // reverse so we can get lowest transition id where we need to unwind account. + // reverse so we can get lowest block number where we need to unwind account. .rev() - // fold all account and get last transition index + // fold all account and get last block number .fold(BTreeMap::new(), |mut accounts: BTreeMap, (index, account)| { - // we just need address and lowest transition id. + // we just need address and lowest block number. accounts.insert(account.address, index); accounts }); @@ -508,13 +508,13 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { let last_indices = storage_changesets .into_iter() - // reverse so we can get lowest transition id where we need to unwind account. + // reverse so we can get lowest block number where we need to unwind account. .rev() - // fold all storages and get last transition index + // fold all storages and get last block number .fold( BTreeMap::new(), |mut accounts: BTreeMap<(Address, H256), u64>, (index, storage)| { - // we just need address and lowest transition id. + // we just need address and lowest block number. accounts.insert((index.address(), storage.key), index.block_number()); accounts }, @@ -543,7 +543,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { /// of blocks. /// /// 1. Iterate over the [BlockBodyIndices][tables::BlockBodyIndices] table to get all - /// the transition indices. + /// the transaction ids. /// 2. Iterate over the [StorageChangeSet][tables::StorageChangeSet] table /// and the [AccountChangeSet][tables::AccountChangeSet] tables in reverse order to reconstruct /// the changesets. @@ -570,7 +570,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { return Ok(Vec::new()) } - // We are not removing block meta as it is used to get block transitions. + // We are not removing block meta as it is used to get block changesets. let block_bodies = self.get_or_take::(range.clone())?; // get transaction receipts @@ -1337,13 +1337,13 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { ) -> std::result::Result<(), TransactionError> { // account history stage { - let indices = self.get_account_transition_ids_from_changeset(range.clone())?; + let indices = self.get_account_block_numbers_from_changesets(range.clone())?; self.insert_account_history_index(indices)?; } // storage history stage { - let indices = self.get_storage_transition_ids_from_changeset(range)?; + let indices = self.get_storage_block_numbers_from_changesets(range)?; self.insert_storage_history_index(indices)?; } @@ -1353,8 +1353,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { /// Calculate the hashes of all changed accounts and storages, and finally calculate the state /// root. /// - /// The chain goes from `fork_block_number + 1` to `current_block_number`, and hashes are - /// calculated from `from_transition_id` to `to_transition_id`. + /// The hashes are calculated from `fork_block_number + 1` to `current_block_number`. /// /// The resulting state root is compared with `expected_state_root`. pub fn insert_hashes( From 3ace10d8350d701572bff4fecfc49380f9a04174 Mon Sep 17 00:00:00 2001 From: Aditya Pandey Date: Fri, 16 Jun 2023 20:10:02 +0530 Subject: [PATCH 057/216] refactor: phase out ether genesis in primitives (#2311) Co-authored-by: Matthias Seitz --- crates/net/eth-wire/src/types/status.rs | 7 +- crates/primitives/src/chain/spec.rs | 56 +- crates/primitives/src/genesis.rs | 969 +++++++++++++++++- .../primitives/src/serde_helper/jsonu256.rs | 11 +- crates/primitives/src/serde_helper/mod.rs | 4 +- .../{storage_key.rs => storage.rs} | 53 +- crates/staged-sync/tests/sync.rs | 12 +- 7 files changed, 1040 insertions(+), 72 deletions(-) rename crates/primitives/src/serde_helper/{storage_key.rs => storage.rs} (60%) diff --git a/crates/net/eth-wire/src/types/status.rs b/crates/net/eth-wire/src/types/status.rs index 63cb0f8f0d3c..a29af1894147 100644 --- a/crates/net/eth-wire/src/types/status.rs +++ b/crates/net/eth-wire/src/types/status.rs @@ -1,8 +1,9 @@ use crate::{EthVersion, StatusBuilder}; -use ethers_core::utils::Genesis; use reth_codecs::derive_arbitrary; -use reth_primitives::{hex, Chain, ChainSpec, ForkId, Hardfork, Head, H256, MAINNET, U256}; +use reth_primitives::{ + hex, Chain, ChainSpec, ForkId, Genesis, Hardfork, Head, H256, MAINNET, U256, +}; use reth_rlp::{RlpDecodable, RlpEncodable}; use std::fmt::{Debug, Display}; @@ -46,7 +47,7 @@ pub struct Status { impl From for Status { fn from(genesis: Genesis) -> Status { let chain = genesis.config.chain_id; - let total_difficulty = genesis.difficulty.into(); + let total_difficulty = genesis.difficulty; let chainspec = ChainSpec::from(genesis); Status { diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 92e16220c520..a3a0f06fe57d 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -3,17 +3,13 @@ use crate::{ forkid::ForkFilterKey, header::Head, proofs::genesis_state_root, - BlockNumber, Chain, ForkFilter, ForkHash, ForkId, Genesis, GenesisAccount, Hardfork, Header, - SealedHeader, H160, H256, U256, + BlockNumber, Chain, ForkFilter, ForkHash, ForkId, Genesis, Hardfork, Header, SealedHeader, + H256, U256, }; -use ethers_core::utils::Genesis as EthersGenesis; use hex_literal::hex; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use std::{ - collections::{BTreeMap, HashMap}, - sync::Arc, -}; +use std::{collections::BTreeMap, sync::Arc}; /// The Ethereum mainnet spec pub static MAINNET: Lazy> = Lazy::new(|| { @@ -316,25 +312,8 @@ impl ChainSpec { } } -impl From for ChainSpec { - fn from(genesis: EthersGenesis) -> Self { - let alloc = genesis - .alloc - .iter() - .map(|(addr, account)| (addr.0.into(), account.clone().into())) - .collect::>(); - - let genesis_block = Genesis { - nonce: genesis.nonce.as_u64(), - timestamp: genesis.timestamp.as_u64(), - gas_limit: genesis.gas_limit.as_u64(), - difficulty: genesis.difficulty.into(), - mix_hash: genesis.mix_hash.0.into(), - coinbase: genesis.coinbase.0.into(), - extra_data: genesis.extra_data.0.into(), - alloc, - }; - +impl From for ChainSpec { + fn from(genesis: Genesis) -> Self { // Block-based hardforks let hardfork_opts = vec![ (Hardfork::Homestead, genesis.config.homestead_block), @@ -361,7 +340,7 @@ impl From for ChainSpec { hardforks.insert( Hardfork::Paris, ForkCondition::TTD { - total_difficulty: ttd.into(), + total_difficulty: ttd, fork_block: genesis.config.merge_netsplit_block, }, ); @@ -379,7 +358,7 @@ impl From for ChainSpec { Self { chain: genesis.config.chain_id.into(), - genesis: genesis_block, + genesis, genesis_hash: None, fork_timestamps: ForkTimestamps::from_hardforks(&hardforks), hardforks, @@ -417,21 +396,26 @@ impl ForkTimestamps { #[serde(untagged)] pub enum AllGenesisFormats { /// The geth genesis format - Geth(EthersGenesis), + Geth(Genesis), /// The reth genesis format Reth(ChainSpec), } -impl From for AllGenesisFormats { - fn from(genesis: EthersGenesis) -> Self { +impl From for AllGenesisFormats { + fn from(genesis: Genesis) -> Self { Self::Geth(genesis) } } +impl From for AllGenesisFormats { + fn from(genesis: ChainSpec) -> Self { + Self::Reth(genesis) + } +} + impl From> for AllGenesisFormats { - fn from(mut genesis: Arc) -> Self { - let cloned_genesis = Arc::make_mut(&mut genesis).clone(); - Self::Reth(cloned_genesis) + fn from(genesis: Arc) -> Self { + Arc::try_unwrap(genesis).unwrap_or_else(|arc| (*arc).clone()).into() } } @@ -1195,7 +1179,7 @@ mod tests { } "#; - let genesis: ethers_core::utils::Genesis = serde_json::from_str(geth_genesis).unwrap(); + let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); let chainspec = ChainSpec::from(genesis); // assert a bunch of hardforks that should be set @@ -1334,6 +1318,8 @@ mod tests { } } "#; + + let _genesis = serde_json::from_str::(hive_json).unwrap(); let genesis = serde_json::from_str::(hive_json).unwrap(); let chainspec: ChainSpec = genesis.into(); assert_eq!(chainspec.genesis_hash, None); diff --git a/crates/primitives/src/genesis.rs b/crates/primitives/src/genesis.rs index 2083b0d2f816..0dd2e4eaf384 100644 --- a/crates/primitives/src/genesis.rs +++ b/crates/primitives/src/genesis.rs @@ -1,21 +1,23 @@ -use std::collections::HashMap; - use crate::{ keccak256, proofs::{KeccakHasher, EMPTY_ROOT}, - serde_helper::deserialize_json_u256, - utils::serde_helpers::deserialize_stringified_u64, + serde_helper::{deserialize_json_u256, deserialize_json_u256_opt, deserialize_storage_map}, + utils::serde_helpers::{deserialize_stringified_u64, deserialize_stringified_u64_opt}, Account, Address, Bytes, H256, KECCAK_EMPTY, U256, }; -use ethers_core::utils::GenesisAccount as EthersGenesisAccount; use reth_rlp::{encode_fixed_size, length_of_length, Encodable, Header as RlpHeader}; +use revm_primitives::B160; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use triehash::sec_trie_root; /// The genesis block specification. #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase", default)] pub struct Genesis { + /// The fork configuration for this network. + #[serde(default)] + pub config: ChainConfig, /// The genesis header nonce. #[serde(deserialize_with = "deserialize_stringified_u64")] pub nonce: u64, @@ -97,16 +99,24 @@ impl Genesis { #[serde(deny_unknown_fields)] pub struct GenesisAccount { /// The nonce of the account at genesis. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt", + default + )] pub nonce: Option, /// The balance of the account at genesis. #[serde(deserialize_with = "deserialize_json_u256")] pub balance: U256, /// The account's bytecode at genesis. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub code: Option, /// The account's storage at genesis. - #[serde(skip_serializing_if = "Option::is_none")] + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_storage_map" + )] pub storage: Option>, } @@ -180,19 +190,6 @@ impl Encodable for GenesisAccount { } } -impl From for GenesisAccount { - fn from(genesis_account: EthersGenesisAccount) -> Self { - Self { - balance: genesis_account.balance.into(), - nonce: genesis_account.nonce, - code: genesis_account.code.as_ref().map(|code| code.0.clone().into()), - storage: genesis_account.storage.as_ref().map(|storage| { - storage.clone().into_iter().map(|(k, v)| (k.0.into(), v.0.into())).collect() - }), - } - } -} - impl From for Account { fn from(value: GenesisAccount) -> Self { Account { @@ -204,10 +201,314 @@ impl From for Account { } } +/// Represents a node's chain configuration. +/// +/// See [geth's `ChainConfig` +/// struct](https://github.com/ethereum/go-ethereum/blob/64dccf7aa411c5c7cd36090c3d9b9892945ae813/params/config.go#L349) +/// for the source of each field. +#[derive(Clone, Debug, Deserialize, Serialize, Default, PartialEq, Eq)] +#[serde(default, rename_all = "camelCase")] +pub struct ChainConfig { + /// The network's chain ID. + #[serde(default = "mainnet_id")] + pub chain_id: u64, + + /// The homestead switch block (None = no fork, 0 = already homestead). + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub homestead_block: Option, + + /// The DAO fork switch block (None = no fork). + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub dao_fork_block: Option, + + /// Whether or not the node supports the DAO hard-fork. + pub dao_fork_support: bool, + + /// The EIP-150 hard fork block (None = no fork). + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub eip150_block: Option, + + /// The EIP-150 hard fork hash. + #[serde(skip_serializing_if = "Option::is_none")] + pub eip150_hash: Option, + + /// The EIP-155 hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub eip155_block: Option, + + /// The EIP-158 hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub eip158_block: Option, + + /// The Byzantium hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub byzantium_block: Option, + + /// The Constantinople hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub constantinople_block: Option, + + /// The Petersburg hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub petersburg_block: Option, + + /// The Istanbul hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub istanbul_block: Option, + + /// The Muir Glacier hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub muir_glacier_block: Option, + + /// The Berlin hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub berlin_block: Option, + + /// The London hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub london_block: Option, + + /// The Arrow Glacier hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub arrow_glacier_block: Option, + + /// The Gray Glacier hard fork block. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub gray_glacier_block: Option, + + /// Virtual fork after the merge to use as a network splitter. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub merge_netsplit_block: Option, + + /// Shanghai switch time. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub shanghai_time: Option, + + /// Cancun switch time. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub cancun_time: Option, + + /// Total difficulty reached that triggers the merge consensus upgrade. + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_json_u256_opt" + )] + pub terminal_total_difficulty: Option, + + /// A flag specifying that the network already passed the terminal total difficulty. Its + /// purpose is to disable legacy sync without having seen the TTD locally. + pub terminal_total_difficulty_passed: bool, + + /// Ethash parameters. + #[serde(skip_serializing_if = "Option::is_none")] + pub ethash: Option, + + /// Clique parameters. + #[serde(skip_serializing_if = "Option::is_none")] + pub clique: Option, +} + +// used only for serde +#[inline] +const fn mainnet_id() -> u64 { + 1 +} + +/// Empty consensus configuration for proof-of-work networks. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct EthashConfig {} + +/// Consensus configuration for Clique. +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] +pub struct CliqueConfig { + /// Number of seconds between blocks to enforce. + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub period: Option, + + /// Epoch length to reset votes and checkpoints. + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "deserialize_stringified_u64_opt" + )] + pub epoch: Option, +} + +mod ethers_compat { + use super::*; + use ethers_core::utils::{ + ChainConfig as EthersChainConfig, CliqueConfig as EthersCliqueConfig, + EthashConfig as EthersEthashConfig, GenesisAccount as EthersGenesisAccount, + }; + + impl From for Genesis { + fn from(genesis: ethers_core::utils::Genesis) -> Genesis { + let alloc = genesis + .alloc + .iter() + .map(|(addr, account)| (addr.0.into(), account.clone().into())) + .collect::>(); + + Genesis { + config: genesis.config.into(), + nonce: genesis.nonce.as_u64(), + timestamp: genesis.timestamp.as_u64(), + gas_limit: genesis.gas_limit.as_u64(), + difficulty: genesis.difficulty.into(), + mix_hash: genesis.mix_hash.0.into(), + coinbase: genesis.coinbase.0.into(), + extra_data: genesis.extra_data.0.into(), + alloc, + } + } + } + + impl From for GenesisAccount { + fn from(genesis_account: EthersGenesisAccount) -> Self { + Self { + balance: genesis_account.balance.into(), + nonce: genesis_account.nonce, + code: genesis_account.code.as_ref().map(|code| code.0.clone().into()), + storage: genesis_account.storage.as_ref().map(|storage| { + storage.clone().into_iter().map(|(k, v)| (k.0.into(), v.0.into())).collect() + }), + } + } + } + + impl From for ChainConfig { + fn from(chain_config: EthersChainConfig) -> Self { + let EthersChainConfig { + chain_id, + homestead_block, + dao_fork_block, + dao_fork_support, + eip150_block, + eip150_hash, + eip155_block, + eip158_block, + byzantium_block, + constantinople_block, + petersburg_block, + istanbul_block, + muir_glacier_block, + berlin_block, + london_block, + arrow_glacier_block, + gray_glacier_block, + merge_netsplit_block, + shanghai_time, + cancun_time, + terminal_total_difficulty, + terminal_total_difficulty_passed, + ethash, + clique, + } = chain_config; + + Self { + chain_id, + homestead_block, + dao_fork_block, + dao_fork_support, + eip150_block, + eip150_hash: eip150_hash.map(Into::into), + eip155_block, + eip158_block, + byzantium_block, + constantinople_block, + petersburg_block, + istanbul_block, + muir_glacier_block, + berlin_block, + london_block, + arrow_glacier_block, + gray_glacier_block, + merge_netsplit_block, + shanghai_time, + cancun_time, + terminal_total_difficulty: terminal_total_difficulty.map(Into::into), + terminal_total_difficulty_passed, + ethash: ethash.map(Into::into), + clique: clique.map(Into::into), + } + } + } + + impl From for EthashConfig { + fn from(_: EthersEthashConfig) -> Self { + EthashConfig {} + } + } + + impl From for CliqueConfig { + fn from(clique_config: EthersCliqueConfig) -> Self { + let EthersCliqueConfig { period, epoch } = clique_config; + Self { period, epoch } + } + } +} + #[cfg(test)] mod tests { use super::*; + use crate::{Address, Bytes, U256}; use hex_literal::hex; + use std::{collections::HashMap, str::FromStr}; #[test] fn test_genesis() { @@ -303,4 +604,630 @@ mod tests { assert_eq!(genesis_account.code, code); assert_eq!(genesis_account.storage, storage); } + + #[test] + fn parse_hive_genesis() { + let geth_genesis = r#" + { + "difficulty": "0x20000", + "gasLimit": "0x1", + "alloc": {}, + "config": { + "ethash": {}, + "chainId": 1 + } + } + "#; + + let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + } + + #[test] + fn parse_hive_clique_smoke_genesis() { + let geth_genesis = r#" + { + "difficulty": "0x1", + "gasLimit": "0x400000", + "extraData": + "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x0", + "timestamp": "0x5c51a607", + "alloc": {} + } + "#; + + let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + } + + #[test] + fn parse_non_hex_prefixed_balance() { + // tests that we can parse balance / difficulty fields that are either hex or decimal + let example_balance_json = r#" + { + "nonce": "0x0000000000000042", + "difficulty": "34747478", + "mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234", + "coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "timestamp": "0x123456", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0xfafbfcfd", + "gasLimit": "0x2fefd8", + "alloc": { + "0x3E951C9f69a06Bc3AD71fF7358DbC56bEd94b9F2": { + "balance": "1000000000000000000000000000" + }, + "0xe228C30d4e5245f967ac21726d5412dA27aD071C": { + "balance": "1000000000000000000000000000" + }, + "0xD59Ce7Ccc6454a2D2C2e06bbcf71D0Beb33480eD": { + "balance": "1000000000000000000000000000" + }, + "0x1CF4D54414eF51b41f9B2238c57102ab2e61D1F2": { + "balance": "1000000000000000000000000000" + }, + "0x249bE3fDEd872338C733cF3975af9736bdCb9D4D": { + "balance": "1000000000000000000000000000" + }, + "0x3fCd1bff94513712f8cD63d1eD66776A67D5F78e": { + "balance": "1000000000000000000000000000" + } + }, + "config": { + "ethash": {}, + "chainId": 10, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0 + } + } + "#; + + let genesis: Genesis = serde_json::from_str(example_balance_json).unwrap(); + + // check difficulty against hex ground truth + let expected_difficulty = U256::from_str("0x2123456").unwrap(); + assert_eq!(expected_difficulty, genesis.difficulty); + + // check all alloc balances + let dec_balance = U256::from_str("1000000000000000000000000000").unwrap(); + for alloc in &genesis.alloc { + assert_eq!(alloc.1.balance, dec_balance); + } + } + + #[test] + fn parse_hive_rpc_genesis() { + let geth_genesis = r#" + { + "config": { + "chainId": 7, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x5de1ee4135274003348e80b788e5afa4b18b18d320a5622218d5c493fedf5689", + "eip155Block": 0, + "eip158Block": 0 + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x20000", + "extraData": + "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "timestamp": "0x1234", + "alloc": { + "cf49fda3be353c69b41ed96333cd24302da4556f": { + "balance": "0x123450000000000000000" + }, + "0161e041aad467a890839d5b08b138c1e6373072": { + "balance": "0x123450000000000000000" + }, + "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { + "balance": "0x123450000000000000000" + }, + "b97de4b8c857e4f6bc354f226dc3249aaee49209": { + "balance": "0x123450000000000000000" + }, + "c5065c9eeebe6df2c2284d046bfc906501846c51": { + "balance": "0x123450000000000000000" + }, + "0000000000000000000000000000000000000314": { + "balance": "0x0", + "code": + "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029" + , "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", + "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" + } + }, + "0000000000000000000000000000000000000315": { + "balance": "0x9999999999999999999999999999999", + "code": + "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" + } + } + } + "#; + + let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + } + + #[test] + fn parse_hive_graphql_genesis() { + let geth_genesis = r#" + { + "config" : {}, + "coinbase" : "0x8888f1f195afa192cfee860698584c030f4c9db1", + "difficulty" : "0x020000", + "extraData" : "0x42", + "gasLimit" : "0x2fefd8", + "mixHash" : "0x2c85bcbce56429100b2108254bb56906257582aeafcbd682bc9af67a9f5aee46", + "nonce" : "0x78cc16f7b4f65485", + "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", + "timestamp" : "0x54c98c81", + "alloc" : { + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance" : "0x09184e72a000" + } + } + } + "#; + + let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + } + + #[test] + fn parse_hive_engine_genesis() { + let geth_genesis = r#" + { + "config": { + "chainId": 7, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x5de1ee4135274003348e80b788e5afa4b18b18d320a5622218d5c493fedf5689", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "yolov2Block": 0, + "yolov3Block": 0, + "londonBlock": 0 + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x30000", + "extraData": + "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "timestamp": "0x1234", + "alloc": { + "cf49fda3be353c69b41ed96333cd24302da4556f": { + "balance": "0x123450000000000000000" + }, + "0161e041aad467a890839d5b08b138c1e6373072": { + "balance": "0x123450000000000000000" + }, + "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { + "balance": "0x123450000000000000000" + }, + "b97de4b8c857e4f6bc354f226dc3249aaee49209": { + "balance": "0x123450000000000000000" + }, + "c5065c9eeebe6df2c2284d046bfc906501846c51": { + "balance": "0x123450000000000000000" + }, + "0000000000000000000000000000000000000314": { + "balance": "0x0", + "code": + "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029" + , "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", + "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" + } + }, + "0000000000000000000000000000000000000315": { + "balance": "0x9999999999999999999999999999999", + "code": + "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" + }, + "0000000000000000000000000000000000000316": { + "balance": "0x0", + "code": "0x444355" + }, + "0000000000000000000000000000000000000317": { + "balance": "0x0", + "code": "0x600160003555" + } + } + } + "#; + + let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + } + + #[test] + fn parse_hive_devp2p_genesis() { + let geth_genesis = r#" + { + "config": { + "chainId": 19763, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "ethash": {} + }, + "nonce": "0xdeadbeefdeadbeef", + "timestamp": "0x0", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x80000000", + "difficulty": "0x20000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "71562b71999873db5b286df957af199ec94617f7": { + "balance": "0xffffffffffffffffffffffffff" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + "#; + + let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + } + + #[test] + fn parse_execution_apis_genesis() { + let geth_genesis = r#" + { + "config": { + "chainId": 1337, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "shanghaiTime": 0, + "terminalTotalDifficulty": 0, + "terminalTotalDifficultyPassed": true, + "ethash": {} + }, + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x", + "gasLimit": "0x4c4b40", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "658bdf435d810c91414ec09147daa6db62406379": { + "balance": "0x487a9a304539440000" + }, + "aa00000000000000000000000000000000000000": { + "code": "0x6042", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0100000000000000000000000000000000000000000000000000000000000000": + "0x0100000000000000000000000000000000000000000000000000000000000000", + "0x0200000000000000000000000000000000000000000000000000000000000000": + "0x0200000000000000000000000000000000000000000000000000000000000000", + "0x0300000000000000000000000000000000000000000000000000000000000000": + "0x0000000000000000000000000000000000000000000000000000000000000303" }, + "balance": "0x1", + "nonce": "0x1" + }, + "bb00000000000000000000000000000000000000": { + "code": "0x600154600354", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": + "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0100000000000000000000000000000000000000000000000000000000000000": + "0x0100000000000000000000000000000000000000000000000000000000000000", + "0x0200000000000000000000000000000000000000000000000000000000000000": + "0x0200000000000000000000000000000000000000000000000000000000000000", + "0x0300000000000000000000000000000000000000000000000000000000000000": + "0x0000000000000000000000000000000000000000000000000000000000000303" }, + "balance": "0x2", + "nonce": "0x1" + } + } + } + "#; + + let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + } + + #[test] + fn parse_hive_rpc_genesis_full() { + let geth_genesis = r#" + { + "config": { + "clique": { + "period": 1 + }, + "chainId": 7, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0 + }, + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x020000", + "extraData": + "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + , "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "timestamp": "0x1234", + "alloc": { + "cf49fda3be353c69b41ed96333cd24302da4556f": { + "balance": "0x123450000000000000000" + }, + "0161e041aad467a890839d5b08b138c1e6373072": { + "balance": "0x123450000000000000000" + }, + "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { + "balance": "0x123450000000000000000" + }, + "b97de4b8c857e4f6bc354f226dc3249aaee49209": { + "balance": "0x123450000000000000000" + }, + "c5065c9eeebe6df2c2284d046bfc906501846c51": { + "balance": "0x123450000000000000000" + }, + "0000000000000000000000000000000000000314": { + "balance": "0x0", + "code": + "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029" + , "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", + "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" + } + }, + "0000000000000000000000000000000000000315": { + "balance": "0x9999999999999999999999999999999", + "code": + "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" + } + }, + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + "#; + + let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap(); + let alloc_entry = genesis + .alloc + .get(&Address::from_str("0000000000000000000000000000000000000314").unwrap()) + .expect("missing account for parsed genesis"); + let storage = alloc_entry.storage.as_ref().expect("missing storage for parsed genesis"); + let expected_storage = HashMap::from_iter(vec![ + ( + H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000000", + ) + .unwrap(), + H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000001234", + ) + .unwrap(), + ), + ( + H256::from_str( + "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9", + ) + .unwrap(), + H256::from_str( + "0x0000000000000000000000000000000000000000000000000000000000000001", + ) + .unwrap(), + ), + ]); + assert_eq!(storage, &expected_storage); + + let expected_code = + Bytes::from_str("0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029" + ).unwrap(); + let code = alloc_entry.code.as_ref().expect( + "missing code for parsed + genesis", + ); + assert_eq!(code, &expected_code); + } + + #[test] + fn test_hive_smoke_alloc_deserialize() { + let hive_genesis = r#" + { + "nonce": "0x0000000000000042", + "difficulty": "0x2123456", + "mixHash": "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234", + "coinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "timestamp": "0x123456", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0xfafbfcfd", + "gasLimit": "0x2fefd8", + "alloc": { + "dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6": { + "balance": "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + }, + "e6716f9544a56c530d868e4bfbacb172315bdead": { + "balance": "0x11", + "code": "0x12" + }, + "b9c015918bdaba24b4ff057a92a3873d6eb201be": { + "balance": "0x21", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22" + } + }, + "1a26338f0d905e295fccb71fa9ea849ffa12aaf4": { + "balance": "0x31", + "nonce": "0x32" + }, + "0000000000000000000000000000000000000001": { + "balance": "0x41" + }, + "0000000000000000000000000000000000000002": { + "balance": "0x51" + }, + "0000000000000000000000000000000000000003": { + "balance": "0x61" + }, + "0000000000000000000000000000000000000004": { + "balance": "0x71" + } + }, + "config": { + "ethash": {}, + "chainId": 10, + "homesteadBlock": 0, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0 + } + } + "#; + + let expected_genesis = + Genesis { + nonce: 0x0000000000000042, + difficulty: U256::from(0x2123456), + mix_hash: H256::from_str( + "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234", + ) + .unwrap(), + coinbase: Address::from_str("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").unwrap(), + timestamp: 0x123456, + extra_data: Bytes::from_str("0xfafbfcfd").unwrap(), + gas_limit: 0x2fefd8, + alloc: HashMap::from_iter(vec![ + ( + Address::from_str("0xdbdbdb2cbd23b783741e8d7fcf51e459b497e4a6").unwrap(), + GenesisAccount { + balance: + U256::from_str("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"). + unwrap(), nonce: None, + code: None, + storage: None, + }, + ), + ( + Address::from_str("0xe6716f9544a56c530d868e4bfbacb172315bdead").unwrap(), + GenesisAccount { + balance: U256::from_str("0x11").unwrap(), + nonce: None, + code: Some(Bytes::from_str("0x12").unwrap()), + storage: None, + }, + ), + ( + Address::from_str("0xb9c015918bdaba24b4ff057a92a3873d6eb201be").unwrap(), + GenesisAccount { + balance: U256::from_str("0x21").unwrap(), + nonce: None, + code: None, + storage: Some(HashMap::from_iter(vec![ + ( + + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001"). + unwrap(), + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000022"). + unwrap(), ), + ])), + }, + ), + ( + Address::from_str("0x1a26338f0d905e295fccb71fa9ea849ffa12aaf4").unwrap(), + GenesisAccount { + balance: U256::from_str("0x31").unwrap(), + nonce: Some(0x32u64), + code: None, + storage: None, + }, + ), + ( + Address::from_str("0x0000000000000000000000000000000000000001").unwrap(), + GenesisAccount { + balance: U256::from_str("0x41").unwrap(), + nonce: None, + code: None, + storage: None, + }, + ), + ( + Address::from_str("0x0000000000000000000000000000000000000002").unwrap(), + GenesisAccount { + balance: U256::from_str("0x51").unwrap(), + nonce: None, + code: None, + storage: None, + }, + ), + ( + Address::from_str("0x0000000000000000000000000000000000000003").unwrap(), + GenesisAccount { + balance: U256::from_str("0x61").unwrap(), + nonce: None, + code: None, + storage: None, + }, + ), + ( + Address::from_str("0x0000000000000000000000000000000000000004").unwrap(), + GenesisAccount { + balance: U256::from_str("0x71").unwrap(), + nonce: None, + code: None, + storage: None, + }, + ), + ]), + config: ChainConfig { + ethash: Some(EthashConfig {}), + chain_id: 10, + homestead_block: Some(0), + eip150_block: Some(0), + eip155_block: Some(0), + eip158_block: Some(0), + byzantium_block: Some(0), + constantinople_block: Some(0), + petersburg_block: Some(0), + istanbul_block: Some(0), + ..Default::default() + }, + }; + + let deserialized_genesis: Genesis = serde_json::from_str(hive_genesis).unwrap(); + assert_eq!( + deserialized_genesis, expected_genesis, + "deserialized genesis + {deserialized_genesis:#?} does not match expected {expected_genesis:#?}" + ); + } } diff --git a/crates/primitives/src/serde_helper/jsonu256.rs b/crates/primitives/src/serde_helper/jsonu256.rs index 3cf187f1f37e..5b854451892a 100644 --- a/crates/primitives/src/serde_helper/jsonu256.rs +++ b/crates/primitives/src/serde_helper/jsonu256.rs @@ -5,7 +5,7 @@ use serde::{ }; use std::{fmt, str::FromStr}; -/// Wrapper around primitive U256 type to handle edge cases of json parser +/// Wrapper around primitive U256 type that also supports deserializing numbers #[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub struct JsonU256(pub U256); @@ -90,6 +90,15 @@ where Ok(num.into()) } +/// Supports parsing `U256` numbers as strings via [JsonU256] +pub fn deserialize_json_u256_opt<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let num = Option::::deserialize(deserializer)?; + Ok(num.map(Into::into)) +} + #[cfg(test)] mod test { use super::JsonU256; diff --git a/crates/primitives/src/serde_helper/mod.rs b/crates/primitives/src/serde_helper/mod.rs index f2018531fae2..c63e7f62756f 100644 --- a/crates/primitives/src/serde_helper/mod.rs +++ b/crates/primitives/src/serde_helper/mod.rs @@ -1,9 +1,9 @@ //! Various serde utilities -mod storage_key; +mod storage; use serde::Serializer; -pub use storage_key::*; +pub use storage::*; mod jsonu256; use crate::H256; diff --git a/crates/primitives/src/serde_helper/storage_key.rs b/crates/primitives/src/serde_helper/storage.rs similarity index 60% rename from crates/primitives/src/serde_helper/storage_key.rs rename to crates/primitives/src/serde_helper/storage.rs index 79402765e0d5..aa5e012874b8 100644 --- a/crates/primitives/src/serde_helper/storage_key.rs +++ b/crates/primitives/src/serde_helper/storage.rs @@ -1,6 +1,6 @@ -use crate::{H256, U256}; -use serde::{Deserialize, Serialize}; -use std::fmt::Write; +use crate::{Bytes, H256, U256}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{collections::HashMap, fmt::Write}; /// A storage key type that can be serialized to and from a hex string up to 32 bytes. Used for /// `eth_getStorageAt` and `eth_getProof` RPCs. @@ -53,3 +53,50 @@ impl From for String { hex } } + +/// Converts a Bytes value into a H256, accepting inputs that are less than 32 bytes long. These +/// inputs will be left padded with zeros. +pub fn from_bytes_to_h256<'de, D>(bytes: Bytes) -> Result +where + D: Deserializer<'de>, +{ + if bytes.0.len() > 32 { + return Err(serde::de::Error::custom("input too long to be a H256")) + } + + // left pad with zeros to 32 bytes + let mut padded = [0u8; 32]; + padded[32 - bytes.0.len()..].copy_from_slice(&bytes.0); + + // then convert to H256 without a panic + Ok(H256::from_slice(&padded)) +} + +/// Deserializes the input into an Option>, using [from_bytes_to_h256] which +/// allows cropped values: +/// +/// ```json +/// { +/// "0x0000000000000000000000000000000000000000000000000000000000000001": "0x22" +/// } +/// ``` +pub fn deserialize_storage_map<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let map = Option::>::deserialize(deserializer)?; + match map { + Some(mut map) => { + let mut res_map = HashMap::with_capacity(map.len()); + for (k, v) in map.drain() { + let k_deserialized = from_bytes_to_h256::<'de, D>(k)?; + let v_deserialized = from_bytes_to_h256::<'de, D>(v)?; + res_map.insert(k_deserialized, v_deserialized); + } + Ok(Some(res_map)) + } + None => Ok(None), + } +} diff --git a/crates/staged-sync/tests/sync.rs b/crates/staged-sync/tests/sync.rs index 0b64f0e5ab85..66ef0f763bbb 100644 --- a/crates/staged-sync/tests/sync.rs +++ b/crates/staged-sync/tests/sync.rs @@ -8,7 +8,7 @@ use reth_network::{ NetworkConfig, NetworkManager, }; use reth_network_api::Peers; -use reth_primitives::{ChainSpec, PeerId, SealedHeader}; +use reth_primitives::{ChainSpec, Genesis, PeerId, SealedHeader}; use reth_provider::test_utils::NoopProvider; use reth_staged_sync::test_utils::{CliqueGethInstance, CliqueMiddleware}; use secp256k1::SecretKey; @@ -98,12 +98,10 @@ async fn init_geth() -> (CliqueGethInstance, Arc) { // === check that we have the same genesis hash === // get the chainspec from the genesis we configured for geth - let chainspec: ChainSpec = clique - .instance - .genesis() - .clone() - .expect("clique should be configured with a genesis") - .into(); + let chainspec = ChainSpec::from(Genesis::from( + clique.instance.genesis().clone().expect("clique should be configured with a genesis"), + )); + let remote_genesis = SealedHeader::from(&clique.provider.remote_genesis_block().await.unwrap()); let local_genesis = chainspec.genesis_header().seal(chainspec.genesis_hash()); From 94d0f3e9edcee6bf825d5b349d889f6ef562d1dd Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 16 Jun 2023 17:02:32 +0200 Subject: [PATCH 058/216] chore: better highlight panicked task name (#3205) --- crates/tasks/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index 4f7e61b6b940..79ce940279b2 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -183,7 +183,7 @@ impl Future for TaskManager { /// Error with the name of the task that panicked. #[derive(Debug, thiserror::Error)] -#[error("Critical task panicked {0}")] +#[error("Critical task panicked: `{0}`")] pub struct PanickedTaskError(&'static str); /// A type that can spawn new tokio tasks From bd9b993215db42e555cf400bc8b32a5c43be0e51 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 16 Jun 2023 18:04:17 +0200 Subject: [PATCH 059/216] test: more js testing support (#3211) --- .../assets/tracer-template.js | 24 ++++ crates/rpc/rpc-testing-util/src/debug.rs | 123 +++++++++++++++++- 2 files changed, 143 insertions(+), 4 deletions(-) create mode 100644 crates/rpc/rpc-testing-util/assets/tracer-template.js diff --git a/crates/rpc/rpc-testing-util/assets/tracer-template.js b/crates/rpc/rpc-testing-util/assets/tracer-template.js new file mode 100644 index 000000000000..29e033dc7353 --- /dev/null +++ b/crates/rpc/rpc-testing-util/assets/tracer-template.js @@ -0,0 +1,24 @@ +{ + // called once + setup: function(cfg) { + // + }, + // required function that is invoked when a step fails + fault: function(log, db) { + // + }, + // required function that returns the result of the tracer: empty object + result: function(ctx, db) { + // + }, + // optional function that is invoked for every opcode + step: function(log, db) { + // + }, + enter: function(frame) { + // + }, + exit: function(res) { + // + } +} \ No newline at end of file diff --git a/crates/rpc/rpc-testing-util/src/debug.rs b/crates/rpc/rpc-testing-util/src/debug.rs index da94fb3eead3..2aad67e1754f 100644 --- a/crates/rpc/rpc-testing-util/src/debug.rs +++ b/crates/rpc/rpc-testing-util/src/debug.rs @@ -5,6 +5,7 @@ use reth_rpc_api::clients::DebugApiClient; use reth_rpc_types::trace::geth::{GethDebugTracerType, GethDebugTracingOptions}; const NOOP_TRACER: &str = include_str!("../assets/noop-tracer.js"); +const JS_TRACER_TEMPLATE: &str = include_str!("../assets/tracer-template.js"); /// An extension trait for the Trace API. #[async_trait::async_trait] @@ -36,6 +37,106 @@ impl DebugApiExt for T { } } +/// A helper type that can be used to build a javascript tracer. +#[derive(Debug, Clone, Default)] +pub struct JsTracerBuilder { + setup_body: Option, + fault_body: Option, + result_body: Option, + enter_body: Option, + step_body: Option, + exit_body: Option, +} + +impl JsTracerBuilder { + /// Sets the body of the fault function + /// + /// The body code has access to the `log` and `db` variables. + pub fn fault_body(mut self, body: impl Into) -> Self { + self.fault_body = Some(body.into()); + self + } + + /// Sets the body of the setup function + /// + /// This body includes the `cfg` object variable + pub fn setup_body(mut self, body: impl Into) -> Self { + self.setup_body = Some(body.into()); + self + } + + /// Sets the body of the result function + /// + /// The body code has access to the `ctx` and `db` variables. + /// + /// ``` + /// use reth_rpc_api_testing_util::debug::JsTracerBuilder; + /// let code = JsTracerBuilder::default().result_body("return {};").code(); + /// ``` + pub fn result_body(mut self, body: impl Into) -> Self { + self.result_body = Some(body.into()); + self + } + + /// Sets the body of the enter function + /// + /// The body code has access to the `frame` variable. + pub fn enter_body(mut self, body: impl Into) -> Self { + self.enter_body = Some(body.into()); + self + } + + /// Sets the body of the step function + /// + /// The body code has access to the `log` and `db` variables. + pub fn step_body(mut self, body: impl Into) -> Self { + self.step_body = Some(body.into()); + self + } + + /// Sets the body of the exit function + /// + /// The body code has access to the `res` variable. + pub fn exit_body(mut self, body: impl Into) -> Self { + self.exit_body = Some(body.into()); + self + } + + /// Returns the tracers JS code + pub fn code(self) -> String { + let mut template = JS_TRACER_TEMPLATE.to_string(); + template = template.replace("//", self.setup_body.as_deref().unwrap_or_default()); + template = template.replace("//", self.fault_body.as_deref().unwrap_or_default()); + template = + template.replace("//", self.result_body.as_deref().unwrap_or("return {};")); + template = template.replace("//", self.step_body.as_deref().unwrap_or_default()); + template = template.replace("//", self.enter_body.as_deref().unwrap_or_default()); + template = template.replace("//", self.exit_body.as_deref().unwrap_or_default()); + template + } +} + +impl std::fmt::Display for JsTracerBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.clone().code()) + } +} + +impl From for GethDebugTracingOptions { + fn from(b: JsTracerBuilder) -> Self { + GethDebugTracingOptions { + tracer: Some(GethDebugTracerType::JsTracer(b.code())), + tracer_config: serde_json::Value::Object(Default::default()).into(), + ..Default::default() + } + } +} +impl From for Option { + fn from(b: JsTracerBuilder) -> Self { + Some(b.into()) + } +} + /// A javascript tracer that does nothing #[derive(Debug, Clone, Copy, Default)] #[non_exhaustive] @@ -59,21 +160,35 @@ impl From for Option { #[cfg(test)] mod tests { use crate::{ - debug::{DebugApiExt, NoopJsTracer}, + debug::{DebugApiExt, JsTracerBuilder, NoopJsTracer}, utils::parse_env_url, }; use jsonrpsee::http_client::HttpClientBuilder; + // random tx + const TX_1: &str = "0x5525c63a805df2b83c113ebcc8c7672a3b290673c4e81335b410cd9ebc64e085"; + #[tokio::test] #[ignore] async fn can_trace_noop_sepolia() { - // random tx - let tx = - "0x5525c63a805df2b83c113ebcc8c7672a3b290673c4e81335b410cd9ebc64e085".parse().unwrap(); + let tx = TX_1.parse().unwrap(); let url = parse_env_url("RETH_RPC_TEST_NODE_URL").unwrap(); let client = HttpClientBuilder::default().build(url).unwrap(); let res = client.debug_trace_transaction_json(tx, NoopJsTracer::default().into()).await.unwrap(); assert_eq!(res, serde_json::Value::Object(Default::default())); } + + #[tokio::test] + #[ignore] + async fn can_trace_default_template() { + let tx = TX_1.parse().unwrap(); + let url = parse_env_url("RETH_RPC_TEST_NODE_URL").unwrap(); + let client = HttpClientBuilder::default().build(url).unwrap(); + let res = client + .debug_trace_transaction_json(tx, JsTracerBuilder::default().into()) + .await + .unwrap(); + assert_eq!(res, serde_json::Value::Object(Default::default())); + } } From 6d81d0c9d1283020cd40cf0e27da17f35d4fa8c5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 16 Jun 2023 23:35:41 +0200 Subject: [PATCH 060/216] fix(rpc): enter,exit functions dont take db object (#3207) --- .../revm-inspectors/src/tracing/js/mod.rs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/js/mod.rs b/crates/revm/revm-inspectors/src/tracing/js/mod.rs index a80a76cb993b..c6b6ad0c5c5a 100644 --- a/crates/revm/revm-inspectors/src/tracing/js/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/js/mod.rs @@ -220,20 +220,18 @@ impl JsInspector { Ok(()) } - fn try_enter(&mut self, frame: CallFrame, db: EvmDb) -> JsResult<()> { + fn try_enter(&mut self, frame: CallFrame) -> JsResult<()> { if let Some(enter_fn) = &self.enter_fn { let frame = frame.into_js_object(&mut self.ctx)?; - let db = db.into_js_object(&mut self.ctx)?; - enter_fn.call(&(self.obj.clone().into()), &[frame.into(), db.into()], &mut self.ctx)?; + enter_fn.call(&(self.obj.clone().into()), &[frame.into()], &mut self.ctx)?; } Ok(()) } - fn try_exit(&mut self, frame: FrameResult, db: EvmDb) -> JsResult<()> { + fn try_exit(&mut self, frame: FrameResult) -> JsResult<()> { if let Some(exit_fn) = &self.exit_fn { let frame = frame.into_js_object(&mut self.ctx)?; - let db = db.into_js_object(&mut self.ctx)?; - exit_fn.call(&(self.obj.clone().into()), &[frame.into(), db.into()], &mut self.ctx)?; + exit_fn.call(&(self.obj.clone().into()), &[frame.into()], &mut self.ctx)?; } Ok(()) } @@ -366,7 +364,7 @@ where fn call( &mut self, - data: &mut EVMData<'_, DB>, + _data: &mut EVMData<'_, DB>, inputs: &mut CallInputs, _is_static: bool, ) -> (InstructionResult, Gas, Bytes) { @@ -395,8 +393,7 @@ where kind: call.kind, gas: inputs.gas_limit, }; - let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); - if let Err(err) = self.try_enter(frame, db) { + if let Err(err) = self.try_enter(frame) { return (InstructionResult::Revert, Gas::new(0), err.to_string().into()) } } @@ -406,7 +403,7 @@ where fn call_end( &mut self, - data: &mut EVMData<'_, DB>, + _data: &mut EVMData<'_, DB>, _inputs: &CallInputs, remaining_gas: Gas, ret: InstructionResult, @@ -416,8 +413,7 @@ where if self.exit_fn.is_some() { let frame_result = FrameResult { gas_used: remaining_gas.spend(), output: out.clone(), error: None }; - let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); - if let Err(err) = self.try_exit(frame_result, db) { + if let Err(err) = self.try_exit(frame_result) { return (InstructionResult::Revert, Gas::new(0), err.to_string().into()) } } @@ -448,8 +444,7 @@ where let call = self.active_call(); let frame = CallFrame { contract: call.contract.clone(), kind: call.kind, gas: call.gas_limit }; - let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); - if let Err(err) = self.try_enter(frame, db) { + if let Err(err) = self.try_enter(frame) { return (InstructionResult::Revert, None, Gas::new(0), err.to_string().into()) } } @@ -459,7 +454,7 @@ where fn create_end( &mut self, - data: &mut EVMData<'_, DB>, + _data: &mut EVMData<'_, DB>, _inputs: &CreateInputs, ret: InstructionResult, address: Option, @@ -469,8 +464,7 @@ where if self.exit_fn.is_some() { let frame_result = FrameResult { gas_used: remaining_gas.spend(), output: out.clone(), error: None }; - let db = EvmDb::new(data.journaled_state.state.clone(), self.to_db_service.clone()); - if let Err(err) = self.try_exit(frame_result, db) { + if let Err(err) = self.try_exit(frame_result) { return (InstructionResult::Revert, None, Gas::new(0), err.to_string().into()) } } @@ -485,8 +479,7 @@ where let call = self.active_call(); let frame = CallFrame { contract: call.contract.clone(), kind: call.kind, gas: call.gas_limit }; - let db = EvmDb::new(Default::default(), self.to_db_service.clone()); - let _ = self.try_enter(frame, db); + let _ = self.try_enter(frame); } } } From 348076ccedfba28029c8dcafd3807171cbac3b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marius=20Kj=C3=A6rstad?= Date: Sat, 17 Jun 2023 01:49:16 +0200 Subject: [PATCH 061/216] Typo in db.md (#3213) --- docs/crates/db.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/crates/db.md b/docs/crates/db.md index a505f28ffa56..79f93b1efad4 100644 --- a/docs/crates/db.md +++ b/docs/crates/db.md @@ -104,7 +104,7 @@ pub trait Database: for<'a> DatabaseGAT<'a> { } ``` -Any type that implements the `Database` trait can create a database transaction, as well as view or update existing transactions. As an example, lets revisit the `Transaction` struct from the `stages` crate. This struct contains a field named `db` which is a reference to a generic type `DB` that implements the `Database` trait. The `Transaction` struct can use the `db` field to store new headers, bodies and senders in the database. In the code snippet below, you can see the `Transaction::open()` method, which uses the `Database::tx_mut()` function to create a mutable transaction. +Any type that implements the `Database` trait can create a database transaction, as well as view or update existing transactions. As an example, let's revisit the `Transaction` struct from the `stages` crate. This struct contains a field named `db` which is a reference to a generic type `DB` that implements the `Database` trait. The `Transaction` struct can use the `db` field to store new headers, bodies and senders in the database. In the code snippet below, you can see the `Transaction::open()` method, which uses the `Database::tx_mut()` function to create a mutable transaction. [File: crates/stages/src/db.rs](https://github.com/paradigmxyz/reth/blob/main/crates/stages/src/db.rs#L95-L98) From d6092cf1d3bf6dcd274815ce12c15726293a529b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 17 Jun 2023 02:46:19 +0200 Subject: [PATCH 062/216] feat: support nonce and balance changes in state diff tracers (#3199) --- .../src/tracing/builder/mod.rs | 7 +- .../src/tracing/builder/parity.rs | 79 +++++++++++++++- .../revm/revm-inspectors/src/tracing/mod.rs | 5 +- .../revm/revm-inspectors/src/tracing/types.rs | 2 - crates/rpc/rpc/src/eth/api/transactions.rs | 60 +++++++++++- crates/rpc/rpc/src/trace.rs | 93 +++++++++++++------ 6 files changed, 208 insertions(+), 38 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/builder/mod.rs b/crates/revm/revm-inspectors/src/tracing/builder/mod.rs index a39a31913eec..677ae88da5e0 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/mod.rs @@ -1,4 +1,7 @@ //! Builder types for building traces -pub(crate) mod geth; -pub(crate) mod parity; +/// Geth style trace builders for `debug_` namespace +pub mod geth; + +/// Parity style trace builders for `trace_` namespace +pub mod parity; diff --git a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs index 82ee58ba0733..8f0fdb43ebcb 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs @@ -1,7 +1,10 @@ use crate::tracing::{types::CallTraceNode, TracingInspectorConfig}; -use reth_primitives::Address; +use reth_primitives::{Address, U64}; use reth_rpc_types::{trace::parity::*, TransactionInfo}; -use revm::primitives::ExecutionResult; +use revm::{ + db::DatabaseRef, + primitives::{AccountInfo, ExecutionResult, ResultAndState}, +}; use std::collections::HashSet; /// A type for creating parity style traces @@ -91,6 +94,12 @@ impl ParityTraceBuilder { /// Consumes the inspector and returns the trace results according to the configured trace /// types. + /// + /// Warning: If `trace_types` contains [TraceType::StateDiff] the returned [StateDiff] will only + /// contain accounts with changed state, not including their balance changes because this is not + /// tracked during inspection and requires the State map returned after inspection. Use + /// [ParityTraceBuilder::into_trace_results_with_state] to populate the balance and nonce + /// changes for the [StateDiff] using the [DatabaseRef]. pub fn into_trace_results( self, res: ExecutionResult, @@ -107,6 +116,36 @@ impl ParityTraceBuilder { TraceResults { output: output.into(), trace, vm_trace, state_diff } } + /// Consumes the inspector and returns the trace results according to the configured trace + /// types. + /// + /// This also takes the [DatabaseRef] to populate the balance and nonce changes for the + /// [StateDiff]. + /// + /// Note: this is considered a convenience method that takes the state map of + /// [ResultAndState] after inspecting a transaction + /// with the [TracingInspector](crate::tracing::TracingInspector). + pub fn into_trace_results_with_state( + self, + res: ResultAndState, + trace_types: &HashSet, + db: DB, + ) -> Result + where + DB: DatabaseRef, + { + let ResultAndState { result, state } = res; + let mut trace_res = self.into_trace_results(result, trace_types); + if let Some(ref mut state_diff) = trace_res.state_diff { + populate_account_balance_nonce_diffs( + state_diff, + &db, + state.into_iter().map(|(addr, acc)| (addr, acc.info)), + )?; + } + Ok(trace_res) + } + /// Returns the tracing types that are configured in the set pub fn into_trace_type_traces( self, @@ -172,3 +211,39 @@ fn vm_trace(nodes: &[CallTraceNode]) -> VmTrace { VmTrace { code: nodes[0].trace.data.clone().into(), ops: vec![] } } + +/// Loops over all state accounts in the accounts diff that contains all accounts that are included +/// in the [ExecutionResult] state map and compares the balance and nonce against what's in the +/// `db`, which should point to the beginning of the transaction. +/// +/// It's expected that `DB` is a [CacheDB](revm::db::CacheDB) which at this point already contains +/// all the accounts that are in the state map and never has to fetch them from disk. +pub fn populate_account_balance_nonce_diffs( + state_diff: &mut StateDiff, + db: DB, + account_diffs: I, +) -> Result<(), DB::Error> +where + I: IntoIterator, + DB: DatabaseRef, +{ + for (addr, changed_acc) in account_diffs.into_iter() { + let entry = state_diff.entry(addr).or_default(); + let db_acc = db.basic(addr)?.unwrap_or_default(); + entry.balance = if db_acc.balance == changed_acc.balance { + Delta::Unchanged + } else { + Delta::Changed(ChangedType { from: db_acc.balance, to: changed_acc.balance }) + }; + entry.nonce = if db_acc.nonce == changed_acc.nonce { + Delta::Unchanged + } else { + Delta::Changed(ChangedType { + from: U64::from(db_acc.nonce), + to: U64::from(changed_acc.nonce), + }) + }; + } + + Ok(()) +} diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index 0ea99f90dd35..c9b727ea1fd6 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -25,7 +25,10 @@ use crate::tracing::{ arena::PushTraceKind, types::{CallTraceNode, StorageChange}, }; -pub use builder::{geth::GethTraceBuilder, parity::ParityTraceBuilder}; +pub use builder::{ + geth::{self, GethTraceBuilder}, + parity::{self, ParityTraceBuilder}, +}; pub use config::TracingInspectorConfig; pub use fourbyte::FourByteInspector; pub use opcount::OpcodeCountInspector; diff --git a/crates/revm/revm-inspectors/src/tracing/types.rs b/crates/revm/revm-inspectors/src/tracing/types.rs index bcd6f0872773..85b28052a77f 100644 --- a/crates/revm/revm-inspectors/src/tracing/types.rs +++ b/crates/revm/revm-inspectors/src/tracing/types.rs @@ -268,8 +268,6 @@ impl CallTraceNode { } } - // TODO: track nonce and balance changes - // iterate over all storage diffs for change in self.trace.steps.iter().filter_map(|s| s.storage_change) { let StorageChange { key, value, had_value } = change; diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 0314a6d7c708..1c5a512c5668 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -173,6 +173,23 @@ pub trait EthTransactions: Send + Sync { where F: FnOnce(TracingInspector, ResultAndState) -> EthResult; + /// Same as [Self::trace_at] but also provides the used database to the callback. + /// + /// Executes the transaction on top of the given [BlockId] with a tracer configured by the + /// config. + /// + /// The callback is then called with the [TracingInspector] and the [ResultAndState] after the + /// configured [Env] was inspected. + fn trace_at_with_state( + &self, + env: Env, + config: TracingInspectorConfig, + at: BlockId, + f: F, + ) -> EthResult + where + F: for<'a> FnOnce(TracingInspector, ResultAndState, StateCacheDB<'a>) -> EthResult; + /// Fetches the transaction and the transaction's block async fn transaction_and_block( &self, @@ -182,7 +199,9 @@ pub trait EthTransactions: Send + Sync { /// Retrieves the transaction if it exists and returns its trace. /// /// Before the transaction is traced, all previous transaction in the block are applied to the - /// state by executing them first + /// state by executing them first. + /// The callback `f` is invoked with the [ResultAndState] after the transaction was executed and + /// the database that points to the beginning of the transaction. async fn trace_transaction_in_block( &self, hash: H256, @@ -190,7 +209,13 @@ pub trait EthTransactions: Send + Sync { f: F, ) -> EthResult> where - F: FnOnce(TransactionInfo, TracingInspector, ResultAndState) -> EthResult + Send; + F: for<'a> FnOnce( + TransactionInfo, + TracingInspector, + ResultAndState, + StateCacheDB<'a>, + ) -> EthResult + + Send; } #[async_trait] @@ -541,6 +566,25 @@ where }) } + fn trace_at_with_state( + &self, + env: Env, + config: TracingInspectorConfig, + at: BlockId, + f: F, + ) -> EthResult + where + F: for<'a> FnOnce(TracingInspector, ResultAndState, StateCacheDB<'a>) -> EthResult, + { + self.with_state_at_block(at, |state| { + let db = SubState::new(State::new(state)); + let mut inspector = TracingInspector::new(config); + let (res, _, db) = inspect_and_return_db(db, env, &mut inspector)?; + + f(inspector, res, db) + }) + } + async fn transaction_and_block( &self, hash: H256, @@ -566,7 +610,13 @@ where f: F, ) -> EthResult> where - F: FnOnce(TransactionInfo, TracingInspector, ResultAndState) -> EthResult + Send, + F: for<'a> FnOnce( + TransactionInfo, + TracingInspector, + ResultAndState, + StateCacheDB<'a>, + ) -> EthResult + + Send, { let (transaction, block) = match self.transaction_and_block(hash).await? { None => return Ok(None), @@ -590,8 +640,8 @@ where let env = Env { cfg, block: block_env, tx: tx_env_with_recovered(&tx) }; let mut inspector = TracingInspector::new(config); - let (res, _) = inspect(db, env, &mut inspector)?; - f(tx_info, inspector, res) + let (res, _, db) = inspect_and_return_db(db, env, &mut inspector)?; + f(tx_info, inspector, res, db) }) .map(Some) } diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 45ff282eb64e..8b61a9f1087f 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -12,11 +12,13 @@ use crate::{ use async_trait::async_trait; use jsonrpsee::core::RpcResult as Result; use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256}; -use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderFactory}; +use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, - tracing::{TracingInspector, TracingInspectorConfig}, + tracing::{ + parity::populate_account_balance_nonce_diffs, TracingInspector, TracingInspectorConfig, + }, }; use reth_rpc_api::TraceApiServer; use reth_rpc_types::{ @@ -25,8 +27,8 @@ use reth_rpc_types::{ BlockError, BlockOverrides, CallRequest, Index, TransactionInfo, }; use reth_tasks::TaskSpawner; -use revm::primitives::Env; -use revm_primitives::{db::DatabaseCommit, ExecutionResult}; +use revm::{db::CacheDB, primitives::Env}; +use revm_primitives::{db::DatabaseCommit, ExecutionResult, ResultAndState}; use std::{collections::HashSet, future::Future, sync::Arc}; use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit}; @@ -127,11 +129,18 @@ where let config = tracing_config(&trace_types); let mut inspector = TracingInspector::new(config); - let (res, _) = - self.inner.eth_api.inspect_call_at(call, at, overrides, &mut inspector).await?; + let (res, _, db) = self + .inner + .eth_api + .inspect_call_at_and_return_state(call, at, overrides, &mut inspector) + .await?; + + let trace_res = inspector.into_parity_builder().into_trace_results_with_state( + res, + &trace_types, + &db, + )?; - let trace_res = - inspector.into_parity_builder().into_trace_results(res.result, &trace_types); Ok(trace_res) } @@ -155,10 +164,12 @@ where let config = tracing_config(&trace_types); self.on_blocking_task(|this| async move { - this.inner.eth_api.trace_at(env, config, at, |inspector, res| { - let trace_res = - inspector.into_parity_builder().into_trace_results(res.result, &trace_types); - Ok(trace_res) + this.inner.eth_api.trace_at_with_state(env, config, at, |inspector, res, db| { + Ok(inspector.into_parity_builder().into_trace_results_with_state( + res, + &trace_types, + &db, + )?) }) }) .await @@ -193,9 +204,11 @@ where let config = tracing_config(&trace_types); let mut inspector = TracingInspector::new(config); let (res, _) = inspect(&mut db, env, &mut inspector)?; - let trace_res = inspector - .into_parity_builder() - .into_trace_results(res.result, &trace_types); + let trace_res = inspector.into_parity_builder().into_trace_results_with_state( + res, + &trace_types, + &db, + )?; results.push(trace_res); } @@ -215,10 +228,12 @@ where self.on_blocking_task(|this| async move { this.inner .eth_api - .trace_transaction_in_block(hash, config, |_, inspector, res| { - let trace_res = inspector - .into_parity_builder() - .into_trace_results(res.result, &trace_types); + .trace_transaction_in_block(hash, config, |_, inspector, res, db| { + let trace_res = inspector.into_parity_builder().into_trace_results_with_state( + res, + &trace_types, + &db, + )?; Ok(trace_res) }) .await @@ -255,7 +270,7 @@ where .trace_transaction_in_block( hash, TracingInspectorConfig::default_parity(), - |tx_info, inspector, _| { + |tx_info, inspector, _, _| { let traces = inspector .into_parity_builder() .into_localized_transaction_traces(tx_info); @@ -268,6 +283,14 @@ where } /// Executes all transactions of a block and returns a list of callback results. + /// + /// This + /// 1. fetches all transactions of the block + /// 2. configures the EVM evn + /// 3. loops over all transactions and executes them + /// 4. calls the callback with the transaction info, the execution result, the changed state + /// _after_ the transaction [State] and the database that points to the state right _before_ the + /// transaction. async fn trace_block_with( &self, block_id: BlockId, @@ -275,7 +298,16 @@ where f: F, ) -> EthResult>> where - F: Fn(TransactionInfo, TracingInspector, ExecutionResult) -> EthResult + Send + 'static, + // This is the callback that's invoked for each transaction with + F: for<'a> Fn( + TransactionInfo, + TracingInspector, + ExecutionResult, + &'a revm_primitives::State, + &'a CacheDB>>, + ) -> EthResult + + Send + + 'static, R: Send + 'static, { let ((cfg, block_env, _), block) = futures::try_join!( @@ -320,14 +352,15 @@ where let mut inspector = TracingInspector::new(config); let (res, _) = inspect(&mut db, env, &mut inspector)?; - results.push(f(tx_info, inspector, res.result)?); + let ResultAndState { result, state } = res; + results.push(f(tx_info, inspector, result, &state, &db)?); // need to apply the state changes of this transaction before executing the // next transaction if transactions.peek().is_some() { // need to apply the state changes of this transaction before executing // the next transaction - db.commit(res.state) + db.commit(state) } } @@ -347,7 +380,7 @@ where .trace_block_with( block_id, TracingInspectorConfig::default_parity(), - |tx_info, inspector, _| { + |tx_info, inspector, _, _, _| { let traces = inspector.into_parity_builder().into_localized_transaction_traces(tx_info); Ok(traces) @@ -367,9 +400,17 @@ where self.trace_block_with( block_id, tracing_config(&trace_types), - move |tx_info, inspector, res| { - let full_trace = + move |tx_info, inspector, res, state, db| { + let mut full_trace = inspector.into_parity_builder().into_trace_results(res, &trace_types); + if let Some(ref mut state_diff) = full_trace.state_diff { + populate_account_balance_nonce_diffs( + state_diff, + db, + state.iter().map(|(addr, acc)| (*addr, acc.info.clone())), + )?; + } + let trace = TraceResultsWithTransactionHash { transaction_hash: tx_info.hash.expect("tx hash is set"), full_trace, From bb1ffd059eddde24b163d473aa5aa0966f8c10f9 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Sat, 17 Jun 2023 01:46:43 +0100 Subject: [PATCH 063/216] fix(bin): calculate ETA only if `EntitiesCheckpoint` is present (#3206) --- bin/reth/src/node/events.rs | 4 +- crates/primitives/src/stage/checkpoints.rs | 57 ++++++++-------------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/bin/reth/src/node/events.rs b/bin/reth/src/node/events.rs index f2e6a53a90b0..3187132b7e14 100644 --- a/bin/reth/src/node/events.rs +++ b/bin/reth/src/node/events.rs @@ -278,9 +278,9 @@ struct Eta { } impl Eta { - /// Update the ETA given the checkpoint. + /// Update the ETA given the checkpoint, if possible. fn update(&mut self, checkpoint: StageCheckpoint) { - let current = checkpoint.entities(); + let Some(current) = checkpoint.entities() else { return }; if let Some(last_checkpoint_time) = &self.last_checkpoint_time { let processed_since_last = current.processed - self.last_checkpoint.processed; diff --git a/crates/primitives/src/stage/checkpoints.rs b/crates/primitives/src/stage/checkpoints.rs index f6ee35c90198..c6de28ff63d4 100644 --- a/crates/primitives/src/stage/checkpoints.rs +++ b/crates/primitives/src/stage/checkpoints.rs @@ -217,48 +217,33 @@ impl StageCheckpoint { self } - /// Get the underlying [`EntitiesCheckpoint`] to determine the number of entities processed, and - /// the number of total entities to process. - pub fn entities(&self) -> EntitiesCheckpoint { - match self.stage_checkpoint { - Some( - StageUnitCheckpoint::Account(AccountHashingCheckpoint { - progress: entities, .. - }) | - StageUnitCheckpoint::Storage(StorageHashingCheckpoint { - progress: entities, .. - }) | - StageUnitCheckpoint::Entities(entities) | - StageUnitCheckpoint::Execution(ExecutionCheckpoint { progress: entities, .. }) | - StageUnitCheckpoint::Headers(HeadersCheckpoint { progress: entities, .. }) | - StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint { - progress: entities, - .. - }), - ) => entities, - None => EntitiesCheckpoint::default(), + /// Get the underlying [`EntitiesCheckpoint`], if any, to determine the number of entities + /// processed, and the number of total entities to process. + pub fn entities(&self) -> Option { + let Some(stage_checkpoint) = self.stage_checkpoint else { return None }; + + match stage_checkpoint { + StageUnitCheckpoint::Account(AccountHashingCheckpoint { + progress: entities, .. + }) | + StageUnitCheckpoint::Storage(StorageHashingCheckpoint { + progress: entities, .. + }) | + StageUnitCheckpoint::Entities(entities) | + StageUnitCheckpoint::Execution(ExecutionCheckpoint { progress: entities, .. }) | + StageUnitCheckpoint::Headers(HeadersCheckpoint { progress: entities, .. }) | + StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint { + progress: entities, + .. + }) => Some(entities), } } } impl Display for StageCheckpoint { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self.stage_checkpoint { - Some( - StageUnitCheckpoint::Account(AccountHashingCheckpoint { - progress: entities, .. - }) | - StageUnitCheckpoint::Storage(StorageHashingCheckpoint { - progress: entities, .. - }) | - StageUnitCheckpoint::Entities(entities) | - StageUnitCheckpoint::Execution(ExecutionCheckpoint { progress: entities, .. }) | - StageUnitCheckpoint::Headers(HeadersCheckpoint { progress: entities, .. }) | - StageUnitCheckpoint::IndexHistory(IndexHistoryCheckpoint { - progress: entities, - .. - }), - ) => entities.fmt(f), + match self.entities() { + Some(entities) => entities.fmt(f), None => write!(f, "{}", self.block_number), } } From 0d9e1f4997f927c2657224defc12062aa838b960 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Sat, 17 Jun 2023 01:58:16 +0100 Subject: [PATCH 064/216] chore: replaces `tx.get::` with provider methods (#3189) --- bin/reth/src/debug_cmd/execution.rs | 8 ++- bin/reth/src/debug_cmd/merkle.rs | 2 +- bin/reth/src/node/mod.rs | 70 +++++++++---------- bin/reth/src/stage/run.rs | 5 +- .../stages/benches/setup/account_hashing.rs | 2 +- crates/stages/benches/setup/mod.rs | 4 +- crates/stages/src/pipeline/mod.rs | 16 +++-- crates/stages/src/stage.rs | 7 +- crates/stages/src/stages/execution.rs | 44 ++++-------- crates/stages/src/stages/hashing_account.rs | 2 +- crates/stages/src/stages/headers.rs | 36 +++++----- crates/stages/src/stages/sender_recovery.rs | 21 +++--- crates/stages/src/stages/total_difficulty.rs | 41 ++++++----- crates/stages/src/stages/tx_lookup.rs | 24 +++---- crates/stages/src/test_utils/runner.rs | 2 + crates/stages/src/test_utils/test_db.rs | 17 +++-- crates/storage/provider/src/post_state/mod.rs | 19 ++--- .../provider/src/providers/database/mod.rs | 18 ++--- .../src/providers/database/provider.rs | 20 ++---- crates/storage/provider/src/providers/mod.rs | 4 ++ .../storage/provider/src/test_utils/mock.rs | 4 ++ .../storage/provider/src/test_utils/noop.rs | 4 ++ .../provider/src/traits/transactions.rs | 5 ++ crates/storage/provider/src/transaction.rs | 3 + 24 files changed, 185 insertions(+), 193 deletions(-) diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index 82e62f9253ea..3aeab6911ca0 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -26,7 +26,7 @@ use reth_interfaces::{ use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256}; -use reth_provider::{providers::get_stage_checkpoint, ProviderFactory}; +use reth_provider::{ProviderFactory, StageCheckpointProvider}; use reth_staged_sync::utils::init::{init_db, init_genesis}; use reth_stages::{ sets::DefaultStages, @@ -242,15 +242,17 @@ impl Command { ctx.task_executor .spawn_critical("events task", events::handle_events(Some(network.clone()), events)); + let factory = ProviderFactory::new(&db, self.chain.clone()); + let provider = factory.provider().map_err(PipelineError::Interface)?; + let latest_block_number = - get_stage_checkpoint(&db.tx()?, StageId::Finish)?.unwrap_or_default().block_number; + provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number; if latest_block_number >= self.to { info!(target: "reth::cli", latest = latest_block_number, "Nothing to run"); return Ok(()) } let mut current_max_block = latest_block_number; - let factory = ProviderFactory::new(&db, self.chain.clone()); while current_max_block < self.to { let next_block = current_max_block + 1; diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index 58e4a5df368a..1009fe6d866f 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -9,7 +9,7 @@ use reth_primitives::{ stage::{StageCheckpoint, StageId}, ChainSpec, }; -use reth_provider::ProviderFactory; +use reth_provider::{ProviderFactory, StageCheckpointProvider}; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 4a90aa5e9210..b7b6b721564b 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -23,8 +23,6 @@ use reth_config::Config; use reth_db::{ database::Database, mdbx::{Env, WriteMap}, - tables, - transaction::DbTx, }; use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_downloaders::{ @@ -41,12 +39,10 @@ use reth_interfaces::{ }; use reth_network::{error::NetworkError, NetworkConfig, NetworkHandle, NetworkManager}; use reth_network_api::NetworkInfo; -use reth_primitives::{ - stage::StageId, BlockHashOrNumber, ChainSpec, Head, Header, SealedHeader, H256, -}; +use reth_primitives::{stage::StageId, BlockHashOrNumber, ChainSpec, Head, SealedHeader, H256}; use reth_provider::{ - providers::get_stage_checkpoint, BlockProvider, CanonStateSubscriptions, HeaderProvider, - ProviderFactory, + BlockHashProvider, BlockProvider, CanonStateSubscriptions, HeaderProvider, ProviderFactory, + StageCheckpointProvider, }; use reth_revm::Factory; use reth_revm_inspectors::stack::Hook; @@ -509,30 +505,31 @@ impl Command { Ok(handle) } - fn lookup_head( - &self, - db: Arc>, - ) -> Result { - db.view(|tx| { - let head = get_stage_checkpoint(tx, StageId::Finish)?.unwrap_or_default().block_number; - let header = tx - .get::(head)? - .expect("the header for the latest block is missing, database is corrupt"); - let total_difficulty = tx.get::(head)?.expect( - "the total difficulty for the latest block is missing, database is corrupt", - ); - let hash = tx - .get::(head)? - .expect("the hash for the latest block is missing, database is corrupt"); - Ok::(Head { - number: head, - hash, - difficulty: header.difficulty, - total_difficulty: total_difficulty.into(), - timestamp: header.timestamp, - }) - })? - .map_err(Into::into) + fn lookup_head(&self, db: Arc>) -> Result { + let factory = ProviderFactory::new(db, self.chain.clone()); + let provider = factory.provider()?; + + let head = provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number; + + let header = provider + .header_by_number(head)? + .expect("the header for the latest block is missing, database is corrupt"); + + let total_difficulty = provider + .header_td_by_number(head)? + .expect("the total difficulty for the latest block is missing, database is corrupt"); + + let hash = provider + .block_hash(head)? + .expect("the hash for the latest block is missing, database is corrupt"); + + Ok(Head { + number: head, + hash, + difficulty: header.difficulty, + total_difficulty, + timestamp: header.timestamp, + }) } /// Attempt to look up the block number for the tip hash in the database. @@ -565,13 +562,10 @@ impl Command { DB: Database, Client: HeadersClient, { - let header = db.view(|tx| -> Result, reth_db::DatabaseError> { - let number = match tip { - BlockHashOrNumber::Hash(hash) => tx.get::(hash)?, - BlockHashOrNumber::Number(number) => Some(number), - }; - Ok(number.map(|number| tx.get::(number)).transpose()?.flatten()) - })??; + let factory = ProviderFactory::new(db, self.chain.clone()); + let provider = factory.provider()?; + + let header = provider.header_by_hash_or_number(tip)?; // try to look up the header in the database if let Some(header) = header { diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 592d0d66c91d..4af0b0017907 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -12,7 +12,7 @@ use reth_beacon_consensus::BeaconConsensus; use reth_config::Config; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_primitives::ChainSpec; -use reth_provider::{providers::get_stage_checkpoint, ProviderFactory}; +use reth_provider::{ProviderFactory, StageCheckpointProvider}; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ @@ -215,8 +215,7 @@ impl Command { assert!(exec_stage.type_id() == unwind_stage.type_id()); } - let checkpoint = - get_stage_checkpoint(provider_rw.tx_ref(), exec_stage.id())?.unwrap_or_default(); + let checkpoint = provider_rw.get_stage_checkpoint(exec_stage.id())?.unwrap_or_default(); let unwind_stage = unwind_stage.as_mut().unwrap_or(&mut exec_stage); diff --git a/crates/stages/benches/setup/account_hashing.rs b/crates/stages/benches/setup/account_hashing.rs index fa3492adff87..c8210ec3ba31 100644 --- a/crates/stages/benches/setup/account_hashing.rs +++ b/crates/stages/benches/setup/account_hashing.rs @@ -63,7 +63,7 @@ fn generate_testdata_db(num_blocks: u64) -> (PathBuf, StageRange) { std::fs::create_dir_all(&path).unwrap(); println!("Account Hashing testdata not found, generating to {:?}", path.display()); let tx = TestTransaction::new(&path); - let mut provider = tx.inner(); + let mut provider = tx.inner_rw(); let _accounts = AccountHashingStage::seed(&mut provider, opts); provider.commit().expect("failed to commit"); } diff --git a/crates/stages/benches/setup/mod.rs b/crates/stages/benches/setup/mod.rs index ee0c394d4736..8fca814717cf 100644 --- a/crates/stages/benches/setup/mod.rs +++ b/crates/stages/benches/setup/mod.rs @@ -123,7 +123,7 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> PathBuf { tx.insert_accounts_and_storages(start_state.clone()).unwrap(); // make first block after genesis have valid state root - let (root, updates) = StateRoot::new(tx.inner().tx_ref()).root_with_updates().unwrap(); + let (root, updates) = StateRoot::new(tx.inner_rw().tx_ref()).root_with_updates().unwrap(); let second_block = blocks.get_mut(1).unwrap(); let cloned_second = second_block.clone(); let mut updated_header = cloned_second.header.unseal(); @@ -144,7 +144,7 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> PathBuf { // make last block have valid state root let root = { - let tx_mut = tx.inner(); + let tx_mut = tx.inner_rw(); let root = StateRoot::new(tx_mut.tx_ref()).root().unwrap(); tx_mut.commit().unwrap(); root diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index e8ceaf874b5c..4032b1bedf15 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -6,7 +6,7 @@ use reth_primitives::{ constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH, listener::EventListeners, stage::StageId, BlockNumber, ChainSpec, H256, }; -use reth_provider::{providers::get_stage_checkpoint, ProviderFactory}; +use reth_provider::{ProviderFactory, StageCheckpointProvider}; use std::{pin::Pin, sync::Arc}; use tokio::sync::watch; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -137,12 +137,14 @@ where /// Registers progress metrics for each registered stage pub fn register_metrics(&mut self) -> Result<(), PipelineError> { - let tx = self.db.tx()?; + let factory = ProviderFactory::new(&self.db, self.chain_spec.clone()); + let provider = factory.provider()?; + for stage in &self.stages { let stage_id = stage.id(); self.metrics.stage_checkpoint( stage_id, - get_stage_checkpoint(&tx, stage_id)?.unwrap_or_default(), + provider.get_stage_checkpoint(stage_id)?.unwrap_or_default(), None, ); } @@ -228,8 +230,14 @@ where } } + let factory = ProviderFactory::new(&self.db, self.chain_spec.clone()); + previous_stage = Some( - get_stage_checkpoint(&self.db.tx()?, stage_id)?.unwrap_or_default().block_number, + factory + .provider()? + .get_stage_checkpoint(stage_id)? + .unwrap_or_default() + .block_number, ); } diff --git a/crates/stages/src/stage.rs b/crates/stages/src/stage.rs index a7de484994cd..30b8f7fca344 100644 --- a/crates/stages/src/stage.rs +++ b/crates/stages/src/stage.rs @@ -5,7 +5,7 @@ use reth_primitives::{ stage::{StageCheckpoint, StageId}, BlockNumber, TxNumber, }; -use reth_provider::{DatabaseProviderRW, ProviderError}; +use reth_provider::DatabaseProviderRW; use std::{ cmp::{max, min}, ops::RangeInclusive, @@ -79,10 +79,7 @@ impl ExecInput { tx_threshold: u64, ) -> Result<(RangeInclusive, RangeInclusive, bool), StageError> { let start_block = self.next_block(); - let start_block_body = provider - .tx_ref() - .get::(start_block)? - .ok_or(ProviderError::BlockBodyIndicesNotFound(start_block))?; + let start_block_body = provider.block_body_indices(start_block)?; let target_block = self.target(); diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index e57c944aea92..b620314963c3 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -422,7 +422,9 @@ mod tests { hex_literal::hex, keccak256, stage::StageUnitCheckpoint, Account, Bytecode, ChainSpecBuilder, SealedBlock, StorageEntry, H160, H256, MAINNET, U256, }; - use reth_provider::{insert_canonical_block, ProviderFactory}; + use reth_provider::{ + insert_canonical_block, AccountProvider, ProviderFactory, ReceiptProvider, + }; use reth_revm::Factory; use reth_rlp::Decodable; use std::sync::Arc; @@ -624,8 +626,9 @@ mod tests { }, done: true } if processed == total && total == block.gas_used); - let mut provider = factory.provider_rw().unwrap(); - let tx = provider.tx_mut(); + + let provider = factory.provider().unwrap(); + // check post state let account1 = H160(hex!("1000000000000000000000000000000000000000")); let account1_info = @@ -645,24 +648,24 @@ mod tests { // assert accounts assert_eq!( - tx.get::(account1), + provider.basic_account(account1), Ok(Some(account1_info)), "Post changed of a account" ); assert_eq!( - tx.get::(account2), + provider.basic_account(account2), Ok(Some(account2_info)), "Post changed of a account" ); assert_eq!( - tx.get::(account3), + provider.basic_account(account3), Ok(Some(account3_info)), "Post changed of a account" ); // assert storage // Get on dupsort would return only first value. This is good enough for this test. assert_eq!( - tx.get::(account1), + provider.tx_ref().get::(account1), Ok(Some(StorageEntry { key: H256::from_low_u64_be(1), value: U256::from(2) })), "Post changed of a account" ); @@ -739,26 +742,13 @@ mod tests { } if total == block.gas_used); // assert unwind stage - let db_tx = provider.tx_ref(); - assert_eq!( - db_tx.get::(acc1), - Ok(Some(acc1_info)), - "Pre changed of a account" - ); - assert_eq!( - db_tx.get::(acc2), - Ok(Some(acc2_info)), - "Post changed of a account" - ); + assert_eq!(provider.basic_account(acc1), Ok(Some(acc1_info)), "Pre changed of a account"); + assert_eq!(provider.basic_account(acc2), Ok(Some(acc2_info)), "Post changed of a account"); let miner_acc = H160(hex!("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba")); - assert_eq!( - db_tx.get::(miner_acc), - Ok(None), - "Third account should be unwound" - ); + assert_eq!(provider.basic_account(miner_acc), Ok(None), "Third account should be unwound"); - assert_eq!(db_tx.get::(0), Ok(None), "First receipt should be unwound"); + assert_eq!(provider.receipt(0), Ok(None), "First receipt should be unwound"); } #[tokio::test] @@ -830,11 +820,7 @@ mod tests { // assert unwind stage let provider = factory.provider_rw().unwrap(); - assert_eq!( - provider.tx_ref().get::(destroyed_address), - Ok(None), - "Account was destroyed" - ); + assert_eq!(provider.basic_account(destroyed_address), Ok(None), "Account was destroyed"); assert_eq!( provider.tx_ref().get::(destroyed_address), diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index ab56a3398494..37f6dfc2c5c7 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -532,7 +532,7 @@ mod tests { type Seed = Vec<(Address, Account)>; fn seed_execution(&mut self, input: ExecInput) -> Result { - let mut provider = self.tx.inner(); + let mut provider = self.tx.inner_rw(); let res = Ok(AccountHashingStage::seed( &mut provider, SeedOpts { blocks: 1..=input.target(), accounts: 0..10, txs: 0..3 }, diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index 6b57dc4009da..7c38fecec4fc 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -403,6 +403,7 @@ mod tests { generators::random_header_range, TestConsensus, TestHeaderDownloader, TestHeadersClient, }; use reth_primitives::U256; + use reth_provider::{BlockHashProvider, BlockNumProvider, HeaderProvider}; use std::sync::Arc; pub(crate) struct HeadersTestRunner { @@ -478,26 +479,21 @@ mod tests { let initial_checkpoint = input.checkpoint().block_number; match output { Some(output) if output.checkpoint.block_number > initial_checkpoint => { - self.tx.query(|tx| { - for block_num in - (initial_checkpoint..output.checkpoint.block_number).rev() - { - // look up the header hash - let hash = tx - .get::(block_num)? - .expect("no header hash"); - - // validate the header number - assert_eq!(tx.get::(hash)?, Some(block_num)); - - // validate the header - let header = tx.get::(block_num)?; - assert!(header.is_some()); - let header = header.unwrap().seal_slow(); - assert_eq!(header.hash(), hash); - } - Ok(()) - })?; + let provider = self.tx.factory.provider()?; + for block_num in (initial_checkpoint..output.checkpoint.block_number).rev() + { + // look up the header hash + let hash = provider.block_hash(block_num)?.expect("no header hash"); + + // validate the header number + assert_eq!(provider.block_number(hash)?, Some(block_num)); + + // validate the header + let header = provider.header_by_number(block_num)?; + assert!(header.is_some()); + let header = header.unwrap().seal_slow(); + assert_eq!(header.hash(), hash); + } } _ => self.check_no_header_entry_above(initial_checkpoint)?, }; diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index 9f74ebcd2085..2fe297e4be67 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -235,6 +235,7 @@ mod tests { use reth_primitives::{ stage::StageUnitCheckpoint, BlockNumber, SealedBlock, TransactionSigned, H256, }; + use reth_provider::TransactionsProvider; use super::*; use crate::test_utils::{ @@ -373,7 +374,7 @@ mod tests { /// 2. If the is no requested block entry in the bodies table, /// but [tables::TxSenders] is not empty. fn ensure_no_senders_by_block(&self, block: BlockNumber) -> Result<(), TestRunnerError> { - let body_result = self.tx.inner().block_body_indices(block); + let body_result = self.tx.inner_rw().block_body_indices(block); match body_result { Ok(body) => self .tx @@ -417,7 +418,8 @@ mod tests { output: Option, ) -> Result<(), TestRunnerError> { match output { - Some(output) => self.tx.query(|tx| { + Some(output) => { + let provider = self.tx.inner(); let start_block = input.next_block(); let end_block = output.checkpoint.block_number; @@ -425,23 +427,20 @@ mod tests { return Ok(()) } - let mut body_cursor = tx.cursor_read::()?; + let mut body_cursor = + provider.tx_ref().cursor_read::()?; body_cursor.seek_exact(start_block)?; while let Some((_, body)) = body_cursor.next()? { for tx_id in body.tx_num_range() { - let transaction: TransactionSigned = tx - .get::(tx_id)? - .expect("no transaction entry") - .into(); + let transaction: TransactionSigned = + provider.transaction_by_id(tx_id)?.expect("no transaction entry"); let signer = transaction.recover_signer().expect("failed to recover signer"); - assert_eq!(Some(signer), tx.get::(tx_id)?); + assert_eq!(Some(signer), provider.transaction_sender(tx_id)?) } } - - Ok(()) - })?, + } None => self.ensure_no_senders_by_block(input.checkpoint().block_number)?, }; diff --git a/crates/stages/src/stages/total_difficulty.rs b/crates/stages/src/stages/total_difficulty.rs index 41afa821300e..7175eb15f4f7 100644 --- a/crates/stages/src/stages/total_difficulty.rs +++ b/crates/stages/src/stages/total_difficulty.rs @@ -130,6 +130,7 @@ mod tests { TestConsensus, }; use reth_primitives::{stage::StageUnitCheckpoint, BlockNumber, SealedHeader}; + use reth_provider::HeaderProvider; use super::*; use crate::test_utils::{ @@ -262,27 +263,25 @@ mod tests { let initial_stage_progress = input.checkpoint().block_number; match output { Some(output) if output.checkpoint.block_number > initial_stage_progress => { - self.tx.query(|tx| { - let mut header_cursor = tx.cursor_read::()?; - let (_, mut current_header) = header_cursor - .seek_exact(initial_stage_progress)? - .expect("no initial header"); - let mut td: U256 = tx - .get::(initial_stage_progress)? - .expect("no initial td") - .into(); - - while let Some((next_key, next_header)) = header_cursor.next()? { - assert_eq!(current_header.number + 1, next_header.number); - td += next_header.difficulty; - assert_eq!( - tx.get::(next_key)?.map(Into::into), - Some(td) - ); - current_header = next_header; - } - Ok(()) - })?; + let provider = self.tx.inner(); + + let mut header_cursor = provider.tx_ref().cursor_read::()?; + let (_, mut current_header) = header_cursor + .seek_exact(initial_stage_progress)? + .expect("no initial header"); + let mut td: U256 = provider + .header_td_by_number(initial_stage_progress)? + .expect("no initial td"); + + while let Some((next_key, next_header)) = header_cursor.next()? { + assert_eq!(current_header.number + 1, next_header.number); + td += next_header.difficulty; + assert_eq!( + provider.header_td_by_number(next_key)?.map(Into::into), + Some(td) + ); + current_header = next_header; + } } _ => self.check_no_td_above(initial_stage_progress)?, }; diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index 6b92d51c42ac..bf4b757cc3bc 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -199,6 +199,7 @@ mod tests { use assert_matches::assert_matches; use reth_interfaces::test_utils::generators::{random_block, random_block_range}; use reth_primitives::{stage::StageUnitCheckpoint, BlockNumber, SealedBlock, H256}; + use reth_provider::TransactionsProvider; // Implement stage test suite. stage_test_suite_ext!(TransactionLookupTestRunner, transaction_lookup); @@ -331,7 +332,7 @@ mod tests { /// 2. If the is no requested block entry in the bodies table, /// but [tables::TxHashNumber] is not empty. fn ensure_no_hash_by_block(&self, number: BlockNumber) -> Result<(), TestRunnerError> { - let body_result = self.tx.inner().block_body_indices(number); + let body_result = self.tx.inner_rw().block_body_indices(number); match body_result { Ok(body) => self.tx.ensure_no_entry_above_by_value::( body.last_tx_num(), @@ -376,7 +377,9 @@ mod tests { output: Option, ) -> Result<(), TestRunnerError> { match output { - Some(output) => self.tx.query(|tx| { + Some(output) => { + let provider = self.tx.inner(); + let start_block = input.next_block(); let end_block = output.checkpoint.block_number; @@ -384,23 +387,18 @@ mod tests { return Ok(()) } - let mut body_cursor = tx.cursor_read::()?; + let mut body_cursor = + provider.tx_ref().cursor_read::()?; body_cursor.seek_exact(start_block)?; while let Some((_, body)) = body_cursor.next()? { for tx_id in body.tx_num_range() { - let transaction = tx - .get::(tx_id)? - .expect("no transaction entry"); - assert_eq!( - Some(tx_id), - tx.get::(transaction.hash())?, - ); + let transaction = + provider.transaction_by_id(tx_id)?.expect("no transaction entry"); + assert_eq!(Some(tx_id), provider.transaction_id(transaction.hash())?); } } - - Ok(()) - })?, + } None => self.ensure_no_hash_by_block(input.checkpoint().block_number)?, }; Ok(()) diff --git a/crates/stages/src/test_utils/runner.rs b/crates/stages/src/test_utils/runner.rs index ff2f0133faa1..01ae19d49b8e 100644 --- a/crates/stages/src/test_utils/runner.rs +++ b/crates/stages/src/test_utils/runner.rs @@ -12,6 +12,8 @@ pub(crate) enum TestRunnerError { Database(#[from] reth_interfaces::db::DatabaseError), #[error("Internal runner error occurred.")] Internal(#[from] Box), + #[error("Internal interface error occurred.")] + Interface(#[from] reth_interfaces::Error), } /// A generic test runner for stages. diff --git a/crates/stages/src/test_utils/test_db.rs b/crates/stages/src/test_utils/test_db.rs index 43763983a80c..ea0b6974cae2 100644 --- a/crates/stages/src/test_utils/test_db.rs +++ b/crates/stages/src/test_utils/test_db.rs @@ -4,7 +4,7 @@ use reth_db::{ mdbx::{ test_utils::{create_test_db, create_test_db_with_path}, tx::Tx, - Env, EnvKind, WriteMap, RW, + Env, EnvKind, WriteMap, RO, RW, }, models::{AccountBeforeTx, StoredBlockBodyIndices}, table::Table, @@ -16,7 +16,7 @@ use reth_primitives::{ keccak256, Account, Address, BlockNumber, SealedBlock, SealedHeader, StorageEntry, H256, MAINNET, U256, }; -use reth_provider::{DatabaseProviderRW, ProviderFactory}; +use reth_provider::{DatabaseProviderRO, DatabaseProviderRW, ProviderFactory}; use std::{ borrow::Borrow, collections::BTreeMap, @@ -37,7 +37,7 @@ pub struct TestTransaction { /// WriteMap DB pub tx: Arc>, pub path: Option, - factory: ProviderFactory>>, + pub factory: ProviderFactory>>, } impl Default for TestTransaction { @@ -59,10 +59,15 @@ impl TestTransaction { } /// Return a database wrapped in [DatabaseProviderRW]. - pub fn inner(&self) -> DatabaseProviderRW<'_, Arc>> { + pub fn inner_rw(&self) -> DatabaseProviderRW<'_, Arc>> { self.factory.provider_rw().expect("failed to create db container") } + /// Return a database wrapped in [DatabaseProviderRO]. + pub fn inner(&self) -> DatabaseProviderRO<'_, Arc>> { + self.factory.provider().expect("failed to create db container") + } + /// Get a pointer to an internal database. pub fn inner_raw(&self) -> Arc> { self.tx.clone() @@ -73,7 +78,7 @@ impl TestTransaction { where F: FnOnce(&mut Tx<'_, RW, WriteMap>) -> Result<(), DbError>, { - let mut tx = self.inner(); + let mut tx = self.inner_rw(); f(tx.tx_mut())?; tx.commit().expect("failed to commit"); Ok(()) @@ -82,7 +87,7 @@ impl TestTransaction { /// Invoke a callback with a read transaction pub fn query(&self, f: F) -> Result where - F: FnOnce(&Tx<'_, RW, WriteMap>) -> Result, + F: FnOnce(&Tx<'_, RO, WriteMap>) -> Result, { f(self.inner().tx_ref()) } diff --git a/crates/storage/provider/src/post_state/mod.rs b/crates/storage/provider/src/post_state/mod.rs index 23106625c4b7..f37d16128c89 100644 --- a/crates/storage/provider/src/post_state/mod.rs +++ b/crates/storage/provider/src/post_state/mod.rs @@ -640,12 +640,13 @@ impl PostState { #[cfg(test)] mod tests { use super::*; + use crate::{AccountProvider, ProviderFactory}; use reth_db::{ database::Database, mdbx::{test_utils, Env, EnvKind, WriteMap}, transaction::DbTx, }; - use reth_primitives::proofs::EMPTY_ROOT; + use reth_primitives::{proofs::EMPTY_ROOT, MAINNET}; use reth_trie::test_utils::state_root; use std::sync::Arc; @@ -1066,7 +1067,8 @@ mod tests { #[test] fn write_to_db_account_info() { let db: Arc> = test_utils::create_test_db(EnvKind::RW); - let tx = db.tx_mut().expect("Could not get database tx"); + let factory = ProviderFactory::new(db, MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); let mut post_state = PostState::new(); @@ -1081,22 +1083,23 @@ mod tests { post_state.create_account(1, address_a, account_a); // 0x11.. is changed (balance + 1, nonce + 1) post_state.change_account(1, address_b, account_b, account_b_changed); - post_state.write_to_db(&tx).expect("Could not write post state to DB"); + post_state.write_to_db(provider.tx_ref()).expect("Could not write post state to DB"); // Check plain state assert_eq!( - tx.get::(address_a).expect("Could not read account state"), + provider.basic_account(address_a).expect("Could not read account state"), Some(account_a), "Account A state is wrong" ); assert_eq!( - tx.get::(address_b).expect("Could not read account state"), + provider.basic_account(address_b).expect("Could not read account state"), Some(account_b_changed), "Account B state is wrong" ); // Check change set - let mut changeset_cursor = tx + let mut changeset_cursor = provider + .tx_ref() .cursor_dup_read::() .expect("Could not open changeset cursor"); assert_eq!( @@ -1113,11 +1116,11 @@ mod tests { let mut post_state = PostState::new(); // 0x11.. is destroyed post_state.destroy_account(2, address_b, account_b_changed); - post_state.write_to_db(&tx).expect("Could not write second post state to DB"); + post_state.write_to_db(provider.tx_ref()).expect("Could not write second post state to DB"); // Check new plain state for account B assert_eq!( - tx.get::(address_b).expect("Could not read account state"), + provider.basic_account(address_b).expect("Could not read account state"), None, "Account B should be deleted" ); diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index c4370e79111a..4ba37607b7a1 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -5,7 +5,7 @@ use crate::{ ProviderError, StageCheckpointProvider, StateProviderBox, TransactionsProvider, WithdrawalsProvider, }; -use reth_db::{database::Database, models::StoredBlockBodyIndices, tables, transaction::DbTx}; +use reth_db::{database::Database, models::StoredBlockBodyIndices}; use reth_interfaces::Result; use reth_primitives::{ stage::{StageCheckpoint, StageId}, @@ -246,6 +246,10 @@ impl TransactionsProvider for ProviderFactory { fn senders_by_tx_range(&self, range: impl RangeBounds) -> Result> { self.provider()?.senders_by_tx_range(range) } + + fn transaction_sender(&self, id: TxNumber) -> Result> { + self.provider()?.transaction_sender(id) + } } impl ReceiptProvider for ProviderFactory { @@ -318,18 +322,6 @@ impl EvmEnvProvider for ProviderFactory { } } -/// Get checkpoint for the given stage. -#[inline] -pub fn get_stage_checkpoint<'a, TX>( - tx: &TX, - id: StageId, -) -> std::result::Result, reth_interfaces::db::DatabaseError> -where - TX: DbTx<'a> + Send + Sync, -{ - tx.get::(id.to_string()) -} - #[cfg(test)] mod tests { use super::ProviderFactory; diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 489bb647dbe4..3916969d76d6 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -43,8 +43,6 @@ use std::{ sync::Arc, }; -use super::get_stage_checkpoint; - /// A [`DatabaseProvider`] that holds a read-only database transaction. pub type DatabaseProviderRO<'this, DB> = DatabaseProvider<'this, >::TX>; @@ -740,8 +738,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { let parent_number = range.start().saturating_sub(1); let parent_state_root = self - .tx - .get::(parent_number)? + .header_by_number(parent_number)? .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))? .state_root; @@ -749,8 +746,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { // but for sake of double verification we will check it again. if new_state_root != parent_state_root { let parent_hash = self - .tx - .get::(parent_number)? + .block_hash(parent_number)? .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))?; return Err(TransactionError::UnwindStateRootMismatch { got: new_state_root, @@ -1076,14 +1072,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// Get the stage checkpoint. - pub fn get_stage_checkpoint( - &self, - id: StageId, - ) -> std::result::Result, DatabaseError> { - get_stage_checkpoint(&self.tx, id) - } - /// Save stage checkpoint. pub fn save_stage_checkpoint( &self, @@ -1743,6 +1731,10 @@ impl<'this, TX: DbTx<'this>> TransactionsProvider for DatabaseProvider<'this, TX .map(|entry| entry.map(|sender| sender.1)) .collect::, _>>()?) } + + fn transaction_sender(&self, id: TxNumber) -> Result> { + Ok(self.tx.get::(id)?) + } } impl<'this, TX: DbTx<'this>> ReceiptProvider for DatabaseProvider<'this, TX> { diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 9594f817363d..6502cacb3151 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -302,6 +302,10 @@ where fn senders_by_tx_range(&self, range: impl RangeBounds) -> Result> { self.database.provider()?.senders_by_tx_range(range) } + + fn transaction_sender(&self, id: TxNumber) -> Result> { + self.database.provider()?.transaction_sender(id) + } } impl ReceiptProvider for BlockchainProvider diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 9c029cd11217..869f972c2f69 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -219,6 +219,10 @@ impl TransactionsProvider for MockEthProvider { ) -> Result> { unimplemented!() } + + fn transaction_sender(&self, _id: TxNumber) -> Result> { + unimplemented!() + } } impl ReceiptProvider for MockEthProvider { diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 97d6933b2df6..bd0ac1fea741 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -159,6 +159,10 @@ impl TransactionsProvider for NoopProvider { ) -> Result> { Ok(Vec::default()) } + + fn transaction_sender(&self, _id: TxNumber) -> Result> { + Ok(None) + } } impl ReceiptProvider for NoopProvider { diff --git a/crates/storage/provider/src/traits/transactions.rs b/crates/storage/provider/src/traits/transactions.rs index ca5e15a88c00..e53a03cbf375 100644 --- a/crates/storage/provider/src/traits/transactions.rs +++ b/crates/storage/provider/src/traits/transactions.rs @@ -51,4 +51,9 @@ pub trait TransactionsProvider: BlockNumProvider + Send + Sync { /// Get Senders from a tx range. fn senders_by_tx_range(&self, range: impl RangeBounds) -> Result>; + + /// Get transaction sender. + /// + /// Returns None if the transaction is not found. + fn transaction_sender(&self, id: TxNumber) -> Result>; } diff --git a/crates/storage/provider/src/transaction.rs b/crates/storage/provider/src/transaction.rs index b75c87deca58..9614f5e1f994 100644 --- a/crates/storage/provider/src/transaction.rs +++ b/crates/storage/provider/src/transaction.rs @@ -39,6 +39,9 @@ pub enum TransactionError { /// Block hash block_hash: BlockHash, }, + /// Internal interfaces error + #[error("Internal error")] + InternalError(#[from] reth_interfaces::Error), } #[cfg(test)] From e252cd6a2f69747e436b95d3ac77b4c36242942a Mon Sep 17 00:00:00 2001 From: Bjerg Date: Sat, 17 Jun 2023 03:05:09 +0200 Subject: [PATCH 065/216] fix: dynamic batch size for tx lookup stage (#3134) --- crates/stages/src/stages/tx_lookup.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index bf4b757cc3bc..5f3cd7056b23 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -60,6 +60,7 @@ impl Stage for TransactionLookupStage { let (tx_range, block_range, is_final_range) = input.next_block_range_with_transaction_threshold(provider, self.commit_threshold)?; let end_block = *block_range.end(); + let tx_range_size = tx_range.clone().count(); debug!(target: "sync::stages::transaction_lookup", ?tx_range, "Updating transaction lookup"); @@ -67,7 +68,7 @@ impl Stage for TransactionLookupStage { let mut tx_cursor = tx.cursor_read::()?; let tx_walker = tx_cursor.walk_range(tx_range)?; - let chunk_size = 100_000 / rayon::current_num_threads(); + let chunk_size = (tx_range_size / rayon::current_num_threads()).max(1); let mut channels = Vec::with_capacity(chunk_size); let mut transaction_count = 0; From 017c9cea9c1685baaab925923d443c3e97769dde Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Sat, 17 Jun 2023 12:06:25 +0100 Subject: [PATCH 066/216] chore: move stage methods to `StageCheckpointProvider` and add `StageCheckpointWriter` (#3195) Co-authored-by: Georgios Konstantopoulos --- bin/reth/src/debug_cmd/execution.rs | 2 +- bin/reth/src/debug_cmd/merkle.rs | 2 +- bin/reth/src/node/mod.rs | 2 +- bin/reth/src/stage/run.rs | 2 +- crates/consensus/beacon/src/engine/mod.rs | 12 ++--- crates/stages/src/pipeline/mod.rs | 2 +- crates/stages/src/stages/merkle.rs | 4 +- crates/storage/provider/src/lib.rs | 4 +- .../provider/src/providers/database/mod.rs | 8 ++- .../src/providers/database/provider.rs | 49 ++++++++----------- crates/storage/provider/src/providers/mod.rs | 8 ++- .../storage/provider/src/test_utils/noop.rs | 8 ++- crates/storage/provider/src/traits/mod.rs | 2 +- .../provider/src/traits/stage_checkpoint.rs | 15 +++++- 14 files changed, 67 insertions(+), 53 deletions(-) diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index 3aeab6911ca0..0865f5abbb02 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -26,7 +26,7 @@ use reth_interfaces::{ use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256}; -use reth_provider::{ProviderFactory, StageCheckpointProvider}; +use reth_provider::{ProviderFactory, StageCheckpointReader}; use reth_staged_sync::utils::init::{init_db, init_genesis}; use reth_stages::{ sets::DefaultStages, diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index 1009fe6d866f..4e7ee12d7bf3 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -9,7 +9,7 @@ use reth_primitives::{ stage::{StageCheckpoint, StageId}, ChainSpec, }; -use reth_provider::{ProviderFactory, StageCheckpointProvider}; +use reth_provider::{ProviderFactory, StageCheckpointReader}; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index b7b6b721564b..3fa6c84784ce 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -42,7 +42,7 @@ use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, ChainSpec, Head, SealedHeader, H256}; use reth_provider::{ BlockHashProvider, BlockProvider, CanonStateSubscriptions, HeaderProvider, ProviderFactory, - StageCheckpointProvider, + StageCheckpointReader, }; use reth_revm::Factory; use reth_revm_inspectors::stack::Hook; diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 4af0b0017907..c7be9961f705 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -12,7 +12,7 @@ use reth_beacon_consensus::BeaconConsensus; use reth_config::Config; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_primitives::ChainSpec; -use reth_provider::{ProviderFactory, StageCheckpointProvider}; +use reth_provider::{ProviderFactory, StageCheckpointReader}; use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 5082a608f606..a43729dabb7c 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -21,7 +21,7 @@ use reth_primitives::{ SealedHeader, H256, U256, }; use reth_provider::{ - BlockProvider, BlockSource, CanonChainTracker, ProviderError, StageCheckpointProvider, + BlockProvider, BlockSource, CanonChainTracker, ProviderError, StageCheckpointReader, }; use reth_rpc_types::engine::{ ExecutionPayload, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, PayloadStatusEnum, @@ -210,7 +210,7 @@ pub struct BeaconConsensusEngine where DB: Database, Client: HeadersClient + BodiesClient, - BT: BlockchainTreeEngine + BlockProvider + CanonChainTracker + StageCheckpointProvider, + BT: BlockchainTreeEngine + BlockProvider + CanonChainTracker + StageCheckpointReader, { /// Controls syncing triggered by engine updates. sync: EngineSyncController, @@ -238,11 +238,7 @@ where impl BeaconConsensusEngine where DB: Database + Unpin + 'static, - BT: BlockchainTreeEngine - + BlockProvider - + CanonChainTracker - + StageCheckpointProvider - + 'static, + BT: BlockchainTreeEngine + BlockProvider + CanonChainTracker + StageCheckpointReader + 'static, Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, { /// Create a new instance of the [BeaconConsensusEngine]. @@ -1235,7 +1231,7 @@ where BT: BlockchainTreeEngine + BlockProvider + CanonChainTracker - + StageCheckpointProvider + + StageCheckpointReader + Unpin + 'static, { diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index 4032b1bedf15..14c78aac1c84 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -6,7 +6,7 @@ use reth_primitives::{ constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH, listener::EventListeners, stage::StageId, BlockNumber, ChainSpec, H256, }; -use reth_provider::{ProviderFactory, StageCheckpointProvider}; +use reth_provider::{ProviderFactory, StageCheckpointReader, StageCheckpointWriter}; use std::{pin::Pin, sync::Arc}; use tokio::sync::watch; use tokio_stream::wrappers::UnboundedReceiverStream; diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index bfb0344e4360..af4802619da4 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -12,7 +12,9 @@ use reth_primitives::{ trie::StoredSubNode, BlockNumber, SealedHeader, H256, }; -use reth_provider::{DatabaseProviderRW, HeaderProvider, ProviderError}; +use reth_provider::{ + DatabaseProviderRW, HeaderProvider, ProviderError, StageCheckpointReader, StageCheckpointWriter, +}; use reth_trie::{IntermediateStateRootState, StateRoot, StateRootProgress}; use std::fmt::Debug; use tracing::*; diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 868f668453b9..00b4f022ab05 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -16,8 +16,8 @@ pub use traits::{ BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, ExecutorFactory, HeaderProvider, PostStateDataProvider, ReceiptProvider, ReceiptProviderIdExt, - StageCheckpointProvider, StateProvider, StateProviderBox, StateProviderFactory, - StateRootProvider, TransactionsProvider, WithdrawalsProvider, + StageCheckpointReader, StageCheckpointWriter, StateProvider, StateProviderBox, + StateProviderFactory, StateRootProvider, TransactionsProvider, WithdrawalsProvider, }; /// Provider trait implementations. diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 4ba37607b7a1..f04fd9c6832f 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -2,7 +2,7 @@ use crate::{ providers::state::{historical::HistoricalStateProvider, latest::LatestStateProvider}, traits::{BlockSource, ReceiptProvider}, BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, HeaderProvider, - ProviderError, StageCheckpointProvider, StateProviderBox, TransactionsProvider, + ProviderError, StageCheckpointReader, StateProviderBox, TransactionsProvider, WithdrawalsProvider, }; use reth_db::{database::Database, models::StoredBlockBodyIndices}; @@ -280,10 +280,14 @@ impl WithdrawalsProvider for ProviderFactory { } } -impl StageCheckpointProvider for ProviderFactory { +impl StageCheckpointReader for ProviderFactory { fn get_stage_checkpoint(&self, id: StageId) -> Result> { self.provider()?.get_stage_checkpoint(id) } + + fn get_stage_checkpoint_progress(&self, id: StageId) -> Result>> { + self.provider()?.get_stage_checkpoint_progress(id) + } } impl EvmEnvProvider for ProviderFactory { diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 3916969d76d6..e24d467042dc 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1,9 +1,9 @@ use crate::{ insert_canonical_block, post_state::StorageChangeset, - traits::{AccountExtProvider, BlockSource, ReceiptProvider}, + traits::{AccountExtProvider, BlockSource, ReceiptProvider, StageCheckpointWriter}, AccountProvider, BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, - HeaderProvider, PostState, ProviderError, StageCheckpointProvider, TransactionError, + HeaderProvider, PostState, ProviderError, StageCheckpointReader, TransactionError, TransactionsProvider, WithdrawalsProvider, }; use itertools::{izip, Itertools}; @@ -1072,32 +1072,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// Save stage checkpoint. - pub fn save_stage_checkpoint( - &self, - id: StageId, - checkpoint: StageCheckpoint, - ) -> std::result::Result<(), DatabaseError> { - self.tx.put::(id.to_string(), checkpoint) - } - - /// Get stage checkpoint progress. - pub fn get_stage_checkpoint_progress( - &self, - id: StageId, - ) -> std::result::Result>, DatabaseError> { - self.tx.get::(id.to_string()) - } - - /// Save stage checkpoint progress. - pub fn save_stage_checkpoint_progress( - &self, - id: StageId, - checkpoint: Vec, - ) -> std::result::Result<(), DatabaseError> { - self.tx.put::(id.to_string(), checkpoint) - } - /// Query the block body by number. pub fn block_body_indices( &self, @@ -1865,8 +1839,25 @@ impl<'this, TX: DbTx<'this>> EvmEnvProvider for DatabaseProvider<'this, TX> { } } -impl<'this, TX: DbTx<'this>> StageCheckpointProvider for DatabaseProvider<'this, TX> { +impl<'this, TX: DbTx<'this>> StageCheckpointReader for DatabaseProvider<'this, TX> { fn get_stage_checkpoint(&self, id: StageId) -> Result> { Ok(self.tx.get::(id.to_string())?) } + + /// Get stage checkpoint progress. + fn get_stage_checkpoint_progress(&self, id: StageId) -> Result>> { + Ok(self.tx.get::(id.to_string())?) + } +} + +impl<'this, TX: DbTxMut<'this>> StageCheckpointWriter for DatabaseProvider<'this, TX> { + /// Save stage checkpoint progress. + fn save_stage_checkpoint_progress(&self, id: StageId, checkpoint: Vec) -> Result<()> { + Ok(self.tx.put::(id.to_string(), checkpoint)?) + } + + /// Save stage checkpoint. + fn save_stage_checkpoint(&self, id: StageId, checkpoint: StageCheckpoint) -> Result<()> { + Ok(self.tx.put::(id.to_string(), checkpoint)?) + } } diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 6502cacb3151..94267675ac2c 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -2,7 +2,7 @@ use crate::{ BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, HeaderProvider, PostStateDataProvider, ProviderError, - ReceiptProvider, StageCheckpointProvider, StateProviderBox, StateProviderFactory, + ReceiptProvider, StageCheckpointReader, StateProviderBox, StateProviderFactory, TransactionsProvider, WithdrawalsProvider, }; use reth_db::{database::Database, models::StoredBlockBodyIndices}; @@ -344,7 +344,7 @@ where } } -impl StageCheckpointProvider for BlockchainProvider +impl StageCheckpointReader for BlockchainProvider where DB: Database, Tree: Send + Sync, @@ -352,6 +352,10 @@ where fn get_stage_checkpoint(&self, id: StageId) -> Result> { self.database.provider()?.get_stage_checkpoint(id) } + + fn get_stage_checkpoint_progress(&self, id: StageId) -> Result>> { + self.database.provider()?.get_stage_checkpoint_progress(id) + } } impl EvmEnvProvider for BlockchainProvider diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index bd0ac1fea741..cf58499130a2 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -1,7 +1,7 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, AccountProvider, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, - BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, StageCheckpointProvider, + BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, WithdrawalsProvider, }; @@ -311,10 +311,14 @@ impl StateProviderFactory for NoopProvider { } } -impl StageCheckpointProvider for NoopProvider { +impl StageCheckpointReader for NoopProvider { fn get_stage_checkpoint(&self, _id: StageId) -> Result> { Ok(None) } + + fn get_stage_checkpoint_progress(&self, _id: StageId) -> Result>> { + Ok(None) + } } impl WithdrawalsProvider for NoopProvider { diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index 1c7c5680714b..d6564a87f228 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -46,4 +46,4 @@ pub use chain::{ }; mod stage_checkpoint; -pub use stage_checkpoint::StageCheckpointProvider; +pub use stage_checkpoint::{StageCheckpointReader, StageCheckpointWriter}; diff --git a/crates/storage/provider/src/traits/stage_checkpoint.rs b/crates/storage/provider/src/traits/stage_checkpoint.rs index a0b735f3ec5d..4610b2643628 100644 --- a/crates/storage/provider/src/traits/stage_checkpoint.rs +++ b/crates/storage/provider/src/traits/stage_checkpoint.rs @@ -3,7 +3,20 @@ use reth_primitives::stage::{StageCheckpoint, StageId}; /// The trait for fetching stage checkpoint related data. #[auto_impl::auto_impl(&, Arc)] -pub trait StageCheckpointProvider: Send + Sync { +pub trait StageCheckpointReader: Send + Sync { /// Fetch the checkpoint for the given stage. fn get_stage_checkpoint(&self, id: StageId) -> Result>; + + /// Get stage checkpoint progress. + fn get_stage_checkpoint_progress(&self, id: StageId) -> Result>>; +} + +/// The trait for updating stage checkpoint related data. +#[auto_impl::auto_impl(&, Arc)] +pub trait StageCheckpointWriter: Send + Sync { + /// Save stage checkpoint. + fn save_stage_checkpoint(&self, id: StageId, checkpoint: StageCheckpoint) -> Result<()>; + + /// Save stage checkpoint progress. + fn save_stage_checkpoint_progress(&self, id: StageId, checkpoint: Vec) -> Result<()>; } From fe59bc8936fe21e65fbd56601c96c27a9931eb7b Mon Sep 17 00:00:00 2001 From: Waylon Jepsen <57912727+0xJepsen@users.noreply.github.com> Date: Sat, 17 Jun 2023 07:19:11 -0600 Subject: [PATCH 067/216] feat: set max response size for auth server on start (#3157) Co-authored-by: Matthias Seitz --- crates/rpc/rpc-builder/src/auth.rs | 36 +++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 11fb8185c86d..08a8ff65406f 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -15,7 +15,8 @@ use reth_provider::{ }; use reth_rpc::{ eth::{cache::EthStateCache, gas_oracle::GasPriceOracle}, - AuthLayer, Claims, EngineEthApi, EthApi, EthFilter, JwtAuthValidator, JwtSecret, + AuthLayer, Claims, EngineEthApi, EthApi, EthFilter, EthSubscriptionIdProvider, + JwtAuthValidator, JwtSecret, }; use reth_rpc_api::{servers::*, EngineApiServer}; use reth_tasks::TaskSpawner; @@ -116,12 +117,14 @@ where } /// Server configuration for the auth server. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct AuthServerConfig { /// Where the server should listen. pub(crate) socket_addr: SocketAddr, /// The secrete for the auth layer of the server. pub(crate) secret: JwtSecret, + /// Configs for JSON-RPC Http. + pub(crate) server_config: ServerBuilder, } // === impl AuthServerConfig === @@ -134,7 +137,7 @@ impl AuthServerConfig { /// Convenience function to start a server in one step. pub async fn start(self, module: AuthRpcModule) -> Result { - let Self { socket_addr, secret } = self; + let Self { socket_addr, secret, server_config } = self; // Create auth middleware. let middleware = tower::ServiceBuilder::new() @@ -142,9 +145,9 @@ impl AuthServerConfig { // By default, both http and ws are enabled. let server = - ServerBuilder::new().set_middleware(middleware).build(socket_addr).await.map_err( - |err| RpcError::from_jsonrpsee_error(err, ServerKind::Auth(socket_addr)), - )?; + server_config.set_middleware(middleware).build(socket_addr).await.map_err(|err| { + RpcError::from_jsonrpsee_error(err, ServerKind::Auth(socket_addr)) + })?; let local_addr = server.local_addr()?; @@ -154,10 +157,11 @@ impl AuthServerConfig { } /// Builder type for configuring an `AuthServerConfig`. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct AuthServerConfigBuilder { socket_addr: Option, secret: JwtSecret, + server_config: Option, } // === impl AuthServerConfigBuilder === @@ -165,7 +169,7 @@ pub struct AuthServerConfigBuilder { impl AuthServerConfigBuilder { /// Create a new `AuthServerConfigBuilder` with the given `secret`. pub fn new(secret: JwtSecret) -> Self { - Self { socket_addr: None, secret } + Self { socket_addr: None, secret, server_config: None } } /// Set the socket address for the server. @@ -185,6 +189,16 @@ impl AuthServerConfigBuilder { self.secret = secret; self } + + /// Configures the JSON-RPC server + /// + /// Note: this always configures an [EthSubscriptionIdProvider] + /// [IdProvider](jsonrpsee::server::IdProvider) for convenience. + pub fn with_server_config(mut self, config: ServerBuilder) -> Self { + self.server_config = Some(config.set_id_provider(EthSubscriptionIdProvider::default())); + self + } + /// Build the `AuthServerConfig`. pub fn build(self) -> AuthServerConfig { AuthServerConfig { @@ -192,6 +206,12 @@ impl AuthServerConfigBuilder { SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), constants::DEFAULT_AUTH_PORT) }), secret: self.secret, + server_config: self.server_config.unwrap_or_else(|| { + ServerBuilder::new() + // allows for 300mb responses (for large eth_getLogs deposit logs) + .max_response_body_size(300 * 1024 * 1024) + .set_id_provider(EthSubscriptionIdProvider::default()) + }), } } } From 547911ac19c68b750f7920a6cd613fa691328ce7 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 17 Jun 2023 16:07:05 +0200 Subject: [PATCH 068/216] fix: set call trace gas limit with call inputs gas limit (#3214) --- crates/revm/revm-inspectors/src/tracing/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index c9b727ea1fd6..ccbc265638d1 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -130,6 +130,7 @@ impl TracingInspector { value: U256, kind: CallKind, caller: Address, + gas_limit: u64, maybe_precompile: Option, ) { // This will only be true if the inspector is configured to exclude precompiles and the call @@ -154,6 +155,7 @@ impl TracingInspector { caller, last_call_return_value: self.last_call_return_data.clone(), maybe_precompile, + gas_limit, ..Default::default() }, )); @@ -178,7 +180,6 @@ impl TracingInspector { let trace = &mut self.traces.arena[trace_idx].trace; trace.gas_used = gas.spend(); - trace.gas_limit = gas.limit(); trace.status = status; trace.success = matches!(status, return_ok!()); trace.output = output.clone(); @@ -383,6 +384,7 @@ where value, inputs.context.scheme.into(), from, + inputs.gas_limit, maybe_precompile, ); @@ -421,6 +423,7 @@ where inputs.value, inputs.scheme.into(), inputs.caller, + inputs.gas_limit, Some(false), ); From 9e32b48bbb49c792541a9c9ff22014d5680d1fd8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 17 Jun 2023 18:21:38 +0200 Subject: [PATCH 069/216] perf: calculate trace address on demand (#3217) --- .../revm-inspectors/src/tracing/builder/parity.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs index 8f0fdb43ebcb..422842904e8c 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs @@ -67,6 +67,13 @@ impl ParityTraceBuilder { graph } + /// Returns an iterator over all nodes to trace + /// + /// This excludes nodes that represent calls to precompiles. + fn iter_traceable_nodes(&self) -> impl Iterator { + self.nodes.iter().filter(|node| !node.is_precompile()) + } + /// Returns an iterator over all recorded traces for `trace_transaction` pub fn into_localized_transaction_traces_iter( self, @@ -164,15 +171,11 @@ impl ParityTraceBuilder { None }; - let trace_addresses = self.trace_addresses(); let mut traces = Vec::with_capacity(if with_traces { self.nodes.len() } else { 0 }); let mut diff = StateDiff::default(); - for (node, trace_address) in self.nodes.iter().zip(trace_addresses) { - // skip precompiles - if node.is_precompile() { - continue - } + for node in self.iter_traceable_nodes() { + let trace_address = self.trace_address(node.idx); if with_traces { let trace = node.parity_transaction_trace(trace_address); From a8be68a82c20ecf6b50ea66d8a706a3493c9bce0 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sat, 17 Jun 2023 19:15:00 +0200 Subject: [PATCH 070/216] fix: prevent panic in trace call (#3216) --- .../src/tracing/builder/parity.rs | 16 ++++++++++++++-- crates/revm/revm-inspectors/src/tracing/types.rs | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs index 422842904e8c..3798d19f8697 100644 --- a/crates/revm/revm-inspectors/src/tracing/builder/parity.rs +++ b/crates/revm/revm-inspectors/src/tracing/builder/parity.rs @@ -8,6 +8,8 @@ use revm::{ use std::collections::HashSet; /// A type for creating parity style traces +/// +/// Note: Parity style traces always ignore calls to precompiles. #[derive(Clone, Debug)] pub struct ParityTraceBuilder { /// Recorded trace nodes @@ -27,7 +29,12 @@ impl ParityTraceBuilder { self.nodes.iter().map(|node| node.trace.caller).collect() } - /// Returns the trace addresses of all transactions in the set + /// Returns the trace addresses of all call nodes in the set + /// + /// Each entry in the returned vector represents the [Self::trace_address] of the corresponding + /// node in the nodes set. + /// + /// CAUTION: This also includes precompiles, which have an empty trace address. fn trace_addresses(&self) -> Vec> { let mut all_addresses = Vec::with_capacity(self.nodes.len()); for idx in 0..self.nodes.len() { @@ -44,6 +51,8 @@ impl ParityTraceBuilder { /// # Panics /// /// if the `idx` does not belong to a node + /// + /// Note: if the call node of `idx` is a precompile, the returned trace address will be empty. fn trace_address(&self, idx: usize) -> Vec { if idx == 0 { // root call has empty traceAddress @@ -51,6 +60,9 @@ impl ParityTraceBuilder { } let mut graph = vec![]; let mut node = &self.nodes[idx]; + if node.is_precompile() { + return graph + } while let Some(parent) = node.parent { // the index of the child call in the arena let child_idx = node.idx; @@ -60,7 +72,7 @@ impl ParityTraceBuilder { .children .iter() .position(|child| *child == child_idx) - .expect("child exists in parent"); + .expect("non precompile child call exists in parent"); graph.push(call_idx); } graph.reverse(); diff --git a/crates/revm/revm-inspectors/src/tracing/types.rs b/crates/revm/revm-inspectors/src/tracing/types.rs index 85b28052a77f..43681a5b8e51 100644 --- a/crates/revm/revm-inspectors/src/tracing/types.rs +++ b/crates/revm/revm-inspectors/src/tracing/types.rs @@ -242,6 +242,7 @@ impl CallTraceNode { } /// Returns true if this is a call to a precompile + #[inline] pub(crate) fn is_precompile(&self) -> bool { self.trace.maybe_precompile.unwrap_or(false) } From 37e8f7b140a1ddd80f7e065f028b6d16db1b247a Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 19 Jun 2023 11:50:00 +0300 Subject: [PATCH 071/216] perf(provider): return empty ommers after merge (#3222) --- crates/primitives/src/chain/spec.rs | 4 ++-- .../provider/src/providers/database/provider.rs | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index a3a0f06fe57d..3ea29f0f29d5 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -221,11 +221,11 @@ impl ChainSpec { } } - /// Returns the final difficulty if the given block number is after the Paris hardfork. + /// Returns the final total difficulty if the given block number is after the Paris hardfork. /// /// Note: technically this would also be valid for the block before the paris upgrade, but this /// edge case is omitted here. - pub fn final_paris_difficulty(&self, block_number: u64) -> Option { + pub fn final_paris_total_difficulty(&self, block_number: u64) -> Option { self.paris_block_and_final_difficulty.and_then(|(activated_at, final_difficulty)| { if block_number >= activated_at { Some(final_difficulty) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index e24d467042dc..d4d3f332a188 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1410,7 +1410,7 @@ impl<'this, TX: DbTx<'this>> HeaderProvider for DatabaseProvider<'this, TX> { } fn header_td_by_number(&self, number: BlockNumber) -> Result> { - if let Some(td) = self.chain_spec.final_paris_difficulty(number) { + if let Some(td) = self.chain_spec.final_paris_total_difficulty(number) { // if this block is higher than the final paris(merge) block, return the final paris // difficulty return Ok(Some(td)) @@ -1505,8 +1505,7 @@ impl<'this, TX: DbTx<'this>> BlockProvider for DatabaseProvider<'this, TX> { if let Some(number) = self.convert_hash_or_number(id)? { if let Some(header) = self.header_by_number(number)? { let withdrawals = self.withdrawals_by_block(number.into(), header.timestamp)?; - let ommers = if withdrawals.is_none() { self.ommers(number.into())? } else { None } - .unwrap_or_default(); + let ommers = self.ommers(number.into())?.unwrap_or_default(); let transactions = self .transactions_by_block(number.into())? .ok_or(ProviderError::BlockBodyIndicesNotFound(number))?; @@ -1524,7 +1523,12 @@ impl<'this, TX: DbTx<'this>> BlockProvider for DatabaseProvider<'this, TX> { fn ommers(&self, id: BlockHashOrNumber) -> Result>> { if let Some(number) = self.convert_hash_or_number(id)? { - // TODO: this can be optimized to return empty Vec post-merge + // If the Paris (Merge) hardfork block is known and block is after it, return empty + // ommers. + if self.chain_spec.final_paris_total_difficulty(number).is_some() { + return Ok(Some(Vec::new())) + } + let ommers = self.tx.get::(number)?.map(|o| o.ommers); return Ok(ommers) } From f44010b8882b10485ab841e0332d0205c14f0aea Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 19 Jun 2023 09:51:12 +0100 Subject: [PATCH 072/216] feat(config): missing stage configs (#3215) --- bin/reth/src/node/mod.rs | 30 ++++++-- crates/config/src/config.rs | 76 ++++++++++++++++++- crates/stages/src/stages/hashing_account.rs | 2 +- .../src/stages/index_account_history.rs | 7 ++ .../src/stages/index_storage_history.rs | 7 ++ crates/stages/src/stages/merkle.rs | 13 +++- 6 files changed, 123 insertions(+), 12 deletions(-) diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 3fa6c84784ce..90c02e9d688a 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -77,6 +77,10 @@ use crate::{ use reth_interfaces::p2p::headers::client::HeadersClient; use reth_payload_builder::PayloadBuilderService; use reth_provider::providers::BlockchainProvider; +use reth_stages::stages::{ + AccountHashingStage, IndexAccountHistoryStage, IndexStorageHistoryStage, MerkleStage, + StorageHashingStage, TransactionLookupStage, +}; pub mod cl_events; pub mod events; @@ -628,7 +632,7 @@ impl Command { H: HeaderDownloader + 'static, B: BodyDownloader + 'static, { - let stage_conf = &config.stages; + let stage_config = &config.stages; let mut builder = Pipeline::builder(); @@ -670,17 +674,33 @@ impl Command { ) .set( TotalDifficultyStage::new(consensus) - .with_commit_threshold(stage_conf.total_difficulty.commit_threshold), + .with_commit_threshold(stage_config.total_difficulty.commit_threshold), ) .set(SenderRecoveryStage { - commit_threshold: stage_conf.sender_recovery.commit_threshold, + commit_threshold: stage_config.sender_recovery.commit_threshold, }) .set(ExecutionStage::new( factory, ExecutionStageThresholds { - max_blocks: stage_conf.execution.max_blocks, - max_changes: stage_conf.execution.max_changes, + max_blocks: stage_config.execution.max_blocks, + max_changes: stage_config.execution.max_changes, }, + )) + .set(AccountHashingStage::new( + stage_config.account_hashing.clean_threshold, + stage_config.account_hashing.commit_threshold, + )) + .set(StorageHashingStage::new( + stage_config.storage_hashing.clean_threshold, + stage_config.storage_hashing.commit_threshold, + )) + .set(MerkleStage::new_execution(stage_config.merkle.clean_threshold)) + .set(TransactionLookupStage::new(stage_config.transaction_lookup.commit_threshold)) + .set(IndexAccountHistoryStage::new( + stage_config.index_account_history.commit_threshold, + )) + .set(IndexStorageHistoryStage::new( + stage_config.index_storage_history.commit_threshold, )), ) .build(db, self.chain.clone()); diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index fa126cd9468b..95d41e022eea 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -51,14 +51,26 @@ impl Config { pub struct StageConfig { /// Header stage configuration. pub headers: HeadersConfig, - /// Total difficulty stage configuration + /// Total Difficulty stage configuration pub total_difficulty: TotalDifficultyConfig, /// Body stage configuration. pub bodies: BodiesConfig, - /// Sender recovery stage configuration. + /// Sender Recovery stage configuration. pub sender_recovery: SenderRecoveryConfig, /// Execution stage configuration. pub execution: ExecutionConfig, + /// Account Hashing stage configuration. + pub account_hashing: HashingConfig, + /// Storage Hashing stage configuration. + pub storage_hashing: HashingConfig, + /// Merkle stage configuration. + pub merkle: MerkleConfig, + /// Transaction Lookup stage configuration. + pub transaction_lookup: TransactionLookupConfig, + /// Index Account History stage configuration. + pub index_account_history: IndexHistoryConfig, + /// Index Storage History stage configuration. + pub index_storage_history: IndexHistoryConfig, } /// Header stage configuration. @@ -204,6 +216,66 @@ impl Default for ExecutionConfig { } } +/// Hashing stage configuration. +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct HashingConfig { + /// The threshold (in number of blocks) for switching between + /// incremental hashing and full hashing. + pub clean_threshold: u64, + /// The maximum number of entities to process before committing progress to the database. + pub commit_threshold: u64, +} + +impl Default for HashingConfig { + fn default() -> Self { + Self { clean_threshold: 500_000, commit_threshold: 100_000 } + } +} + +/// Merkle stage configuration. +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct MerkleConfig { + /// The threshold (in number of blocks) for switching from incremental trie building of changes + /// to whole rebuild. + pub clean_threshold: u64, +} + +impl Default for MerkleConfig { + fn default() -> Self { + Self { clean_threshold: 50_000 } + } +} + +/// Transaction Lookup stage configuration. +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct TransactionLookupConfig { + /// The maximum number of transactions to process before committing progress to the database. + pub commit_threshold: u64, +} + +impl Default for TransactionLookupConfig { + fn default() -> Self { + Self { commit_threshold: 5_000_000 } + } +} + +/// History History stage configuration. +#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)] +#[serde(default)] +pub struct IndexHistoryConfig { + /// The maximum number of blocks to process before committing progress to the database. + pub commit_threshold: u64, +} + +impl Default for IndexHistoryConfig { + fn default() -> Self { + Self { commit_threshold: 100_000 } + } +} + #[cfg(test)] mod tests { use super::Config; diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 37f6dfc2c5c7..0178a73fa7f4 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -29,7 +29,7 @@ use tracing::*; /// This is preparation before generating intermediate hashes and calculating Merkle tree root. #[derive(Clone, Debug)] pub struct AccountHashingStage { - /// The threshold (in number of state transitions) for switching between incremental + /// The threshold (in number of blocks) for switching between incremental /// hashing and full storage hashing. pub clean_threshold: u64, /// The maximum number of accounts to process before committing. diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index b3adc01a9483..df087a48c5ff 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -14,6 +14,13 @@ pub struct IndexAccountHistoryStage { pub commit_threshold: u64, } +impl IndexAccountHistoryStage { + /// Create new instance of [IndexAccountHistoryStage]. + pub fn new(commit_threshold: u64) -> Self { + Self { commit_threshold } + } +} + impl Default for IndexAccountHistoryStage { fn default() -> Self { Self { commit_threshold: 100_000 } diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index 0f6e3fba262a..1abb389b3f07 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -14,6 +14,13 @@ pub struct IndexStorageHistoryStage { pub commit_threshold: u64, } +impl IndexStorageHistoryStage { + /// Create new instance of [IndexStorageHistoryStage]. + pub fn new(commit_threshold: u64) -> Self { + Self { commit_threshold } + } +} + impl Default for IndexStorageHistoryStage { fn default() -> Self { Self { commit_threshold: 100_000 } diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index af4802619da4..bf70559f0a35 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -44,8 +44,8 @@ use tracing::*; pub enum MerkleStage { /// The execution portion of the merkle stage. Execution { - /// The threshold for switching from incremental trie building - /// of changes to whole rebuild. Num of transitions. + /// The threshold (in number of blocks) for switching from incremental trie building + /// of changes to whole rebuild. clean_threshold: u64, }, /// The unwind portion of the merkle stage. @@ -58,16 +58,21 @@ pub enum MerkleStage { } impl MerkleStage { - /// Stage default for the Execution variant. + /// Stage default for the [MerkleStage::Execution]. pub fn default_execution() -> Self { Self::Execution { clean_threshold: 50_000 } } - /// Stage default for the Unwind variant. + /// Stage default for the [MerkleStage::Unwind]. pub fn default_unwind() -> Self { Self::Unwind } + /// Create new instance of [MerkleStage::Execution]. + pub fn new_execution(clean_threshold: u64) -> Self { + Self::Execution { clean_threshold } + } + /// Check that the computed state root matches the root in the expected header. fn validate_state_root( &self, From 187af8b38025654bf25274d992a4b508f90eb4cc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 19 Jun 2023 10:51:30 +0200 Subject: [PATCH 073/216] test: add another fork id test (#3219) --- crates/net/discv4/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index e4ccc65e59cb..49df08c1a005 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -1997,6 +1997,24 @@ mod tests { use reth_rlp::{Decodable, Encodable}; use std::{future::poll_fn, net::Ipv4Addr}; + #[tokio::test] + async fn test_configured_enr_forkid_entry() { + let fork: ForkId = ForkId { hash: ForkHash([220, 233, 108, 45]), next: 0u64 }; + let mut disc_conf = Discv4Config::default(); + disc_conf.add_eip868_pair("eth", EnrForkIdEntry::from(fork)); + let (_discv4, service) = create_discv4_with_config(disc_conf).await; + let eth = service.local_eip_868_enr.get_raw_rlp(b"eth").unwrap(); + let fork_entry_id = EnrForkIdEntry::decode(&mut ð[..]).unwrap(); + + let raw: [u8; 8] = [0xc7, 0xc6, 0x84, 0xdc, 0xe9, 0x6c, 0x2d, 0x80]; + let decoded = EnrForkIdEntry::decode(&mut &raw[..]).unwrap(); + let expected = EnrForkIdEntry { + fork_id: ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 0 }, + }; + assert_eq!(expected, fork_entry_id); + assert_eq!(expected, decoded); + } + #[test] fn test_enr_forkid_entry_decode() { let raw: [u8; 8] = [0xc7, 0xc6, 0x84, 0xdc, 0xe9, 0x6c, 0x2d, 0x80]; From dce1e655c5b8a2f6d69fba559b62a9c0dde43ba4 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 19 Jun 2023 10:56:34 +0200 Subject: [PATCH 074/216] perf: better state retrieval (#3221) --- crates/rpc/rpc/src/eth/api/mod.rs | 35 +--------- crates/storage/provider/src/providers/mod.rs | 66 +++++++++++++++++++ .../storage/provider/src/test_utils/mock.rs | 8 +++ .../storage/provider/src/test_utils/noop.rs | 4 ++ crates/storage/provider/src/traits/state.rs | 11 +++- 5 files changed, 90 insertions(+), 34 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 4e50bb2f70f5..29aebdb6a576 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -177,21 +177,14 @@ impl EthApi where Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, { - fn convert_block_number(&self, num: BlockNumberOrTag) -> Result> { - self.provider().convert_block_number(num) - } - /// Returns the state at the given [BlockId] enum. pub fn state_at_block_id(&self, at: BlockId) -> EthResult> { - match at { - BlockId::Hash(hash) => Ok(self.state_at_hash(hash.into())?), - BlockId::Number(num) => { - self.state_at_block_number(num)?.ok_or(EthApiError::UnknownBlockNumber) - } - } + Ok(self.provider().state_by_block_id(at)?) } /// Returns the state at the given [BlockId] enum or the latest. + /// + /// Convenience function to interprets `None` as `BlockId::Number(BlockNumberOrTag::Latest)` pub fn state_at_block_id_or_latest( &self, block_id: Option, @@ -203,33 +196,11 @@ where } } - /// Returns the state at the given [BlockNumberOrTag] enum - /// - /// Returns `None` if no state available. - pub fn state_at_block_number( - &self, - num: BlockNumberOrTag, - ) -> Result>> { - if let Some(number) = self.convert_block_number(num)? { - self.state_at_number(number).map(Some) - } else { - Ok(None) - } - } - /// Returns the state at the given block number pub fn state_at_hash(&self, block_hash: H256) -> Result> { self.provider().history_by_block_hash(block_hash) } - /// Returns the state at the given block number - pub fn state_at_number(&self, block_number: u64) -> Result> { - match self.convert_block_number(BlockNumberOrTag::Latest)? { - Some(num) if num == block_number => self.latest_state(), - _ => self.provider().history_by_block_number(block_number), - } - } - /// Returns the _latest_ state pub fn latest_state(&self) -> Result> { self.provider().latest() diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index 94267675ac2c..a027bea8ae6e 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -409,6 +409,65 @@ where self.database.latest() } + /// Returns a [StateProviderBox] indexed by the given [BlockId]. + fn state_by_block_id(&self, block_id: BlockId) -> Result> { + match block_id { + BlockId::Number(block_number) => self.state_by_block_number_or_tag(block_number), + BlockId::Hash(rpc_block_hash) => { + let block_hash = rpc_block_hash.into(); + let mut state = self.history_by_block_hash(block_hash); + + // we failed to get the state by hash, from disk, hash block be the pending block + if state.is_err() && !rpc_block_hash.require_canonical.unwrap_or(false) { + if let Ok(Some(pending)) = self.pending_state_by_hash(block_hash) { + // we found pending block by hash + state = Ok(pending) + } + } + + state + } + } + } + + /// Returns a [StateProviderBox] indexed by the given block number or tag. + fn state_by_block_number_or_tag( + &self, + number_or_tag: BlockNumberOrTag, + ) -> Result> { + match number_or_tag { + BlockNumberOrTag::Latest => self.latest(), + BlockNumberOrTag::Finalized => { + // we can only get the finalized state by hash, not by num + let hash = match self.finalized_block_hash()? { + Some(hash) => hash, + None => return Err(ProviderError::FinalizedBlockNotFound.into()), + }; + + self.state_by_block_hash(hash) + } + BlockNumberOrTag::Safe => { + // we can only get the safe state by hash, not by num + let hash = match self.safe_block_hash()? { + Some(hash) => hash, + None => return Err(ProviderError::SafeBlockNotFound.into()), + }; + + self.state_by_block_hash(hash) + } + BlockNumberOrTag::Earliest => self.history_by_block_number(0), + BlockNumberOrTag::Pending => self.pending(), + BlockNumberOrTag::Number(num) => { + let mut state = self.history_by_block_number(num); + if state.is_err() && num == self.chain_info.get_canonical_block_number() + 1 { + // we don't have the block on disk yet but the number is the pending block + state = self.pending(); + } + state + } + } + } + fn history_by_block_number(&self, block_number: BlockNumber) -> Result> { trace!(target: "providers::blockchain", ?block_number, "Getting history by block number"); self.ensure_canonical_block(block_number)?; @@ -443,6 +502,13 @@ where self.latest() } + fn pending_state_by_hash(&self, block_hash: H256) -> Result>> { + if let Some(state) = self.tree.find_pending_state_provider(block_hash) { + return Ok(Some(self.pending_with_provider(state)?)) + } + Ok(None) + } + fn pending_with_provider( &self, post_state_data: Box, diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 869f972c2f69..49cda80e9632 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -462,6 +462,10 @@ impl StateProviderFactory for MockEthProvider { todo!() } + fn pending_state_by_hash(&self, _block_hash: H256) -> Result>> { + todo!() + } + fn pending_with_provider<'a>( &'a self, _post_state_data: Box, @@ -491,6 +495,10 @@ impl StateProviderFactory for Arc { todo!() } + fn pending_state_by_hash(&self, _block_hash: H256) -> Result>> { + todo!() + } + fn pending_with_provider<'a>( &'a self, _post_state_data: Box, diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index cf58499130a2..9a72152cb48f 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -303,6 +303,10 @@ impl StateProviderFactory for NoopProvider { Ok(Box::new(*self)) } + fn pending_state_by_hash(&self, _block_hash: H256) -> Result>> { + Ok(Some(Box::new(*self))) + } + fn pending_with_provider<'a>( &'a self, _post_state_data: Box, diff --git a/crates/storage/provider/src/traits/state.rs b/crates/storage/provider/src/traits/state.rs index e1fac70a5a93..bbd13ec3df7b 100644 --- a/crates/storage/provider/src/traits/state.rs +++ b/crates/storage/provider/src/traits/state.rs @@ -103,13 +103,13 @@ pub trait StateProviderFactory: BlockIdProvider + Send + Sync { /// Returns a [StateProvider] indexed by the given [BlockId]. fn state_by_block_id(&self, block_id: BlockId) -> Result> { match block_id { - BlockId::Number(block_number) => self.history_by_block_number_or_tag(block_number), + BlockId::Number(block_number) => self.state_by_block_number_or_tag(block_number), BlockId::Hash(block_hash) => self.history_by_block_hash(block_hash.into()), } } /// Returns a [StateProvider] indexed by the given block number or tag. - fn history_by_block_number_or_tag( + fn state_by_block_number_or_tag( &self, number_or_tag: BlockNumberOrTag, ) -> Result> { @@ -161,6 +161,13 @@ pub trait StateProviderFactory: BlockIdProvider + Send + Sync { /// If there's no `pending` block, then this is equal to [StateProviderFactory::latest] fn pending(&self) -> Result>; + /// Storage provider for pending state for the given block hash. + /// + /// Represents the state at the block that extends the canonical chain. + /// + /// If the block couldn't be found, returns `None`. + fn pending_state_by_hash(&self, block_hash: H256) -> Result>>; + /// Return a [StateProvider] that contains post state data provider. /// Used to inspect or execute transaction on the pending state. fn pending_with_provider( From efb100c124f1f6ff55179b2fdd94e00530244285 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 19 Jun 2023 10:56:42 +0200 Subject: [PATCH 075/216] fix: fork id decoding from enr response (#3220) --- crates/net/discv4/src/proto.rs | 39 ++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/net/discv4/src/proto.rs b/crates/net/discv4/src/proto.rs index e22cd4c028ce..88b3bb93644d 100644 --- a/crates/net/discv4/src/proto.rs +++ b/crates/net/discv4/src/proto.rs @@ -305,7 +305,7 @@ impl EnrResponse { /// /// See also pub fn eth_fork_id(&self) -> Option { - let mut maybe_fork_id = self.enr.0.get(b"eth")?; + let mut maybe_fork_id = self.enr.0.get_raw_rlp(b"eth")?; EnrForkIdEntry::decode(&mut maybe_fork_id).ok().map(|entry| entry.fork_id) } } @@ -500,7 +500,7 @@ mod tests { }; use enr::{EnrBuilder, EnrPublicKey}; use rand::{thread_rng, Rng, RngCore}; - use reth_primitives::hex_literal::hex; + use reth_primitives::{hex_literal::hex, ForkHash}; #[test] fn test_endpoint_ipv_v4() { @@ -714,6 +714,41 @@ mod tests { Message::decode(&data).unwrap(); } + #[test] + fn encode_decode_enr_msg() { + use self::EnrWrapper; + use enr::secp256k1::SecretKey; + use reth_rlp::Decodable; + use std::net::Ipv4Addr; + + let key = SecretKey::new(&mut rand::rngs::OsRng); + let ip = Ipv4Addr::new(127, 0, 0, 1); + let tcp = 3000; + + let fork_id: ForkId = ForkId { hash: ForkHash([220, 233, 108, 45]), next: 0u64 }; + + let enr = { + let mut builder = EnrBuilder::new("v4"); + builder.ip(ip.into()); + builder.tcp4(tcp); + let mut buf = Vec::new(); + let forkentry = EnrForkIdEntry { fork_id }; + forkentry.encode(&mut buf); + builder.add_value_rlp("eth", buf.into()); + EnrWrapper::new(builder.build(&key).unwrap()) + }; + + let enr_respone = EnrResponse { request_hash: H256::random(), enr }; + + let mut buf = Vec::new(); + enr_respone.encode(&mut buf); + + let decoded = EnrResponse::decode(&mut &buf[..]).unwrap(); + + let fork_id_decoded = decoded.eth_fork_id().unwrap(); + assert_eq!(fork_id, fork_id_decoded); + } + // test vector from the enr library rlp encoding tests // From 9484de09f596708e2cf62a3ef457b4da94ea41bf Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 19 Jun 2023 11:19:34 +0200 Subject: [PATCH 076/216] ci: use `actions/deploy-pages@v2` (#3223) --- .github/workflows/book.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 1f50c2d6cd46..3ff003ba26a3 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -87,4 +87,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 \ No newline at end of file + uses: actions/deploy-pages@v2 From 13dcfb8e6e637c80cb4360398ba9d59d4d82fb35 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 19 Jun 2023 13:58:07 +0300 Subject: [PATCH 077/216] chore: expose txpool types that enable implementing `TransactionPool` trait (#3225) --- crates/transaction-pool/src/lib.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index f24b134f4a7d..d633a18f4fa4 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -80,31 +80,29 @@ //! [`Pool`](crate::Pool) type is just an `Arc` wrapper around `PoolInner`. This is the usable type //! that provides the `TransactionPool` interface. +use crate::pool::PoolInner; +use aquamarine as _; +use reth_primitives::{Address, TxHash, U256}; +use reth_provider::StateProviderFactory; +use std::{collections::HashMap, sync::Arc}; +use tokio::sync::mpsc::Receiver; +use tracing::{instrument, trace}; + pub use crate::{ config::PoolConfig, + error::PoolResult, ordering::{CostOrdering, TransactionOrdering}, pool::TransactionEvents, traits::{ AllPoolTransactions, BestTransactions, BlockInfo, CanonicalStateUpdate, ChangedAccount, - PoolTransaction, PooledTransaction, PropagateKind, PropagatedTransactions, - TransactionOrigin, TransactionPool, + NewTransactionEvent, PoolSize, PoolTransaction, PooledTransaction, PropagateKind, + PropagatedTransactions, TransactionOrigin, TransactionPool, }, validate::{ EthTransactionValidator, TransactionValidationOutcome, TransactionValidator, ValidPoolTransaction, }, }; -use crate::{ - error::PoolResult, - pool::PoolInner, - traits::{NewTransactionEvent, PoolSize}, -}; -use aquamarine as _; -use reth_primitives::{Address, TxHash, U256}; -use reth_provider::StateProviderFactory; -use std::{collections::HashMap, sync::Arc}; -use tokio::sync::mpsc::Receiver; -use tracing::{instrument, trace}; mod config; pub mod error; From 96abde09654c7abe79cbae7c68dd0ad7a9bec90b Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:46:47 +0100 Subject: [PATCH 078/216] chore: `AccountProvider` -> `AccountReader` & `AccountWriter` (#3228) --- crates/consensus/common/src/validation.rs | 8 +- crates/revm/src/executor.rs | 4 +- crates/rpc/rpc/src/eth/api/state.rs | 2 +- crates/staged-sync/src/utils/init.rs | 4 +- crates/stages/src/stages/execution.rs | 4 +- crates/stages/src/stages/hashing_account.rs | 2 +- .../src/stages/index_account_history.rs | 4 +- crates/storage/provider/src/lib.rs | 4 +- crates/storage/provider/src/post_state/mod.rs | 2 +- .../src/providers/database/provider.rs | 410 ++++++++---------- .../src/providers/post_state_provider.rs | 6 +- .../src/providers/state/historical.rs | 8 +- .../provider/src/providers/state/latest.rs | 6 +- .../provider/src/providers/state/macros.rs | 4 +- .../storage/provider/src/test_utils/mock.rs | 4 +- .../storage/provider/src/test_utils/noop.rs | 4 +- crates/storage/provider/src/traits/account.rs | 48 +- crates/storage/provider/src/traits/mod.rs | 2 +- crates/storage/provider/src/traits/state.rs | 4 +- crates/transaction-pool/src/validate.rs | 2 +- 20 files changed, 259 insertions(+), 273 deletions(-) diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index 6eff13b20bc0..f61e160f0390 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -4,7 +4,7 @@ use reth_primitives::{ constants, BlockNumber, ChainSpec, Hardfork, Header, InvalidTransactionError, SealedBlock, SealedHeader, Transaction, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxLegacy, }; -use reth_provider::{AccountProvider, HeaderProvider, WithdrawalsProvider}; +use reth_provider::{AccountReader, HeaderProvider, WithdrawalsProvider}; use std::{ collections::{hash_map::Entry, HashMap}, time::SystemTime, @@ -120,7 +120,7 @@ pub fn validate_transaction_regarding_header( /// There is no gas check done as [REVM](https://github.com/bluealloy/revm/blob/fd0108381799662098b7ab2c429ea719d6dfbf28/crates/revm/src/evm_impl.rs#L113-L131) already checks that. pub fn validate_all_transaction_regarding_block_and_nonces< 'a, - Provider: HeaderProvider + AccountProvider, + Provider: HeaderProvider + AccountReader, >( transactions: impl Iterator, header: &Header, @@ -363,7 +363,7 @@ pub fn validate_block_regarding_chain( +pub fn full_validation( block: &SealedBlock, provider: Provider, chain_spec: &ChainSpec, @@ -444,7 +444,7 @@ mod tests { } } - impl AccountProvider for Provider { + impl AccountReader for Provider { fn basic_account(&self, _address: Address) -> Result> { Ok(self.account) } diff --git a/crates/revm/src/executor.rs b/crates/revm/src/executor.rs index d9ca094488ac..8f5fcc3e4200 100644 --- a/crates/revm/src/executor.rs +++ b/crates/revm/src/executor.rs @@ -654,7 +654,7 @@ mod tests { }; use reth_provider::{ post_state::{AccountChanges, Storage, StorageTransition, StorageWipe}, - AccountProvider, BlockHashProvider, StateProvider, StateRootProvider, + AccountReader, BlockHashProvider, StateProvider, StateRootProvider, }; use reth_rlp::Decodable; use std::{collections::HashMap, str::FromStr}; @@ -693,7 +693,7 @@ mod tests { } } - impl AccountProvider for StateProviderTest { + impl AccountReader for StateProviderTest { fn basic_account(&self, address: Address) -> reth_interfaces::Result> { let ret = Ok(self.accounts.get(&address).map(|(_, acc)| *acc)); ret diff --git a/crates/rpc/rpc/src/eth/api/state.rs b/crates/rpc/rpc/src/eth/api/state.rs index 9a9669c3fb6d..4785ea3846ba 100644 --- a/crates/rpc/rpc/src/eth/api/state.rs +++ b/crates/rpc/rpc/src/eth/api/state.rs @@ -9,7 +9,7 @@ use reth_primitives::{ U256, }; use reth_provider::{ - AccountProvider, BlockProviderIdExt, EvmEnvProvider, StateProvider, StateProviderFactory, + AccountReader, BlockProviderIdExt, EvmEnvProvider, StateProvider, StateProviderFactory, }; use reth_rpc_types::{EIP1186AccountProofResponse, StorageProof}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index 4f6eebb469c5..088d9901e298 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -6,7 +6,9 @@ use reth_db::{ transaction::{DbTx, DbTxMut}, }; use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, H256, U256}; -use reth_provider::{DatabaseProviderRW, PostState, ProviderFactory, TransactionError}; +use reth_provider::{ + AccountWriter, DatabaseProviderRW, PostState, ProviderFactory, TransactionError, +}; use std::{path::Path, sync::Arc}; use tracing::debug; diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index b620314963c3..4dae22d19324 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -422,9 +422,7 @@ mod tests { hex_literal::hex, keccak256, stage::StageUnitCheckpoint, Account, Bytecode, ChainSpecBuilder, SealedBlock, StorageEntry, H160, H256, MAINNET, U256, }; - use reth_provider::{ - insert_canonical_block, AccountProvider, ProviderFactory, ReceiptProvider, - }; + use reth_provider::{insert_canonical_block, AccountReader, ProviderFactory, ReceiptProvider}; use reth_revm::Factory; use reth_rlp::Decodable; use std::sync::Arc; diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 0178a73fa7f4..255a09aa1445 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -16,7 +16,7 @@ use reth_primitives::{ StageId, }, }; -use reth_provider::{AccountExtProvider, DatabaseProviderRW}; +use reth_provider::{AccountExtReader, AccountWriter, DatabaseProviderRW}; use std::{ cmp::max, fmt::Debug, diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index df087a48c5ff..30bc39aec83a 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -1,7 +1,7 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::database::Database; use reth_primitives::stage::{StageCheckpoint, StageId}; -use reth_provider::DatabaseProviderRW; +use reth_provider::{AccountExtReader, AccountWriter, DatabaseProviderRW}; use std::fmt::Debug; /// Stage is indexing history the account changesets generated in @@ -46,7 +46,7 @@ impl Stage for IndexAccountHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); - let indices = provider.get_account_block_numbers_from_changesets(range.clone())?; + let indices = provider.changed_accounts_and_blocks_with_range(range.clone())?; // Insert changeset to history index provider.insert_account_history_index(indices)?; diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 00b4f022ab05..c290ed61ef5b 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -11,8 +11,8 @@ /// Various provider traits. mod traits; pub use traits::{ - AccountExtProvider, AccountProvider, BlockExecutor, BlockHashProvider, BlockIdProvider, - BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockSource, + AccountExtReader, AccountReader, AccountWriter, BlockExecutor, BlockHashProvider, + BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockSource, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, ExecutorFactory, HeaderProvider, PostStateDataProvider, ReceiptProvider, ReceiptProviderIdExt, diff --git a/crates/storage/provider/src/post_state/mod.rs b/crates/storage/provider/src/post_state/mod.rs index f37d16128c89..e9eb216f1e14 100644 --- a/crates/storage/provider/src/post_state/mod.rs +++ b/crates/storage/provider/src/post_state/mod.rs @@ -640,7 +640,7 @@ impl PostState { #[cfg(test)] mod tests { use super::*; - use crate::{AccountProvider, ProviderFactory}; + use crate::{AccountReader, ProviderFactory}; use reth_db::{ database::Database, mdbx::{test_utils, Env, EnvKind, WriteMap}, diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index d4d3f332a188..e92f079df2cb 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1,10 +1,10 @@ use crate::{ insert_canonical_block, post_state::StorageChangeset, - traits::{AccountExtProvider, BlockSource, ReceiptProvider, StageCheckpointWriter}, - AccountProvider, BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, - HeaderProvider, PostState, ProviderError, StageCheckpointReader, TransactionError, - TransactionsProvider, WithdrawalsProvider, + traits::{AccountExtReader, BlockSource, ReceiptProvider, StageCheckpointWriter}, + AccountReader, AccountWriter, BlockHashProvider, BlockNumProvider, BlockProvider, + EvmEnvProvider, HeaderProvider, PostState, ProviderError, StageCheckpointReader, + TransactionError, TransactionsProvider, WithdrawalsProvider, }; use itertools::{izip, Itertools}; use reth_db::{ @@ -109,7 +109,7 @@ fn unwind_account_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( cursor: &mut >::CursorMut, address: Address, block_number: BlockNumber, -) -> std::result::Result, TransactionError> { +) -> Result> { let mut item = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; while let Some((sharded_key, list)) = item { @@ -276,56 +276,6 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { Ok(storage_changeset_lists) } - - /// Get all block numbers where account got changed. - /// - /// NOTE: Get inclusive range of blocks. - pub fn get_account_block_numbers_from_changesets( - &self, - range: RangeInclusive, - ) -> std::result::Result>, TransactionError> { - let mut changeset_cursor = self.tx.cursor_read::()?; - - let account_transtions = changeset_cursor.walk_range(range)?.try_fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, - entry| - -> std::result::Result<_, TransactionError> { - let (index, account) = entry?; - accounts.entry(account.address).or_default().push(index); - Ok(accounts) - }, - )?; - - Ok(account_transtions) - } - - /// Iterate over account changesets and return all account address that were changed. - pub fn get_addresses_of_changed_accounts( - &self, - range: RangeInclusive, - ) -> std::result::Result, TransactionError> { - self.tx.cursor_read::()?.walk_range(range)?.try_fold( - BTreeSet::new(), - |mut accounts: BTreeSet
, entry| { - let (_, account_before) = entry?; - accounts.insert(account_before.address); - Ok(accounts) - }, - ) - } - - /// Get plainstate account from iterator - pub fn get_plainstate_accounts( - &self, - iter: impl IntoIterator, - ) -> std::result::Result)>, TransactionError> { - let mut plain_accounts = self.tx.cursor_read::()?; - Ok(iter - .into_iter() - .map(|address| plain_accounts.seek_exact(address).map(|a| (address, a.map(|(_, v)| v)))) - .collect::, _>>()?) - } } impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { @@ -354,49 +304,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { self.get_take_block_and_execution_range::(chain_spec, range) } - /// Unwind and clear account hashing - pub fn unwind_account_hashing( - &self, - range: RangeInclusive, - ) -> std::result::Result<(), TransactionError> { - let mut hashed_accounts = self.tx.cursor_write::()?; - - // Aggregate all block changesets and make a list of accounts that have been changed. - self.tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()? - .into_iter() - .rev() - // fold all account to get the old balance/nonces and account that needs to be removed - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, (_, account_before)| { - accounts.insert(account_before.address, account_before.info); - accounts - }, - ) - .into_iter() - // hash addresses and collect it inside sorted BTreeMap. - // We are doing keccak only once per address. - .map(|(address, account)| (keccak256(address), account)) - .collect::>() - .into_iter() - // Apply values to HashedState (if Account is None remove it); - .try_for_each( - |(hashed_address, account)| -> std::result::Result<(), TransactionError> { - if let Some(account) = account { - hashed_accounts.upsert(hashed_address, account)?; - } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { - hashed_accounts.delete_current()?; - } - Ok(()) - }, - )?; - - Ok(()) - } - /// Unwind and clear storage hashing pub fn unwind_storage_hashing( &self, @@ -447,49 +354,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// Unwind and clear account history indices. - /// - /// Returns number of changesets walked. - pub fn unwind_account_history_indices( - &self, - range: RangeInclusive, - ) -> std::result::Result { - let account_changeset = self - .tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; - let changesets = account_changeset.len(); - - let last_indices = account_changeset - .into_iter() - // reverse so we can get lowest block number where we need to unwind account. - .rev() - // fold all account and get last block number - .fold(BTreeMap::new(), |mut accounts: BTreeMap, (index, account)| { - // we just need address and lowest block number. - accounts.insert(account.address, index); - accounts - }); - // try to unwind the index - let mut cursor = self.tx.cursor_write::()?; - for (address, rem_index) in last_indices { - let shard_part = unwind_account_history_shards::(&mut cursor, address, rem_index)?; - - // check last shard_part, if present, items needs to be reinserted. - if !shard_part.is_empty() { - // there are items in list - self.tx.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(shard_part) - .expect("There is at least one element in list and it is sorted."), - )?; - } - } - - Ok(changesets) - } - /// Unwind and clear storage history indices. /// /// Returns number of changesets walked. @@ -1034,44 +898,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// Insert account change index to database. Used inside AccountHistoryIndex stage - pub fn insert_account_history_index( - &self, - account_transitions: BTreeMap>, - ) -> std::result::Result<(), TransactionError> { - // insert indexes to AccountHistory. - for (address, mut indices) in account_transitions { - let mut last_shard = self.take_last_account_shard(address)?; - last_shard.append(&mut indices); - // chunk indices and insert them in shards of N size. - let mut chunks = last_shard - .iter() - .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) - .into_iter() - .map(|chunks| chunks.map(|i| *i as usize).collect::>()) - .collect::>(); - let last_chunk = chunks.pop(); - - chunks.into_iter().try_for_each(|list| { - self.tx.put::( - ShardedKey::new( - address, - *list.last().expect("Chuck does not return empty list") as BlockNumber, - ), - BlockNumberList::new(list).expect("Indices are presorted and not empty"), - ) - })?; - // Insert last list with u64::MAX - if let Some(last_list) = last_chunk { - self.tx.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), - )? - } - } - Ok(()) - } - /// Query the block body by number. pub fn block_body_indices( &self, @@ -1141,23 +967,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// Load last shard and check if it is full and remove if it is not. If list is empty, last - /// shard was full or there is no shards at all. - fn take_last_account_shard( - &self, - address: Address, - ) -> std::result::Result, TransactionError> { - let mut cursor = self.tx.cursor_read::()?; - let last = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; - if let Some((shard_key, list)) = last { - // delete old shard so new one can be inserted. - self.tx.delete::(shard_key, None)?; - let list = list.iter(0).map(|i| i as u64).collect::>(); - return Ok(list) - } - Ok(Vec::new()) - } - /// Load last shard and check if it is full and remove if it is not. If list is empty, last /// shard was full or there is no shards at all. pub fn take_last_storage_shard( @@ -1214,34 +1023,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// iterate over accounts and insert them to hashing table - pub fn insert_account_for_hashing( - &self, - accounts: impl IntoIterator)>, - ) -> std::result::Result<(), TransactionError> { - let mut hashed_accounts = self.tx.cursor_write::()?; - - let hashes_accounts = accounts.into_iter().fold( - BTreeMap::new(), - |mut map: BTreeMap>, (address, account)| { - map.insert(keccak256(address), account); - map - }, - ); - - hashes_accounts.into_iter().try_for_each( - |(hashed_address, account)| -> std::result::Result<(), TransactionError> { - if let Some(account) = account { - hashed_accounts.upsert(hashed_address, account)? - } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { - hashed_accounts.delete_current()?; - } - Ok(()) - }, - )?; - Ok(()) - } - /// Append blocks and insert its post state. /// This will insert block data to all related tables and will update pipeline progress. pub fn append_blocks_with_post_state( @@ -1299,7 +1080,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { ) -> std::result::Result<(), TransactionError> { // account history stage { - let indices = self.get_account_block_numbers_from_changesets(range.clone())?; + let indices = self.changed_accounts_and_blocks_with_range(range.clone())?; self.insert_account_history_index(indices)?; } @@ -1333,8 +1114,8 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { // account hashing stage { - let lists = self.get_addresses_of_changed_accounts(range.clone())?; - let accounts = self.get_plainstate_accounts(lists.into_iter())?; + let lists = self.changed_accounts_with_range(range.clone())?; + let accounts = self.basic_accounts(lists.into_iter())?; self.insert_account_for_hashing(accounts.into_iter())?; } @@ -1356,13 +1137,13 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { } } -impl<'this, TX: DbTx<'this>> AccountProvider for DatabaseProvider<'this, TX> { +impl<'this, TX: DbTx<'this>> AccountReader for DatabaseProvider<'this, TX> { fn basic_account(&self, address: Address) -> Result> { Ok(self.tx.get::(address)?) } } -impl<'this, TX: DbTx<'this>> AccountExtProvider for DatabaseProvider<'this, TX> { +impl<'this, TX: DbTx<'this>> AccountExtReader for DatabaseProvider<'this, TX> { fn changed_accounts_with_range( &self, range: impl RangeBounds, @@ -1386,6 +1167,177 @@ impl<'this, TX: DbTx<'this>> AccountExtProvider for DatabaseProvider<'this, TX> .map(|address| plain_accounts.seek_exact(address).map(|a| (address, a.map(|(_, v)| v)))) .collect::, _>>()?) } + + fn changed_accounts_and_blocks_with_range( + &self, + range: RangeInclusive, + ) -> Result>> { + let mut changeset_cursor = self.tx.cursor_read::()?; + + let account_transitions = changeset_cursor.walk_range(range)?.try_fold( + BTreeMap::new(), + |mut accounts: BTreeMap>, entry| -> Result<_> { + let (index, account) = entry?; + accounts.entry(account.address).or_default().push(index); + Ok(accounts) + }, + )?; + + Ok(account_transitions) + } +} + +impl<'this, TX: DbTxMut<'this> + DbTx<'this>> AccountWriter for DatabaseProvider<'this, TX> { + fn unwind_account_hashing(&self, range: RangeInclusive) -> Result<()> { + let mut hashed_accounts = self.tx.cursor_write::()?; + + // Aggregate all block changesets and make a list of accounts that have been changed. + self.tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()? + .into_iter() + .rev() + // fold all account to get the old balance/nonces and account that needs to be removed + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap>, (_, account_before)| { + accounts.insert(account_before.address, account_before.info); + accounts + }, + ) + .into_iter() + // hash addresses and collect it inside sorted BTreeMap. + // We are doing keccak only once per address. + .map(|(address, account)| (keccak256(address), account)) + .collect::>() + .into_iter() + // Apply values to HashedState (if Account is None remove it); + .try_for_each(|(hashed_address, account)| -> Result<()> { + if let Some(account) = account { + hashed_accounts.upsert(hashed_address, account)?; + } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { + hashed_accounts.delete_current()?; + } + Ok(()) + })?; + + Ok(()) + } + + fn unwind_account_history_indices(&self, range: RangeInclusive) -> Result { + let account_changeset = self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()?; + let changesets = account_changeset.len(); + + let last_indices = account_changeset + .into_iter() + // reverse so we can get lowest block number where we need to unwind account. + .rev() + // fold all account and get last block number + .fold(BTreeMap::new(), |mut accounts: BTreeMap, (index, account)| { + // we just need address and lowest block number. + accounts.insert(account.address, index); + accounts + }); + // try to unwind the index + let mut cursor = self.tx.cursor_write::()?; + for (address, rem_index) in last_indices { + let shard_part = unwind_account_history_shards::(&mut cursor, address, rem_index)?; + + // check last shard_part, if present, items needs to be reinserted. + if !shard_part.is_empty() { + // there are items in list + self.tx.put::( + ShardedKey::new(address, u64::MAX), + BlockNumberList::new(shard_part) + .expect("There is at least one element in list and it is sorted."), + )?; + } + } + + Ok(changesets) + } + + fn insert_account_history_index( + &self, + account_transitions: BTreeMap>, + ) -> Result<()> { + // insert indexes to AccountHistory. + for (address, mut indices) in account_transitions { + // Load last shard and check if it is full and remove if it is not. If list is empty, + // last shard was full or there is no shards at all. + let mut last_shard = { + let mut cursor = self.tx.cursor_read::()?; + let last = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; + if let Some((shard_key, list)) = last { + // delete old shard so new one can be inserted. + self.tx.delete::(shard_key, None)?; + let list = list.iter(0).map(|i| i as u64).collect::>(); + list + } else { + Vec::new() + } + }; + + last_shard.append(&mut indices); + // chunk indices and insert them in shards of N size. + let mut chunks = last_shard + .iter() + .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) + .into_iter() + .map(|chunks| chunks.map(|i| *i as usize).collect::>()) + .collect::>(); + let last_chunk = chunks.pop(); + + chunks.into_iter().try_for_each(|list| { + self.tx.put::( + ShardedKey::new( + address, + *list.last().expect("Chuck does not return empty list") as BlockNumber, + ), + BlockNumberList::new(list).expect("Indices are presorted and not empty"), + ) + })?; + + // Insert last list with u64::MAX + if let Some(last_list) = last_chunk { + self.tx.put::( + ShardedKey::new(address, u64::MAX), + BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), + )? + } + } + Ok(()) + } + + fn insert_account_for_hashing( + &self, + accounts: impl IntoIterator)>, + ) -> Result<()> { + let mut hashed_accounts = self.tx.cursor_write::()?; + + let hashes_accounts = accounts.into_iter().fold( + BTreeMap::new(), + |mut map: BTreeMap>, (address, account)| { + map.insert(keccak256(address), account); + map + }, + ); + + hashes_accounts.into_iter().try_for_each(|(hashed_address, account)| -> Result<()> { + if let Some(account) = account { + hashed_accounts.upsert(hashed_address, account)? + } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { + hashed_accounts.delete_current()?; + } + Ok(()) + })?; + Ok(()) + } } impl<'this, TX: DbTx<'this>> HeaderProvider for DatabaseProvider<'this, TX> { diff --git a/crates/storage/provider/src/providers/post_state_provider.rs b/crates/storage/provider/src/providers/post_state_provider.rs index b2c6e5e99469..065e5a1efea2 100644 --- a/crates/storage/provider/src/providers/post_state_provider.rs +++ b/crates/storage/provider/src/providers/post_state_provider.rs @@ -1,5 +1,5 @@ use crate::{ - AccountProvider, BlockHashProvider, PostState, PostStateDataProvider, StateProvider, + AccountReader, BlockHashProvider, PostState, PostStateDataProvider, StateProvider, StateRootProvider, }; use reth_interfaces::{provider::ProviderError, Result}; @@ -39,9 +39,7 @@ impl BlockHashProvider } } -impl AccountProvider - for PostStateProvider -{ +impl AccountReader for PostStateProvider { fn basic_account(&self, address: Address) -> Result> { if let Some(account) = self.post_state_data_provider.state().account(&address) { Ok(*account) diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 5bf571c99b4f..686dd0469c2f 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -1,6 +1,6 @@ use crate::{ - providers::state::macros::delegate_provider_impls, AccountProvider, BlockHashProvider, - PostState, ProviderError, StateProvider, StateRootProvider, + providers::state::macros::delegate_provider_impls, AccountReader, BlockHashProvider, PostState, + ProviderError, StateProvider, StateRootProvider, }; use reth_db::{ cursor::{DbCursorRO, DbDupCursorRO}, @@ -102,7 +102,7 @@ impl<'a, 'b, TX: DbTx<'a>> HistoricalStateProviderRef<'a, 'b, TX> { } } -impl<'a, 'b, TX: DbTx<'a>> AccountProvider for HistoricalStateProviderRef<'a, 'b, TX> { +impl<'a, 'b, TX: DbTx<'a>> AccountReader for HistoricalStateProviderRef<'a, 'b, TX> { /// Get basic account information. fn basic_account(&self, address: Address) -> Result> { match self.account_history_lookup(address)? { @@ -219,7 +219,7 @@ delegate_provider_impls!(HistoricalStateProvider<'a, TX> where [TX: DbTx<'a>]); #[cfg(test)] mod tests { use crate::{ - AccountProvider, HistoricalStateProvider, HistoricalStateProviderRef, StateProvider, + AccountReader, HistoricalStateProvider, HistoricalStateProviderRef, StateProvider, }; use reth_db::{ database::Database, diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 63d264c468d2..4d53351cf18e 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -1,6 +1,6 @@ use crate::{ - providers::state::macros::delegate_provider_impls, AccountProvider, BlockHashProvider, - PostState, StateProvider, StateRootProvider, + providers::state::macros::delegate_provider_impls, AccountReader, BlockHashProvider, PostState, + StateProvider, StateRootProvider, }; use reth_db::{ cursor::{DbCursorRO, DbDupCursorRO}, @@ -28,7 +28,7 @@ impl<'a, 'b, TX: DbTx<'a>> LatestStateProviderRef<'a, 'b, TX> { } } -impl<'a, 'b, TX: DbTx<'a>> AccountProvider for LatestStateProviderRef<'a, 'b, TX> { +impl<'a, 'b, TX: DbTx<'a>> AccountReader for LatestStateProviderRef<'a, 'b, TX> { /// Get basic account information. fn basic_account(&self, address: Address) -> Result> { self.db.get::(address).map_err(Into::into) diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index 83c9c756d18d..299032e99b85 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -23,7 +23,7 @@ pub(crate) use delegate_impls_to_as_ref; /// Delegates the provider trait implementations to the `as_ref` function of the type: /// -/// [AccountProvider](crate::AccountProvider) +/// [AccountReader](crate::AccountReader) /// [BlockHashProvider](crate::BlockHashProvider) /// [StateProvider](crate::StateProvider) macro_rules! delegate_provider_impls { @@ -33,7 +33,7 @@ macro_rules! delegate_provider_impls { StateRootProvider $(where [$($generics)*])? { fn state_root(&self, state: crate::PostState) -> reth_interfaces::Result; } - AccountProvider $(where [$($generics)*])? { + AccountReader $(where [$($generics)*])? { fn basic_account(&self, address: reth_primitives::Address) -> reth_interfaces::Result>; } BlockHashProvider $(where [$($generics)*])? { diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 49cda80e9632..4e09510e720f 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -1,6 +1,6 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, - AccountProvider, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, + AccountReader, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, PostStateDataProvider, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, WithdrawalsProvider, @@ -362,7 +362,7 @@ impl BlockProviderIdExt for MockEthProvider { } } -impl AccountProvider for MockEthProvider { +impl AccountReader for MockEthProvider { fn basic_account(&self, address: Address) -> Result> { Ok(self.accounts.lock().get(&address).cloned().map(|a| a.account)) } diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 9a72152cb48f..2edbde9ca58d 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -1,6 +1,6 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, - AccountProvider, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, + AccountReader, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, WithdrawalsProvider, @@ -212,7 +212,7 @@ impl HeaderProvider for NoopProvider { } } -impl AccountProvider for NoopProvider { +impl AccountReader for NoopProvider { fn basic_account(&self, _address: Address) -> Result> { Ok(None) } diff --git a/crates/storage/provider/src/traits/account.rs b/crates/storage/provider/src/traits/account.rs index 6a48104f55ff..ceafaec2458b 100644 --- a/crates/storage/provider/src/traits/account.rs +++ b/crates/storage/provider/src/traits/account.rs @@ -1,20 +1,23 @@ use auto_impl::auto_impl; use reth_interfaces::Result; use reth_primitives::{Account, Address, BlockNumber}; -use std::{collections::BTreeSet, ops::RangeBounds}; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::{RangeBounds, RangeInclusive}, +}; -/// Account provider +/// Account reader #[auto_impl(&, Arc, Box)] -pub trait AccountProvider: Send + Sync { +pub trait AccountReader: Send + Sync { /// Get basic account information. /// /// Returns `None` if the account doesn't exist. fn basic_account(&self, address: Address) -> Result>; } -/// Account provider +/// Account reader #[auto_impl(&, Arc, Box)] -pub trait AccountExtProvider: Send + Sync { +pub trait AccountExtReader: Send + Sync { /// Iterate over account changesets and return all account address that were changed. fn changed_accounts_with_range( &self, @@ -22,11 +25,44 @@ pub trait AccountExtProvider: Send + Sync { ) -> Result>; /// Get basic account information for multiple accounts. A more efficient version than calling - /// [`AccountProvider::basic_account`] repeatedly. + /// [`AccountReader::basic_account`] repeatedly. /// /// Returns `None` if the account doesn't exist. fn basic_accounts( &self, _iter: impl IntoIterator, ) -> Result)>>; + + /// Iterate over account changesets and return all account addresses that were changed alongside + /// each specific set of blocks. + /// + /// NOTE: Get inclusive range of blocks. + fn changed_accounts_and_blocks_with_range( + &self, + range: RangeInclusive, + ) -> Result>>; +} + +/// Account reader +#[auto_impl(&, Arc, Box)] +pub trait AccountWriter: Send + Sync { + /// Unwind and clear account hashing + fn unwind_account_hashing(&self, range: RangeInclusive) -> Result<()>; + + /// Unwind and clear account history indices. + /// + /// Returns number of changesets walked. + fn unwind_account_history_indices(&self, range: RangeInclusive) -> Result; + + /// Insert account change index to database. Used inside AccountHistoryIndex stage + fn insert_account_history_index( + &self, + account_transitions: BTreeMap>, + ) -> Result<()>; + + /// iterate over accounts and insert them to hashing table + fn insert_account_for_hashing( + &self, + accounts: impl IntoIterator)>, + ) -> Result<()>; } diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index d6564a87f228..07b918a01acf 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -1,7 +1,7 @@ //! Collection of common provider traits. mod account; -pub use account::{AccountExtProvider, AccountProvider}; +pub use account::{AccountExtReader, AccountReader, AccountWriter}; mod block; pub use block::{BlockProvider, BlockProviderIdExt, BlockSource}; diff --git a/crates/storage/provider/src/traits/state.rs b/crates/storage/provider/src/traits/state.rs index bbd13ec3df7b..5e9dca9959f0 100644 --- a/crates/storage/provider/src/traits/state.rs +++ b/crates/storage/provider/src/traits/state.rs @@ -1,4 +1,4 @@ -use super::AccountProvider; +use super::AccountReader; use crate::{post_state::PostState, BlockHashProvider, BlockIdProvider}; use auto_impl::auto_impl; use reth_interfaces::{provider::ProviderError, Result}; @@ -13,7 +13,7 @@ pub type StateProviderBox<'a> = Box; /// An abstraction for a type that provides state data. #[auto_impl(&, Arc, Box)] pub trait StateProvider: - BlockHashProvider + AccountProvider + StateRootProvider + Send + Sync + BlockHashProvider + AccountReader + StateRootProvider + Send + Sync { /// Get storage of given account. fn storage(&self, account: Address, storage_key: StorageKey) -> Result>; diff --git a/crates/transaction-pool/src/validate.rs b/crates/transaction-pool/src/validate.rs index f8705b324e99..d2343caafb15 100644 --- a/crates/transaction-pool/src/validate.rs +++ b/crates/transaction-pool/src/validate.rs @@ -11,7 +11,7 @@ use reth_primitives::{ TransactionSignedEcRecovered, TxHash, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, }; -use reth_provider::{AccountProvider, StateProviderFactory}; +use reth_provider::{AccountReader, StateProviderFactory}; use std::{fmt, marker::PhantomData, sync::Arc, time::Instant}; /// A Result type returned after checking a transaction's validity. From 1049202f0fbb3da4a57817c73d14a5ae56b58c75 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 19 Jun 2023 12:53:18 +0100 Subject: [PATCH 079/216] chore: move `update_pipeline_stages` to `StageCheckpointWriter` (#3229) --- .../src/providers/database/provider.rs | 41 +++++++++---------- .../provider/src/traits/stage_checkpoint.rs | 12 +++++- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index e92f079df2cb..b9b6ea5203d4 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -837,27 +837,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(blocks) } - /// Update all pipeline sync stage progress. - pub fn update_pipeline_stages( - &self, - block_number: BlockNumber, - drop_stage_checkpoint: bool, - ) -> std::result::Result<(), TransactionError> { - // iterate over all existing stages in the table and update its progress. - let mut cursor = self.tx.cursor_write::()?; - while let Some((stage_name, checkpoint)) = cursor.next()? { - cursor.upsert( - stage_name, - StageCheckpoint { - block_number, - ..if drop_stage_checkpoint { Default::default() } else { checkpoint } - }, - )? - } - - Ok(()) - } - /// Insert storage change index to database. Used inside StorageHistoryIndex stage pub fn insert_storage_history_index( &self, @@ -1816,4 +1795,24 @@ impl<'this, TX: DbTxMut<'this>> StageCheckpointWriter for DatabaseProvider<'this fn save_stage_checkpoint(&self, id: StageId, checkpoint: StageCheckpoint) -> Result<()> { Ok(self.tx.put::(id.to_string(), checkpoint)?) } + + fn update_pipeline_stages( + &self, + block_number: BlockNumber, + drop_stage_checkpoint: bool, + ) -> Result<()> { + // iterate over all existing stages in the table and update its progress. + let mut cursor = self.tx.cursor_write::()?; + while let Some((stage_name, checkpoint)) = cursor.next()? { + cursor.upsert( + stage_name, + StageCheckpoint { + block_number, + ..if drop_stage_checkpoint { Default::default() } else { checkpoint } + }, + )? + } + + Ok(()) + } } diff --git a/crates/storage/provider/src/traits/stage_checkpoint.rs b/crates/storage/provider/src/traits/stage_checkpoint.rs index 4610b2643628..ef5568c10590 100644 --- a/crates/storage/provider/src/traits/stage_checkpoint.rs +++ b/crates/storage/provider/src/traits/stage_checkpoint.rs @@ -1,5 +1,8 @@ use reth_interfaces::Result; -use reth_primitives::stage::{StageCheckpoint, StageId}; +use reth_primitives::{ + stage::{StageCheckpoint, StageId}, + BlockNumber, +}; /// The trait for fetching stage checkpoint related data. #[auto_impl::auto_impl(&, Arc)] @@ -19,4 +22,11 @@ pub trait StageCheckpointWriter: Send + Sync { /// Save stage checkpoint progress. fn save_stage_checkpoint_progress(&self, id: StageId, checkpoint: Vec) -> Result<()>; + + /// Update all pipeline sync stage progress. + fn update_pipeline_stages( + &self, + block_number: BlockNumber, + drop_stage_checkpoint: bool, + ) -> Result<()>; } From b9c19e82b252b18d1d9f021e7170e06e5ba4cb0f Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 19 Jun 2023 15:54:30 +0300 Subject: [PATCH 080/216] chore: block gas limit constant (#3224) --- crates/consensus/auto-seal/src/task.rs | 4 ++-- crates/payload/basic/src/lib.rs | 10 +++++----- crates/primitives/src/constants.rs | 6 ++++++ crates/transaction-pool/src/pool/txpool.rs | 7 +++++-- crates/transaction-pool/src/validate.rs | 8 ++++---- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/consensus/auto-seal/src/task.rs b/crates/consensus/auto-seal/src/task.rs index 65cdf3eb79e7..06f637d38905 100644 --- a/crates/consensus/auto-seal/src/task.rs +++ b/crates/consensus/auto-seal/src/task.rs @@ -3,7 +3,7 @@ use futures_util::{future::BoxFuture, FutureExt, StreamExt}; use reth_beacon_consensus::BeaconEngineMessage; use reth_interfaces::consensus::ForkchoiceState; use reth_primitives::{ - constants::{EMPTY_RECEIPTS, EMPTY_TRANSACTIONS}, + constants::{EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, ETHEREUM_BLOCK_GAS_LIMIT}, proofs, stage::StageId, Block, BlockBody, ChainSpec, Header, IntoRecoveredTransaction, ReceiptWithBloom, @@ -142,7 +142,7 @@ where logs_bloom: Default::default(), difficulty: U256::from(2), number: storage.best_block + 1, - gas_limit: 30_000_000, + gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, gas_used: 0, timestamp: SystemTime::now() .duration_since(UNIX_EPOCH) diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index b866a339407a..a16e730abc01 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -17,8 +17,8 @@ use reth_payload_builder::{ use reth_primitives::{ bytes::{Bytes, BytesMut}, constants::{ - BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, EMPTY_WITHDRAWALS, RETH_CLIENT_VERSION, - SLOT_DURATION, + BEACON_NONCE, EMPTY_RECEIPTS, EMPTY_TRANSACTIONS, EMPTY_WITHDRAWALS, + ETHEREUM_BLOCK_GAS_LIMIT, RETH_CLIENT_VERSION, SLOT_DURATION, }, proofs, Block, BlockNumberOrTag, ChainSpec, Header, IntoRecoveredTransaction, Receipt, SealedBlock, Withdrawal, EMPTY_OMMER_ROOT, H256, U256, @@ -173,7 +173,7 @@ impl PayloadTaskGuard { pub struct BasicPayloadJobGeneratorConfig { /// Data to include in the block's extra data field. extradata: Bytes, - /// Target gas ceiling for built blocks, defaults to 30_000_000 gas. + /// Target gas ceiling for built blocks, defaults to [ETHEREUM_BLOCK_GAS_LIMIT] gas. max_gas_limit: u64, /// The interval at which the job should build a new payload after the last. interval: Duration, @@ -219,7 +219,7 @@ impl BasicPayloadJobGeneratorConfig { /// Sets the target gas ceiling for mined blocks. /// - /// Defaults to 30_000_000 gas. + /// Defaults to [ETHEREUM_BLOCK_GAS_LIMIT] gas. pub fn max_gas_limit(mut self, max_gas_limit: u64) -> Self { self.max_gas_limit = max_gas_limit; self @@ -232,7 +232,7 @@ impl Default for BasicPayloadJobGeneratorConfig { RETH_CLIENT_VERSION.as_bytes().encode(&mut extradata); Self { extradata: extradata.freeze(), - max_gas_limit: 30_000_000, + max_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, interval: Duration::from_secs(1), // 12s slot time deadline: SLOT_DURATION, diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index f5e2ba00753e..ca7ed45e1f34 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -25,6 +25,12 @@ pub const EPOCH_DURATION: Duration = Duration::from_secs(12 * 32); /// The default block nonce in the beacon consensus pub const BEACON_NONCE: u64 = 0u64; +/// The default Ethereum block gas limit. +/// +/// TODO: This should be a chain spec parameter. +/// See . +pub const ETHEREUM_BLOCK_GAS_LIMIT: u64 = 30_000_000; + /// The minimal value the basefee can decrease to. /// /// The `BASE_FEE_MAX_CHANGE_DENOMINATOR` is `8`, or 12.5%. diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index faa13e4cdb60..c501816335bb 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -16,7 +16,10 @@ use crate::{ PoolConfig, PoolResult, PoolTransaction, TransactionOrdering, ValidPoolTransaction, U256, }; use fnv::FnvHashMap; -use reth_primitives::{constants::MIN_PROTOCOL_BASE_FEE, TxHash, H256}; +use reth_primitives::{ + constants::{ETHEREUM_BLOCK_GAS_LIMIT, MIN_PROTOCOL_BASE_FEE}, + TxHash, H256, +}; use std::{ cmp::Ordering, collections::{btree_map::Entry, hash_map, BTreeMap, HashMap}, @@ -1163,7 +1166,7 @@ impl Default for AllTransactions { Self { max_account_slots: MAX_ACCOUNT_SLOTS_PER_SENDER, minimal_protocol_basefee: MIN_PROTOCOL_BASE_FEE, - block_gas_limit: 30_000_000, + block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, by_hash: Default::default(), txs: Default::default(), tx_counter: Default::default(), diff --git a/crates/transaction-pool/src/validate.rs b/crates/transaction-pool/src/validate.rs index d2343caafb15..e7bd6f19081a 100644 --- a/crates/transaction-pool/src/validate.rs +++ b/crates/transaction-pool/src/validate.rs @@ -7,9 +7,9 @@ use crate::{ MAX_INIT_CODE_SIZE, TX_MAX_SIZE, }; use reth_primitives::{ - Address, ChainSpec, IntoRecoveredTransaction, InvalidTransactionError, TransactionKind, - TransactionSignedEcRecovered, TxHash, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, - LEGACY_TX_TYPE_ID, U256, + constants::ETHEREUM_BLOCK_GAS_LIMIT, Address, ChainSpec, IntoRecoveredTransaction, + InvalidTransactionError, TransactionKind, TransactionSignedEcRecovered, TxHash, + EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, }; use reth_provider::{AccountReader, StateProviderFactory}; use std::{fmt, marker::PhantomData, sync::Arc, time::Instant}; @@ -138,7 +138,7 @@ impl EthTransactionValidator { shanghai: true, eip2718: true, eip1559: true, - block_gas_limit: 30_000_000, + block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, minimum_priority_fee: None, _marker: Default::default(), } From 71d6d7b480af8dd73f1d025178d3359b3f69f577 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 19 Jun 2023 15:23:11 +0200 Subject: [PATCH 081/216] chore: make block hash not found a warning (#3234) --- crates/blockchain-tree/src/blockchain_tree.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 0ed28c89bfe4..93a0b077d860 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -28,7 +28,7 @@ use std::{ collections::{BTreeMap, HashMap}, sync::Arc, }; -use tracing::{debug, error, info, instrument, trace}; +use tracing::{debug, error, info, instrument, trace, warn}; #[cfg_attr(doc, aquamarine::aquamarine)] /// Tree of chains and its identifications. @@ -887,7 +887,7 @@ impl BlockchainTree } let Some(chain_id) = self.block_indices.get_blocks_chain_id(block_hash) else { - error!(target: "blockchain_tree", ?block_hash, "Block hash not found in block indices"); + warn!(target: "blockchain_tree", ?block_hash, "Block hash not found in block indices"); // TODO: better error return Err(BlockExecutionError::BlockHashNotFoundInChain { block_hash: *block_hash }.into()) }; From c46c11b3d718da4e8b31faf3f54004c48d213f01 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 19 Jun 2023 15:25:21 +0200 Subject: [PATCH 082/216] fix: don't send engine events for dupe payloads (#3231) --- crates/blockchain-tree/src/blockchain_tree.rs | 45 +++++++++++++------ crates/blockchain-tree/src/shareable.rs | 9 ++-- crates/consensus/beacon/src/engine/mod.rs | 20 ++++++--- crates/interfaces/src/blockchain_tree/mod.rs | 19 +++++++- crates/storage/provider/src/providers/mod.rs | 8 ++-- 5 files changed, 74 insertions(+), 27 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 93a0b077d860..9cd869c11e15 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -8,7 +8,7 @@ use reth_db::{cursor::DbCursorRO, database::Database, tables, transaction::DbTx} use reth_interfaces::{ blockchain_tree::{ error::{BlockchainTreeError, InsertBlockError, InsertBlockErrorKind}, - BlockStatus, CanonicalOutcome, + BlockStatus, CanonicalOutcome, InsertPayloadOk, }, consensus::{Consensus, ConsensusError}, executor::{BlockExecutionError, BlockValidationError}, @@ -595,7 +595,7 @@ impl BlockchainTree pub fn insert_block_without_senders( &mut self, block: SealedBlock, - ) -> Result { + ) -> Result { match block.try_seal_with_senders() { Ok(block) => self.insert_block(block), Err(block) => Err(InsertBlockError::sender_recovery_error(block)), @@ -683,10 +683,10 @@ impl BlockchainTree pub fn insert_block( &mut self, block: SealedBlockWithSenders, - ) -> Result { + ) -> Result { // check if we already have this block match self.is_block_known(block.num_hash()) { - Ok(Some(status)) => return Ok(status), + Ok(Some(status)) => return Ok(InsertPayloadOk::AlreadySeen(status)), Err(err) => return Err(InsertBlockError::new(block.block, err)), _ => {} } @@ -696,7 +696,7 @@ impl BlockchainTree return Err(InsertBlockError::consensus_error(err, block.block)) } - self.try_insert_validated_block(block) + Ok(InsertPayloadOk::Inserted(self.try_insert_validated_block(block)?)) } /// Finalize blocks up until and including `finalized_block`, and remove them from the tree. @@ -1220,7 +1220,9 @@ mod tests { // block 2 parent is not known, block2 is buffered. assert_eq!( tree.insert_block(block2.clone()).unwrap(), - BlockStatus::Disconnected { missing_parent: block2.parent_num_hash() } + InsertPayloadOk::Inserted(BlockStatus::Disconnected { + missing_parent: block2.parent_num_hash() + }) ); // Buffered block: [block2] @@ -1248,7 +1250,10 @@ mod tests { assert_eq!(tree.is_block_known(old_block).unwrap_err().as_tree_error(), Some(err)); // insert block1 and buffered block2 is inserted - assert_eq!(tree.insert_block(block1.clone()).unwrap(), BlockStatus::Valid); + assert_eq!( + tree.insert_block(block1.clone()).unwrap(), + InsertPayloadOk::Inserted(BlockStatus::Valid) + ); // Buffered blocks: [] // Trie state: @@ -1267,11 +1272,17 @@ mod tests { .with_pending_blocks((block1.number, HashSet::from([block1.hash]))) .assert(&tree); - // already inserted block will return true. - assert_eq!(tree.insert_block(block1.clone()).unwrap(), BlockStatus::Valid); + // already inserted block will `InsertPayloadOk::AlreadySeen(_)` + assert_eq!( + tree.insert_block(block1.clone()).unwrap(), + InsertPayloadOk::AlreadySeen(BlockStatus::Valid) + ); // block two is already inserted. - assert_eq!(tree.insert_block(block2.clone()).unwrap(), BlockStatus::Valid); + assert_eq!( + tree.insert_block(block2.clone()).unwrap(), + InsertPayloadOk::AlreadySeen(BlockStatus::Valid) + ); // make block1 canonical assert!(tree.make_canonical(&block1.hash()).is_ok()); @@ -1308,7 +1319,10 @@ mod tests { block2a.hash = block2a_hash; // reinsert two blocks that point to canonical chain - assert_eq!(tree.insert_block(block1a.clone()).unwrap(), BlockStatus::Accepted); + assert_eq!( + tree.insert_block(block1a.clone()).unwrap(), + InsertPayloadOk::Inserted(BlockStatus::Accepted) + ); TreeTester::default() .with_chain_num(1) @@ -1320,7 +1334,10 @@ mod tests { .with_pending_blocks((block2.number + 1, HashSet::from([]))) .assert(&tree); - assert_eq!(tree.insert_block(block2a.clone()).unwrap(), BlockStatus::Accepted); + assert_eq!( + tree.insert_block(block2a.clone()).unwrap(), + InsertPayloadOk::Inserted(BlockStatus::Accepted) + ); // Trie state: // b2 b2a (side chain) // | / @@ -1504,7 +1521,9 @@ mod tests { assert_eq!( tree.insert_block(block2b.clone()).unwrap(), - BlockStatus::Disconnected { missing_parent: block2b.parent_num_hash() } + InsertPayloadOk::Inserted(BlockStatus::Disconnected { + missing_parent: block2b.parent_num_hash() + }) ); TreeTester::default() diff --git a/crates/blockchain-tree/src/shareable.rs b/crates/blockchain-tree/src/shareable.rs index aa7f9215e003..75ced6dc90f3 100644 --- a/crates/blockchain-tree/src/shareable.rs +++ b/crates/blockchain-tree/src/shareable.rs @@ -4,8 +4,8 @@ use parking_lot::RwLock; use reth_db::database::Database; use reth_interfaces::{ blockchain_tree::{ - error::InsertBlockError, BlockStatus, BlockchainTreeEngine, BlockchainTreeViewer, - CanonicalOutcome, + error::InsertBlockError, BlockchainTreeEngine, BlockchainTreeViewer, CanonicalOutcome, + InsertPayloadOk, }, consensus::Consensus, Error, @@ -44,7 +44,10 @@ impl BlockchainTreeEngine self.tree.write().buffer_block(block) } - fn insert_block(&self, block: SealedBlockWithSenders) -> Result { + fn insert_block( + &self, + block: SealedBlockWithSenders, + ) -> Result { trace!(target: "blockchain_tree", hash=?block.hash, number=block.number, parent_hash=?block.parent_hash, "Inserting block"); self.tree.write().insert_block(block) } diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index a43729dabb7c..b31dea409258 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -60,6 +60,7 @@ pub(crate) mod sync; use crate::engine::forkchoice::{ForkchoiceStateHash, ForkchoiceStateTracker}; pub use event::BeaconConsensusEngineEvent; +use reth_interfaces::blockchain_tree::InsertPayloadOk; /// The maximum number of invalid headers that can be tracked by the engine. const MAX_INVALID_HEADERS: u32 = 512u32; @@ -893,16 +894,17 @@ where let mut latest_valid_hash = None; let block = Arc::new(block); let status = match status { - BlockStatus::Valid => { + InsertPayloadOk::Inserted(BlockStatus::Valid) => { latest_valid_hash = Some(block_hash); self.listeners.notify(BeaconConsensusEngineEvent::CanonicalBlockAdded(block)); PayloadStatusEnum::Valid } - BlockStatus::Accepted => { + InsertPayloadOk::Inserted(BlockStatus::Accepted) => { self.listeners.notify(BeaconConsensusEngineEvent::ForkBlockAdded(block)); PayloadStatusEnum::Accepted } - BlockStatus::Disconnected { .. } => { + InsertPayloadOk::Inserted(BlockStatus::Disconnected { .. }) | + InsertPayloadOk::AlreadySeen(BlockStatus::Disconnected { .. }) => { // check if the block's parent is already marked as invalid if let Some(status) = self.check_invalid_ancestor_with_head(block.parent_hash, block.hash) @@ -913,6 +915,11 @@ where // not known to be invalid, but we don't know anything else PayloadStatusEnum::Syncing } + InsertPayloadOk::AlreadySeen(BlockStatus::Valid) => { + latest_valid_hash = Some(block_hash); + PayloadStatusEnum::Valid + } + InsertPayloadOk::AlreadySeen(BlockStatus::Accepted) => PayloadStatusEnum::Accepted, }; Ok(PayloadStatus::new(status, latest_valid_hash)) } @@ -1014,18 +1021,19 @@ where match self.blockchain.insert_block_without_senders(block) { Ok(status) => { match status { - BlockStatus::Valid => { + InsertPayloadOk::Inserted(BlockStatus::Valid) => { // block is connected to the current canonical head and is valid. self.try_make_sync_target_canonical(num_hash); } - BlockStatus::Accepted => { + InsertPayloadOk::Inserted(BlockStatus::Accepted) => { // block is connected to the canonical chain, but not the current head self.try_make_sync_target_canonical(num_hash); } - BlockStatus::Disconnected { missing_parent } => { + InsertPayloadOk::Inserted(BlockStatus::Disconnected { missing_parent }) => { // continue downloading the missing parent self.sync.download_full_block(missing_parent.hash); } + _ => (), } } Err(err) => { diff --git a/crates/interfaces/src/blockchain_tree/mod.rs b/crates/interfaces/src/blockchain_tree/mod.rs index 6a63de47758c..4e9e3b477189 100644 --- a/crates/interfaces/src/blockchain_tree/mod.rs +++ b/crates/interfaces/src/blockchain_tree/mod.rs @@ -21,7 +21,7 @@ pub trait BlockchainTreeEngine: BlockchainTreeViewer + Send + Sync { fn insert_block_without_senders( &self, block: SealedBlock, - ) -> Result { + ) -> Result { match block.try_seal_with_senders() { Ok(block) => self.insert_block(block), Err(block) => Err(InsertBlockError::sender_recovery_error(block)), @@ -43,7 +43,10 @@ pub trait BlockchainTreeEngine: BlockchainTreeViewer + Send + Sync { fn buffer_block(&self, block: SealedBlockWithSenders) -> Result<(), InsertBlockError>; /// Insert block with senders - fn insert_block(&self, block: SealedBlockWithSenders) -> Result; + fn insert_block( + &self, + block: SealedBlockWithSenders, + ) -> Result; /// Finalize blocks up until and including `finalized_block`, and remove them from the tree. fn finalize_block(&self, finalized_block: BlockNumber); @@ -135,6 +138,18 @@ pub enum BlockStatus { }, } +/// How a payload was inserted if it was valid. +/// +/// If the payload was valid, but has already been seen, [`InsertPayloadOk::AlreadySeen(_)`] is +/// returned, otherwise [`InsertPayloadOk::Inserted(_)`] is returned. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum InsertPayloadOk { + /// The payload was valid, but we have already seen it. + AlreadySeen(BlockStatus), + /// The payload was valid and inserted into the tree. + Inserted(BlockStatus), +} + /// Allows read only functionality on the blockchain tree. /// /// Tree contains all blocks that are not canonical that can potentially be included diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index a027bea8ae6e..e45080d2bd8b 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -7,7 +7,7 @@ use crate::{ }; use reth_db::{database::Database, models::StoredBlockBodyIndices}; use reth_interfaces::{ - blockchain_tree::{BlockStatus, BlockchainTreeEngine, BlockchainTreeViewer}, + blockchain_tree::{BlockchainTreeEngine, BlockchainTreeViewer}, consensus::ForkchoiceState, Error, Result, }; @@ -37,7 +37,9 @@ mod state; use crate::{providers::chain_info::ChainInfoTracker, traits::BlockSource}; pub use database::*; pub use post_state_provider::PostStateProvider; -use reth_interfaces::blockchain_tree::{error::InsertBlockError, CanonicalOutcome}; +use reth_interfaces::blockchain_tree::{ + error::InsertBlockError, CanonicalOutcome, InsertPayloadOk, +}; /// The main type for interacting with the blockchain. /// @@ -537,7 +539,7 @@ where fn insert_block( &self, block: SealedBlockWithSenders, - ) -> std::result::Result { + ) -> std::result::Result { self.tree.insert_block(block) } From 4709fcf16cdbebcef21e6fb2a1d10899cb31c934 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 19 Jun 2023 15:35:25 +0200 Subject: [PATCH 083/216] feat(txpool): add price bump for `is_underpriced` transaction check (#3202) --- crates/transaction-pool/src/lib.rs | 3 ++ crates/transaction-pool/src/pool/txpool.rs | 45 +++++++++++++++++++++- crates/transaction-pool/src/validate.rs | 5 --- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index d633a18f4fa4..fa1c73b93f93 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -136,6 +136,9 @@ pub(crate) const MAX_CODE_SIZE: usize = 24576; // Maximum initcode to permit in a creation transaction and create instructions pub(crate) const MAX_INIT_CODE_SIZE: usize = 2 * MAX_CODE_SIZE; +// Price bump (in %) for the transaction pool underpriced check +pub(crate) const PRICE_BUMP: u128 = 10; + /// A shareable, generic, customizable `TransactionPool` implementation. #[derive(Debug)] pub struct Pool { diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index c501816335bb..e9d370c0ab2d 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -13,7 +13,8 @@ use crate::{ AddedPendingTransaction, AddedTransaction, OnNewCanonicalStateOutcome, }, traits::{BlockInfo, PoolSize}, - PoolConfig, PoolResult, PoolTransaction, TransactionOrdering, ValidPoolTransaction, U256, + PoolConfig, PoolResult, PoolTransaction, TransactionOrdering, ValidPoolTransaction, PRICE_BUMP, + U256, }; use fnv::FnvHashMap; use reth_primitives::{ @@ -971,6 +972,25 @@ impl AllTransactions { Ok(transaction) } + /// Returns true if `transaction_a` is underpriced compared to `transaction_B`. + fn is_underpriced( + transaction_a: &ValidPoolTransaction, + transaction_b: &ValidPoolTransaction, + price_bump: u128, + ) -> bool { + let tx_a_max_priority_fee_per_gas = + transaction_a.transaction.max_priority_fee_per_gas().unwrap_or(0); + let tx_b_max_priority_fee_per_gas = + transaction_b.transaction.max_priority_fee_per_gas().unwrap_or(0); + + transaction_a.max_fee_per_gas() <= + transaction_b.max_fee_per_gas() * (100 + price_bump) / 100 || + (tx_a_max_priority_fee_per_gas <= + tx_b_max_priority_fee_per_gas * (100 + price_bump) / 100 && + tx_a_max_priority_fee_per_gas != 0 && + tx_b_max_priority_fee_per_gas != 0) + } + /// Inserts a new transaction into the pool. /// /// If the transaction already exists, it will be replaced if not underpriced. @@ -1038,7 +1058,12 @@ impl AllTransactions { Entry::Occupied(mut entry) => { // Transaction already exists // Ensure the new transaction is not underpriced - if transaction.is_underpriced(entry.get().transaction.as_ref()) { + + if Self::is_underpriced( + transaction.as_ref(), + entry.get().transaction.as_ref(), + PRICE_BUMP, + ) { return Err(InsertErr::Underpriced { transaction: pool_tx.transaction, existing: *entry.get().transaction.hash(), @@ -1410,6 +1435,22 @@ mod tests { assert_eq!(pool.len(), 1); } + #[test] + fn insert_replace_underpriced() { + let on_chain_balance = U256::ZERO; + let on_chain_nonce = 0; + let mut f = MockTransactionFactory::default(); + let mut pool = AllTransactions::default(); + let tx = MockTransaction::eip1559().inc_price().inc_limit(); + let first = f.validated(tx.clone()); + let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce); + let mut replacement = f.validated(tx.rng_hash()); + replacement.transaction = replacement.transaction.decr_price(); + let err = + pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err(); + assert!(matches!(err, InsertErr::Underpriced { .. })); + } + // insert nonce then nonce - 1 #[test] fn insert_previous() { diff --git a/crates/transaction-pool/src/validate.rs b/crates/transaction-pool/src/validate.rs index e7bd6f19081a..959d5958bc8b 100644 --- a/crates/transaction-pool/src/validate.rs +++ b/crates/transaction-pool/src/validate.rs @@ -367,11 +367,6 @@ impl ValidPoolTransaction { self.transaction.gas_limit() } - /// Returns true if this transaction is underpriced compared to the other. - pub(crate) fn is_underpriced(&self, other: &Self) -> bool { - self.transaction.effective_gas_price() <= other.transaction.effective_gas_price() - } - /// Whether the transaction originated locally. pub fn is_local(&self) -> bool { self.origin.is_local() From 0ffb9c56532263c9643f781bc1b4c3f4dbbc102b Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 19 Jun 2023 15:47:34 +0200 Subject: [PATCH 084/216] feat: display hardforks on startup (#3227) Co-authored-by: seroze --- bin/reth/src/node/mod.rs | 3 + crates/primitives/src/chain/mod.rs | 3 +- crates/primitives/src/chain/spec.rs | 207 +++++++++++++++++++++++++++- crates/primitives/src/lib.rs | 4 +- 4 files changed, 211 insertions(+), 6 deletions(-) diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 90c02e9d688a..3e698af272eb 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -76,6 +76,7 @@ use crate::{ }; use reth_interfaces::p2p::headers::client::HeadersClient; use reth_payload_builder::PayloadBuilderService; +use reth_primitives::DisplayHardforks; use reth_provider::providers::BlockchainProvider; use reth_stages::stages::{ AccountHashingStage, IndexAccountHistoryStage, IndexStorageHistoryStage, MerkleStage, @@ -171,6 +172,8 @@ impl Command { let genesis_hash = init_genesis(db.clone(), self.chain.clone())?; + info!(target: "reth::cli", "{}", DisplayHardforks::from(self.chain.hardforks().clone())); + let consensus: Arc = if self.auto_mine { debug!(target: "reth::cli", "Using auto seal"); Arc::new(AutoSealConsensus::new(Arc::clone(&self.chain))) diff --git a/crates/primitives/src/chain/mod.rs b/crates/primitives/src/chain/mod.rs index f10d08bfd97e..36d2092ad9d4 100644 --- a/crates/primitives/src/chain/mod.rs +++ b/crates/primitives/src/chain/mod.rs @@ -11,7 +11,8 @@ use std::{fmt, str::FromStr}; // The chain spec module. mod spec; pub use spec::{ - AllGenesisFormats, ChainSpec, ChainSpecBuilder, ForkCondition, GOERLI, MAINNET, SEPOLIA, + AllGenesisFormats, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, GOERLI, + MAINNET, SEPOLIA, }; // The chain info module. diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 3ea29f0f29d5..84f5901e3c68 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -9,7 +9,11 @@ use crate::{ use hex_literal::hex; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, sync::Arc}; +use std::{ + collections::BTreeMap, + fmt::{Display, Formatter}, + sync::Arc, +}; /// The Ethereum mainnet spec pub static MAINNET: Lazy> = Lazy::new(|| { @@ -706,11 +710,164 @@ impl ForkCondition { } } +/// A container to pretty-print a hardfork. +/// +/// The fork is formatted depending on its fork condition: +/// +/// - Block and timestamp based forks are formatted in the same manner (`{name} <({eip})> +/// @{condition}`) +/// - TTD based forks are formatted separately as `{name} <({eip})> @{ttd} (network is known +/// to be merged)` +/// +/// An optional EIP can be attached to the fork to display as well. This should generally be in the +/// form of just `EIP-x`, e.g. `EIP-1559`. +#[derive(Debug)] +struct DisplayFork { + /// The name of the hardfork (e.g. Frontier) + name: String, + /// The fork condition + activated_at: ForkCondition, + /// An optional EIP (e.g. `EIP-1559`). + eip: Option, +} + +impl Display for DisplayFork { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let name_with_eip = if let Some(eip) = &self.eip { + format!("{} ({})", self.name, eip) + } else { + self.name.clone() + }; + + match self.activated_at { + ForkCondition::Block(at) | ForkCondition::Timestamp(at) => { + write!(f, "{:32} @{}", name_with_eip, at)?; + } + ForkCondition::TTD { fork_block, total_difficulty } => { + writeln!( + f, + "{:32} @{} ({})", + name_with_eip, + total_difficulty, + if fork_block.is_some() { + "network is known to be merged" + } else { + "network is not known to be merged" + } + )?; + } + ForkCondition::Never => unreachable!(), + } + + Ok(()) + } +} + +/// A container for pretty-printing a list of hardforks. +/// +/// # Example +/// +/// ``` +/// # use reth_primitives::MAINNET; +/// # use reth_primitives::DisplayHardforks; +/// println!("{}", DisplayHardforks::from(MAINNET.hardforks().clone())); +/// ``` +/// +/// An example of the output: +/// +/// ```text +/// Pre-merge hard forks (block based): +// - Frontier @0 +// - Homestead @1150000 +// - Dao @1920000 +// - Tangerine @2463000 +// - SpuriousDragon @2675000 +// - Byzantium @4370000 +// - Constantinople @7280000 +// - Petersburg @7280000 +// - Istanbul @9069000 +// - MuirGlacier @9200000 +// - Berlin @12244000 +// - London @12965000 +// - ArrowGlacier @13773000 +// - GrayGlacier @15050000 +// Merge hard forks: +// - Paris @58750000000000000000000 (network is not known to be merged) +// +// Post-merge hard forks (timestamp based): +// - Shanghai @1681338455 +/// ``` +#[derive(Debug)] +pub struct DisplayHardforks { + /// A list of pre-merge (block based) hardforks + pre_merge: Vec, + /// A list of merge (TTD based) hardforks + with_merge: Vec, + /// A list of post-merge (timestamp based) hardforks + post_merge: Vec, +} + +impl Display for DisplayHardforks { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Pre-merge hard forks (block based):")?; + for fork in self.pre_merge.iter() { + writeln!(f, "- {fork}")?; + } + + if !self.with_merge.is_empty() { + writeln!(f, "Merge hard forks:")?; + for fork in self.with_merge.iter() { + writeln!(f, "- {fork}")?; + } + } + + if !self.post_merge.is_empty() { + writeln!(f, "Post-merge hard forks (timestamp based):")?; + for fork in self.post_merge.iter() { + writeln!(f, "- {fork}")?; + } + } + + Ok(()) + } +} + +impl From for DisplayHardforks +where + I: IntoIterator, +{ + fn from(iter: I) -> Self { + let mut pre_merge = Vec::new(); + let mut with_merge = Vec::new(); + let mut post_merge = Vec::new(); + + for (fork, condition) in iter.into_iter() { + let display_fork = + DisplayFork { name: fork.to_string(), activated_at: condition, eip: None }; + + match condition { + ForkCondition::Block(_) => { + pre_merge.push(display_fork); + } + ForkCondition::TTD { .. } => { + with_merge.push(display_fork); + } + ForkCondition::Timestamp(_) => { + post_merge.push(display_fork); + } + ForkCondition::Never => continue, + } + } + + Self { pre_merge, with_merge, post_merge } + } +} + #[cfg(test)] mod tests { use crate::{ - AllGenesisFormats, Chain, ChainSpec, ChainSpecBuilder, ForkCondition, ForkHash, ForkId, - Genesis, Hardfork, Head, GOERLI, H256, MAINNET, SEPOLIA, U256, + AllGenesisFormats, Chain, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, + ForkHash, ForkId, Genesis, Hardfork, Head, GOERLI, H256, MAINNET, SEPOLIA, U256, }; use bytes::BytesMut; use ethers_core::types as EtherType; @@ -726,6 +883,50 @@ mod tests { } } + #[test] + fn test_hardfork_list_display_mainnet() { + assert_eq!( + DisplayHardforks::from(MAINNET.hardforks().clone()).to_string(), + r##"Pre-merge hard forks (block based): +- Frontier @0 +- Homestead @1150000 +- Dao @1920000 +- Tangerine @2463000 +- SpuriousDragon @2675000 +- Byzantium @4370000 +- Constantinople @7280000 +- Petersburg @7280000 +- Istanbul @9069000 +- MuirGlacier @9200000 +- Berlin @12244000 +- London @12965000 +- ArrowGlacier @13773000 +- GrayGlacier @15050000 +Merge hard forks: +- Paris @58750000000000000000000 (network is not known to be merged) + +Post-merge hard forks (timestamp based): +- Shanghai @1681338455 +"## + ); + } + + #[test] + fn test_hardfork_list_ignores_disabled_forks() { + let spec = ChainSpec::builder() + .chain(Chain::mainnet()) + .genesis(Genesis::default()) + .with_fork(Hardfork::Frontier, ForkCondition::Block(0)) + .with_fork(Hardfork::Shanghai, ForkCondition::Never) + .build(); + assert_eq!( + DisplayHardforks::from(spec.hardforks().clone()).to_string(), + r##"Pre-merge hard forks (block based): +- Frontier @0 +"## + ); + } + // Tests that the ForkTimestamps are correctly set up. #[test] fn test_fork_timestamps() { diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 09f5d2f2e28c..3084617989c8 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -47,8 +47,8 @@ pub use block::{ }; pub use bloom::Bloom; pub use chain::{ - AllGenesisFormats, Chain, ChainInfo, ChainSpec, ChainSpecBuilder, ForkCondition, GOERLI, - MAINNET, SEPOLIA, + AllGenesisFormats, Chain, ChainInfo, ChainSpec, ChainSpecBuilder, DisplayHardforks, + ForkCondition, GOERLI, MAINNET, SEPOLIA, }; pub use compression::*; pub use constants::{ From 35a8096b07d83d3fbf69a3fc20e399bfed433976 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 19 Jun 2023 18:21:48 +0200 Subject: [PATCH 085/216] fix: use latest header in autoseal (#3239) --- crates/consensus/auto-seal/src/lib.rs | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index 30ae6d072a25..2d6d66da74df 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -20,7 +20,7 @@ use reth_primitives::{ BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Header, SealedBlock, SealedHeader, H256, U256, }; -use reth_provider::CanonStateNotificationSender; +use reth_provider::{BlockProviderIdExt, CanonStateNotificationSender}; use reth_transaction_pool::TransactionPool; use std::{collections::HashMap, sync::Arc}; use tokio::sync::{mpsc::UnboundedSender, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -88,7 +88,10 @@ pub struct AutoSealBuilder { // === impl AutoSealBuilder === -impl AutoSealBuilder { +impl AutoSealBuilder +where + Client: BlockProviderIdExt, +{ /// Creates a new builder instance to configure all parts. pub fn new( chain_spec: Arc, @@ -97,9 +100,15 @@ impl AutoSealBuilder { to_engine: UnboundedSender, canon_state_notification: CanonStateNotificationSender, ) -> Self { + let latest_header = client + .latest_header() + .ok() + .flatten() + .unwrap_or_else(|| chain_spec.sealed_genesis_header()); let mode = MiningMode::interval(std::time::Duration::from_secs(1)); + Self { - storage: Storage::new(&chain_spec), + storage: Storage::new(latest_header), client, consensus: AutoSealConsensus::new(chain_spec), pool, @@ -116,6 +125,7 @@ impl AutoSealBuilder { } /// Consumes the type and returns all components + #[track_caller] pub fn build(self) -> (AutoSealConsensus, AutoSealClient, MiningTask) { let Self { client, consensus, pool, mode, storage, to_engine, canon_state_notification } = self; @@ -142,11 +152,14 @@ pub(crate) struct Storage { // == impl Storage === impl Storage { - fn new(chain_spec: &ChainSpec) -> Self { - let header = chain_spec.genesis_header(); - let best_hash = header.hash_slow(); - let mut storage = - StorageInner { best_hash, total_difficulty: header.difficulty, ..Default::default() }; + fn new(header: SealedHeader) -> Self { + let (header, best_hash) = header.split(); + let mut storage = StorageInner { + best_hash, + total_difficulty: header.difficulty, + best_block: header.number, + ..Default::default() + }; storage.headers.insert(0, header); storage.bodies.insert(best_hash, BlockBody::default()); Self { inner: Arc::new(RwLock::new(storage)) } From d02c87d20efa4787472ffa60b8b894f0b8ec0620 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 19 Jun 2023 17:25:25 +0100 Subject: [PATCH 086/216] chore(primitives): reorder `StageId` variants (#3238) --- crates/primitives/src/stage/id.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/primitives/src/stage/id.rs b/crates/primitives/src/stage/id.rs index fb0ce846bc22..4b82298a13df 100644 --- a/crates/primitives/src/stage/id.rs +++ b/crates/primitives/src/stage/id.rs @@ -5,17 +5,17 @@ #[allow(missing_docs)] pub enum StageId { Headers, + TotalDifficulty, Bodies, SenderRecovery, - TotalDifficulty, + Execution, + MerkleUnwind, AccountHashing, StorageHashing, - IndexAccountHistory, - IndexStorageHistory, MerkleExecute, - MerkleUnwind, - Execution, TransactionLookup, + IndexStorageHistory, + IndexAccountHistory, Finish, Other(&'static str), } @@ -24,17 +24,17 @@ impl StageId { /// All supported Stages pub const ALL: [StageId; 13] = [ StageId::Headers, + StageId::TotalDifficulty, StageId::Bodies, StageId::SenderRecovery, - StageId::TotalDifficulty, + StageId::Execution, + StageId::MerkleUnwind, StageId::AccountHashing, StageId::StorageHashing, - StageId::IndexAccountHistory, - StageId::IndexStorageHistory, StageId::MerkleExecute, - StageId::MerkleUnwind, - StageId::Execution, StageId::TransactionLookup, + StageId::IndexStorageHistory, + StageId::IndexAccountHistory, StageId::Finish, ]; @@ -42,17 +42,17 @@ impl StageId { pub fn as_str(&self) -> &str { match self { StageId::Headers => "Headers", + StageId::TotalDifficulty => "TotalDifficulty", StageId::Bodies => "Bodies", StageId::SenderRecovery => "SenderRecovery", - StageId::TotalDifficulty => "TotalDifficulty", + StageId::Execution => "Execution", + StageId::MerkleUnwind => "MerkleUnwind", StageId::AccountHashing => "AccountHashing", StageId::StorageHashing => "StorageHashing", - StageId::IndexAccountHistory => "IndexAccountHistory", - StageId::IndexStorageHistory => "IndexStorageHistory", StageId::MerkleExecute => "MerkleExecute", - StageId::MerkleUnwind => "MerkleUnwind", - StageId::Execution => "Execution", StageId::TransactionLookup => "TransactionLookup", + StageId::IndexAccountHistory => "IndexAccountHistory", + StageId::IndexStorageHistory => "IndexStorageHistory", StageId::Finish => "Finish", StageId::Other(s) => s, } From 2b6a0468fc367266db42ad6dd9104acd6022ae22 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 19 Jun 2023 17:43:17 +0100 Subject: [PATCH 087/216] feat(bin, storage): db versioning (#3130) --- Cargo.lock | 3 + bin/reth/src/db/mod.rs | 8 +-- bin/reth/src/stage/dump/mod.rs | 9 +-- bin/reth/src/stage/run.rs | 2 + crates/staged-sync/Cargo.toml | 1 + crates/staged-sync/src/utils/init.rs | 69 ++++++++++++++++++-- crates/storage/db/Cargo.toml | 5 ++ crates/storage/db/build.rs | 8 +++ crates/storage/db/src/lib.rs | 2 + crates/storage/db/src/utils.rs | 16 +++++ crates/storage/db/src/version.rs | 98 ++++++++++++++++++++++++++++ 11 files changed, 204 insertions(+), 17 deletions(-) create mode 100644 crates/storage/db/build.rs create mode 100644 crates/storage/db/src/version.rs diff --git a/Cargo.lock b/Cargo.lock index cfa24a16fdd1..080489e423e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5097,6 +5097,7 @@ name = "reth-db" version = "0.1.0" dependencies = [ "arbitrary", + "assert_matches", "async-trait", "bytes", "criterion", @@ -5127,6 +5128,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", + "vergen", ] [[package]] @@ -5740,6 +5742,7 @@ dependencies = [ name = "reth-staged-sync" version = "0.1.0" dependencies = [ + "assert_matches", "async-trait", "confy", "enr 0.8.1", diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index 864fd7b8ddf9..272a67763807 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -10,6 +10,7 @@ use eyre::WrapErr; use human_bytes::human_bytes; use reth_db::{database::Database, tables}; use reth_primitives::ChainSpec; +use reth_staged_sync::utils::init::init_db; use std::sync::Arc; use tracing::error; @@ -92,13 +93,8 @@ impl Command { // add network name to data dir let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain); let db_path = data_dir.db_path(); - std::fs::create_dir_all(&db_path)?; - // TODO: Auto-impl for Database trait - let db = reth_db::mdbx::Env::::open( - db_path.as_ref(), - reth_db::mdbx::EnvKind::RW, - )?; + let db = init_db(&db_path)?; let mut tool = DbTool::new(&db, self.chain.clone())?; diff --git a/bin/reth/src/stage/dump/mod.rs b/bin/reth/src/stage/dump/mod.rs index c749eb68ec35..4ceffba6f1b4 100644 --- a/bin/reth/src/stage/dump/mod.rs +++ b/bin/reth/src/stage/dump/mod.rs @@ -98,13 +98,8 @@ impl Command { let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain); let db_path = data_dir.db_path(); info!(target: "reth::cli", path = ?db_path, "Opening database"); - std::fs::create_dir_all(&db_path)?; - - // TODO: Auto-impl for Database trait - let db = reth_db::mdbx::Env::::open( - db_path.as_ref(), - reth_db::mdbx::EnvKind::RW, - )?; + let db = Arc::new(init_db(db_path)?); + info!(target: "reth::cli", "Database opened"); let mut tool = DbTool::new(&db, self.chain.clone())?; diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index c7be9961f705..e6521c2800fb 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -120,6 +120,8 @@ impl Command { info!(target: "reth::cli", path = ?db_path, "Opening database"); let db = Arc::new(init_db(db_path)?); + info!(target: "reth::cli", "Database opened"); + let factory = ProviderFactory::new(&db, self.chain.clone()); let mut provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; diff --git a/crates/staged-sync/Cargo.toml b/crates/staged-sync/Cargo.toml index ebdbed6eb77f..a32223af1b0a 100644 --- a/crates/staged-sync/Cargo.toml +++ b/crates/staged-sync/Cargo.toml @@ -81,6 +81,7 @@ secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recov confy = "0.5" tempfile = "3.4" +assert_matches = "1.5.0" [features] test-utils = [ diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index 088d9901e298..d04a11f0db6e 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -1,21 +1,37 @@ +use eyre::WrapErr; use reth_db::{ cursor::DbCursorRO, database::{Database, DatabaseGAT}, + is_database_empty, mdbx::{Env, WriteMap}, tables, transaction::{DbTx, DbTxMut}, + version::{check_db_version_file, create_db_version_file, DatabaseVersionError}, }; use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, H256, U256}; use reth_provider::{ AccountWriter, DatabaseProviderRW, PostState, ProviderFactory, TransactionError, }; -use std::{path::Path, sync::Arc}; +use std::{fs, path::Path, sync::Arc}; use tracing::debug; /// Opens up an existing database or creates a new one at the specified path. pub fn init_db>(path: P) -> eyre::Result> { - std::fs::create_dir_all(path.as_ref())?; + if is_database_empty(&path) { + fs::create_dir_all(&path).wrap_err_with(|| { + format!("Could not create database directory {}", path.as_ref().display()) + })?; + create_db_version_file(&path)?; + } else { + match check_db_version_file(&path) { + Ok(_) => (), + Err(DatabaseVersionError::MissingFile) => create_db_version_file(&path)?, + Err(err) => return Err(err.into()), + } + } + let db = Env::::open(path.as_ref(), reth_db::mdbx::EnvKind::RW)?; + db.create_tables()?; Ok(db) @@ -165,11 +181,17 @@ pub fn insert_genesis_header( #[cfg(test)] mod tests { - use super::{init_genesis, InitDatabaseError}; - use reth_db::mdbx::test_utils::create_test_rw_db; + use super::{init_db, init_genesis, InitDatabaseError}; + use assert_matches::assert_matches; + use reth_db::{ + mdbx::test_utils::create_test_rw_db, + version::{db_version_file_path, DatabaseVersionError}, + }; use reth_primitives::{ GOERLI, GOERLI_GENESIS, MAINNET, MAINNET_GENESIS, SEPOLIA, SEPOLIA_GENESIS, }; + use std::fs; + use tempfile::tempdir; #[test] fn success_init_genesis_mainnet() { @@ -214,4 +236,43 @@ mod tests { } ) } + + #[test] + fn db_version() { + let path = tempdir().unwrap(); + + // Database is empty + { + let db = init_db(&path); + assert_matches!(db, Ok(_)); + } + + // Database is not empty, current version is the same as in the file + { + let db = init_db(&path); + assert_matches!(db, Ok(_)); + } + + // Database is not empty, version file is malformed + { + fs::write(path.path().join(db_version_file_path(&path)), "invalid-version").unwrap(); + let db = init_db(&path); + assert!(db.is_err()); + assert_matches!( + db.unwrap_err().downcast_ref::(), + Some(DatabaseVersionError::MalformedFile) + ) + } + + // Database is not empty, version file contains not matching version + { + fs::write(path.path().join(db_version_file_path(&path)), "0").unwrap(); + let db = init_db(&path); + assert!(db.is_err()); + assert_matches!( + db.unwrap_err().downcast_ref::(), + Some(DatabaseVersionError::VersionMismatch { version: 0 }) + ) + } + } } diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index c32d17b4aa5c..51b30ef2f523 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -71,6 +71,11 @@ serde_json = { workspace = true } paste = "1.0" +assert_matches = "1.5.0" + +[build-dependencies] +vergen = { version = "8.0.0", features = ["git", "gitcl"] } + [features] default = ["mdbx"] test-utils = ["tempfile", "arbitrary"] diff --git a/crates/storage/db/build.rs b/crates/storage/db/build.rs new file mode 100644 index 000000000000..edec6873ad9c --- /dev/null +++ b/crates/storage/db/build.rs @@ -0,0 +1,8 @@ +use std::error::Error; +use vergen::EmitBuilder; + +fn main() -> Result<(), Box> { + // Emit the instructions + EmitBuilder::builder().git_sha(true).emit()?; + Ok(()) +} diff --git a/crates/storage/db/src/lib.rs b/crates/storage/db/src/lib.rs index e0d7326f2cd9..31cd55cda939 100644 --- a/crates/storage/db/src/lib.rs +++ b/crates/storage/db/src/lib.rs @@ -68,6 +68,7 @@ pub mod abstraction; mod implementation; pub mod tables; mod utils; +pub mod version; #[cfg(feature = "mdbx")] /// Bindings for [MDBX](https://libmdbx.dqdkfa.ru/). @@ -79,3 +80,4 @@ pub mod mdbx { pub use abstraction::*; pub use reth_interfaces::db::DatabaseError; pub use tables::*; +pub use utils::is_database_empty; diff --git a/crates/storage/db/src/utils.rs b/crates/storage/db/src/utils.rs index 4115b52df177..d3e760f3d998 100644 --- a/crates/storage/db/src/utils.rs +++ b/crates/storage/db/src/utils.rs @@ -1,5 +1,7 @@ //! Utils crate for `db`. +use std::path::Path; + /// Returns the default page size that can be used in this OS. pub(crate) fn default_page_size() -> usize { let os_page_size = page_size::get(); @@ -13,3 +15,17 @@ pub(crate) fn default_page_size() -> usize { os_page_size.clamp(min_page_size, libmdbx_max_page_size) } + +/// Check if a db is empty. It does not provide any information on the +/// validity of the data in it. We consider a database as non empty when it's a non empty directory. +pub fn is_database_empty>(path: P) -> bool { + let path = path.as_ref(); + + if !path.exists() { + true + } else if let Ok(dir) = path.read_dir() { + dir.count() == 0 + } else { + true + } +} diff --git a/crates/storage/db/src/version.rs b/crates/storage/db/src/version.rs new file mode 100644 index 000000000000..6a9b492325de --- /dev/null +++ b/crates/storage/db/src/version.rs @@ -0,0 +1,98 @@ +//! Database version utils. + +use std::{ + fs, io, + path::{Path, PathBuf}, +}; + +/// The name of the file that contains the version of the database. +pub const DB_VERSION_FILE_NAME: &str = "database.version"; +/// The version of the database stored in the [DB_VERSION_FILE_NAME] file in the same directory as +/// database. Example: `1`. +pub const DB_VERSION: u64 = 1; + +/// Error when checking a database version using [check_db_version_file] +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug)] +pub enum DatabaseVersionError { + #[error("Unable to determine the version of the database, file is missing.")] + MissingFile, + #[error("Unable to determine the version of the database, file is malformed.")] + MalformedFile, + #[error( + "Breaking database change detected. \ + Your database version (v{version}) is incompatible with the latest database version (v{}).", + DB_VERSION.to_string() + )] + VersionMismatch { version: u64 }, + #[error("IO error occurred while reading {path}: {err}")] + IORead { err: io::Error, path: PathBuf }, +} + +/// Checks the database version file with [DB_VERSION_FILE_NAME] name. +/// +/// Returns [Ok] if file is found and has one line which equals to [DB_VERSION]. +/// Otherwise, returns different [DatabaseVersionError] error variants. +pub fn check_db_version_file>(db_path: P) -> Result<(), DatabaseVersionError> { + let version_file_path = db_version_file_path(db_path); + match fs::read_to_string(&version_file_path) { + Ok(raw_version) => { + let version = + raw_version.parse::().map_err(|_| DatabaseVersionError::MalformedFile)?; + if version != DB_VERSION { + return Err(DatabaseVersionError::VersionMismatch { version }) + } + + Ok(()) + } + Err(err) if err.kind() == io::ErrorKind::NotFound => Err(DatabaseVersionError::MissingFile), + Err(err) => Err(DatabaseVersionError::IORead { err, path: version_file_path }), + } +} + +/// Creates a database version file with [DB_VERSION_FILE_NAME] name containing [DB_VERSION] string. +/// +/// This function will create a file if it does not exist, +/// and will entirely replace its contents if it does. +pub fn create_db_version_file>(db_path: P) -> io::Result<()> { + fs::write(db_version_file_path(db_path), DB_VERSION.to_string()) +} + +/// Returns a database version file path. +pub fn db_version_file_path>(db_path: P) -> PathBuf { + db_path.as_ref().join(DB_VERSION_FILE_NAME) +} + +#[cfg(test)] +mod tests { + use super::{check_db_version_file, db_version_file_path, DatabaseVersionError}; + use assert_matches::assert_matches; + use std::fs; + use tempfile::tempdir; + + #[test] + fn missing_file() { + let dir = tempdir().unwrap(); + + let result = check_db_version_file(&dir); + assert_matches!(result, Err(DatabaseVersionError::MissingFile)); + } + + #[test] + fn malformed_file() { + let dir = tempdir().unwrap(); + fs::write(db_version_file_path(&dir), "invalid-version").unwrap(); + + let result = check_db_version_file(&dir); + assert_matches!(result, Err(DatabaseVersionError::MalformedFile)); + } + + #[test] + fn version_mismatch() { + let dir = tempdir().unwrap(); + fs::write(db_version_file_path(&dir), "0").unwrap(); + + let result = check_db_version_file(&dir); + assert_matches!(result, Err(DatabaseVersionError::VersionMismatch { version: 0 })); + } +} From 7ab8a7f3ecccf9b71295e01f5716bd819b00f74f Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 19 Jun 2023 19:29:15 +0200 Subject: [PATCH 088/216] chore: use workspace version (#3240) --- Cargo.lock | 8 ++++---- Cargo.toml | 1 + bin/reth/Cargo.toml | 2 +- crates/blockchain-tree/Cargo.toml | 2 +- crates/config/Cargo.toml | 2 +- crates/consensus/auto-seal/Cargo.toml | 2 +- crates/consensus/beacon/Cargo.toml | 2 +- crates/consensus/common/Cargo.toml | 2 +- crates/interfaces/Cargo.toml | 2 +- crates/metrics/Cargo.toml | 2 +- crates/metrics/metrics-derive/Cargo.toml | 2 +- crates/net/common/Cargo.toml | 2 +- crates/net/discv4/Cargo.toml | 2 +- crates/net/dns/Cargo.toml | 2 +- crates/net/downloaders/Cargo.toml | 2 +- crates/net/ecies/Cargo.toml | 2 +- crates/net/eth-wire/Cargo.toml | 2 +- crates/net/nat/Cargo.toml | 2 +- crates/net/network-api/Cargo.toml | 2 +- crates/net/network/Cargo.toml | 2 +- crates/payload/basic/Cargo.toml | 2 +- crates/payload/builder/Cargo.toml | 2 +- crates/primitives/Cargo.toml | 4 ++-- crates/revm/Cargo.toml | 2 +- crates/revm/revm-inspectors/Cargo.toml | 2 +- crates/revm/revm-primitives/Cargo.toml | 2 +- crates/rlp/Cargo.toml | 4 ++-- crates/rlp/rlp-derive/Cargo.toml | 2 +- crates/rpc/ipc/Cargo.toml | 2 +- crates/rpc/rpc-api/Cargo.toml | 2 +- crates/rpc/rpc-builder/Cargo.toml | 2 +- crates/rpc/rpc-engine-api/Cargo.toml | 2 +- crates/rpc/rpc-testing-util/Cargo.toml | 2 +- crates/rpc/rpc-types/Cargo.toml | 2 +- crates/rpc/rpc/Cargo.toml | 2 +- crates/staged-sync/Cargo.toml | 2 +- crates/stages/Cargo.toml | 2 +- crates/storage/codecs/Cargo.toml | 4 ++-- crates/storage/codecs/derive/Cargo.toml | 2 +- crates/storage/db/Cargo.toml | 2 +- crates/storage/libmdbx-rs/Cargo.toml | 2 +- crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml | 2 +- crates/storage/provider/Cargo.toml | 2 +- crates/tasks/Cargo.toml | 2 +- crates/tracing/Cargo.toml | 2 +- crates/transaction-pool/Cargo.toml | 2 +- crates/trie/Cargo.toml | 2 +- testing/ef-tests/Cargo.toml | 2 +- 48 files changed, 54 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 080489e423e6..63bdf8eb7026 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5316,7 +5316,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "0.1.6" +version = "0.1.0" dependencies = [ "bitflags 1.3.2", "byteorder", @@ -5336,7 +5336,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "0.12.6-0" +version = "0.1.0" dependencies = [ "bindgen", "cc", @@ -5579,7 +5579,7 @@ dependencies = [ [[package]] name = "reth-rlp" -version = "0.1.2" +version = "0.1.0" dependencies = [ "arrayvec", "auto_impl", @@ -5597,7 +5597,7 @@ dependencies = [ [[package]] name = "reth-rlp-derive" -version = "0.1.1" +version = "0.1.0" dependencies = [ "proc-macro2 1.0.60", "quote 1.0.28", diff --git a/Cargo.toml b/Cargo.toml index 28657600630f..0075efa63a7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ default-members = ["bin/reth"] resolver = "2" [workspace.package] +version = "0.1.0" edition = "2021" rust-version = "1.70" # Remember to update .clippy.toml and README.md license = "MIT OR Apache-2.0" diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index ddaa59a9a933..59a7b2651e93 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index a95766ee4c98..f955fde9ef3a 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-blockchain-tree" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index c87a361487fe..ac8084ae10dd 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-config" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/consensus/auto-seal/Cargo.toml b/crates/consensus/auto-seal/Cargo.toml index d4890b9cc5a5..0805bba21715 100644 --- a/crates/consensus/auto-seal/Cargo.toml +++ b/crates/consensus/auto-seal/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-auto-seal-consensus" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/consensus/beacon/Cargo.toml b/crates/consensus/beacon/Cargo.toml index 74a2a9094770..64845e971d84 100644 --- a/crates/consensus/beacon/Cargo.toml +++ b/crates/consensus/beacon/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-beacon-consensus" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/consensus/common/Cargo.toml b/crates/consensus/common/Cargo.toml index 82023d756970..55253ae5ece6 100644 --- a/crates/consensus/common/Cargo.toml +++ b/crates/consensus/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-consensus-common" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/interfaces/Cargo.toml b/crates/interfaces/Cargo.toml index 8cdcbaabb8bc..b13b0b7698d1 100644 --- a/crates/interfaces/Cargo.toml +++ b/crates/interfaces/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-interfaces" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml index e1e01e8c0800..75719a7aabb8 100644 --- a/crates/metrics/Cargo.toml +++ b/crates/metrics/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-metrics" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/metrics/metrics-derive/Cargo.toml b/crates/metrics/metrics-derive/Cargo.toml index ce992b589e74..c78a48db9f83 100644 --- a/crates/metrics/metrics-derive/Cargo.toml +++ b/crates/metrics/metrics-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-metrics-derive" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/common/Cargo.toml b/crates/net/common/Cargo.toml index 8cfdd2a5384a..998c9212f808 100644 --- a/crates/net/common/Cargo.toml +++ b/crates/net/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-net-common" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/discv4/Cargo.toml b/crates/net/discv4/Cargo.toml index 9c80b638460e..1775e98ebef3 100644 --- a/crates/net/discv4/Cargo.toml +++ b/crates/net/discv4/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-discv4" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index da6c70f233d7..4fe6d4c4534b 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-dns-discovery" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/downloaders/Cargo.toml b/crates/net/downloaders/Cargo.toml index 6554005db5e9..07942b2ad0e2 100644 --- a/crates/net/downloaders/Cargo.toml +++ b/crates/net/downloaders/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-downloaders" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/ecies/Cargo.toml b/crates/net/ecies/Cargo.toml index 1b3421d2541d..67e146d68544 100644 --- a/crates/net/ecies/Cargo.toml +++ b/crates/net/ecies/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-ecies" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index f9acb39bacff..c39a527a0819 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "reth-eth-wire" description = "Implements the eth/64 and eth/65 P2P protocols" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/nat/Cargo.toml b/crates/net/nat/Cargo.toml index 7db0e6d99019..f0fe8f3e2556 100644 --- a/crates/net/nat/Cargo.toml +++ b/crates/net/nat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-net-nat" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/network-api/Cargo.toml b/crates/net/network-api/Cargo.toml index f5fb4e62a828..31c93a0769f0 100644 --- a/crates/net/network-api/Cargo.toml +++ b/crates/net/network-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-network-api" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 6b90bb35b050..e1f29f39ba36 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-network" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/payload/basic/Cargo.toml b/crates/payload/basic/Cargo.toml index 4f3c19ceaf24..a2a9f3288aff 100644 --- a/crates/payload/basic/Cargo.toml +++ b/crates/payload/basic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-basic-payload-builder" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index 70e4954faa3c..b83236fd9b35 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-payload-builder" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 0efed1faa76a..4661f92bc693 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-primitives" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true @@ -12,7 +12,7 @@ description = "Commonly used types in reth." # reth reth-rlp = { workspace = true, features = ["std", "derive", "ethereum-types"] } reth-rlp-derive = { path = "../rlp/rlp-derive" } -reth-codecs = { version = "0.1.0", path = "../storage/codecs" } +reth-codecs = { path = "../storage/codecs" } revm-primitives = { workspace = true, features = ["serde"] } diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index 5e3f9673454a..e80ab708c0f3 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-revm" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/revm/revm-inspectors/Cargo.toml b/crates/revm/revm-inspectors/Cargo.toml index 8e17b60f0664..874f41dfce15 100644 --- a/crates/revm/revm-inspectors/Cargo.toml +++ b/crates/revm/revm-inspectors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-revm-inspectors" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/revm/revm-primitives/Cargo.toml b/crates/revm/revm-primitives/Cargo.toml index 7ada96d4b9cc..96efb46ff864 100644 --- a/crates/revm/revm-primitives/Cargo.toml +++ b/crates/revm/revm-primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-revm-primitives" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rlp/Cargo.toml b/crates/rlp/Cargo.toml index 8ac894e3f16d..c7977b49492c 100644 --- a/crates/rlp/Cargo.toml +++ b/crates/rlp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rlp" -version = "0.1.2" +version.workspace = true edition.workspace = true rust-version.workspace = true license = "Apache-2.0" @@ -16,7 +16,7 @@ ethnum = { version = "1", default-features = false, optional = true } smol_str = { version = "0.1", default-features = false, optional = true } ethereum-types = { version = "0.14", features = ["codec"], optional = true } revm-primitives = { workspace = true, features = ["serde"] } -reth-rlp-derive = { version = "0.1", path = "./rlp-derive", optional = true } +reth-rlp-derive = { path = "./rlp-derive", optional = true } [dev-dependencies] reth-rlp = { workspace = true, features = [ diff --git a/crates/rlp/rlp-derive/Cargo.toml b/crates/rlp/rlp-derive/Cargo.toml index 1ae76e95a6ad..299cb0993e7d 100644 --- a/crates/rlp/rlp-derive/Cargo.toml +++ b/crates/rlp/rlp-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rlp-derive" -version = "0.1.1" +version.workspace = true license = "Apache-2.0" edition.workspace = true rust-version.workspace = true diff --git a/crates/rpc/ipc/Cargo.toml b/crates/rpc/ipc/Cargo.toml index 78aef3dc37c4..7f118f83c2e3 100644 --- a/crates/rpc/ipc/Cargo.toml +++ b/crates/rpc/ipc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-ipc" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rpc/rpc-api/Cargo.toml b/crates/rpc/rpc-api/Cargo.toml index 63aa8974bf05..a6dde25c1a7c 100644 --- a/crates/rpc/rpc-api/Cargo.toml +++ b/crates/rpc/rpc-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rpc-api" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index b318f539f426..d0dfab2ca3dc 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rpc-builder" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rpc/rpc-engine-api/Cargo.toml b/crates/rpc/rpc-engine-api/Cargo.toml index bd1a4afac889..0f417a3a65fa 100644 --- a/crates/rpc/rpc-engine-api/Cargo.toml +++ b/crates/rpc/rpc-engine-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rpc-engine-api" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rpc/rpc-testing-util/Cargo.toml b/crates/rpc/rpc-testing-util/Cargo.toml index c3e5cafd49c4..7fc6976a87e8 100644 --- a/crates/rpc/rpc-testing-util/Cargo.toml +++ b/crates/rpc/rpc-testing-util/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rpc-api-testing-util" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index f92ced2a42b1..037cf873bb46 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rpc-types" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 0cf7a4f83397..a698109232f6 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-rpc" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/staged-sync/Cargo.toml b/crates/staged-sync/Cargo.toml index a32223af1b0a..a88472583a06 100644 --- a/crates/staged-sync/Cargo.toml +++ b/crates/staged-sync/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-staged-sync" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/stages/Cargo.toml b/crates/stages/Cargo.toml index e8db9ce29748..d60bc3dc2294 100644 --- a/crates/stages/Cargo.toml +++ b/crates/stages/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-stages" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/storage/codecs/Cargo.toml b/crates/storage/codecs/Cargo.toml index b5ca60a5d137..5b3a9497bcfa 100644 --- a/crates/storage/codecs/Cargo.toml +++ b/crates/storage/codecs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-codecs" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true @@ -17,7 +17,7 @@ arbitrary = ["revm-primitives/arbitrary", "dep:arbitrary", "dep:proptest", "dep: [dependencies] bytes = "1.4" -codecs-derive = { version = "0.1.0", path = "./derive", default-features = false } +codecs-derive = { path = "./derive", default-features = false } revm-primitives = { workspace = true, features = ["serde"] } # arbitrary utils diff --git a/crates/storage/codecs/derive/Cargo.toml b/crates/storage/codecs/derive/Cargo.toml index 09f9f16855b9..a59b26c578e1 100644 --- a/crates/storage/codecs/derive/Cargo.toml +++ b/crates/storage/codecs/derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codecs-derive" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 51b30ef2f523..57370c3fc1f9 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-db" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/storage/libmdbx-rs/Cargo.toml b/crates/storage/libmdbx-rs/Cargo.toml index 8cfd762329c6..bac79ec882fb 100644 --- a/crates/storage/libmdbx-rs/Cargo.toml +++ b/crates/storage/libmdbx-rs/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-libmdbx" -version = "0.1.6" +version.workspace = true edition.workspace = true rust-version.workspace = true license = "Apache-2.0" diff --git a/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml b/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml index 00420504c558..51e8da1788a1 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml +++ b/crates/storage/libmdbx-rs/mdbx-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-mdbx-sys" -version = "0.12.6-0" +version.workspace = true edition.workspace = true rust-version.workspace = true license = "Apache-2.0" diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 9c39874e9d84..d270c0c2d0f1 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-provider" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/tasks/Cargo.toml b/crates/tasks/Cargo.toml index d0f5a3206178..183224bbda6a 100644 --- a/crates/tasks/Cargo.toml +++ b/crates/tasks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-tasks" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/tracing/Cargo.toml b/crates/tracing/Cargo.toml index 9e6569ac61f3..63531a08ff65 100644 --- a/crates/tracing/Cargo.toml +++ b/crates/tracing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-tracing" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index 4a9df81266a9..efc0cda13337 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-transaction-pool" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/crates/trie/Cargo.toml b/crates/trie/Cargo.toml index d772cffd22be..1c9369413bd6 100644 --- a/crates/trie/Cargo.toml +++ b/crates/trie/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "reth-trie" -version = "0.1.0" +version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index 4cabdf2edd3a..109fefd4563a 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ef-tests" -version = "0.1.0" +version.workspace = true description = "EF testing support for reth." edition.workspace = true rust-version.workspace = true From 79aa9cb2c298b4debe9986624bdbde58791fac07 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 19 Jun 2023 18:53:26 +0100 Subject: [PATCH 089/216] feat(bin): `db version` CLI (#3243) --- bin/reth/src/db/mod.rs | 23 ++++++++++++++++++++++- crates/storage/db/src/version.rs | 21 ++++++++++++++------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index 272a67763807..4f3706ba1d4d 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -8,7 +8,11 @@ use clap::{Parser, Subcommand}; use comfy_table::{Cell, Row, Table as ComfyTable}; use eyre::WrapErr; use human_bytes::human_bytes; -use reth_db::{database::Database, tables}; +use reth_db::{ + database::Database, + tables, + version::{get_db_version, DatabaseVersionError, DB_VERSION}, +}; use reth_primitives::ChainSpec; use reth_staged_sync::utils::init::init_db; use std::sync::Arc; @@ -66,6 +70,8 @@ pub enum Subcommands { Get(get::Command), /// Deletes all database entries Drop, + /// Lists current and local database versions + Version, } #[derive(Parser, Debug)] @@ -222,6 +228,21 @@ impl Command { Subcommands::Drop => { tool.drop(db_path)?; } + Subcommands::Version => { + let local_db_version = match get_db_version(&db_path) { + Ok(version) => Some(version), + Err(DatabaseVersionError::MissingFile) => None, + Err(err) => return Err(err.into()), + }; + + println!("Current database version: {DB_VERSION}"); + + if let Some(version) = local_db_version { + println!("Local database version: {version}"); + } else { + println!("Local database is uninitialized"); + } + } } Ok(()) diff --git a/crates/storage/db/src/version.rs b/crates/storage/db/src/version.rs index 6a9b492325de..db617445901d 100644 --- a/crates/storage/db/src/version.rs +++ b/crates/storage/db/src/version.rs @@ -34,16 +34,23 @@ pub enum DatabaseVersionError { /// Returns [Ok] if file is found and has one line which equals to [DB_VERSION]. /// Otherwise, returns different [DatabaseVersionError] error variants. pub fn check_db_version_file>(db_path: P) -> Result<(), DatabaseVersionError> { + let version = get_db_version(db_path)?; + if version != DB_VERSION { + return Err(DatabaseVersionError::VersionMismatch { version }) + } + + Ok(()) +} + +/// Returns the database version from file with [DB_VERSION_FILE_NAME] name. +/// +/// Returns [Ok] if file is found and contains a valid version. +/// Otherwise, returns different [DatabaseVersionError] error variants. +pub fn get_db_version>(db_path: P) -> Result { let version_file_path = db_version_file_path(db_path); match fs::read_to_string(&version_file_path) { Ok(raw_version) => { - let version = - raw_version.parse::().map_err(|_| DatabaseVersionError::MalformedFile)?; - if version != DB_VERSION { - return Err(DatabaseVersionError::VersionMismatch { version }) - } - - Ok(()) + Ok(raw_version.parse::().map_err(|_| DatabaseVersionError::MalformedFile)?) } Err(err) if err.kind() == io::ErrorKind::NotFound => Err(DatabaseVersionError::MissingFile), Err(err) => Err(DatabaseVersionError::IORead { err, path: version_file_path }), From c4819875584b9a207e4fa94423bc32bd3df20d50 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 19 Jun 2023 19:54:16 +0200 Subject: [PATCH 090/216] feat(blockchain-tree): add `Display` for blocks in `Chain` (#3230) --- crates/blockchain-tree/src/blockchain_tree.rs | 6 ++-- crates/storage/provider/src/chain.rs | 30 ++++++++++++++++++- crates/storage/provider/src/lib.rs | 2 +- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 9cd869c11e15..7b013bf9bd29 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -22,7 +22,8 @@ use reth_provider::{ chain::{ChainSplit, SplitAt}, post_state::PostState, BlockNumProvider, CanonStateNotification, CanonStateNotificationSender, - CanonStateNotifications, Chain, DatabaseProvider, ExecutorFactory, HeaderProvider, + CanonStateNotifications, Chain, DatabaseProvider, DisplayBlocksChain, ExecutorFactory, + HeaderProvider, }; use std::{ collections::{BTreeMap, HashMap}, @@ -923,8 +924,7 @@ impl BlockchainTree let chain_notification; info!( target: "blockchain_tree", - "Committing new canonical chain: {:?}", - new_canon_chain.blocks().iter().map(|(_, b)| b.num_hash()).collect::>() + "Committing new canonical chain: {}", DisplayBlocksChain(new_canon_chain.blocks()) ); // if joins to the tip; if new_canon_chain.fork_block_hash() == old_tip.hash { diff --git a/crates/storage/provider/src/chain.rs b/crates/storage/provider/src/chain.rs index 90928f13e985..ff74d4cedbaa 100644 --- a/crates/storage/provider/src/chain.rs +++ b/crates/storage/provider/src/chain.rs @@ -6,7 +6,7 @@ use reth_primitives::{ BlockHash, BlockNumHash, BlockNumber, ForkBlock, Receipt, SealedBlock, SealedBlockWithSenders, TransactionSigned, TxHash, }; -use std::{borrow::Cow, collections::BTreeMap}; +use std::{borrow::Cow, collections::BTreeMap, fmt}; /// A chain of blocks and their final state. /// @@ -217,6 +217,34 @@ impl Chain { } } +/// Wrapper type for `blocks` display in `Chain` +pub struct DisplayBlocksChain<'a>(pub &'a BTreeMap); + +impl<'a> fmt::Display for DisplayBlocksChain<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.0.len() <= 3 { + write!(f, "[")?; + let mut iter = self.0.values().map(|block| block.num_hash()); + if let Some(block_num_hash) = iter.next() { + write!(f, "{:?}", block_num_hash)?; + for block_num_hash_iter in iter { + write!(f, ", {:?}", block_num_hash_iter)?; + } + } + write!(f, "]")?; + } else { + write!( + f, + "[{:?}, ..., {:?}]", + self.0.values().next().unwrap().num_hash(), + self.0.values().last().unwrap().num_hash() + )?; + } + + Ok(()) + } +} + /// All blocks in the chain #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ChainBlocks<'a> { diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index c290ed61ef5b..51b74379525d 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -47,4 +47,4 @@ pub mod test_utils; pub use reth_interfaces::provider::ProviderError; pub mod chain; -pub use chain::Chain; +pub use chain::{Chain, DisplayBlocksChain}; From 255780d3810d8e35e012d4bdb0df5b1c3fdf6e9e Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 19 Jun 2023 14:21:38 -0400 Subject: [PATCH 091/216] feat: run pipeline only if missing range is large (#3059) --- bin/reth/src/node/mod.rs | 3 +- crates/consensus/beacon/src/engine/mod.rs | 417 ++++++++++++++-------- 2 files changed, 266 insertions(+), 154 deletions(-) diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 3e698af272eb..c6cf5cc94542 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -15,7 +15,7 @@ use fdlimit::raise_fd_limit; use futures::{future::Either, pin_mut, stream, stream_select, StreamExt}; use reth_auto_seal_consensus::{AutoSealBuilder, AutoSealConsensus}; use reth_basic_payload_builder::{BasicPayloadJobGenerator, BasicPayloadJobGeneratorConfig}; -use reth_beacon_consensus::{BeaconConsensus, BeaconConsensusEngine}; +use reth_beacon_consensus::{BeaconConsensus, BeaconConsensusEngine, MIN_BLOCKS_FOR_PIPELINE_RUN}; use reth_blockchain_tree::{ config::BlockchainTreeConfig, externals::TreeExternals, BlockchainTree, ShareableBlockchainTree, }; @@ -341,6 +341,7 @@ impl Command { self.debug.continuous, payload_builder.clone(), initial_target, + MIN_BLOCKS_FOR_PIPELINE_RUN, consensus_engine_tx, consensus_engine_rx, )?; diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index b31dea409258..b4c77e761c3d 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -65,6 +65,10 @@ use reth_interfaces::blockchain_tree::InsertPayloadOk; /// The maximum number of invalid headers that can be tracked by the engine. const MAX_INVALID_HEADERS: u32 = 512u32; +/// The largest gap for which the tree will be used for sync. See docs for `pipeline_run_threshold` +/// for more information. +pub const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = 128; + /// A _shareable_ beacon consensus frontend. Used to interact with the spawned beacon consensus /// engine. /// @@ -234,6 +238,18 @@ where invalid_headers: InvalidHeaderCache, /// Consensus engine metrics. metrics: EngineMetrics, + /// After downloading a block corresponding to a recent forkchoice update, the engine will + /// check whether or not we can connect the block to the current canonical chain. If we can't, + /// we need to download and execute the missing parents of that block. + /// + /// When the block can't be connected, its block number will be compared to the canonical head, + /// resulting in a heuristic for the number of missing blocks, or the size of the gap between + /// the new block and the canonical head. + /// + /// If the gap is larger than this threshold, the engine will download and execute the missing + /// blocks using the pipeline. Otherwise, the engine, sync controller, and blockchain tree will + /// be used to download and execute the missing blocks. + pipeline_run_threshold: u64, } impl BeaconConsensusEngine @@ -254,6 +270,7 @@ where run_pipeline_continuously: bool, payload_builder: PayloadBuilderHandle, target: Option, + pipeline_run_threshold: u64, ) -> Result<(Self, BeaconConsensusEngineHandle), reth_interfaces::Error> { let (to_engine, rx) = mpsc::unbounded_channel(); Self::with_channel( @@ -266,6 +283,7 @@ where run_pipeline_continuously, payload_builder, target, + pipeline_run_threshold, to_engine, rx, ) @@ -294,6 +312,7 @@ where run_pipeline_continuously: bool, payload_builder: PayloadBuilderHandle, target: Option, + pipeline_run_threshold: u64, to_engine: UnboundedSender, rx: UnboundedReceiver, ) -> Result<(Self, BeaconConsensusEngineHandle), reth_interfaces::Error> { @@ -316,6 +335,7 @@ where listeners: EventListeners::default(), invalid_headers: InvalidHeaderCache::new(MAX_INVALID_HEADERS), metrics: EngineMetrics::default(), + pipeline_run_threshold, }; let maybe_pipeline_target = match target { @@ -678,10 +698,7 @@ where // we assume the FCU is valid and at least the head is missing, so we need to start syncing // to it - - // if this is the first FCU we received from the beacon node, then we start triggering the - // pipeline - if self.forkchoice_state_tracker.is_empty() { + let target = if self.forkchoice_state_tracker.is_empty() { // find the appropriate target to sync to, if we don't have the safe block hash then we // start syncing to the safe block via pipeline first let target = if !state.safe_block_hash.is_zero() && @@ -694,19 +711,24 @@ where // we need to first check the buffer for the head and its ancestors let lowest_unknown_hash = self.lowest_buffered_ancestor_or(target); - - trace!(target: "consensus::engine", request=?lowest_unknown_hash, "Triggering pipeline with target instead of downloading"); - - self.sync.set_pipeline_sync_target(lowest_unknown_hash); + trace!(target: "consensus::engine", request=?lowest_unknown_hash, "Triggering full block download for missing ancestors of the new head"); + lowest_unknown_hash } else { // we need to first check the buffer for the head and its ancestors let lowest_unknown_hash = self.lowest_buffered_ancestor_or(state.head_block_hash); - trace!(target: "consensus::engine", request=?lowest_unknown_hash, "Triggering full block download for missing ancestors of the new head"); + lowest_unknown_hash + }; + // if the threshold is zero, we should not download the block first, and just use the + // pipeline. Otherwise we use the tree to insert the block first + if self.pipeline_run_threshold == 0 { + // use the pipeline to sync to the target + self.sync.set_pipeline_sync_target(target); + } else { // trigger a full block download for missing hash, or the parent of its lowest buffered // ancestor - self.sync.download_full_block(lowest_unknown_hash); + self.sync.download_full_block(target); } PayloadStatus::from_status(PayloadStatusEnum::Syncing) @@ -797,9 +819,7 @@ where let block_hash = block.hash(); // now check the block itself - if let Some(status) = self.check_invalid_ancestor(block.parent_hash) { - // The parent is invalid, so this block is also invalid - self.invalid_headers.insert(block.header); + if let Some(status) = self.check_invalid_ancestor_with_head(block.parent_hash, block.hash) { return Ok(status) } @@ -1030,8 +1050,39 @@ where self.try_make_sync_target_canonical(num_hash); } InsertPayloadOk::Inserted(BlockStatus::Disconnected { missing_parent }) => { - // continue downloading the missing parent - self.sync.download_full_block(missing_parent.hash); + // compare the missing parent with the canonical tip + let canonical_tip_num = self.blockchain.canonical_tip().number; + + // if the number of missing blocks is greater than the max, run the + // pipeline + if missing_parent.number >= canonical_tip_num && + missing_parent.number - canonical_tip_num > + self.pipeline_run_threshold + { + if let Some(state) = self.forkchoice_state_tracker.sync_target_state() { + // if we have already canonicalized the finalized block, we should + // skip the pipeline run + if Ok(None) == + self.blockchain.header_by_hash_or_number( + state.finalized_block_hash.into(), + ) + { + self.sync.set_pipeline_sync_target(state.finalized_block_hash) + } + } + } else { + // continue downloading the missing parent + // + // this happens if either: + // * the missing parent block num < canonical tip num + // * this case represents a missing block on a fork that is shorter + // than the canonical chain + // * the missing parent block num >= canonical tip num, but the number + // of missing blocks is less than the pipeline threshold + // * this case represents a potentially long range of blocks to + // download and execute + self.sync.download_full_block(missing_parent.hash); + } } _ => (), } @@ -1434,52 +1485,112 @@ mod tests { } } - fn setup_consensus_engine( + struct TestConsensusEngineBuilder { chain_spec: Arc, pipeline_exec_outputs: VecDeque>, executor_results: Vec, - ) -> (TestBeaconConsensusEngine, TestEnv>>) { - reth_tracing::init_test_tracing(); - let db = create_test_rw_db(); - let consensus = TestConsensus::default(); - let payload_builder = spawn_test_payload_service(); - - let executor_factory = TestExecutorFactory::new(chain_spec.clone()); - executor_factory.extend(executor_results); - - // Setup pipeline - let (tip_tx, tip_rx) = watch::channel(H256::default()); - let pipeline = Pipeline::builder() - .add_stages(TestStages::new(pipeline_exec_outputs, Default::default())) - .with_tip_sender(tip_tx) - .build(db.clone(), chain_spec.clone()); - - // Setup blockchain tree - let externals = - TreeExternals::new(db.clone(), consensus, executor_factory, chain_spec.clone()); - let config = BlockchainTreeConfig::new(1, 2, 3, 2); - let (canon_state_notification_sender, _) = tokio::sync::broadcast::channel(3); - let tree = ShareableBlockchainTree::new( - BlockchainTree::new(externals, canon_state_notification_sender, config) - .expect("failed to create tree"), - ); - let factory = ProviderFactory::new(db.clone(), chain_spec.clone()); - let latest = chain_spec.genesis_header().seal_slow(); - let blockchain_provider = BlockchainProvider::with_latest(factory, tree, latest); - let (engine, handle) = BeaconConsensusEngine::new( - NoopFullBlockClient::default(), - pipeline, - blockchain_provider, - Box::::default(), - Box::::default(), - None, - false, - payload_builder, - None, - ) - .expect("failed to create consensus engine"); + pipeline_run_threshold: Option, + max_block: Option, + } + + impl TestConsensusEngineBuilder { + /// Create a new `TestConsensusEngineBuilder` with the given `ChainSpec`. + fn new(chain_spec: Arc) -> Self { + Self { + chain_spec, + pipeline_exec_outputs: VecDeque::new(), + executor_results: Vec::new(), + pipeline_run_threshold: None, + max_block: None, + } + } + + /// Set the pipeline execution outputs to use for the test consensus engine. + fn with_pipeline_exec_outputs( + mut self, + pipeline_exec_outputs: VecDeque>, + ) -> Self { + self.pipeline_exec_outputs = pipeline_exec_outputs; + self + } + + /// Set the executor results to use for the test consensus engine. + fn with_executor_results(mut self, executor_results: Vec) -> Self { + self.executor_results = executor_results; + self + } + + /// Sets the max block for the pipeline to run. + fn with_max_block(mut self, max_block: BlockNumber) -> Self { + self.max_block = Some(max_block); + self + } + + /// Disables blockchain tree driven sync. This is the same as setting the pipeline run + /// threshold to 0. + fn disable_blockchain_tree_sync(mut self) -> Self { + self.pipeline_run_threshold = Some(0); + self + } + + /// Builds the test consensus engine into a `TestConsensusEngine` and `TestEnv`. + fn build(self) -> (TestBeaconConsensusEngine, TestEnv>>) { + reth_tracing::init_test_tracing(); + let db = create_test_rw_db(); + let consensus = TestConsensus::default(); + let payload_builder = spawn_test_payload_service(); + + let executor_factory = TestExecutorFactory::new(self.chain_spec.clone()); + executor_factory.extend(self.executor_results); + + // Setup pipeline + let (tip_tx, tip_rx) = watch::channel(H256::default()); + let mut pipeline = Pipeline::builder() + .add_stages(TestStages::new(self.pipeline_exec_outputs, Default::default())) + .with_tip_sender(tip_tx); + + if let Some(max_block) = self.max_block { + pipeline = pipeline.with_max_block(max_block); + } + + let pipeline = pipeline.build(db.clone(), self.chain_spec.clone()); - (engine, TestEnv::new(db, tip_rx, handle)) + // Setup blockchain tree + let externals = TreeExternals::new( + db.clone(), + consensus, + executor_factory, + self.chain_spec.clone(), + ); + let config = BlockchainTreeConfig::new(1, 2, 3, 2); + let (canon_state_notification_sender, _) = tokio::sync::broadcast::channel(3); + let tree = ShareableBlockchainTree::new( + BlockchainTree::new(externals, canon_state_notification_sender, config) + .expect("failed to create tree"), + ); + let shareable_db = ProviderFactory::new(db.clone(), self.chain_spec.clone()); + let latest = self.chain_spec.genesis_header().seal_slow(); + let blockchain_provider = BlockchainProvider::with_latest(shareable_db, tree, latest); + let (mut engine, handle) = BeaconConsensusEngine::new( + NoopFullBlockClient::default(), + pipeline, + blockchain_provider, + Box::::default(), + Box::::default(), + None, + false, + payload_builder, + None, + self.pipeline_run_threshold.unwrap_or(MIN_BLOCKS_FOR_PIPELINE_RUN), + ) + .expect("failed to create consensus engine"); + + if let Some(max_block) = self.max_block { + engine.sync.set_max_block(max_block) + } + + (engine, TestEnv::new(db, tip_rx, handle)) + } } fn spawn_consensus_engine( @@ -1503,11 +1614,13 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Err(StageError::ChannelClosed)]), - Vec::default(), - ); + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Err(StageError::ChannelClosed)])) + .disable_blockchain_tree_sync() + .with_max_block(1) + .build(); + let res = spawn_consensus_engine(consensus_engine); let _ = env @@ -1532,11 +1645,13 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Err(StageError::ChannelClosed)]), - Vec::default(), - ); + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Err(StageError::ChannelClosed)])) + .disable_blockchain_tree_sync() + .with_max_block(1) + .build(); + let mut rx = spawn_consensus_engine(consensus_engine); // consensus engine is idle @@ -1572,14 +1687,16 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([ + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([ Ok(ExecOutput { checkpoint: StageCheckpoint::new(1), done: true }), Err(StageError::ChannelClosed), - ]), - Vec::default(), - ); + ])) + .disable_blockchain_tree_sync() + .with_max_block(2) + .build(); + let rx = spawn_consensus_engine(consensus_engine); let _ = env @@ -1605,15 +1722,16 @@ mod tests { .paris_activated() .build(), ); - let (mut consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(max_block), done: true, - })]), - Vec::default(), - ); - consensus_engine.sync.set_max_block(max_block); + })])) + .with_max_block(max_block) + .disable_blockchain_tree_sync() + .build(); + let rx = spawn_consensus_engine(consensus_engine); let _ = env @@ -1651,14 +1769,13 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), - })]), - Vec::default(), - ); + done: true, + })])) + .build(); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1682,14 +1799,13 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), - })]), - Vec::default(), - ); + done: true, + })])) + .build(); let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); @@ -1730,14 +1846,14 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([ - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - ]), - Vec::default(), - ); + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([ + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), done: true }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), done: true }), + ])) + .disable_blockchain_tree_sync() + .build(); let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); @@ -1779,14 +1895,14 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), - })]), - Vec::default(), - ); + done: true, + })])) + .disable_blockchain_tree_sync() + .build(); let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); @@ -1816,14 +1932,13 @@ mod tests { .paris_at_ttd(U256::from(3)) .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([ - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - ]), - Vec::default(), - ); + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([ + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), done: true }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), done: true }), + ])) + .build(); let genesis = random_block(0, None, None, Some(0)); let mut block1 = random_block(1, Some(genesis.hash), None, Some(0)); @@ -1869,14 +1984,13 @@ mod tests { .london_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([ - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - Ok(ExecOutput { done: true, checkpoint: StageCheckpoint::new(0) }), - ]), - Vec::default(), - ); + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([ + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), done: true }), + Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), done: true }), + ])) + .build(); let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); @@ -1916,14 +2030,13 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), - })]), - Vec::default(), - ); + done: true, + })])) + .build(); let mut engine_rx = spawn_consensus_engine(consensus_engine); @@ -1949,14 +2062,13 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), - })]), - Vec::default(), - ); + done: true, + })])) + .build(); let genesis = random_block(0, None, None, Some(0)); let block1 = random_block(1, Some(genesis.hash), None, Some(0)); @@ -1999,14 +2111,13 @@ mod tests { .paris_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), - })]), - Vec::default(), - ); + done: true, + })])) + .build(); let genesis = random_block(0, None, None, Some(0)); @@ -2056,14 +2167,14 @@ mod tests { .london_activated() .build(), ); - let (consensus_engine, env) = setup_consensus_engine( - chain_spec.clone(), - VecDeque::from([Ok(ExecOutput { - done: true, + + let (consensus_engine, env) = TestConsensusEngineBuilder::new(chain_spec.clone()) + .with_pipeline_exec_outputs(VecDeque::from([Ok(ExecOutput { checkpoint: StageCheckpoint::new(0), - })]), - Vec::from([exec_result2]), - ); + done: true, + })])) + .with_executor_results(Vec::from([exec_result2])) + .build(); insert_blocks( env.db.as_ref(), From c702efb9bc60026e8f582026c51932ccb762f58f Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 19 Jun 2023 20:35:53 +0200 Subject: [PATCH 092/216] docs: config chapter (#3173) Co-authored-by: Alexey Shekhirin --- book/run/config.md | 331 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 330 insertions(+), 1 deletion(-) diff --git a/book/run/config.md b/book/run/config.md index ea29fd80ed9f..2935e418f3a8 100644 --- a/book/run/config.md +++ b/book/run/config.md @@ -1,2 +1,331 @@ # Configuring Reth - \ No newline at end of file + +Reth places a configuration file named `reth.toml` in the data directory specified when starting the node. It is written in the [TOML] format. + +The default data directory is platform dependent: + +- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` +- Windows: `{FOLDERID_RoamingAppData}/reth/` +- macOS: `$HOME/Library/Application Support/reth/` + +The configuration file contains the following sections: + +- [`[stages]`](#the-stages-section) -- Configuration of the individual sync stages + - [`headers`](#headers) + - [`total_difficulty`](#total_difficulty) + - [`bodies`](#bodies) + - [`sender_recovery`](#sender_recovery) + - [`execution`](#execution) + - [`account_hashing`](#account_hashing) + - [`storage_hashing`](#storage_hashing) + - [`merkle`](#merkle) + - [`transaction_lookup`](#transaction_lookup) + - [`index_account_history`](#index_account_history) + - [`index_storage_history`](#index_storage_history) +- [`[peers]`](#the-peers-section) + - [`connection_info`](#connection_info) + - [`reputation_weights`](#reputation_weights) + - [`backoff_durations`](#backoff_durations) +- [`[sessions]`](#the-sessions-section) + +## The `[stages]` section + +The stages section is used to configure how individual stages in reth behave, which has a direct impact on resource utilization and sync speed. + +The defaults shipped with Reth try to be relatively reasonable, but may not be optimal for your specific set of hardware. + +### `headers` + +The headers section controls both the behavior of the header stage, which download historical headers, as well as the primary downloader that fetches headers over P2P. + +```toml +[stages.headers] +# The minimum and maximum number of concurrent requests to have in flight at a time. +# +# The downloader uses these as best effort targets, which means that the number +# of requests may be outside of these thresholds within a reasonable degree. +# +# Increase these for faster sync speeds at the cost of additional bandwidth and memory +downloader_max_concurrent_requests = 100 +downloader_min_concurrent_requests = 5 +# The maximum number of responses to buffer in the downloader at any one time. +# +# If the buffer is full, no more requests will be sent until room opens up. +# +# Increase the value for a larger buffer at the cost of additional memory consumption +downloader_max_buffered_responses = 100 +# The maximum number of headers to request from a peer at a time. +downloader_request_limit = 1000 +# The amount of headers to persist to disk at a time. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 10000 +``` + +### `total_difficulty` + +The total difficulty stage calculates the total difficulty reached for each header in the chain. + +```toml +[stages.total_difficulty] +# The amount of headers to calculate the total difficulty for +# before writing the results to disk. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 100000 +``` + +### `bodies` + +The bodies section controls both the behavior of the bodies stage, which download historical block bodies, as well as the primary downloader that fetches block bodies over P2P. + +```toml +[stages.bodies] +# The maximum number of bodies to request from a peer at a time. +downloader_request_limit = 200 +# The maximum amount of bodies to download before writing them to disk. +# +# A lower value means more frequent disk I/O (writes), but also +# lowers memory usage. +downloader_stream_batch_size = 10000 +# The maximum amount of blocks to keep in the internal buffer of the downloader. +# +# A bigger buffer means that bandwidth can be saturated for longer periods, +# but also increases memory consumption. +# +# If the buffer is full, no more requests will be made to peers until +# space is made for new blocks in the buffer. +downloader_max_buffered_blocks = 42949 +# The minimum and maximum number of concurrent requests to have in flight at a time. +# +# The downloader uses these as best effort targets, which means that the number +# of requests may be outside of these thresholds within a reasonable degree. +# +# Increase these for faster sync speeds at the cost of additional bandwidth and memory +downloader_min_concurrent_requests = 5 +downloader_max_concurrent_requests = 100 +``` + +### `sender_recovery` + +The sender recovery stage recovers the address of transaction senders using transaction signatures. + +```toml +[stages.sender_recovery] +# The amount of transactions to recover senders for before +# writing the results to disk. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 100000 +``` + +### `execution` + +The execution stage executes historical transactions. This stage is generally very I/O and memory intensive, since executing transactions involves reading block headers, transactions, accounts and account storage. + +Each executed transaction also generates a number of changesets, and mutates the current state of accounts and storage. + +For this reason, there are two ways to control how much work to perform before the results are written to disk. + +```toml +[stages.execution] +# The maximum amount of blocks to execute before writing the results to disk. +max_blocks = 500000 +# The maximum amount of account and storage changes to collect before writing +# the results to disk. +max_changes = 5000000 +``` + +Either one of `max_blocks` or `max_changes` must be specified, and both can also be specified at the same time: + +- If only `max_blocks` is specified, reth will execute (up to) that amount of blocks before writing to disk. +- If only `max_changes` is specified, reth will execute as many blocks as possible until the target amount of state transitions have occured before writing to disk. +- If both are specified, then the first threshold to be hit will determine when the results are written to disk. + +Lower values correspond to more frequent disk writes, but also lower memory consumption. A lower value also negatively impacts sync speed, since reth keeps a cache around for the entire duration of blocks executed in the same range. + +### `account_hashing` + +The account hashing stage builds a secondary table of accounts, where the key is the hash of the address instead of the raw address. + +This is used to later compute the state root. + +```toml +[stages.account_hashing] +# The threshold in number of blocks before the stage starts from scratch +# and re-hashes all accounts as opposed to just the accounts that changed. +clean_threshold = 500000 +# The amount of accounts to process before writing the results to disk. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 100000 +``` + +### `storage_hashing` + +The storage hashing stage builds a secondary table of account storages, where the key is the hash of the address and the slot, instead of the raw address and slot. + +This is used to later compute the state root. + +```toml +[stages.storage_hashing] +# The threshold in number of blocks before the stage starts from scratch +# and re-hashes all storages as opposed to just the storages that changed. +clean_threshold = 500000 +# The amount of storage slots to process before writing the results to disk. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 100000 +``` + +### `merkle` + +The merkle stage uses the indexes built in the hashing stages (storage and account hashing) to compute the state root of the latest block. + +```toml +[stages.merkle] +# The threshold in number of blocks before the stage starts from scratch +# and re-computes the state root, discarding the trie that has already been built, +# as opposed to incrementally updating the trie. +clean_threshold = 50000 +``` + +### `transaction_lookup` + +The transaction lookup stage builds an index of transaction hashes to their sequential transaction ID. + +```toml +[stages.transaction_lookup] +# The maximum number of transactions to process before writing the results to disk. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 5000000 +``` + +### `index_account_history` + +The account history indexing stage builds an index of what blocks a particular account changed. + +```toml +[stages.index_account_history] +# The maximum amount of blocks to process before writing the results to disk. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 100000 +``` + +### `index_storage_history` + +The storage history indexing stage builds an index of what blocks a particular storage slot changed. + +```toml +[stages.index_storage_history] +# The maximum amount of blocks to process before writing the results to disk. +# +# Lower thresholds correspond to more frequent disk I/O (writes), +# but lowers memory usage +commit_threshold = 100000 +``` + +## The `[peers]` section + +The peers section is used to configure how the networking component of reth establishes and maintains connections to peers. + +In the top level of the section you can configure trusted nodes, and how often reth will try to connect to new peers. + +```toml +[peers] +# How often reth will attempt to make outgoing connections, +# if there is room for more peers +refill_slots_interval = '1s' +# A list of ENRs for trusted peers, which are peers reth will always try to connect to. +trusted_nodes = [] +# Whether reth will only attempt to connect to the peers specified above, +# or if it will connect to other peers in the network +connect_trusted_nodes_only = false +# The duration for which a badly behaving peer is banned +ban_duration = '12h' +``` + +### `connection_info` + +This section configures how many peers reth will connect to. + +```toml +[peers.connection_info] +# The maximum number of outbound peers (peers we connect to) +max_outbound = 100 +# The maximum number of inbound peers (peers that connect to us) +max_inbound = 30 +``` + +### `reputation_weights` + +This section configures the penalty for various offences peers can commit. + +All peers start out with a reputation of 0, which increases over time as the peer stays connected to us. + +If the peer misbehaves, various penalties are exacted to their reputation, and if it falls below a certain threshold (currently `50 * -1024`), reth will disconnect and ban the peer temporarily (except for protocol violations which constitute a permanent ban). + +```toml +[peers.reputation_weights] +bad_message = -16384 +bad_block = -16384 +bad_transactions = -16384 +already_seen_transactions = 0 +timeout = -4096 +bad_protocol = -2147483648 +failed_to_connect = -25600 +dropped = -4096 +``` + +### `backoff_durations` + +If reth fails to establish a connection to a peer, it will not re-attempt for some amount of time, depending on the reason the connection failed. + +```toml +[peers.backoff_durations] +low = '30s' +medium = '3m' +high = '15m' +max = '1h' +``` + +## The `[sessions]` section + +The sessions section configures the internal behavior of a single peer-to-peer connection. + +You can configure the session buffer sizes, which limits the amount of pending events (incoming messages) and commands (outgoing messages) each session can hold before it will start to ignore messages. + +> **Note** +> +> These buffers are allocated *per peer*, which means that increasing the buffer sizes can have large impact on memory consumption. + +```toml +[sessions] +session_command_buffer = 32 +session_event_buffer = 260 +``` + +You can also configure request timeouts: + +```toml +[sessions.initial_internal_request_timeout] +secs = 20 +nanos = 0 + +# The amount of time before the peer will be penalized for +# being in violation of the protocol. This exacts a permaban on the peer. +[sessions.protocol_breach_request_timeout] +secs = 120 +nanos = 0 +``` + +[TOML]: https://toml.io/ \ No newline at end of file From d3d23b4450ce7d89f337677c5dc5722a4f591af9 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 19 Jun 2023 21:15:06 +0200 Subject: [PATCH 093/216] ci: fix book deployment (#3247) --- .github/workflows/book.yml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 3ff003ba26a3..3c89bafdb16a 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -64,10 +64,28 @@ jobs: run: | mv target/doc target/book/docs - - name: Save pages artifact - uses: actions/upload-pages-artifact@v1 + - name: Archive artifact + shell: sh + run: | + chmod -c -R +rX "target/book" | + while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + tar \ + --dereference --hard-dereference \ + --directory "target/book" \ + -cvf "$RUNNER_TEMP/artifact.tar" \ + --exclude=.git \ + --exclude=.github \ + . + + - name: Upload artifact + uses: actions/upload-artifact@v3 with: - path: target/book + name: github-pages + path: ${{ runner.temp }}/artifact.tar + retention-days: 1 + if-no-files-found: error deploy: # Only deploy if a push to main From 6f854dc834bcfba8ebb64876c3885a93fcb0b473 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 19 Jun 2023 20:50:19 +0100 Subject: [PATCH 094/216] chore: `db-tools` Make target for MDBX debugging tools (#3245) --- .gitignore | 3 +++ Makefile | 27 +++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 3184275d0fa1..dd772483c51c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,5 +31,8 @@ proptest-regressions/ # Release artifacts dist/ +# Database debugging tools +db-tools/ + # VSCode .vscode \ No newline at end of file diff --git a/Makefile b/Makefile index 69bb5482b26f..b355991fec66 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ # Heavily inspired by Lighthouse: https://github.com/sigp/lighthouse/blob/693886b94176faa4cb450f024696cb69cda2fe58/Makefile -GIT_TAG := $(shell git describe --tags --abbrev=0) +GIT_TAG ?= $(shell git describe --tags --abbrev=0) BIN_DIR = "dist/bin" +MDBX_PATH = "crates/storage/libmdbx-rs/mdbx-sys/libmdbx" +DB_TOOLS_DIR = "db-tools" +FULL_DB_TOOLS_DIR := $(shell pwd)/$(DB_TOOLS_DIR)/ + BUILD_PATH = "target" # List of features to use when building. Can be overriden via the environment. @@ -147,4 +151,23 @@ endef clean: cargo clean rm -rf $(BIN_DIR) - rm -rf $(EF_TESTS_DIR) \ No newline at end of file + rm -rf $(EF_TESTS_DIR) + +# Compile MDBX debugging tools +.PHONY: db-tools +db-tools: + @echo "Building MDBX debugging tools..." + # `IOARENA=1` silences benchmarking info message that is printed to stderr + @$(MAKE) -C $(MDBX_PATH) IOARENA=1 tools > /dev/null + @mkdir -p $(DB_TOOLS_DIR) + @cd $(MDBX_PATH) && \ + mv mdbx_chk $(FULL_DB_TOOLS_DIR) && \ + mv mdbx_copy $(FULL_DB_TOOLS_DIR) && \ + mv mdbx_dump $(FULL_DB_TOOLS_DIR) && \ + mv mdbx_drop $(FULL_DB_TOOLS_DIR) && \ + mv mdbx_load $(FULL_DB_TOOLS_DIR) && \ + mv mdbx_stat $(FULL_DB_TOOLS_DIR) + # `IOARENA=1` silences benchmarking info message that is printed to stderr + @$(MAKE) -C $(MDBX_PATH) IOARENA=1 clean > /dev/null + @echo "Run \"$(DB_TOOLS_DIR)/mdbx_stat\" for the info about MDBX db file." + @echo "Run \"$(DB_TOOLS_DIR)/mdbx_chk\" for the MDBX db file integrity check." From 99b26ba2eb921a31798590da7dd444462eed9fd8 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 19 Jun 2023 21:58:25 +0200 Subject: [PATCH 095/216] docs: add install link to readme (#3248) --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 657ba02d9ee9..609dca8d69a8 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,10 @@ ![](./assets/reth.jpg) -**[User Book](https://paradigmxyz.github.io/reth)** -| **[Developer Docs](./docs)** -| **[Crate Docs](https://paradigmxyz.github.io/reth/docs)** +**[Install](https://https://paradigmxyz.github.io/reth/installation/installation.html)** +| [User Book](https://paradigmxyz.github.io/reth) +| [Developer Docs](./docs) +| [Crate Docs](https://paradigmxyz.github.io/reth/docs) *The project is still work in progress, see the [disclaimer below](#status).* From 501fd0d92610c68945decd86ba55655b1adbc8b3 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 19 Jun 2023 21:02:35 +0100 Subject: [PATCH 096/216] chore: ask for more info in GitHub bug issue template (#3246) --- .github/ISSUE_TEMPLATE/bug.yml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index cdab87538ffc..d17ebe3c1765 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -58,7 +58,20 @@ body: - type: input attributes: label: What version/commit are you on? - description: This can be obtained with e.g. `git rev-parse HEAD` + description: This can be obtained with `reth --version` or `git rev-parse HEAD` if you've built Reth from source + validations: + required: false + - type: textarea + attributes: + label: What database version are you on? + description: This can be obtained with `reth db version` + validations: + required: false + - type: input + attributes: + label: If you've built Reth from source, provide the full command you used + validations: + required: false - type: checkboxes id: terms attributes: From 8dfdf658a10c52134ab6e5f42475b5c3652358f0 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Mon, 19 Jun 2023 18:35:10 -0400 Subject: [PATCH 097/216] chore: decrease pipeline threshold to 64 (#3249) Co-authored-by: Matthias Seitz --- crates/consensus/beacon/src/engine/mod.rs | 3 ++- crates/primitives/src/constants.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index b4c77e761c3d..ca681adb0bb9 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -61,13 +61,14 @@ pub(crate) mod sync; use crate::engine::forkchoice::{ForkchoiceStateHash, ForkchoiceStateTracker}; pub use event::BeaconConsensusEngineEvent; use reth_interfaces::blockchain_tree::InsertPayloadOk; +use reth_primitives::constants::EPOCH_SLOTS; /// The maximum number of invalid headers that can be tracked by the engine. const MAX_INVALID_HEADERS: u32 = 512u32; /// The largest gap for which the tree will be used for sync. See docs for `pipeline_run_threshold` /// for more information. -pub const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = 128; +pub const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = 2 * EPOCH_SLOTS; /// A _shareable_ beacon consensus frontend. Used to interact with the spawned beacon consensus /// engine. diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index ca7ed45e1f34..005d69380ab2 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -13,6 +13,9 @@ pub const SELECTOR_LEN: usize = 4; /// Maximum extra data size in a block after genesis pub const MAXIMUM_EXTRA_DATA_SIZE: usize = 32; +/// An EPOCH is a series of 32 slots. +pub const EPOCH_SLOTS: u64 = 32; + /// The duration of a slot in seconds. /// /// This is the time period of 12 seconds in which a randomly chosen validator has time to propose a @@ -20,7 +23,7 @@ pub const MAXIMUM_EXTRA_DATA_SIZE: usize = 32; pub const SLOT_DURATION: Duration = Duration::from_secs(12); /// An EPOCH is a series of 32 slots (~6.4min). -pub const EPOCH_DURATION: Duration = Duration::from_secs(12 * 32); +pub const EPOCH_DURATION: Duration = Duration::from_secs(12 * EPOCH_SLOTS); /// The default block nonce in the beacon consensus pub const BEACON_NONCE: u64 = 0u64; From 729d4ad30f1d2a7e81683a94510b22935922f7fb Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 20 Jun 2023 01:22:18 +0200 Subject: [PATCH 098/216] fix: dont panic on invalid opcode (#3255) --- crates/revm/revm-inspectors/src/tracing/mod.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index ccbc265638d1..1bbad2687418 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -212,11 +212,20 @@ impl TracingInspector { let stack = self.config.record_stack_snapshots.then(|| interp.stack.clone()).unwrap_or_default(); + let op = OpCode::try_from_u8(interp.contract.bytecode.bytecode()[pc]) + .or_else(|| { + // if the opcode is invalid, we'll use the invalid opcode to represent it because + // this is invoked before the opcode is executed, the evm will eventually return a + // `Halt` with invalid/unknown opcode as result + let invalid_opcode = 0xfe; + OpCode::try_from_u8(invalid_opcode) + }) + .expect("is valid opcode;"); + trace.trace.steps.push(CallTraceStep { depth: data.journaled_state.depth(), pc, - op: OpCode::try_from_u8(interp.contract.bytecode.bytecode()[pc]) - .expect("is valid opcode;"), + op, contract: interp.contract.address, stack, memory, From 07b499f11bd162302f9012f5e7a3e201e52610d1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 20 Jun 2023 01:59:10 +0200 Subject: [PATCH 099/216] perf: spawn engine as blocking (#3257) --- bin/reth/src/node/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index c6cf5cc94542..247a370bfaec 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -392,7 +392,7 @@ impl Command { // Run consensus engine to completion let (tx, rx) = oneshot::channel(); info!(target: "reth::cli", "Starting consensus engine"); - ctx.task_executor.spawn_critical("consensus engine", async move { + ctx.task_executor.spawn_critical_blocking("consensus engine", async move { let res = beacon_consensus_engine.await; let _ = tx.send(res); }); From b2451931bdb948b5f45f8753be5cf4230cf652e6 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Tue, 20 Jun 2023 03:16:02 +0200 Subject: [PATCH 100/216] release: v0.1.0-alpha.1 (#3241) --- Cargo.lock | 92 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 63bdf8eb7026..329c3bdcedcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -904,7 +904,7 @@ checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" [[package]] name = "codecs-derive" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "convert_case 0.6.0", "parity-scale-codec", @@ -1726,7 +1726,7 @@ dependencies = [ [[package]] name = "ef-tests" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "reth-db", "reth-interfaces", @@ -4912,7 +4912,7 @@ dependencies = [ [[package]] name = "reth" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "backon", "clap 4.1.8", @@ -4975,7 +4975,7 @@ dependencies = [ [[package]] name = "reth-auto-seal-consensus" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "futures-util", "reth-beacon-consensus", @@ -4992,7 +4992,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "futures-core", "futures-util", @@ -5011,7 +5011,7 @@ dependencies = [ [[package]] name = "reth-beacon-consensus" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "assert_matches", "futures", @@ -5036,7 +5036,7 @@ dependencies = [ [[package]] name = "reth-blockchain-tree" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "aquamarine", "assert_matches", @@ -5053,7 +5053,7 @@ dependencies = [ [[package]] name = "reth-codecs" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "arbitrary", "bytes", @@ -5068,7 +5068,7 @@ dependencies = [ [[package]] name = "reth-config" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "confy", "reth-discv4", @@ -5083,7 +5083,7 @@ dependencies = [ [[package]] name = "reth-consensus-common" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "assert_matches", "mockall", @@ -5094,7 +5094,7 @@ dependencies = [ [[package]] name = "reth-db" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "arbitrary", "assert_matches", @@ -5133,7 +5133,7 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "discv5", "enr 0.8.1", @@ -5156,7 +5156,7 @@ dependencies = [ [[package]] name = "reth-dns-discovery" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "async-trait", "data-encoding", @@ -5180,7 +5180,7 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "assert_matches", "futures", @@ -5205,7 +5205,7 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "aes 0.8.2", "block-padding", @@ -5236,7 +5236,7 @@ dependencies = [ [[package]] name = "reth-eth-wire" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "arbitrary", "async-trait", @@ -5269,7 +5269,7 @@ dependencies = [ [[package]] name = "reth-interfaces" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "arbitrary", "async-trait", @@ -5296,7 +5296,7 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "async-trait", "bytes", @@ -5316,7 +5316,7 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "bitflags 1.3.2", "byteorder", @@ -5336,7 +5336,7 @@ dependencies = [ [[package]] name = "reth-mdbx-sys" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "bindgen", "cc", @@ -5345,7 +5345,7 @@ dependencies = [ [[package]] name = "reth-metrics" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "metrics", "reth-metrics-derive", @@ -5354,7 +5354,7 @@ dependencies = [ [[package]] name = "reth-metrics-derive" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "metrics", "once_cell", @@ -5368,7 +5368,7 @@ dependencies = [ [[package]] name = "reth-net-common" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "pin-project", "reth-primitives", @@ -5377,7 +5377,7 @@ dependencies = [ [[package]] name = "reth-net-nat" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "igd", "pin-project-lite", @@ -5391,7 +5391,7 @@ dependencies = [ [[package]] name = "reth-network" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "aquamarine", "async-trait", @@ -5441,7 +5441,7 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "async-trait", "reth-eth-wire", @@ -5454,7 +5454,7 @@ dependencies = [ [[package]] name = "reth-payload-builder" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "futures-util", "reth-interfaces", @@ -5473,7 +5473,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "arbitrary", "bytes", @@ -5518,7 +5518,7 @@ dependencies = [ [[package]] name = "reth-provider" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "auto_impl", "derive_more", @@ -5539,7 +5539,7 @@ dependencies = [ [[package]] name = "reth-revm" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "once_cell", "reth-consensus-common", @@ -5555,7 +5555,7 @@ dependencies = [ [[package]] name = "reth-revm-inspectors" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "boa_engine", "boa_gc", @@ -5571,7 +5571,7 @@ dependencies = [ [[package]] name = "reth-revm-primitives" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "reth-primitives", "revm", @@ -5579,7 +5579,7 @@ dependencies = [ [[package]] name = "reth-rlp" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "arrayvec", "auto_impl", @@ -5597,7 +5597,7 @@ dependencies = [ [[package]] name = "reth-rlp-derive" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "proc-macro2 1.0.60", "quote 1.0.28", @@ -5606,7 +5606,7 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "async-trait", "bytes", @@ -5648,7 +5648,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "jsonrpsee", "reth-primitives", @@ -5658,7 +5658,7 @@ dependencies = [ [[package]] name = "reth-rpc-api-testing-util" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "async-trait", "futures", @@ -5672,7 +5672,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "hyper", "jsonrpsee", @@ -5702,7 +5702,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "assert_matches", "async-trait", @@ -5722,7 +5722,7 @@ dependencies = [ [[package]] name = "reth-rpc-types" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "assert_matches", "jsonrpsee-types", @@ -5740,7 +5740,7 @@ dependencies = [ [[package]] name = "reth-staged-sync" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "assert_matches", "async-trait", @@ -5779,7 +5779,7 @@ dependencies = [ [[package]] name = "reth-stages" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "aquamarine", "assert_matches", @@ -5813,7 +5813,7 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "dyn-clone", "futures-util", @@ -5826,7 +5826,7 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "tracing", "tracing-appender", @@ -5836,7 +5836,7 @@ dependencies = [ [[package]] name = "reth-transaction-pool" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "aquamarine", "async-trait", @@ -5860,7 +5860,7 @@ dependencies = [ [[package]] name = "reth-trie" -version = "0.1.0" +version = "0.1.0-alpha.1" dependencies = [ "criterion", "derive_more", diff --git a/Cargo.toml b/Cargo.toml index 0075efa63a7b..52113588c4dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ default-members = ["bin/reth"] resolver = "2" [workspace.package] -version = "0.1.0" +version = "0.1.0-alpha.1" edition = "2021" rust-version = "1.70" # Remember to update .clippy.toml and README.md license = "MIT OR Apache-2.0" From a98bbf7228f4c86f097ff426fec737f350facf9a Mon Sep 17 00:00:00 2001 From: Bjerg Date: Tue, 20 Jun 2023 13:33:38 +0200 Subject: [PATCH 101/216] chore: link to ghcr in draft releases (#3261) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4ca09240167e..c40eaa34d975 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -222,7 +222,7 @@ jobs: | | aarch64 | [reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz.asc) | | | | | | | **System** | **Option** | - | **Resource** | - | | Docker | [${{ env.VERSION }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}/tags?page=1&ordering=last_updated&name=${{ env.VERSION }}) | [${{ env.IMAGE_NAME }}](https://hub.docker.com/r/${{ env.IMAGE_NAME }}) | + | | Docker | [${{ env.VERSION }}](https://github.com/paradigmxyz/reth/pkgs/container/reth/102974600?tag=${{ env.VERSION }}) | [${{ env.IMAGE_NAME }}](https://github.com/paradigmxyz/reth/pkgs/container/reth) | ENDBODY ) assets=() From 1d0c65b09f8c089d3540414782b8c282a5369f32 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Tue, 20 Jun 2023 13:53:50 +0200 Subject: [PATCH 102/216] docs: document `e-` labels (#3263) --- docs/repo/labels.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/repo/labels.md b/docs/repo/labels.md index 3e1f9c2b5423..6b3dba97ee6d 100644 --- a/docs/repo/labels.md +++ b/docs/repo/labels.md @@ -11,6 +11,7 @@ There are 7 label categories in the repository: - **Platform labels**: These describe the platform an issue is present on. They start with [`O-`][platform]. - **Priority labels**: These are reserved for issues that require more immediate attention (high priority and critical priority) and they start with [`P-`][priority]. - **Status labels**: These labels convey meaning to contributors about an issue or PR's status, e.g. whether they are blocked or need triage. They start with [`S-`][status]. +- **EIP/network upgrade labels**: These labels are attached to PRs and issues related to specific EIPs or network upgrades. They start with [`E-`][eip] ### Status labels @@ -40,4 +41,5 @@ For easier at-a-glance communication of the status of issues and PRs the followi [meta]: https://github.com/paradigmxyz/reth/labels?q=m- [platform]: https://github.com/paradigmxyz/reth/labels?q=o- [priority]: https://github.com/paradigmxyz/reth/labels?q=p- -[status]: https://github.com/paradigmxyz/reth/labels?q=s- \ No newline at end of file +[status]: https://github.com/paradigmxyz/reth/labels?q=s- +[eip]: https://github.com/paradigmxyz/reth/labels?q=e- From c75cacf85551f9d6ccbdfddf5530541b6da7658e Mon Sep 17 00:00:00 2001 From: Bjerg Date: Tue, 20 Jun 2023 14:12:39 +0200 Subject: [PATCH 103/216] docs: fix install link in readme (#3265) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 609dca8d69a8..01557f925513 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ![](./assets/reth.jpg) -**[Install](https://https://paradigmxyz.github.io/reth/installation/installation.html)** +**[Install](https://paradigmxyz.github.io/reth/installation/installation.html)** | [User Book](https://paradigmxyz.github.io/reth) | [Developer Docs](./docs) | [Crate Docs](https://paradigmxyz.github.io/reth/docs) From fb710e5fdb1601ae22b214da4ade6b6b6dd5fc11 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 20 Jun 2023 13:43:34 +0100 Subject: [PATCH 104/216] fix(book): broken links & missing `SUMMARY.md` entries (#3262) --- book/SUMMARY.md | 9 +++++++-- book/fundamentals/command-line_options.md | 13 ------------- book/fundamentals/logs.md | 3 --- book/fundamentals/node.md | 7 ------- book/installation/installation.md | 4 ++-- book/installation/source.md | 2 +- book/intro.md | 4 ++-- book/jsonrpc/admin.md | 6 +++--- book/run/mainnet.md | 4 ++-- book/run/observability.md | 2 +- book/run/run-a-node.md | 1 - 11 files changed, 18 insertions(+), 37 deletions(-) delete mode 100644 book/fundamentals/command-line_options.md delete mode 100644 book/fundamentals/logs.md delete mode 100644 book/fundamentals/node.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 27cbb66eef2c..9bcc6d8d257f 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -21,8 +21,13 @@ 1. [rpc](./jsonrpc/rpc.md) 1. [CLI Reference](./cli/cli.md) 1. [reth node](./cli/node.md) + 1. [reth init](./cli/init.md) + 1. [reth import](./cli/import.md) 1. [reth db](./cli/db.md) - 1. [reth p2p](./cli/p2p.md) 1. [reth stage](./cli/stage.md) + 1. [reth p2p](./cli/p2p.md) + 1. [reth test-vectors](./cli/test-vectors.md) + 1. [reth config](./cli/config.md) + 1. [reth debug](./cli/debug.md) 1. [Developers](./developers/developers.md) - 1. [Contribute](./developers/contribute.md) \ No newline at end of file + 1. [Contribute](./developers/contribute.md) diff --git a/book/fundamentals/command-line_options.md b/book/fundamentals/command-line_options.md deleted file mode 100644 index 15bb8986eb0f..000000000000 --- a/book/fundamentals/command-line_options.md +++ /dev/null @@ -1,13 +0,0 @@ -# Command-Line Options - -## Command -- **node:** Start Reth node -- **test-chain:** Run Ethereum blockchain tests -- **db:** Debugging utilities to troubleshoot and fix issues with database systems. - -## Options -- **-v, --verbose:** Use verbose output -- **--silent:** Silence all output -- **--disable-discovery:** Do not spawn the discv4 service when running node. -- **-h, --help:** Print help information -- **-V, --version:** Print version information diff --git a/book/fundamentals/logs.md b/book/fundamentals/logs.md deleted file mode 100644 index ee4e2481e988..000000000000 --- a/book/fundamentals/logs.md +++ /dev/null @@ -1,3 +0,0 @@ -# Logs - -Reth nodes constantly output messages to the console, giving users the ability to monitor the current status of Reth in real-time. These logs can be helpful in understanding the normal operation of Reth, as well as identifying when issues may arise. However, interpreting these logs can be challenging for new users. This page aims to provide guidance on how to read and understand the log messages generated by Reth. \ No newline at end of file diff --git a/book/fundamentals/node.md b/book/fundamentals/node.md deleted file mode 100644 index 0abe3fa8a39e..000000000000 --- a/book/fundamentals/node.md +++ /dev/null @@ -1,7 +0,0 @@ -# Node - -Reth is a new Ethereum full node that allows users to sync and interact with the entire blockchain, including its historical state if in archive mode. - -- Full node: It can be used as a full node, which stores and processes the entire blockchain, validates blocks and transactions, and participates in the consensus process. - -- Archive node: It can also be used as an archive node, which stores the entire history of the blockchain and is useful for applications that need access to historical data. \ No newline at end of file diff --git a/book/installation/installation.md b/book/installation/installation.md index d4653b97f91c..7f6dc4decde8 100644 --- a/book/installation/installation.md +++ b/book/installation/installation.md @@ -23,7 +23,7 @@ The most important requirement is by far the disk, whereas CPU and RAM requireme ### Disk -There are multiple types of disks to sync Reth, with varying size requirements, depending on the [syncing mode](../run/sync-modes.md): +There are multiple types of disks to sync Reth, with varying size requirements, depending on the syncing mode: * Archive Node: At least 2TB is required to store * Full Node: TBD @@ -36,7 +36,7 @@ At the time of writing, syncing an Ethereum mainnet node to block 17.4M on NVMe Most of the time during syncing is spent executing transactions, which is a single-threaded operation due to potential state dependencies of a transaction on previous ones. -As a result, the number of cores matters less, but in general higher clock speeds are better. More cores are better for parallelizable [stages](../developers/architecture.md) (like sender recovery or bodies downloading), but these stages are not the primary bottleneck for syncing. +As a result, the number of cores matters less, but in general higher clock speeds are better. More cores are better for parallelizable [stages](https://github.com/paradigmxyz/reth/blob/main/docs/crates/stages.md) (like sender recovery or bodies downloading), but these stages are not the primary bottleneck for syncing. ### Memory diff --git a/book/installation/source.md b/book/installation/source.md index 63c62af59505..a076e0003c88 100644 --- a/book/installation/source.md +++ b/book/installation/source.md @@ -123,7 +123,7 @@ Rust Version (MSRV) which is listed under the `rust-version` key in Reth's If compilation fails with `(signal: 9, SIGKILL: kill)`, this could mean your machine ran out of memory during compilation. If you are on Docker, consider increasing the memory of the container, or use a [pre-built -binary](../binaries.md). +binary](../installation/binaries.md). If compilation fails with `error: linking with cc failed: exit code: 1`, try running `cargo clean`. diff --git a/book/intro.md b/book/intro.md index a1325e4bc624..b2fada745b9b 100644 --- a/book/intro.md +++ b/book/intro.md @@ -82,8 +82,8 @@ Here are some useful sections to jump to: - Install Reth by following the [guide](./installation/installation.md). - Sync your node on any [official network](./run/run-a-node.md). - View [statistics and metrics](./run/observability.md) about your node. -- Query the [JSON-RPC](./api/api.md) using Foundry's `cast` or `curl`. -- Set up your [development environment and contribute](./contribute.md)! +- Query the [JSON-RPC](./jsonrpc/intro.md) using Foundry's `cast` or `curl`. +- Set up your [development environment and contribute](./developers/contribute.md)! > 📖 **About this book** > diff --git a/book/jsonrpc/admin.md b/book/jsonrpc/admin.md index 67b520b86ab4..31fdf490204f 100644 --- a/book/jsonrpc/admin.md +++ b/book/jsonrpc/admin.md @@ -27,9 +27,9 @@ The method accepts a single argument, the [`enode`][enode] URL of the remote pee Disconnects from a peer if the connection exists. Returns a `bool` indicating whether the peer was successfully removed or not. -| Client | Method invocation | -|--------|-------------------------------------------------------| -| RPC | `{"method": "admin_removePeer", "params": [url]}` | +| Client | Method invocation | +|--------|----------------------------------------------------| +| RPC | `{"method": "admin_removePeer", "params": [url]}` | ### Example diff --git a/book/run/mainnet.md b/book/run/mainnet.md index 1a7c01b6d07b..697d9927a58a 100644 --- a/book/run/mainnet.md +++ b/book/run/mainnet.md @@ -14,7 +14,7 @@ Now, start the node as follows: RUST_LOG=info reth node ``` -> Note that this command will not open any HTTP/WS ports by default. You can change this by adding the `--http`, `--ws` flags, respectively and using the `--http.api` and `--ws.api` flags to enable various [JSON-RPC APIs](../api/api.md). For more commands, see the [`reth node` CLI reference](../cli/node.md). +> Note that this command will not open any HTTP/WS ports by default. You can change this by adding the `--http`, `--ws` flags, respectively and using the `--http.api` and `--ws.api` flags to enable various [JSON-RPC APIs](../jsonrpc/intro.md). For more commands, see the [`reth node` CLI reference](../cli/node.md). The EL <> CL communication happens over the [Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md), which is by default exposed at `http://localhost:8551`. The connection is authenticated over JWT using a JWT secret which is auto-generated by Reth under `~/.local/share/reth/mainnet/jwt.hex` in Linux (`/Users//Library/Application Support/reth/mainnet/jwt.hex` in Mac). @@ -49,7 +49,7 @@ Your Reth node should start receiving "fork choice updated" messages, and begin ## Verify the chain is growing You can easily verify that by inspecting the logs, and seeing that headers are arriving in Reth. Sit back now and wait for the stages to run! -In the meantime, consider setting up [observability](./observability.md) to monitor your node's health or [test the JSON RPC API](../api/api.md). +In the meantime, consider setting up [observability](./observability.md) to monitor your node's health or [test the JSON RPC API](../jsonrpc/intro.md). diff --git a/book/run/observability.md b/book/run/observability.md index 0b24f139a71d..bae6f9a767fc 100644 --- a/book/run/observability.md +++ b/book/run/observability.md @@ -61,7 +61,7 @@ In this runbook, we took you through starting the node, exposing different log l This will all be very useful to you, whether you're simply running a home node and want to keep an eye on its performance, or if you're a contributor and want to see the effect that your (or others') changes have on Reth's operations. -[installation]: ./installation.md +[installation]: ../installation/installation.md [release-profile]: https://doc.rust-lang.org/cargo/reference/profiles.html#release [docs]: https://github.com/paradigmxyz/reth/tree/main/docs [metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics.md#current-metrics \ No newline at end of file diff --git a/book/run/run-a-node.md b/book/run/run-a-node.md index 4aa7ffef6704..1363761b9364 100644 --- a/book/run/run-a-node.md +++ b/book/run/run-a-node.md @@ -4,7 +4,6 @@ Congratulations, now that you have installed Reth, it's time to run it! In this chapter we'll go through a few different topics you'll encounter when running Reth, including: 1. [Running on mainnet or official testnets](./mainnet.md) -1. [Setting up a local testnet](./local_testnet.md) 1. [Logs and Observability](./observability.md) 1. [Configuring reth.toml](./config.md) From 236a10e73b3887744198e2573b73c7fa5b945a03 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 20 Jun 2023 15:50:43 +0200 Subject: [PATCH 105/216] fix: give js service access to modified state (#3267) --- .../src/tracing/js/bindings.rs | 6 +- .../revm-inspectors/src/tracing/js/mod.rs | 2 +- crates/rpc/rpc/src/debug.rs | 82 ++++++++++++++----- crates/rpc/rpc/src/eth/revm_utils.rs | 23 +++++- 4 files changed, 88 insertions(+), 25 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/js/bindings.rs b/crates/revm/revm-inspectors/src/tracing/js/bindings.rs index 5e988c955c61..bfcd9c086c16 100644 --- a/crates/revm/revm-inspectors/src/tracing/js/bindings.rs +++ b/crates/revm/revm-inspectors/src/tracing/js/bindings.rs @@ -711,7 +711,11 @@ impl EvmDBInner { let slot = bytes_to_hash(buf); let (tx, rx) = channel(); - if self.to_db.try_send(JsDbRequest::StorageAt { address, index: slot, resp: tx }).is_err() { + if self + .to_db + .try_send(JsDbRequest::StorageAt { address, index: slot.into(), resp: tx }) + .is_err() + { return Err(JsError::from_native(JsNativeError::error().with_message(format!( "Failed to read state for {address:?} at {slot:?} from database", )))) diff --git a/crates/revm/revm-inspectors/src/tracing/js/mod.rs b/crates/revm/revm-inspectors/src/tracing/js/mod.rs index c6b6ad0c5c5a..450cbb42821f 100644 --- a/crates/revm/revm-inspectors/src/tracing/js/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/js/mod.rs @@ -506,7 +506,7 @@ pub enum JsDbRequest { /// The address of the account address: Address, /// Index of the storage slot - index: H256, + index: U256, /// The response channel resp: std::sync::mpsc::Sender>, }, diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index dc5a423cec26..3eb98eabccc2 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,7 +1,9 @@ use crate::{ eth::{ error::{EthApiError, EthResult}, - revm_utils::{inspect, prepare_call_env, replay_transactions_until, EvmOverrides}, + revm_utils::{ + clone_into_empty_db, inspect, prepare_call_env, replay_transactions_until, EvmOverrides, + }, EthTransactions, TransactionSource, }, result::{internal_rpc_err, ToRpcResult}, @@ -9,10 +11,8 @@ use crate::{ }; use async_trait::async_trait; use jsonrpsee::core::RpcResult; -use reth_primitives::{Block, BlockId, BlockNumberOrTag, Bytes, TransactionSigned, H256}; -use reth_provider::{ - BlockProviderIdExt, HeaderProvider, ReceiptProviderIdExt, StateProvider, StateProviderBox, -}; +use reth_primitives::{Account, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSigned, H256}; +use reth_provider::{BlockProviderIdExt, HeaderProvider, ReceiptProviderIdExt, StateProviderBox}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, @@ -31,8 +31,14 @@ use reth_rpc_types::{ BlockError, CallRequest, RichBlock, }; use reth_tasks::TaskSpawner; -use revm::primitives::Env; -use revm_primitives::{db::DatabaseCommit, BlockEnv, CfgEnv}; +use revm::{ + db::{CacheDB, EmptyDB}, + primitives::Env, +}; +use revm_primitives::{ + db::{DatabaseCommit, DatabaseRef}, + BlockEnv, CfgEnv, +}; use std::{future::Future, sync::Arc}; use tokio::sync::{mpsc, oneshot, AcquireError, OwnedSemaphorePermit}; use tokio_stream::{wrappers::ReceiverStream, StreamExt}; @@ -310,9 +316,17 @@ where let (cfg, block_env, at) = self.inner.eth_api.evm_env_at(at).await?; let state = self.inner.eth_api.state_at(at)?; let mut db = SubState::new(State::new(state)); + let has_state_overrides = overrides.has_state(); let env = prepare_call_env(cfg, block_env, call, &mut db, overrides)?; - let to_db_service = self.spawn_js_trace_service(at)?; + // If the caller provided state overrides we need to clone the DB so the js + // service has access these modifications + let mut maybe_override_db = None; + if has_state_overrides { + maybe_override_db = Some(clone_into_empty_db(&db)); + } + + let to_db_service = self.spawn_js_trace_service(at, maybe_override_db)?; let mut inspector = JsInspector::new(code, config, to_db_service)?; let (res, env) = inspect(db, env, &mut inspector)?; @@ -384,10 +398,12 @@ where GethDebugTracerType::JsTracer(code) => { let config = tracer_config.into_json(); + // We need to clone the database because the JS tracer will need to access the + // current state via the spawned service + let js_db = clone_into_empty_db(db); // we spawn the database service that will be used by the JS tracer - // TODO(mattsse) this is not quite accurate when tracing a block inside a // transaction because the service needs access to the committed state changes - let to_db_service = self.spawn_js_trace_service(at)?; + let to_db_service = self.spawn_js_trace_service(at, Some(js_db))?; let mut inspector = JsInspector::new(code, config, to_db_service)?; let (res, env) = inspect(db, env, &mut inspector)?; @@ -416,13 +432,17 @@ where /// to it. /// /// Note: This blocks until the service is ready to receive requests. - fn spawn_js_trace_service(&self, at: BlockId) -> EthResult> { + fn spawn_js_trace_service( + &self, + at: BlockId, + db: Option>, + ) -> EthResult> { let (to_db_service, rx) = mpsc::channel(1); let (ready_tx, ready_rx) = std::sync::mpsc::channel(); let this = self.clone(); - self.inner - .task_spawner - .spawn(Box::pin(async move { this.js_trace_db_service_task(at, rx, ready_tx).await })); + self.inner.task_spawner.spawn(Box::pin(async move { + this.js_trace_db_service_task(at, rx, ready_tx, db).await + })); // wait for initialization ready_rx.recv().map_err(|_| { EthApiError::InternalJsTracerError("js tracer initialization failed".to_string()) @@ -431,11 +451,16 @@ where } /// A services that handles database requests issued from inside the JavaScript tracing engine. + /// + /// If this traces with modified state, this takes a `db` parameter that contains the modified + /// in memory state. This is required because [StateProviderBox] can not be cloned or shared + /// across threads. async fn js_trace_db_service_task( self, at: BlockId, rx: mpsc::Receiver, on_ready: std::sync::mpsc::Sender>, + db: Option>, ) { let state = match self.inner.eth_api.state_at(at) { Ok(state) => { @@ -448,25 +473,38 @@ where } }; + let db = if let Some(db) = db { + let CacheDB { accounts, contracts, logs, block_hashes, .. } = db; + CacheDB { accounts, contracts, logs, block_hashes, db: State::new(state) } + } else { + CacheDB::new(State::new(state)) + }; + let mut stream = ReceiverStream::new(rx); while let Some(req) = stream.next().await { match req { JsDbRequest::Basic { address, resp } => { - let acc = state.basic_account(address).map_err(|err| err.to_string()); + let acc = db + .basic(address) + .map(|maybe_acc| { + maybe_acc.map(|acc| Account { + nonce: acc.nonce, + balance: acc.balance, + bytecode_hash: Some(acc.code_hash), + }) + }) + .map_err(|err| err.to_string()); let _ = resp.send(acc); } JsDbRequest::Code { code_hash, resp } => { - let code = state - .bytecode_by_hash(code_hash) - .map(|code| code.map(|c| c.bytecode.clone()).unwrap_or_default()) + let code = db + .code_by_hash(code_hash) + .map(|code| code.bytecode) .map_err(|err| err.to_string()); let _ = resp.send(code); } JsDbRequest::StorageAt { address, index, resp } => { - let value = state - .storage(address, index) - .map(|val| val.unwrap_or_default()) - .map_err(|err| err.to_string()); + let value = db.storage(address, index).map_err(|err| err.to_string()); let _ = resp.send(value); } } diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 9b14e0b0db98..2313e19fd643 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -10,7 +10,7 @@ use reth_rpc_types::{ BlockOverrides, CallRequest, }; use revm::{ - db::CacheDB, + db::{CacheDB, EmptyDB}, precompile::{Precompiles, SpecId as PrecompilesSpecId}, primitives::{BlockEnv, CfgEnv, Env, ResultAndState, SpecId, TransactTo, TxEnv}, Database, Inspector, @@ -44,6 +44,11 @@ impl EvmOverrides { pub fn state(state: Option) -> Self { Self { state, block: None } } + + /// Returns `true` if the overrides contain state overrides. + pub fn has_state(&self) -> bool { + self.state.is_some() + } } impl From> for EvmOverrides { @@ -478,3 +483,19 @@ where Ok(()) } + +/// This clones and transforms the given [CacheDB] with an arbitrary [DatabaseRef] into a new +/// [CacheDB] with [EmptyDB] as the database type +#[inline] +pub(crate) fn clone_into_empty_db(db: &CacheDB) -> CacheDB +where + DB: DatabaseRef, +{ + CacheDB { + accounts: db.accounts.clone(), + contracts: db.contracts.clone(), + logs: db.logs.clone(), + block_hashes: db.block_hashes.clone(), + db: Default::default(), + } +} From 8eeba4dcc8f12ac17ac51af1d1cc8bd94c7d130d Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 20 Jun 2023 15:02:43 +0100 Subject: [PATCH 106/216] chore(ci): book lint (#3266) --- .github/workflows/book.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 3c89bafdb16a..684acb8e966c 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -29,6 +29,24 @@ jobs: - name: Run tests run: mdbook test + lint: + runs-on: ubuntu-latest + name: lint + + steps: + - uses: actions/checkout@v3 + + - name: Install mdbook-linkcheck + run: | + mkdir mdbook-linkcheck + curl -sSL -o mdbook-linkcheck.zip https://github.com/Michael-F-Bryan/mdbook-linkcheck/releases/latest/download/mdbook-linkcheck.x86_64-unknown-linux-gnu.zip + unzip mdbook-linkcheck.zip -d ./mdbook-linkcheck + chmod +x `pwd`/mdbook-linkcheck/mdbook-linkcheck + echo `pwd`/mdbook-linkcheck >> $GITHUB_PATH + + - name: Run linkcheck + run: mdbook-linkcheck --standalone + build: runs-on: ubuntu-latest steps: @@ -91,7 +109,7 @@ jobs: # Only deploy if a push to main if: github.ref_name == 'main' && github.event_name == 'push' runs-on: ubuntu-latest - needs: [test, build] + needs: [test, lint, build] # Grant GITHUB_TOKEN the permissions required to make a Pages deployment permissions: From 4c9e112a4635391e633ba8cc8d327d76c19abb71 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 20 Jun 2023 16:50:35 +0100 Subject: [PATCH 107/216] feat(db): `reth db path` CLI (#3272) --- bin/reth/src/db/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index 4f3706ba1d4d..43051d07b662 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -72,6 +72,8 @@ pub enum Subcommands { Drop, /// Lists current and local database versions Version, + /// Returns the full database path + Path, } #[derive(Parser, Debug)] @@ -243,6 +245,9 @@ impl Command { println!("Local database is uninitialized"); } } + Subcommands::Path => { + println!("{}", db_path.display()); + } } Ok(()) From c0fba3578d0d4afa59052ff4512c7cacbf2b6763 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 20 Jun 2023 16:54:06 +0100 Subject: [PATCH 108/216] chore(book): troubleshooting db issues (#3271) --- book/SUMMARY.md | 1 + book/run/run-a-node.md | 1 + book/run/troubleshooting.md | 38 +++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+) create mode 100644 book/run/troubleshooting.md diff --git a/book/SUMMARY.md b/book/SUMMARY.md index 9bcc6d8d257f..8faf1b1ab3be 100644 --- a/book/SUMMARY.md +++ b/book/SUMMARY.md @@ -10,6 +10,7 @@ 1. [Mainnet or official testnets](./run/mainnet.md) 1. [Metrics](./run/observability.md) 1. [Configuring Reth](./run/config.md) + 1. [Troubleshooting](./run/troubleshooting.md) 1. [Interacting with Reth over JSON-RPC](./jsonrpc/intro.md) 1. [eth](./jsonrpc/eth.md) 1. [web3](./jsonrpc/web3.md) diff --git a/book/run/run-a-node.md b/book/run/run-a-node.md index 1363761b9364..164d76945e44 100644 --- a/book/run/run-a-node.md +++ b/book/run/run-a-node.md @@ -6,5 +6,6 @@ In this chapter we'll go through a few different topics you'll encounter when ru 1. [Running on mainnet or official testnets](./mainnet.md) 1. [Logs and Observability](./observability.md) 1. [Configuring reth.toml](./config.md) +1. [Troubleshooting](./troubleshooting.md) In the future, we also intend to support the [OP Stack](https://stack.optimism.io/docs/understand/explainer/), which will allow you to run Reth as a Layer 2 client. More there soon! \ No newline at end of file diff --git a/book/run/troubleshooting.md b/book/run/troubleshooting.md new file mode 100644 index 000000000000..4fb2ef61e0be --- /dev/null +++ b/book/run/troubleshooting.md @@ -0,0 +1,38 @@ +# Troubleshooting + +As Reth is still in alpha, while running the node you can experience some problems related to different parts of the system: pipeline sync, blockchain tree, p2p, database, etc. + +This page tries to answer how to deal with the most popular issues. + +## Database + +### Database write error + +If you encounter an irrecoverable database-related errors, in most of the cases it's related to the RAM/NVMe/SSD you use. For example: +```console +Error: A stage encountered an irrecoverable error. + +Caused by: + 0: An internal database error occurred: Database write error code: -30796 + 1: Database write error code: -30796 +``` + +or + +```console +Error: A stage encountered an irrecoverable error. + +Caused by: + 0: An internal database error occurred: Database read error code: -30797 + 1: Database read error code: -30797 +``` + +1. Check your memory health: use [memtest86+](https://www.memtest.org/) or [memtester](https://linux.die.net/man/8/memtester). If your memory is faulty, it's better to resync the node on different hardware. +2. Check database integrity: + ```bash + git clone https://github.com/paradigmxyz/reth + cd reth + make db-tools + db-tools/mdbx_chk $(reth db path)/mdbx.dat + ``` + If `mdbx_chk` has detected any errors, please [open an issue](https://github.com/paradigmxyz/reth/issues) and post the output. \ No newline at end of file From 6aee59a685b1feb9f2ec34db7298d7d1f42e6418 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Tue, 20 Jun 2023 09:38:09 -0700 Subject: [PATCH 109/216] docs: update cover --- README.md | 2 +- assets/reth-alpha.png | Bin 0 -> 464942 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 assets/reth-alpha.png diff --git a/README.md b/README.md index 01557f925513..f15395a82dc8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ **Modular, contributor-friendly and blazing-fast implementation of the Ethereum protocol** -![](./assets/reth.jpg) +![](./assets/reth-alpha.png) **[Install](https://paradigmxyz.github.io/reth/installation/installation.html)** | [User Book](https://paradigmxyz.github.io/reth) diff --git a/assets/reth-alpha.png b/assets/reth-alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..d29f95e59e2a56ef6c5c9886f9ab74e9c0bac104 GIT binary patch literal 464942 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

&>lJO0|}bwfV7{zFQC8+;JJF}#*I%k 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%0De<&; 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

;<^>roW#bF}a1lgIZFrb!4iKckfb+37m>2gQ*D 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�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 literal 0 HcmV?d00001 From be9f94de8ef81386f8e534c3f2d7953c59c47930 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Tue, 20 Jun 2023 18:16:42 +0200 Subject: [PATCH 110/216] docs: mention jwt is in datadir (#3274) --- book/run/mainnet.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/run/mainnet.md b/book/run/mainnet.md index 697d9927a58a..8b9c87cc66c4 100644 --- a/book/run/mainnet.md +++ b/book/run/mainnet.md @@ -16,7 +16,7 @@ RUST_LOG=info reth node > Note that this command will not open any HTTP/WS ports by default. You can change this by adding the `--http`, `--ws` flags, respectively and using the `--http.api` and `--ws.api` flags to enable various [JSON-RPC APIs](../jsonrpc/intro.md). For more commands, see the [`reth node` CLI reference](../cli/node.md). -The EL <> CL communication happens over the [Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md), which is by default exposed at `http://localhost:8551`. The connection is authenticated over JWT using a JWT secret which is auto-generated by Reth under `~/.local/share/reth/mainnet/jwt.hex` in Linux (`/Users//Library/Application Support/reth/mainnet/jwt.hex` in Mac). +The EL <> CL communication happens over the [Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md), which is by default exposed at `http://localhost:8551`. The connection is authenticated over JWT using a JWT secret which is auto-generated by Reth and placed in a file called `jwt.hex` in the data directory, which on Linux by default is `$HOME/.local/share/reth/` (`/Users//Library/Application Support/reth/mainnet/jwt.hex` in Mac). You can override this path using the `--authrpc.jwtsecret` option. You MUST use the same JWT secret in BOTH Reth and the chosen Consensus Layer. If you want to override the address or port, you can use the `--authrpc.addr` and `--authrpc.port` options, respectively. @@ -59,4 +59,4 @@ In the meantime, consider setting up [observability](./observability.md) to moni ## Running without a Consensus Layer -We provide a method for running Reth without a Consensus Layer via the `--debug.tip ` parameter. If you provide that to your node, it will simulate sending a `engine_forkChoiceUpdated` message _once_ and will trigger syncing to the provided block hash. This is useful for testing and debugging purposes, but in order to have a node that can keep up with the tip you'll need to run a CL alongside it. At the moment we have no plans of including a Consensus Layer implementation in Reth, and we are open to including light clients other methods of syncing like importing Lighthouse as a library. \ No newline at end of file +We provide a method for running Reth without a Consensus Layer via the `--debug.tip ` parameter. If you provide that to your node, it will simulate sending a `engine_forkChoiceUpdated` message _once_ and will trigger syncing to the provided block hash. This is useful for testing and debugging purposes, but in order to have a node that can keep up with the tip you'll need to run a CL alongside it. At the moment we have no plans of including a Consensus Layer implementation in Reth, and we are open to including light clients other methods of syncing like importing Lighthouse as a library. From a450eb1b9ebb811e2bacf54c70df0b77fd9a7959 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Tue, 20 Jun 2023 19:21:43 +0200 Subject: [PATCH 111/216] docs: images (#3280) Co-authored-by: Georgios Konstantopoulos --- book.toml | 1 + book/intro.md | 2 +- book/theme/head.hbs | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 book/theme/head.hbs diff --git a/book.toml b/book.toml index 25f598eaf0d7..0ecc92b4097b 100644 --- a/book.toml +++ b/book.toml @@ -7,6 +7,7 @@ title = "reth Book" description = "A book on all things Reth" [output.html] +theme = "book/theme" git-repository-url = "https://github.com/paradigmxyz/reth" default-theme = "ayu" no-section-label = true diff --git a/book/intro.md b/book/intro.md index b2fada745b9b..143a8360e66b 100644 --- a/book/intro.md +++ b/book/intro.md @@ -5,7 +5,7 @@ _Documentation for Reth users and developers._ Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is an **Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient.** - + diff --git a/book/theme/head.hbs b/book/theme/head.hbs new file mode 100644 index 000000000000..6ff65ee74b4c --- /dev/null +++ b/book/theme/head.hbs @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file From ac887ce527812c1540156560ac32ea91a80ec6f2 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 20 Jun 2023 17:51:47 +0100 Subject: [PATCH 112/216] chore(book): add missing db commands (#3277) --- book/cli/db.md | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/book/cli/db.md b/book/cli/db.md index f20aa6130b98..1a87ad8bc3d4 100644 --- a/book/cli/db.md +++ b/book/cli/db.md @@ -16,6 +16,10 @@ Commands: Gets the content of a table for the given key drop Deletes all database entries + version + Lists current and local database versions + path + Returns the full database path help Print this message or the help of the given subcommand(s) @@ -67,6 +71,7 @@ Usage: reth db stats [OPTIONS] Options: -h, --help + Print help (see a summary with '-h') ``` ## `reth db list` @@ -136,3 +141,29 @@ Options: -h, --help Print help (see a summary with '-h') ``` + +## `reth db version` + +```bash +$ reth db version --help +Lists current and local database versions + +Usage: reth db version [OPTIONS] + +Options: + -h, --help + Print help (see a summary with '-h') +``` + +## `reth db path` + +```bash +$ reth db path --help +Returns the full database path + +Usage: reth db path [OPTIONS] + +Options: + -h, --help + Print help (see a summary with '-h') +``` \ No newline at end of file From e78d2fc0b0df1b700ae5db2843592bb47bb38224 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 20 Jun 2023 19:05:39 +0200 Subject: [PATCH 113/216] chore: improve empty hardforks panic message (#3279) --- crates/revm/revm-primitives/src/config.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/revm/revm-primitives/src/config.rs b/crates/revm/revm-primitives/src/config.rs index a3bb6b48dcba..ec71880072a9 100644 --- a/crates/revm/revm-primitives/src/config.rs +++ b/crates/revm/revm-primitives/src/config.rs @@ -42,7 +42,10 @@ pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm::primitives::SpecI } else if chain_spec.fork(Hardfork::Frontier).active_at_head(&block) { revm::primitives::FRONTIER } else { - panic!("wrong configuration") + panic!( + "invalid hardfork chainspec: expected at least one hardfork, got {:?}", + chain_spec.hardforks + ) } } From c328c8234e9fc17bc3915dd538738a2ee89b9e80 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 20 Jun 2023 21:13:02 +0200 Subject: [PATCH 114/216] fix: fix index out of bounds error if block is empty (#3281) --- crates/rpc/rpc/src/eth/api/fees.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs index ebed8a30c11a..ba6885240ead 100644 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ b/crates/rpc/rpc/src/eth/api/fees.rs @@ -137,7 +137,7 @@ where sorter.sort(); let mut rewards = Vec::with_capacity(reward_percentiles.len()); - let mut sum_gas_used = sorter[0].gas_used; + let mut sum_gas_used = sorter.first().map(|tx| tx.gas_used).unwrap_or_default(); let mut tx_index = 0; for percentile in reward_percentiles.iter() { From 0a752d741db0dc1260bc68bc1f8e6557d425cd1a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 20 Jun 2023 21:16:45 +0200 Subject: [PATCH 115/216] perf: fetch total difficulty by block number (#3284) --- crates/rpc/rpc/src/eth/api/block.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 4c48c8e67435..1aa3108ee166 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -102,8 +102,10 @@ where None => return Ok(None), }; let block_hash = block.hash; - let total_difficulty = - self.provider().header_td(&block_hash)?.ok_or(EthApiError::UnknownBlockNumber)?; + let total_difficulty = self + .provider() + .header_td_by_number(block.number)? + .ok_or(EthApiError::UnknownBlockNumber)?; let block = Block::from_block(block.into(), total_difficulty, full.into(), Some(block_hash))?; Ok(Some(block.into())) From b1c77b396a12227e7339c449b69f50d574fb39f9 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Tue, 20 Jun 2023 21:21:43 +0200 Subject: [PATCH 116/216] fix: rm `node` from dockerfile entrypoint (#3275) --- Dockerfile | 5 ++++- Dockerfile.cross | 5 ++++- book/installation/docker.md | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index edde758c6b2b..46ce23954763 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef WORKDIR app +LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth +LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0" + # Builds a cargo-chef plan FROM chef AS planner COPY . . @@ -31,4 +34,4 @@ WORKDIR app COPY --from=builder /app/target/release/reth /usr/local/bin EXPOSE 30303 30303/udp 9000 8545 8546 -ENTRYPOINT ["/usr/local/bin/reth", "node"] +ENTRYPOINT ["/usr/local/bin/reth"] diff --git a/Dockerfile.cross b/Dockerfile.cross index 1c34aa01f6d5..64e5c7c02ee0 100644 --- a/Dockerfile.cross +++ b/Dockerfile.cross @@ -3,10 +3,13 @@ # locatable in `./dist/bin/$TARGETARCH` FROM --platform=$TARGETPLATFORM ubuntu:22.04 +LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth +LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0" + # Filled by docker buildx ARG TARGETARCH COPY ./dist/bin/$TARGETARCH/reth /usr/local/bin/reth EXPOSE 30303 30303/udp 9000 8545 8546 -ENTRYPOINT ["/usr/local/bin/reth", "node"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/reth"] diff --git a/book/installation/docker.md b/book/installation/docker.md index 6fc29fd1f1c5..936e226c6bc7 100644 --- a/book/installation/docker.md +++ b/book/installation/docker.md @@ -27,7 +27,7 @@ docker pull ghcr.io/paradigmxyz/reth:v0.0.1 You can test the image with: ```bash -docker run --rm ghcr.io/paradigmxyz/reth reth --version +docker run --rm ghcr.io/paradigmxyz/reth --version ``` If you can see the latest [Reth release](https://github.com/paradigmxyz/reth/releases) version, then you've successfully installed Reth via Docker. @@ -43,5 +43,5 @@ docker build . -t reth:local The build will likely take several minutes. Once it's built, test it with: ```bash -docker run reth:local reth --version +docker run reth:local --version ``` From 4dd9c9b25a4dc4c5dbc8777ac7c633757521495a Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:07:14 -0400 Subject: [PATCH 117/216] chore: make clippy happy (#3291) --- crates/consensus/beacon/src/engine/mod.rs | 18 ++++++++---------- crates/net/downloaders/src/bodies/bodies.rs | 4 ++-- crates/stages/src/stages/sender_recovery.rs | 7 +++---- crates/stages/src/stages/tx_lookup.rs | 8 ++++---- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index ca681adb0bb9..909cf36747ca 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -758,21 +758,19 @@ where head: Header, state: ForkchoiceState, ) -> OnForkChoiceUpdated { - // 7. Client software MUST ensure that payloadAttributes.timestamp is - // greater than timestamp of a block referenced by - // forkchoiceState.headBlockHash. If this condition isn't held client - // software MUST respond with -38003: `Invalid payload attributes` and - // MUST NOT begin a payload build process. In such an event, the - // forkchoiceState update MUST NOT be rolled back. + // 7. Client software MUST ensure that payloadAttributes.timestamp is greater than timestamp + // of a block referenced by forkchoiceState.headBlockHash. If this condition isn't held + // client software MUST respond with -38003: `Invalid payload attributes` and MUST NOT + // begin a payload build process. In such an event, the forkchoiceState update MUST NOT + // be rolled back. if attrs.timestamp <= head.timestamp.into() { return OnForkChoiceUpdated::invalid_payload_attributes() } // 8. Client software MUST begin a payload build process building on top of - // forkchoiceState.headBlockHash and identified via buildProcessId value - // if payloadAttributes is not null and the forkchoice state has been - // updated successfully. The build process is specified in the Payload - // building section. + // forkchoiceState.headBlockHash and identified via buildProcessId value if + // payloadAttributes is not null and the forkchoice state has been updated successfully. + // The build process is specified in the Payload building section. let attributes = PayloadBuilderAttributes::new(state.head_block_hash, attrs); // send the payload to the builder and return the receiver for the pending payload id, diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index 9d8287674faa..acc61f65b0ca 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -88,8 +88,8 @@ where /// This method is going to return the batch as soon as one of the conditions below /// is fulfilled: /// 1. The number of non-empty headers in the batch equals requested. - /// 2. The total number of headers in the batch (both empty and non-empty) - /// is greater than or equal to the stream batch size. + /// 2. The total number of headers in the batch (both empty and non-empty) is greater than + /// or equal to the stream batch size. /// 3. Downloader reached the end of the range /// /// NOTE: The batches returned have a variable length. diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index 2fe297e4be67..ba60007ac7ae 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -368,11 +368,10 @@ mod tests { /// # Panics /// - /// 1. If there are any entries in the [tables::TxSenders] table above - /// a given block number. + /// 1. If there are any entries in the [tables::TxSenders] table above a given block number. /// - /// 2. If the is no requested block entry in the bodies table, - /// but [tables::TxSenders] is not empty. + /// 2. If the is no requested block entry in the bodies table, but [tables::TxSenders] is + /// not empty. fn ensure_no_senders_by_block(&self, block: BlockNumber) -> Result<(), TestRunnerError> { let body_result = self.tx.inner_rw().block_body_indices(block); match body_result { diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index 5f3cd7056b23..d59209007efa 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -327,11 +327,11 @@ mod tests { /// # Panics /// - /// 1. If there are any entries in the [tables::TxHashNumber] table above - /// a given block number. + /// 1. If there are any entries in the [tables::TxHashNumber] table above a given block + /// number. /// - /// 2. If the is no requested block entry in the bodies table, - /// but [tables::TxHashNumber] is not empty. + /// 2. If the is no requested block entry in the bodies table, but [tables::TxHashNumber] is + /// not empty. fn ensure_no_hash_by_block(&self, number: BlockNumber) -> Result<(), TestRunnerError> { let body_result = self.tx.inner_rw().block_body_indices(number); match body_result { From b578718eb8fdeb314d106e1129c118474d190acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Vincent?= <28714795+leovct@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:25:55 +0200 Subject: [PATCH 118/216] chore: change `metrics` default port from `9000` to `9001` (#3295) --- Dockerfile | 2 +- Dockerfile.cross | 2 +- bin/reth/src/node/mod.rs | 12 ++++++------ book/run/observability.md | 10 +++++----- etc/README.md | 2 +- etc/prometheus/prometheus.yml | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index 46ce23954763..9289d0056e2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,5 +33,5 @@ WORKDIR app # Copy reth over from the build stage COPY --from=builder /app/target/release/reth /usr/local/bin -EXPOSE 30303 30303/udp 9000 8545 8546 +EXPOSE 30303 30303/udp 9001 8545 8546 ENTRYPOINT ["/usr/local/bin/reth"] diff --git a/Dockerfile.cross b/Dockerfile.cross index 64e5c7c02ee0..f477f1ed3e0b 100644 --- a/Dockerfile.cross +++ b/Dockerfile.cross @@ -11,5 +11,5 @@ ARG TARGETARCH COPY ./dist/bin/$TARGETARCH/reth /usr/local/bin/reth -EXPOSE 30303 30303/udp 9000 8545 8546 +EXPOSE 30303 30303/udp 9001 8545 8546 ENTRYPOINT ["/usr/local/bin/reth"] diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 247a370bfaec..3bd2609b2911 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -781,14 +781,14 @@ mod tests { #[test] fn parse_metrics_port() { - let cmd = Command::try_parse_from(["reth", "--metrics", "9000"]).unwrap(); - assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9000))); + let cmd = Command::try_parse_from(["reth", "--metrics", "9001"]).unwrap(); + assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))); - let cmd = Command::try_parse_from(["reth", "--metrics", ":9000"]).unwrap(); - assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9000))); + let cmd = Command::try_parse_from(["reth", "--metrics", ":9001"]).unwrap(); + assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))); - let cmd = Command::try_parse_from(["reth", "--metrics", "localhost:9000"]).unwrap(); - assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9000))); + let cmd = Command::try_parse_from(["reth", "--metrics", "localhost:9001"]).unwrap(); + assert_eq!(cmd.metrics, Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9001))); } #[test] diff --git a/book/run/observability.md b/book/run/observability.md index bae6f9a767fc..9e951532d4cb 100644 --- a/book/run/observability.md +++ b/book/run/observability.md @@ -3,13 +3,13 @@ Reth exposes a number of metrics, which are listed [here][metrics]. We can serve them from an HTTP endpoint by adding the `--metrics` flag: ```bash -RUST_LOG=info reth node --metrics 127.0.0.1:9000 +RUST_LOG=info reth node --metrics 127.0.0.1:9001 ``` Now, as the node is running, you can `curl` the endpoint you provided to the `--metrics` flag to get a text dump of the metrics at that time: ```bash -curl 127.0.0.1:9000 +curl 127.0.0.1:9001 ``` The response from this is quite descriptive, but it can be a bit verbose. Plus, it's just a snapshot of the metrics at the time that you `curl`ed the endpoint. @@ -17,7 +17,7 @@ The response from this is quite descriptive, but it can be a bit verbose. Plus, You can run the following command in a separate terminal to periodically poll the endpoint, and just print the values (without the header text) to the terminal: ```bash -while true; do date; curl -s localhost:9000 | grep -Ev '^(#|$)' | sort; echo; sleep 10; done +while true; do date; curl -s localhost:9001 | grep -Ev '^(#|$)' | sort; echo; sleep 10; done ``` We're finally getting somewhere! As a final step, though, wouldn't it be great to see how these metrics progress over time (and generally, in a GUI)? @@ -43,13 +43,13 @@ brew services start prometheus brew services start grafana ``` -This will start a Prometheus service which by default scrapes the metrics exposed at `localhost:9000`. If you launched reth with a different `--metrics` endpoint, you can change the Prometheus config file at `/usr/local/etc/prometheus/prometheus.yml` to point to the correct endpoint, and then restart the Prometheus service. You can also stop the service and launch Prometheus with a custom `prometheus.yml` like the one provided under [`etc/prometheus/prometheus.yml`](https://github.com/paradigmxyz/reth/blob/main/etc/prometheus/prometheus.yml) in this repo. +This will start a Prometheus service which by default scrapes the metrics exposed at `localhost:9001`. If you launched reth with a different `--metrics` endpoint, you can change the Prometheus config file at `/usr/local/etc/prometheus/prometheus.yml` to point to the correct endpoint, and then restart the Prometheus service. You can also stop the service and launch Prometheus with a custom `prometheus.yml` like the one provided under [`etc/prometheus/prometheus.yml`](https://github.com/paradigmxyz/reth/blob/main/etc/prometheus/prometheus.yml) in this repo. Next, open up "localhost:3000" in your browser, which is the default URL for Grafana. Here, "admin" is the default for both the username and password. Once you've logged in, click on the gear icon in the lower left, and select "Data Sources". Click on "Add data source", and select "Prometheus" as the type. In the HTTP URL field, enter `http://localhost:9090`, this is the default endpoint for the Prometheus scrape endpoint. Finally, click "Save & Test". -As this might be a point of confusion, `localhost:9000`, which we supplied to `--metrics`, is the endpoint that Reth exposes, from which Prometheus collects metrics. Prometheus then exposes `localhost:9090` (by default) for other services (such as Grafana) to consume Prometheus metrics. +As this might be a point of confusion, `localhost:9001`, which we supplied to `--metrics`, is the endpoint that Reth exposes, from which Prometheus collects metrics. Prometheus then exposes `localhost:9090` (by default) for other services (such as Grafana) to consume Prometheus metrics. To configure the dashboard in Grafana, click on the squares icon in the upper left, and click on "New", then "Import". From there, click on "Upload JSON file", and select the example file in `reth/etc/grafana/overview.json`. Finally, select the Prometheus data source you just created, and click "Import". diff --git a/etc/README.md b/etc/README.md index 68e610d7b68e..10df5260b8ec 100644 --- a/etc/README.md +++ b/etc/README.md @@ -12,7 +12,7 @@ The files in this directory may undergo a lot of changes while reth is unstable, ### Docker Compose To run Grafana dashboard with example dashboard and pre-configured Prometheus data source pointing at -the locally running Reth instance with metrics exposed on `localhost:9000`: +the locally running Reth instance with metrics exposed on `localhost:9001`: ```sh docker compose -p reth -f ./etc/docker-monitoring.yml up ``` diff --git a/etc/prometheus/prometheus.yml b/etc/prometheus/prometheus.yml index a3e083f81270..6444d4d87eca 100644 --- a/etc/prometheus/prometheus.yml +++ b/etc/prometheus/prometheus.yml @@ -3,4 +3,4 @@ scrape_configs: metrics_path: "/" scrape_interval: 5s static_configs: - - targets: ['localhost:9000', 'host.docker.internal:9000'] + - targets: ['localhost:9001', 'host.docker.internal:9001'] From 6c1719314661e22ede5e26e1a67bc7a03e2c3987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Vincent?= <28714795+leovct@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:33:52 +0200 Subject: [PATCH 119/216] docs: describe `reth db version` in book (#3296) --- book/cli/db.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/book/cli/db.md b/book/cli/db.md index 1a87ad8bc3d4..370fbdb0aad6 100644 --- a/book/cli/db.md +++ b/book/cli/db.md @@ -129,6 +129,42 @@ Options: Print help (see a summary with '-h') ``` +## `reth db version` + +```bash +$ reth db version --help +Lists current and local database versions + +Usage: reth db version [OPTIONS] + +Options: + --datadir + The path to the data dir for all reth files and subdirectories. + + Defaults to the OS-specific data directory: + + - Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/` + - Windows: `{FOLDERID_RoamingAppData}/reth/` + - macOS: `$HOME/Library/Application Support/reth/` + + [default: default] + + --chain + The chain this node is running. + + Possible values are either a built-in chain or the path to a chain specification file. + + Built-in chains: + - mainnet + - goerli + - sepolia + + [default: mainnet] + + -h, --help + Print help (see a summary with '-h') +``` + ## `reth db drop` ```bash From 00e6adfa57a337e75c27d2388e179bd6f9966332 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jun 2023 14:50:17 +0200 Subject: [PATCH 120/216] perf: handle sync and engine messages in same loop (#3276) --- crates/consensus/beacon/src/engine/mod.rs | 37 ++++++++++------------ crates/consensus/beacon/src/engine/sync.rs | 1 + 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 909cf36747ca..cb3bac489e96 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1301,20 +1301,9 @@ where // Process all incoming messages from the CL, these can affect the state of the // SyncController, hence they are polled first, and they're also time sensitive. loop { - // If a new pipeline run is pending we poll the sync controller first so that it takes - // precedence over any FCU messages. This ensures that a queued pipeline run via - // [EngineSyncController::set_pipeline_sync_target] are processed before any forkchoice - // updates. - if this.sync.is_pipeline_sync_pending() { - // the next event is guaranteed to be a [EngineSyncEvent::PipelineStarted] - if let Poll::Ready(sync_event) = this.sync.poll(cx) { - if let Some(res) = this.on_sync_event(sync_event) { - return Poll::Ready(res) - } - } - } + let mut engine_messages_pending = false; - // handle next engine message, else exit the loop + // handle next engine message match this.engine_message_rx.poll_next_unpin(cx) { Poll::Ready(Some(msg)) => match msg { BeaconEngineMessage::ForkchoiceUpdated { state, payload_attrs, tx } => { @@ -1339,19 +1328,25 @@ where } Poll::Pending => { // no more CL messages to process - break + engine_messages_pending = true; } } - } - // drain the sync controller - while let Poll::Ready(sync_event) = this.sync.poll(cx) { - if let Some(res) = this.on_sync_event(sync_event) { - return Poll::Ready(res) + // process sync events if any + match this.sync.poll(cx) { + Poll::Ready(sync_event) => { + if let Some(res) = this.on_sync_event(sync_event) { + return Poll::Ready(res) + } + } + Poll::Pending => { + if engine_messages_pending { + // both the sync and the engine message receiver are pending + return Poll::Pending + } + } } } - - Poll::Pending } } diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index 70e99657c065..bc64c6d74380 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -107,6 +107,7 @@ where } /// Returns `true` if a pipeline target is queued and will be triggered on the next `poll`. + #[allow(unused)] pub(crate) fn is_pipeline_sync_pending(&self) -> bool { self.pending_pipeline_target.is_some() && self.pipeline_state.is_idle() } From 0c222e5258a90b8c474d95620afbf61a7a2ea70a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jun 2023 15:15:22 +0200 Subject: [PATCH 121/216] chore: update defaults in builder (#3299) Co-authored-by: 0xlosha Co-authored-by: rya <83345377+rya0x@users.noreply.github.com> --- bin/reth/src/args/rpc_server_args.rs | 10 +++++----- crates/rpc/rpc-builder/src/auth.rs | 2 +- crates/rpc/rpc-builder/src/lib.rs | 13 ++++++------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/bin/reth/src/args/rpc_server_args.rs b/bin/reth/src/args/rpc_server_args.rs index fa16ee358863..b7e7e2b8e933 100644 --- a/bin/reth/src/args/rpc_server_args.rs +++ b/bin/reth/src/args/rpc_server_args.rs @@ -353,7 +353,7 @@ impl RpcServerArgs { Tasks: TaskSpawner + Clone + 'static, { let socket_address = SocketAddr::new( - self.auth_addr.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + self.auth_addr.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)), self.auth_port.unwrap_or(constants::DEFAULT_AUTH_PORT), ); @@ -424,7 +424,7 @@ impl RpcServerArgs { if self.http { let socket_address = SocketAddr::new( - self.http_addr.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + self.http_addr.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)), self.http_port.unwrap_or(constants::DEFAULT_HTTP_RPC_PORT), ); config = config @@ -436,7 +436,7 @@ impl RpcServerArgs { if self.ws { let socket_address = SocketAddr::new( - self.ws_addr.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + self.ws_addr.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)), self.ws_port.unwrap_or(constants::DEFAULT_WS_RPC_PORT), ); config = config.with_ws_address(socket_address).with_ws(self.http_ws_server_builder()); @@ -454,7 +454,7 @@ impl RpcServerArgs { /// Creates the [AuthServerConfig] from cli args. fn auth_server_config(&self, jwt_secret: JwtSecret) -> Result { let address = SocketAddr::new( - self.auth_addr.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED)), + self.auth_addr.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)), self.auth_port.unwrap_or(constants::DEFAULT_AUTH_PORT), ); @@ -556,7 +556,7 @@ mod tests { assert_eq!( config.http_address().unwrap(), SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::UNSPECIFIED, + Ipv4Addr::LOCALHOST, constants::DEFAULT_HTTP_RPC_PORT )) ); diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index 08a8ff65406f..eb83578ee609 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -203,7 +203,7 @@ impl AuthServerConfigBuilder { pub fn build(self) -> AuthServerConfig { AuthServerConfig { socket_addr: self.socket_addr.unwrap_or_else(|| { - SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), constants::DEFAULT_AUTH_PORT) + SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), constants::DEFAULT_AUTH_PORT) }), secret: self.secret, server_config: self.server_config.unwrap_or_else(|| { diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index c82573e7a5dd..fb73bddf67d6 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1030,7 +1030,7 @@ impl RpcServerConfig { /// Configures the [SocketAddr] of the http server /// - /// Default is [Ipv4Addr::UNSPECIFIED] and [DEFAULT_HTTP_RPC_PORT] + /// Default is [Ipv4Addr::LOCALHOST] and [DEFAULT_HTTP_RPC_PORT] pub fn with_http_address(mut self, addr: SocketAddr) -> Self { self.http_addr = Some(addr); self @@ -1038,7 +1038,7 @@ impl RpcServerConfig { /// Configures the [SocketAddr] of the ws server /// - /// Default is [Ipv4Addr::UNSPECIFIED] and [DEFAULT_WS_RPC_PORT] + /// Default is [Ipv4Addr::LOCALHOST] and [DEFAULT_WS_RPC_PORT] pub fn with_ws_address(mut self, addr: SocketAddr) -> Self { self.ws_addr = Some(addr); self @@ -1118,14 +1118,13 @@ impl RpcServerConfig { /// If both are on the same port, they are combined into one server. async fn build_ws_http(&mut self) -> Result { let http_socket_addr = self.http_addr.unwrap_or(SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::UNSPECIFIED, + Ipv4Addr::LOCALHOST, DEFAULT_HTTP_RPC_PORT, ))); - let ws_socket_addr = self.ws_addr.unwrap_or(SocketAddr::V4(SocketAddrV4::new( - Ipv4Addr::UNSPECIFIED, - DEFAULT_WS_RPC_PORT, - ))); + let ws_socket_addr = self + .ws_addr + .unwrap_or(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, DEFAULT_WS_RPC_PORT))); // If both are configured on the same port, we combine them into one server. if self.http_addr == self.ws_addr && From 35b005ea0a774b60cdf44f9fc429ab7cde2d93e8 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:36:26 +0200 Subject: [PATCH 122/216] feat(revm): `record_logs` boolean in `TracingInspectorConfig` to limit log record (#3286) Co-authored-by: Matthias Seitz --- crates/revm/revm-inspectors/src/tracing/config.rs | 11 +++++++++++ crates/revm/revm-inspectors/src/tracing/mod.rs | 7 +++++-- crates/rpc/rpc/src/debug.rs | 3 ++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/config.rs b/crates/revm/revm-inspectors/src/tracing/config.rs index 76821b29ff44..2a0cb69bd679 100644 --- a/crates/revm/revm-inspectors/src/tracing/config.rs +++ b/crates/revm/revm-inspectors/src/tracing/config.rs @@ -16,6 +16,8 @@ pub struct TracingInspectorConfig { pub record_state_diff: bool, /// Whether to ignore precompile calls. pub exclude_precompile_calls: bool, + /// Whether to record logs + pub record_logs: bool, } impl TracingInspectorConfig { @@ -27,6 +29,7 @@ impl TracingInspectorConfig { record_stack_snapshots: true, record_state_diff: false, exclude_precompile_calls: false, + record_logs: true, } } @@ -40,6 +43,7 @@ impl TracingInspectorConfig { record_stack_snapshots: false, record_state_diff: false, exclude_precompile_calls: true, + record_logs: false, } } @@ -53,6 +57,7 @@ impl TracingInspectorConfig { record_stack_snapshots: true, record_state_diff: true, exclude_precompile_calls: false, + record_logs: false, } } @@ -97,4 +102,10 @@ impl TracingInspectorConfig { self.record_state_diff = record_state_diff; self } + + /// Configure whether the tracer should record logs + pub fn set_record_logs(mut self, record_logs: bool) -> Self { + self.record_logs = record_logs; + self + } } diff --git a/crates/revm/revm-inspectors/src/tracing/mod.rs b/crates/revm/revm-inspectors/src/tracing/mod.rs index 1bbad2687418..51bbc46f7ae5 100644 --- a/crates/revm/revm-inspectors/src/tracing/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/mod.rs @@ -336,8 +336,11 @@ where let trace_idx = self.last_trace_idx(); let trace = &mut self.traces.arena[trace_idx]; - trace.ordering.push(LogCallOrder::Log(trace.logs.len())); - trace.logs.push(RawLog { topics: topics.to_vec(), data: data.clone() }); + + if self.config.record_logs { + trace.ordering.push(LogCallOrder::Log(trace.logs.len())); + trace.logs.push(RawLog { topics: topics.to_vec(), data: data.clone() }); + } } fn step_end( diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 3eb98eabccc2..3cdf35b8b409 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -290,7 +290,8 @@ where .map_err(|_| EthApiError::InvalidTracerConfig)?; let mut inspector = TracingInspector::new( - TracingInspectorConfig::from_geth_config(&config), + TracingInspectorConfig::from_geth_config(&config) + .set_record_logs(call_config.with_log.unwrap_or_default()), ); let _ = self From 8fd3cd2d81b7dbf68d9801fe50078b8e2a61420b Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:11:55 +0200 Subject: [PATCH 123/216] feat(stages): replace `progress` by `block_number` in `PipelineProgress` (#3256) --- crates/consensus/beacon/src/engine/sync.rs | 4 +- crates/stages/src/pipeline/ctrl.rs | 12 +++--- crates/stages/src/pipeline/mod.rs | 43 +++++++++++----------- crates/stages/src/pipeline/progress.rs | 26 ++++++------- 4 files changed, 43 insertions(+), 42 deletions(-) diff --git a/crates/consensus/beacon/src/engine/sync.rs b/crates/consensus/beacon/src/engine/sync.rs index bc64c6d74380..3e9aa91b68f2 100644 --- a/crates/consensus/beacon/src/engine/sync.rs +++ b/crates/consensus/beacon/src/engine/sync.rs @@ -182,9 +182,9 @@ where }; let ev = match res { Ok((pipeline, result)) => { - let minimum_progress = pipeline.minimum_progress(); + let minimum_block_number = pipeline.minimum_block_number(); let reached_max_block = - self.has_reached_max_block(minimum_progress.unwrap_or_default()); + self.has_reached_max_block(minimum_block_number.unwrap_or_default()); self.pipeline_state = PipelineState::Idle(Some(pipeline)); EngineSyncEvent::PipelineFinished { result, reached_max_block } } diff --git a/crates/stages/src/pipeline/ctrl.rs b/crates/stages/src/pipeline/ctrl.rs index 8c514eea692b..deece92d25fa 100644 --- a/crates/stages/src/pipeline/ctrl.rs +++ b/crates/stages/src/pipeline/ctrl.rs @@ -12,13 +12,13 @@ pub enum ControlFlow { }, /// The pipeline is allowed to continue executing stages. Continue { - /// The progress of the last stage - progress: BlockNumber, + /// Block number reached by the stage. + block_number: BlockNumber, }, /// Pipeline made no progress NoProgress { - /// The current stage progress. - stage_progress: Option, + /// Block number reached by the stage. + block_number: Option, }, } @@ -37,8 +37,8 @@ impl ControlFlow { pub fn progress(&self) -> Option { match self { ControlFlow::Unwind { .. } => None, - ControlFlow::Continue { progress } => Some(*progress), - ControlFlow::NoProgress { stage_progress } => *stage_progress, + ControlFlow::Continue { block_number } => Some(*block_number), + ControlFlow::NoProgress { block_number } => *block_number, } } } diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index 14c78aac1c84..ca9b7204e6d0 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -117,9 +117,10 @@ where PipelineBuilder::default() } - /// Return the minimum pipeline progress - pub fn minimum_progress(&self) -> Option { - self.progress.minimum_progress + /// Return the minimum block number achieved by + /// any stage during the execution of the pipeline. + pub fn minimum_block_number(&self) -> Option { + self.progress.minimum_block_number } /// Set tip for reverse sync. @@ -181,14 +182,14 @@ where // configured block. if next_action.should_continue() && self.progress - .minimum_progress + .minimum_block_number .zip(self.max_block) .map_or(false, |(progress, target)| progress >= target) { trace!( target: "sync::pipeline", ?next_action, - minimum_progress = ?self.progress.minimum_progress, + minimum_block_number = ?self.progress.minimum_block_number, max_block = ?self.max_block, "Terminating pipeline." ); @@ -218,12 +219,12 @@ where trace!(target: "sync::pipeline", stage = %stage_id, ?next, "Completed stage"); match next { - ControlFlow::NoProgress { stage_progress } => { - if let Some(progress) = stage_progress { - self.progress.update(progress); + ControlFlow::NoProgress { block_number } => { + if let Some(block_number) = block_number { + self.progress.update(block_number); } } - ControlFlow::Continue { progress } => self.progress.update(progress), + ControlFlow::Continue { block_number } => self.progress.update(block_number), ControlFlow::Unwind { target, bad_block } => { self.unwind(target, Some(bad_block.number)).await?; return Ok(ControlFlow::Unwind { target, bad_block }) @@ -345,7 +346,7 @@ where // We reached the maximum block, so we skip the stage return Ok(ControlFlow::NoProgress { - stage_progress: prev_checkpoint.map(|progress| progress.block_number), + block_number: prev_checkpoint.map(|progress| progress.block_number), }) } @@ -386,11 +387,11 @@ where provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; if done { - let stage_progress = checkpoint.block_number; + let block_number = checkpoint.block_number; return Ok(if made_progress { - ControlFlow::Continue { progress: stage_progress } + ControlFlow::Continue { block_number } } else { - ControlFlow::NoProgress { stage_progress: Some(stage_progress) } + ControlFlow::NoProgress { block_number: Some(block_number) } }) } } @@ -491,26 +492,26 @@ mod tests { let mut progress = PipelineProgress::default(); progress.update(10); - assert_eq!(progress.minimum_progress, Some(10)); - assert_eq!(progress.maximum_progress, Some(10)); + assert_eq!(progress.minimum_block_number, Some(10)); + assert_eq!(progress.maximum_block_number, Some(10)); progress.update(20); - assert_eq!(progress.minimum_progress, Some(10)); - assert_eq!(progress.maximum_progress, Some(20)); + assert_eq!(progress.minimum_block_number, Some(10)); + assert_eq!(progress.maximum_block_number, Some(20)); progress.update(1); - assert_eq!(progress.minimum_progress, Some(1)); - assert_eq!(progress.maximum_progress, Some(20)); + assert_eq!(progress.minimum_block_number, Some(1)); + assert_eq!(progress.maximum_block_number, Some(20)); } #[test] fn progress_ctrl_flow() { let mut progress = PipelineProgress::default(); - assert_eq!(progress.next_ctrl(), ControlFlow::NoProgress { stage_progress: None }); + assert_eq!(progress.next_ctrl(), ControlFlow::NoProgress { block_number: None }); progress.update(1); - assert_eq!(progress.next_ctrl(), ControlFlow::Continue { progress: 1 }); + assert_eq!(progress.next_ctrl(), ControlFlow::Continue { block_number: 1 }); } /// Runs a simple pipeline. diff --git a/crates/stages/src/pipeline/progress.rs b/crates/stages/src/pipeline/progress.rs index ef1a17d70fb7..1c4bbcf6cb01 100644 --- a/crates/stages/src/pipeline/progress.rs +++ b/crates/stages/src/pipeline/progress.rs @@ -4,26 +4,26 @@ use reth_primitives::BlockNumber; #[derive(Debug, Default)] pub(crate) struct PipelineProgress { - /// The progress of the current stage - pub(crate) progress: Option, - /// The maximum progress achieved by any stage during the execution of the pipeline. - pub(crate) maximum_progress: Option, - /// The minimum progress achieved by any stage during the execution of the pipeline. - pub(crate) minimum_progress: Option, + /// Block number reached by the stage. + pub(crate) block_number: Option, + /// The maximum block number achieved by any stage during the execution of the pipeline. + pub(crate) maximum_block_number: Option, + /// The minimum block number achieved by any stage during the execution of the pipeline. + pub(crate) minimum_block_number: Option, } impl PipelineProgress { - pub(crate) fn update(&mut self, progress: BlockNumber) { - self.progress = Some(progress); - self.minimum_progress = opt::min(self.minimum_progress, progress); - self.maximum_progress = opt::max(self.maximum_progress, progress); + pub(crate) fn update(&mut self, block_number: BlockNumber) { + self.block_number = Some(block_number); + self.minimum_block_number = opt::min(self.minimum_block_number, block_number); + self.maximum_block_number = opt::max(self.maximum_block_number, block_number); } /// Get next control flow step pub(crate) fn next_ctrl(&self) -> ControlFlow { - match self.progress { - Some(progress) => ControlFlow::Continue { progress }, - None => ControlFlow::NoProgress { stage_progress: None }, + match self.block_number { + Some(block_number) => ControlFlow::Continue { block_number }, + None => ControlFlow::NoProgress { block_number: None }, } } } From 7b77fc1b6ba8023f1c20cfa5c134d5e332e2198e Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 21 Jun 2023 20:08:16 +0100 Subject: [PATCH 124/216] test(ethereum): ignore invalid string sequence in ethereum state tests (#3307) --- Cargo.lock | 10 ++++++++++ testing/ef-tests/Cargo.toml | 1 + testing/ef-tests/src/models.rs | 14 ++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 329c3bdcedcb..81afcebc75ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1736,6 +1736,7 @@ dependencies = [ "reth-rlp", "reth-stages", "serde", + "serde_bytes", "serde_json", "thiserror", "tokio", @@ -6379,6 +6380,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416bda436f9aab92e02c8e10d49a15ddd339cea90b6e340fe51ed97abb548294" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.164" diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index 109fefd4563a..c394b9a8d48b 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -24,3 +24,4 @@ walkdir = "2.3.3" serde = "1.0.163" serde_json = { workspace = true } thiserror = { workspace = true } +serde_bytes = "0.11.9" \ No newline at end of file diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index 31a9612f033e..abc9bf425b91 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -37,6 +37,20 @@ pub struct BlockchainTest { #[serde(default)] /// Engine spec. pub self_engine: SealEngine, + #[serde(rename = "_info")] + #[allow(unused)] + info: BlockchainTestInfo, +} + +#[derive(Debug, PartialEq, Eq, Deserialize)] +struct BlockchainTestInfo { + #[serde(rename = "filling-rpc-server")] + #[allow(unused)] + // One test has an invalid string in this field, which breaks our CI: + // https://github.com/ethereum/tests/blob/6c252923bdd1bd5a70f680df1214f866f76839db/GeneralStateTests/stTransactionTest/ValueOverflow.json#L5 + // By using `serde_bytes::ByteBuf`, we ignore the validation of this field as a string. + // TODO(alexey): remove when `ethereum/tests` is fixed + filling_rpc_server: serde_bytes::ByteBuf, } /// A block header in an Ethereum blockchain test. From ee4f6b32a0b77eb4fff781d26694f2565a12d80f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jun 2023 21:13:13 +0200 Subject: [PATCH 125/216] chore(deps): bump test-fuzz 4 (#3303) --- Cargo.lock | 116 ++++++++++++++----------------- crates/net/eth-wire/Cargo.toml | 2 +- crates/primitives/Cargo.toml | 2 +- crates/storage/codecs/Cargo.toml | 2 +- crates/storage/db/Cargo.toml | 2 +- 5 files changed, 57 insertions(+), 67 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81afcebc75ae..8493f819ded4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,7 +726,7 @@ checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.17", + "semver", "serde", "serde_json", "thiserror", @@ -1351,6 +1351,16 @@ dependencies = [ "darling_macro 0.14.3", ] +[[package]] +name = "darling" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" +dependencies = [ + "darling_core 0.20.1", + "darling_macro 0.20.1", +] + [[package]] name = "darling_core" version = "0.10.2" @@ -1379,6 +1389,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_core" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2 1.0.60", + "quote 1.0.28", + "strsim 0.10.0", + "syn 2.0.18", +] + [[package]] name = "darling_macro" version = "0.10.2" @@ -1401,6 +1425,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "darling_macro" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" +dependencies = [ + "darling_core 0.20.1", + "quote 1.0.28", + "syn 2.0.18", +] + [[package]] name = "dashmap" version = "5.4.0" @@ -2094,7 +2129,7 @@ checksum = "84ebb401ba97c6f5af278c2c9936c4546cad75dec464b439ae6df249906f4caa" dependencies = [ "ethers-core", "reqwest", - "semver 1.0.17", + "semver", "serde", "serde_json", "thiserror", @@ -4217,16 +4252,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -[[package]] -name = "pest" -version = "2.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7" -dependencies = [ - "thiserror", - "ucd-trie", -] - [[package]] name = "pharos" version = "0.5.3" @@ -6061,7 +6086,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.17", + "semver", ] [[package]] @@ -6332,15 +6357,6 @@ dependencies = [ "libc", ] -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser", -] - [[package]] name = "semver" version = "1.0.17" @@ -6350,15 +6366,6 @@ dependencies = [ "serde", ] -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "send_wrapper" version = "0.4.0" @@ -6975,9 +6982,9 @@ checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" [[package]] name = "test-fuzz" -version = "3.0.5" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4a3a7f00909d5a1d1f83b86b65d91e4c94f80b0c2d0ae37e2ef44da7b7a0a0" +checksum = "a7bb2f404d5d20140588fb209481f5841920a7e29c36124f3d1ac1041eb1842c" dependencies = [ "serde", "test-fuzz-internal", @@ -6987,9 +6994,9 @@ dependencies = [ [[package]] name = "test-fuzz-internal" -version = "3.0.5" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9186daca5c58cb307d09731e0ba06b13fd6c036c90672b9bfc31cecf76cf689" +checksum = "385624eb0031d550fe1bf99c08af79b838605fc4fcec2c4d55e229a2c342fdd0" dependencies = [ "cargo_metadata", "proc-macro2 1.0.60", @@ -7000,27 +7007,27 @@ dependencies = [ [[package]] name = "test-fuzz-macro" -version = "3.0.5" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d187b450bfb5b7939f82f9747dc1ebb15a7a9c4a93cd304a41aece7149608b" +checksum = "69247423e2d89bd51160e42200f6f45f921a23e5b44a0e5b57b888a378334037" dependencies = [ - "darling 0.14.3", + "darling 0.20.1", "if_chain", + "itertools", "lazy_static", "proc-macro2 1.0.60", "quote 1.0.28", "subprocess", - "syn 1.0.109", + "syn 2.0.18", "test-fuzz-internal", "toolchain_find", - "unzip-n", ] [[package]] name = "test-fuzz-runtime" -version = "3.0.5" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0d69068569b9b7311095823fe0e49eedfd05ad4277eb64fc334cf1a5bc5116" +checksum = "f5054a92d02b1a95a0120155d20aef49b5c5673ba8a65d6f4ce667c2a6f3146c" dependencies = [ "bincode", "hex", @@ -7283,14 +7290,14 @@ dependencies = [ [[package]] name = "toolchain_find" -version = "0.2.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e85654a10e7a07a47c6f19d93818f3f343e22927f2fa280c84f7c8042743413" +checksum = "ebc8c9a7f0a2966e1acdaf0461023d0b01471eeead645370cf4c3f5cff153f2a" dependencies = [ "home", - "lazy_static", + "once_cell", "regex", - "semver 0.11.0", + "semver", "walkdir", ] @@ -7636,12 +7643,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "ucd-trie" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" - [[package]] name = "uint" version = "0.9.5" @@ -7730,17 +7731,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "unzip-n" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7e85a0596447f0f2ac090e16bc4c516c6fe91771fb0c0ccf7fa3dae896b9c" -dependencies = [ - "proc-macro2 1.0.60", - "quote 1.0.28", - "syn 1.0.109", -] - [[package]] name = "url" version = "2.3.1" diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index c39a527a0819..518f30b348ad 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -49,7 +49,7 @@ reth-primitives = { workspace = true, features = ["arbitrary"] } reth-tracing = { path = "../../tracing" } ethers-core = { workspace = true, default-features = false } -test-fuzz = "3.0.4" +test-fuzz = "4" tokio-util = { workspace = true, features = ["io", "codec"] } hex-literal = "0.3" hex = "0.4" diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 4661f92bc693..bea2afbc9671 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -73,7 +73,7 @@ strum = { version = "0.24", features = ["derive"] } [dev-dependencies] serde_json = { workspace = true } hex-literal = "0.3" -test-fuzz = "3.0.4" +test-fuzz = "4" rand = { workspace = true } revm-primitives = { workspace = true, features = ["arbitrary"] } arbitrary = { version = "1.1.7", features = ["derive"] } diff --git a/crates/storage/codecs/Cargo.toml b/crates/storage/codecs/Cargo.toml index 5b3a9497bcfa..76b894a40345 100644 --- a/crates/storage/codecs/Cargo.toml +++ b/crates/storage/codecs/Cargo.toml @@ -30,7 +30,7 @@ revm-primitives = { workspace = true, features = ["serde", "arbitrary"] } serde = "1.0" modular-bitfield = "0.11.2" -test-fuzz = "3.0.4" +test-fuzz = "4" arbitrary = { version = "1.1.7", features = ["derive"] } proptest = { version = "1.0" } diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 57370c3fc1f9..67204d54ffc4 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -50,7 +50,7 @@ reth-codecs = { path = "../codecs", features = ["arbitrary"] } reth-interfaces = { workspace = true, features = ["bench"] } tempfile = "3.3.0" -test-fuzz = "3.0.4" +test-fuzz = "4" pprof = { version = "0.11", features = ["flamegraph", "frame-pointer", "criterion"] } criterion = "0.4.0" From 5b20aece2b4176ddeaed7050e710c5c45ec34aeb Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jun 2023 21:13:32 +0200 Subject: [PATCH 126/216] chore: move bytes to workspace dep (#3302) --- Cargo.toml | 3 ++- crates/net/eth-wire/Cargo.toml | 2 +- crates/primitives/Cargo.toml | 2 +- crates/rlp/Cargo.toml | 2 +- crates/rpc/ipc/Cargo.toml | 2 +- crates/rpc/rpc/Cargo.toml | 2 +- crates/storage/codecs/Cargo.toml | 2 +- crates/storage/db/Cargo.toml | 2 +- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 52113588c4dd..09bf2988a245 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,8 @@ ethers-signers = { version = "2.0.7", default-features = false } ethers-middleware = { version = "2.0.7", default-features = false } ## misc -tracing = "^0.1.0" +bytes = "1.4" +tracing = "0.1.0" thiserror = "1.0.37" serde_json = "1.0.94" serde = { version = "1.0", default-features = false } diff --git a/crates/net/eth-wire/Cargo.toml b/crates/net/eth-wire/Cargo.toml index 518f30b348ad..de43afd7130b 100644 --- a/crates/net/eth-wire/Cargo.toml +++ b/crates/net/eth-wire/Cargo.toml @@ -9,7 +9,7 @@ homepage.workspace = true repository.workspace = true [dependencies] -bytes = "1.4" +bytes.workspace = true thiserror = { workspace = true } serde = { workspace = true, optional = true } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index bea2afbc9671..a34e21094d04 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -43,7 +43,7 @@ tokio = { workspace = true, default-features = false, features = ["sync"] } tokio-stream = { workspace = true } # misc -bytes = "1.4" +bytes.workspace = true serde = { workspace = true } serde_json = { workspace = true } serde_with = "2.1.0" diff --git a/crates/rlp/Cargo.toml b/crates/rlp/Cargo.toml index c7977b49492c..234e9a52c983 100644 --- a/crates/rlp/Cargo.toml +++ b/crates/rlp/Cargo.toml @@ -11,7 +11,7 @@ repository.workspace = true [dependencies] arrayvec = { version = "0.7", default-features = false } auto_impl = "1" -bytes = { version = "1", default-features = false } +bytes.workspace = true ethnum = { version = "1", default-features = false, optional = true } smol_str = { version = "0.1", default-features = false, optional = true } ethereum-types = { version = "0.14", features = ["codec"], optional = true } diff --git a/crates/rpc/ipc/Cargo.toml b/crates/rpc/ipc/Cargo.toml index 7f118f83c2e3..a7fa2c30c1f9 100644 --- a/crates/rpc/ipc/Cargo.toml +++ b/crates/rpc/ipc/Cargo.toml @@ -26,7 +26,7 @@ tower = "0.4" jsonrpsee = { version = "0.18", features = ["server", "client"] } serde_json = { workspace = true } tracing = { workspace = true } -bytes = "1.4" +bytes = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index a698109232f6..30b94277f700 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -47,7 +47,7 @@ tokio-stream = { workspace = true, features = ["sync"] } tokio-util = "0.7" pin-project = { workspace = true } -bytes = "1.4" +bytes.workspace = true secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/crates/storage/codecs/Cargo.toml b/crates/storage/codecs/Cargo.toml index 76b894a40345..3b6719ae0ad8 100644 --- a/crates/storage/codecs/Cargo.toml +++ b/crates/storage/codecs/Cargo.toml @@ -16,7 +16,7 @@ no_codec = ["codecs-derive/no_codec"] arbitrary = ["revm-primitives/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"] [dependencies] -bytes = "1.4" +bytes.workspace = true codecs-derive = { path = "./derive", default-features = false } revm-primitives = { workspace = true, features = ["serde"] } diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 67204d54ffc4..8b0da0567814 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -32,7 +32,7 @@ secp256k1 = { workspace = true, default-features = false, features = [ modular-bitfield = "0.11.2" # misc -bytes = "1.4" +bytes.workspace = true page_size = "0.4.2" thiserror = { workspace = true } tempfile = { version = "3.3.0", optional = true } From 704223010257b6e8307ab97a75f407aceead756a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jun 2023 21:13:48 +0200 Subject: [PATCH 127/216] chore: remove unused client feature (#3300) --- crates/rpc/ipc/Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/rpc/ipc/Cargo.toml b/crates/rpc/ipc/Cargo.toml index a7fa2c30c1f9..417dfd8239d0 100644 --- a/crates/rpc/ipc/Cargo.toml +++ b/crates/rpc/ipc/Cargo.toml @@ -32,6 +32,3 @@ thiserror = { workspace = true } [dev-dependencies] tracing-test = "0.2" tokio-stream = { workspace = true, features = ["sync"] } - -[features] -client = ["jsonrpsee/client", "jsonrpsee/async-client"] From 77167134d005c2582c9674fa968d7c43d1d9cfea Mon Sep 17 00:00:00 2001 From: Bjerg Date: Wed, 21 Jun 2023 21:35:38 +0200 Subject: [PATCH 128/216] docs: document crate features (#3269) --- bin/reth/src/lib.rs | 8 ++++++++ crates/blockchain-tree/src/lib.rs | 12 +++++++++++- crates/interfaces/Cargo.toml | 1 - crates/interfaces/src/lib.rs | 6 +++++- crates/metrics/src/lib.rs | 5 +++++ crates/net/discv4/src/lib.rs | 5 +++++ crates/net/dns/src/lib.rs | 6 +++++- crates/net/downloaders/src/lib.rs | 4 ++++ crates/net/eth-wire/src/lib.rs | 5 +++++ crates/net/nat/src/lib.rs | 4 ++++ crates/net/network-api/src/lib.rs | 5 ++++- crates/net/network/src/lib.rs | 4 ++-- crates/payload/builder/src/lib.rs | 4 ++++ crates/primitives/src/lib.rs | 6 +++++- crates/revm/revm-inspectors/src/lib.rs | 7 ++++++- crates/rlp/src/lib.rs | 9 +++++++++ crates/rpc/ipc/src/lib.rs | 6 +++++- crates/rpc/rpc-api/src/lib.rs | 4 ++++ crates/staged-sync/src/lib.rs | 2 +- crates/stages/src/lib.rs | 4 ++++ crates/storage/db/Cargo.toml | 2 +- crates/storage/provider/Cargo.toml | 1 - crates/storage/provider/src/lib.rs | 4 ++++ crates/transaction-pool/src/lib.rs | 6 +++++- crates/trie/src/lib.rs | 4 ++++ 25 files changed, 110 insertions(+), 14 deletions(-) diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index eaffb131c3ab..a3ba783bfdf2 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -6,6 +6,14 @@ ))] //! Rust Ethereum (reth) binary executable. +//! +//! ## Feature Flags +//! +//! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. +//! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. +//! - `only-info-logs`: Disables all logs below `info` level. This can speed up the node, since +//! fewer calls to the logging component is made. pub mod args; pub mod chain; diff --git a/crates/blockchain-tree/src/lib.rs b/crates/blockchain-tree/src/lib.rs index cc7dcb058f20..c4f06910f4c7 100644 --- a/crates/blockchain-tree/src/lib.rs +++ b/crates/blockchain-tree/src/lib.rs @@ -4,7 +4,17 @@ no_crate_inject, attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] -//! Implementation of the [BlockchainTree] +//! Implementation of a tree-like structure for blockchains. +//! +//! The [BlockchainTree] can validate, execute, and revert blocks in multiple competing sidechains. +//! This structure is used for Reth's sync mode at the tip instead of the pipeline, and is the +//! primary executor and validator of payloads sent from the consensus layer. +//! +//! Blocks and their resulting state transitions are kept in-memory until they are persisted. +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing /// Execution result types. pub use reth_provider::post_state; diff --git a/crates/interfaces/Cargo.toml b/crates/interfaces/Cargo.toml index b13b0b7698d1..3fbd20672caf 100644 --- a/crates/interfaces/Cargo.toml +++ b/crates/interfaces/Cargo.toml @@ -52,5 +52,4 @@ secp256k1 = { workspace = true, features = [ ] } [features] -bench = [] test-utils = ["tokio-stream/sync", "secp256k1"] diff --git a/crates/interfaces/src/lib.rs b/crates/interfaces/src/lib.rs index 41b0fdee47ff..7c92bc21b8bd 100644 --- a/crates/interfaces/src/lib.rs +++ b/crates/interfaces/src/lib.rs @@ -5,7 +5,11 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] -//! Reth interface bindings +//! A collection of shared traits and error types used in Reth. +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing /// Consensus traits. pub mod consensus; diff --git a/crates/metrics/src/lib.rs b/crates/metrics/src/lib.rs index 65360ada32a3..ff0aaa4b0e01 100644 --- a/crates/metrics/src/lib.rs +++ b/crates/metrics/src/lib.rs @@ -6,6 +6,11 @@ ))] //! Collection of metrics utilities. +//! +//! ## Feature Flags +//! +//! - `common`: Common metrics utilities, such as wrappers around tokio senders and receivers. Pulls +//! in `tokio`. /// Metrics derive macro. pub use reth_metrics_derive::Metrics; diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 49df08c1a005..712b77a00553 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -17,6 +17,11 @@ //! state and drives the UDP socket. The (optional) [`Discv4`] serves as the frontend to interact //! with the service via a channel. Whenever the underlying table changes service produces a //! [`DiscoveryUpdate`] that listeners will receive. +//! +//! ## Feature Flags +//! +//! - `serde` (default): Enable serde support +//! - `test-utils`: Export utilities for testing use crate::{ error::{DecodePacketError, Discv4Error}, proto::{FindNode, Message, Neighbours, Packet, Ping, Pong}, diff --git a/crates/net/dns/src/lib.rs b/crates/net/dns/src/lib.rs index d32dd00691e4..0edcf3df0751 100644 --- a/crates/net/dns/src/lib.rs +++ b/crates/net/dns/src/lib.rs @@ -6,7 +6,11 @@ ))] //! Implementation of [EIP-1459](https://eips.ethereum.org/EIPS/eip-1459) Node Discovery via DNS. - +//! +//! ## Feature Flags +//! +//! - `serde` (default): Enable serde support +//! - `test-utils`: Export utilities for testing pub use crate::resolver::{DnsResolver, MapResolver, Resolver}; use crate::{ query::{QueryOutcome, QueryPool, ResolveEntryResult, ResolveRootResult}, diff --git a/crates/net/downloaders/src/lib.rs b/crates/net/downloaders/src/lib.rs index c192c9231c6e..5a59412a2ce4 100644 --- a/crates/net/downloaders/src/lib.rs +++ b/crates/net/downloaders/src/lib.rs @@ -7,6 +7,10 @@ #![allow(clippy::result_large_err)] //! Implements the downloader algorithms. +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing /// The collection of algorithms for downloading block bodies. pub mod bodies; diff --git a/crates/net/eth-wire/src/lib.rs b/crates/net/eth-wire/src/lib.rs index 44715489b62c..24197e22c81e 100644 --- a/crates/net/eth-wire/src/lib.rs +++ b/crates/net/eth-wire/src/lib.rs @@ -5,6 +5,11 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] //! Implementation of the `eth` wire protocol. +//! +//! ## Feature Flags +//! +//! - `serde` (default): Enable serde support +//! - `arbitrary`: Adds `proptest` and `arbitrary` support for wire types. pub mod builder; pub mod capability; diff --git a/crates/net/nat/src/lib.rs b/crates/net/nat/src/lib.rs index 6d6bd0536a0a..fadd2d01f614 100644 --- a/crates/net/nat/src/lib.rs +++ b/crates/net/nat/src/lib.rs @@ -6,6 +6,10 @@ ))] //! Helpers for resolving the external IP. +//! +//! ## Feature Flags +//! +//! - `serde` (default): Enable serde support use igd::aio::search_gateway; use pin_project_lite::pin_project; diff --git a/crates/net/network-api/src/lib.rs b/crates/net/network-api/src/lib.rs index 644613c5235e..62af27bc22e6 100644 --- a/crates/net/network-api/src/lib.rs +++ b/crates/net/network-api/src/lib.rs @@ -8,7 +8,10 @@ //! Reth network interface definitions. //! //! Provides abstractions for the reth-network crate. - +//! +//! ## Feature Flags +//! +//! - `serde` (default): Enable serde support use async_trait::async_trait; use reth_eth_wire::DisconnectReason; use reth_primitives::{NodeRecord, PeerId}; diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index f6e424eb9b09..acf5f408459d 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -107,9 +107,9 @@ //! } //! ``` //! -//! # Features +//! # Feature Flags //! -//! - `serde`: Enable serde support for configuration types (enabled by default). +//! - `serde` (default): Enable serde support for configuration types. //! - `test-utils`: Various utilities helpful for writing tests //! - `geth-tests`: Runs tests that require Geth to be installed locally. diff --git a/crates/payload/builder/src/lib.rs b/crates/payload/builder/src/lib.rs index 2c91a2abf256..49a12ef2ccf5 100644 --- a/crates/payload/builder/src/lib.rs +++ b/crates/payload/builder/src/lib.rs @@ -16,6 +16,10 @@ //! - [PayloadJobGenerator]: a type that knows how to create new jobs for creating payloads based //! on [PayloadAttributes](reth_rpc_types::engine::PayloadAttributes). //! - [PayloadJob]: a type that can yields (better) payloads over time. +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing pub mod database; pub mod error; diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 3084617989c8..4fee46ffb4bb 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -8,7 +8,11 @@ //! Commonly used types in reth. //! //! This crate contains Ethereum primitive types and helper functions. - +//! +//! ## Feature Flags +//! +//! - `arbitrary`: Adds `proptest` and `arbitrary` support for primitive types. +//! - `test-utils`: Export utilities for testing pub mod abi; mod account; pub mod basefee; diff --git a/crates/revm/revm-inspectors/src/lib.rs b/crates/revm/revm-inspectors/src/lib.rs index 04cffd048c81..ff8ba39ffa2f 100644 --- a/crates/revm/revm-inspectors/src/lib.rs +++ b/crates/revm/revm-inspectors/src/lib.rs @@ -5,7 +5,12 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] -//! revm [Inspector](revm::Inspector) implementations +//! revm [Inspector](revm::Inspector) implementations, such as call tracers +//! +//! ## Feature Flags +//! +//! - `js-tracer` (default): Enables a JavaScript tracer implementation. This pulls in extra +//! dependencies (such as `boa`, `tokio` and `serde_json`). /// An inspector implementation for an EIP2930 Accesslist pub mod access_list; diff --git a/crates/rlp/src/lib.rs b/crates/rlp/src/lib.rs index c4671f55bfff..1e60ea5dd4b2 100644 --- a/crates/rlp/src/lib.rs +++ b/crates/rlp/src/lib.rs @@ -6,6 +6,15 @@ ))] #![cfg_attr(not(feature = "std"), no_std)] +//! A fast RLP implementation. +//! +//! ## Feature Flags +//! +//! This crate works on `#[no_std]` targets if `std` is not enabled. +//! +//! - `derive`: Enables derive macros. +//! - `std`: Uses the Rust standard library. + #[cfg(feature = "alloc")] extern crate alloc; diff --git a/crates/rpc/ipc/src/lib.rs b/crates/rpc/ipc/src/lib.rs index f8ed954c16e9..13517148a5ea 100644 --- a/crates/rpc/ipc/src/lib.rs +++ b/crates/rpc/ipc/src/lib.rs @@ -5,7 +5,11 @@ attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) ))] -//! Reth IPC implementation +//! Reth IPC transport implementation +//! +//! ## Feature Flags +//! +//! - `client`: Enables JSON-RPC client support. #[cfg(unix)] pub mod client; diff --git a/crates/rpc/rpc-api/src/lib.rs b/crates/rpc/rpc-api/src/lib.rs index cecbe0f90752..54b8ddb4c12a 100644 --- a/crates/rpc/rpc-api/src/lib.rs +++ b/crates/rpc/rpc-api/src/lib.rs @@ -8,6 +8,10 @@ //! Reth RPC interface definitions //! //! Provides all RPC interfaces. +//! +//! ## Feature Flags +//! +//! - `client`: Enables JSON-RPC client support. mod admin; mod debug; diff --git a/crates/staged-sync/src/lib.rs b/crates/staged-sync/src/lib.rs index b32db26cbbc3..e864aa337c31 100644 --- a/crates/staged-sync/src/lib.rs +++ b/crates/staged-sync/src/lib.rs @@ -7,7 +7,7 @@ //! Puts together all the Reth stages in a unified abstraction. //! -//! # Features +//! ## Feature Flags //! //! - `test-utils`: Various utilities helpful for writing tests //! - `geth-tests`: Runs tests that require Geth to be installed locally. diff --git a/crates/stages/src/lib.rs b/crates/stages/src/lib.rs index 14b31668e40d..0a8ba2f10f0a 100644 --- a/crates/stages/src/lib.rs +++ b/crates/stages/src/lib.rs @@ -53,6 +53,10 @@ //! ) //! .build(db, MAINNET.clone()); //! ``` +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing mod error; mod pipeline; mod stage; diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 8b0da0567814..818c22738b74 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -47,7 +47,7 @@ proptest-derive = { version = "0.3", optional = true } # reth libs with arbitrary reth-primitives = { workspace = true, features = ["arbitrary"] } reth-codecs = { path = "../codecs", features = ["arbitrary"] } -reth-interfaces = { workspace = true, features = ["bench"] } +reth-interfaces = { workspace = true } tempfile = "3.3.0" test-fuzz = "4" diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index d270c0c2d0f1..22fcaba5e781 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -42,5 +42,4 @@ reth-trie = { path = "../../trie", features = ["test-utils"] } parking_lot = "0.12" [features] -bench = [] test-utils = ["reth-rlp"] diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 51b74379525d..5a988cf88cfa 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -7,6 +7,10 @@ //! This crate contains a collection of traits and trait implementations for common database //! operations. +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing /// Various provider traits. mod traits; diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index fa1c73b93f93..d0054c9cc407 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -79,7 +79,11 @@ //! The transaction pool will be used by separate consumers (RPC, P2P), to make sharing easier, the //! [`Pool`](crate::Pool) type is just an `Arc` wrapper around `PoolInner`. This is the usable type //! that provides the `TransactionPool` interface. - +//! +//! ## Feature Flags +//! +//! - `serde` (default): Enable serde support +//! - `test-utils`: Export utilities for testing use crate::pool::PoolInner; use aquamarine as _; use reth_primitives::{Address, TxHash, U256}; diff --git a/crates/trie/src/lib.rs b/crates/trie/src/lib.rs index ea45b5fcadc9..d52d4acc06c3 100644 --- a/crates/trie/src/lib.rs +++ b/crates/trie/src/lib.rs @@ -8,6 +8,10 @@ //! The implementation of Merkle Patricia Trie, a cryptographically //! authenticated radix trie that is used to store key-value bindings. //! +//! +//! ## Feature Flags +//! +//! - `test-utils`: Export utilities for testing /// The Ethereum account as represented in the trie. pub mod account; From 2596934464d2771ea6ab74573dd10e1da93a2148 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 21 Jun 2023 20:35:59 +0100 Subject: [PATCH 129/216] fix(rpc): use max gas limit if no transaction gas is specified (#3260) --- crates/rpc/rpc/src/eth/revm_utils.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index 2313e19fd643..b0a66e6619ed 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -2,7 +2,8 @@ use crate::eth::error::{EthApiError, EthResult, RpcInvalidTransactionError}; use reth_primitives::{ - AccessList, Address, TransactionSigned, TransactionSignedEcRecovered, TxHash, H256, U256, + constants::ETHEREUM_BLOCK_GAS_LIMIT, AccessList, Address, TransactionSigned, + TransactionSignedEcRecovered, TxHash, H256, U256, }; use reth_revm::env::{fill_tx_env, fill_tx_env_with_recovered}; use reth_rpc_types::{ @@ -236,10 +237,21 @@ where apply_block_overrides(*block_overrides, &mut env.block); } - if request_gas.is_none() && env.tx.gas_price > U256::ZERO { - trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap"); - // no gas limit was provided in the request, so we need to cap the request's gas limit - cap_tx_gas_limit_with_caller_allowance(db, &mut env.tx)?; + if request_gas.is_none() { + // No gas limit was provided in the request, so we need to cap the transaction gas limit + if env.tx.gas_price > U256::ZERO { + // If gas price is specified, cap transaction gas limit with caller allowance + trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap with caller allowance"); + cap_tx_gas_limit_with_caller_allowance(db, &mut env.tx)?; + } else { + // If no gas price is specified, use maximum allowed gas limit. The reason for this is + // that both Erigon and Geth use pre-configured gas cap even if it's possible + // to derive the gas limit from the block: + // https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/cmd/rpcdaemon/commands/trace_adhoc.go#L956 + // https://github.com/ledgerwatch/erigon/blob/eae2d9a79cb70dbe30b3a6b79c436872e4605458/eth/ethconfig/config.go#L94 + trace!(target: "rpc::eth::call", ?env, "Applying gas limit cap as the maximum gas limit"); + env.tx.gas_limit = ETHEREUM_BLOCK_GAS_LIMIT; + } } Ok(env) From 44da381591fb9ec3d7f42efc11f95328cd66214a Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 21 Jun 2023 15:36:20 -0400 Subject: [PATCH 130/216] feat: add initial blockchain tree metrics (#3289) --- Cargo.lock | 1 + crates/blockchain-tree/Cargo.toml | 1 + crates/blockchain-tree/src/block_buffer.rs | 8 +++++ crates/blockchain-tree/src/blockchain_tree.rs | 10 +++++++ crates/blockchain-tree/src/lib.rs | 3 ++ crates/blockchain-tree/src/metrics.rs | 22 ++++++++++++++ crates/blockchain-tree/src/shareable.rs | 29 +++++++++++++++---- 7 files changed, 68 insertions(+), 6 deletions(-) create mode 100644 crates/blockchain-tree/src/metrics.rs diff --git a/Cargo.lock b/Cargo.lock index 8493f819ded4..9929ef06af38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5071,6 +5071,7 @@ dependencies = [ "parking_lot 0.12.1", "reth-db", "reth-interfaces", + "reth-metrics", "reth-primitives", "reth-provider", "tokio", diff --git a/crates/blockchain-tree/Cargo.toml b/crates/blockchain-tree/Cargo.toml index f955fde9ef3a..0a4bf9559037 100644 --- a/crates/blockchain-tree/Cargo.toml +++ b/crates/blockchain-tree/Cargo.toml @@ -18,6 +18,7 @@ normal = [ reth-primitives = { workspace = true } reth-interfaces = { workspace = true } reth-db = { path = "../storage/db" } +reth-metrics = { workspace = true, features = ["common"] } reth-provider = { workspace = true } # common diff --git a/crates/blockchain-tree/src/block_buffer.rs b/crates/blockchain-tree/src/block_buffer.rs index 5905a54a9799..a34427e0a75e 100644 --- a/crates/blockchain-tree/src/block_buffer.rs +++ b/crates/blockchain-tree/src/block_buffer.rs @@ -4,6 +4,8 @@ use std::{ collections::{btree_map::Entry, hash_map, BTreeMap, HashMap, HashSet}, num::NonZeroUsize, }; + +use crate::metrics::BlockBufferMetrics; /// Type that contains blocks by number and hash. pub type BufferedBlocks = BTreeMap>; @@ -35,6 +37,8 @@ pub struct BlockBuffer { /// /// Used as counter of amount of blocks inside buffer. pub(crate) lru: LruCache, + /// Various metrics for the block buffer. + pub(crate) metrics: BlockBufferMetrics, } impl BlockBuffer { @@ -45,6 +49,7 @@ impl BlockBuffer { parent_to_child: Default::default(), hash_to_num: Default::default(), lru: LruCache::new(NonZeroUsize::new(limit).unwrap()), + metrics: Default::default(), } } @@ -65,6 +70,7 @@ impl BlockBuffer { self.remove_from_parent(evicted_block.parent_hash, &evicted_num_hash); } } + self.metrics.blocks.set(self.len() as f64); } /// Removes the given block from the buffer and also all the children of the block. @@ -81,6 +87,7 @@ impl BlockBuffer { } taken.extend(self.remove_children(vec![parent]).into_iter()); + self.metrics.blocks.set(self.len() as f64); taken } @@ -105,6 +112,7 @@ impl BlockBuffer { } self.remove_children(remove_parent_children); + self.metrics.blocks.set(self.len() as f64); } /// Return reference to buffered blocks diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 7b013bf9bd29..efc7e82ce6d4 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -2,6 +2,7 @@ use crate::{ canonical_chain::CanonicalChain, chain::{BlockChainId, BlockKind}, + metrics::TreeMetrics, AppendableChain, BlockBuffer, BlockIndices, BlockchainTreeConfig, PostStateData, TreeExternals, }; use reth_db::{cursor::DbCursorRO, database::Database, tables, transaction::DbTx}; @@ -87,6 +88,8 @@ pub struct BlockchainTree { config: BlockchainTreeConfig, /// Broadcast channel for canon state changes notifications. canon_state_notification_sender: CanonStateNotificationSender, + /// Metrics for the blockchain tree. + metrics: TreeMetrics, } /// A container that wraps chains and block indices to allow searching for block hashes across all @@ -137,6 +140,7 @@ impl BlockchainTree ), config, canon_state_notification_sender, + metrics: Default::default(), }) } @@ -1058,6 +1062,12 @@ impl BlockchainTree Ok(Some(Chain::new(blocks_and_execution))) } } + + /// Update blockchain tree metrics + pub(crate) fn update_tree_metrics(&self) { + self.metrics.sidechains.set(self.chains.len() as f64); + self.metrics.canonical_chain_height.set(self.canonical_chain().tip().number as f64); + } } #[cfg(test)] diff --git a/crates/blockchain-tree/src/lib.rs b/crates/blockchain-tree/src/lib.rs index c4f06910f4c7..87bdcaeb2279 100644 --- a/crates/blockchain-tree/src/lib.rs +++ b/crates/blockchain-tree/src/lib.rs @@ -44,4 +44,7 @@ pub use post_state_data::{PostStateData, PostStateDataRef}; pub mod block_buffer; mod canonical_chain; +/// Common blockchain tree metrics. +pub mod metrics; + pub use block_buffer::BlockBuffer; diff --git a/crates/blockchain-tree/src/metrics.rs b/crates/blockchain-tree/src/metrics.rs new file mode 100644 index 000000000000..38610c5a69bd --- /dev/null +++ b/crates/blockchain-tree/src/metrics.rs @@ -0,0 +1,22 @@ +use reth_metrics::{ + metrics::{self, Gauge}, + Metrics, +}; + +/// Metrics for the entire blockchain tree +#[derive(Metrics)] +#[metrics(scope = "blockchain_tree")] +pub struct TreeMetrics { + /// Total number of sidechains (not including the canonical chain) + pub sidechains: Gauge, + /// The highest block number in the canonical chain + pub canonical_chain_height: Gauge, +} + +/// Metrics for the blockchain tree block buffer +#[derive(Metrics)] +#[metrics(scope = "blockchain_tree.block_buffer")] +pub struct BlockBufferMetrics { + /// Total blocks in the block buffer + pub blocks: Gauge, +} diff --git a/crates/blockchain-tree/src/shareable.rs b/crates/blockchain-tree/src/shareable.rs index 75ced6dc90f3..bd3eb3247797 100644 --- a/crates/blockchain-tree/src/shareable.rs +++ b/crates/blockchain-tree/src/shareable.rs @@ -41,7 +41,10 @@ impl BlockchainTreeEngine for ShareableBlockchainTree { fn buffer_block(&self, block: SealedBlockWithSenders) -> Result<(), InsertBlockError> { - self.tree.write().buffer_block(block) + let mut tree = self.tree.write(); + let res = tree.buffer_block(block); + tree.update_tree_metrics(); + res } fn insert_block( @@ -49,27 +52,41 @@ impl BlockchainTreeEngine block: SealedBlockWithSenders, ) -> Result { trace!(target: "blockchain_tree", hash=?block.hash, number=block.number, parent_hash=?block.parent_hash, "Inserting block"); - self.tree.write().insert_block(block) + let mut tree = self.tree.write(); + let res = tree.insert_block(block); + tree.update_tree_metrics(); + res } fn finalize_block(&self, finalized_block: BlockNumber) { trace!(target: "blockchain_tree", ?finalized_block, "Finalizing block"); - self.tree.write().finalize_block(finalized_block) + let mut tree = self.tree.write(); + tree.finalize_block(finalized_block); + tree.update_tree_metrics(); } fn restore_canonical_hashes(&self, last_finalized_block: BlockNumber) -> Result<(), Error> { trace!(target: "blockchain_tree", ?last_finalized_block, "Restoring canonical hashes for last finalized block"); - self.tree.write().restore_canonical_hashes(last_finalized_block) + let mut tree = self.tree.write(); + let res = tree.restore_canonical_hashes(last_finalized_block); + tree.update_tree_metrics(); + res } fn make_canonical(&self, block_hash: &BlockHash) -> Result { trace!(target: "blockchain_tree", ?block_hash, "Making block canonical"); - self.tree.write().make_canonical(block_hash) + let mut tree = self.tree.write(); + let res = tree.make_canonical(block_hash); + tree.update_tree_metrics(); + res } fn unwind(&self, unwind_to: BlockNumber) -> Result<(), Error> { trace!(target: "blockchain_tree", ?unwind_to, "Unwinding to block number"); - self.tree.write().unwind(unwind_to) + let mut tree = self.tree.write(); + let res = tree.unwind(unwind_to); + tree.update_tree_metrics(); + res } } From a4a15ab81947365e2bfc95661435ebc14c2fb8c1 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 21 Jun 2023 21:16:48 +0100 Subject: [PATCH 131/216] refactor(rpc): gas allowance calculation (#3304) --- crates/rpc/rpc/src/eth/api/call.rs | 19 +++--------- crates/rpc/rpc/src/eth/revm_utils.rs | 44 ++++++++++++++++++---------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index 63d8d1d3d6ec..f7039b3ea9ca 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -4,8 +4,8 @@ use crate::{ eth::{ error::{ensure_success, EthApiError, EthResult, RevertError, RpcInvalidTransactionError}, revm_utils::{ - build_call_evm_env, cap_tx_gas_limit_with_caller_allowance, get_precompiles, inspect, - transact, EvmOverrides, + build_call_evm_env, caller_gas_allowance, cap_tx_gas_limit_with_caller_allowance, + get_precompiles, inspect, transact, EvmOverrides, }, EthTransactions, }, @@ -122,19 +122,8 @@ where } // check funds of the sender - let gas_price = env.tx.gas_price; - if gas_price > U256::ZERO { - let mut available_funds = - db.basic(env.tx.caller)?.map(|acc| acc.balance).unwrap_or_default(); - if env.tx.value > available_funds { - return Err(RpcInvalidTransactionError::InsufficientFunds.into()) - } - // subtract transferred value from available funds - // SAFETY: value < available_funds, checked above - available_funds -= env.tx.value; - // amount of gas the sender can afford with the `gas_price` - // SAFETY: gas_price not zero - let allowance = available_funds.checked_div(gas_price).unwrap_or_default(); + if env.tx.gas_price > U256::ZERO { + let allowance = caller_gas_allowance(&mut db, &env.tx)?; if highest_gas_limit > allowance { // cap the highest gas limit by max gas caller can afford with given gas price diff --git a/crates/rpc/rpc/src/eth/revm_utils.rs b/crates/rpc/rpc/src/eth/revm_utils.rs index b0a66e6619ed..54e996870af4 100644 --- a/crates/rpc/rpc/src/eth/revm_utils.rs +++ b/crates/rpc/rpc/src/eth/revm_utils.rs @@ -317,31 +317,45 @@ pub(crate) fn create_txn_env(block_env: &BlockEnv, request: CallRequest) -> EthR } /// Caps the configured [TxEnv] `gas_limit` with the allowance of the caller. -/// -/// Returns an error if the caller has insufficient funds -pub(crate) fn cap_tx_gas_limit_with_caller_allowance( - mut db: DB, - env: &mut TxEnv, -) -> EthResult<()> +pub(crate) fn cap_tx_gas_limit_with_caller_allowance(db: DB, env: &mut TxEnv) -> EthResult<()> where DB: Database, EthApiError: From<::Error>, { - let mut allowance = db.basic(env.caller)?.map(|acc| acc.balance).unwrap_or_default(); - - // subtract transferred value - allowance = allowance - .checked_sub(env.value) - .ok_or_else(|| RpcInvalidTransactionError::InsufficientFunds)?; - - // cap the gas limit - if let Ok(gas_limit) = allowance.checked_div(env.gas_price).unwrap_or_default().try_into() { + if let Ok(gas_limit) = caller_gas_allowance(db, env)?.try_into() { env.gas_limit = gas_limit; } Ok(()) } +/// Calculates the caller gas allowance. +/// +/// `allowance = (account.balance - tx.value) / tx.gas_price` +/// +/// Returns an error if the caller has insufficient funds. +/// Caution: This assumes non-zero `env.gas_price`. Otherwise, zero allowance will be returned. +pub(crate) fn caller_gas_allowance(mut db: DB, env: &TxEnv) -> EthResult +where + DB: Database, + EthApiError: From<::Error>, +{ + Ok(db + // Get the caller account. + .basic(env.caller)? + // Get the caller balance. + .map(|acc| acc.balance) + .unwrap_or_default() + // Subtract transferred value from the caller balance. + .checked_sub(env.value) + // Return error if the caller has insufficient funds. + .ok_or_else(|| RpcInvalidTransactionError::InsufficientFunds)? + // Calculate the amount of gas the caller can afford with the specified gas price. + .checked_div(env.gas_price) + // This will be 0 if gas price is 0. It is fine, because we check it before. + .unwrap_or_default()) +} + /// Helper type for representing the fees of a [CallRequest] pub(crate) struct CallFees { /// EIP-1559 priority fee From 938b979703ce26204da25cc3d61b5012e5cc255a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 21 Jun 2023 22:18:33 +0200 Subject: [PATCH 132/216] chore: bump engine server size limits (#3308) --- crates/rpc/rpc-builder/src/auth.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index eb83578ee609..edf398117e85 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -208,8 +208,13 @@ impl AuthServerConfigBuilder { secret: self.secret, server_config: self.server_config.unwrap_or_else(|| { ServerBuilder::new() - // allows for 300mb responses (for large eth_getLogs deposit logs) - .max_response_body_size(300 * 1024 * 1024) + // This needs to large enough to handle large eth_getLogs responses and maximum + // payload bodies limit for `engine_getPayloadBodiesByRangeV` + // ~750MB per response should be enough + .max_response_body_size(750 * 1024 * 1024) + // bump the default request size slightly, there aren't any methods exposed with + // dynamic request params that can exceed this + .max_request_body_size(25 * 1024 * 1024) .set_id_provider(EthSubscriptionIdProvider::default()) }), } From dc74fad816c74de008d4c368eb55918912ab66aa Mon Sep 17 00:00:00 2001 From: Bjerg Date: Wed, 21 Jun 2023 23:35:28 +0200 Subject: [PATCH 133/216] test: add support for seeded rng (#3270) --- .github/workflows/integration.yml | 1 + .github/workflows/unit.yml | 1 + README.md | 11 ++ crates/blockchain-tree/src/block_buffer.rs | 96 +++++++++------ crates/consensus/beacon/src/engine/mod.rs | 55 +++++---- crates/interfaces/Cargo.toml | 2 +- .../interfaces/src/test_utils/generators.rs | 114 +++++++++++------- crates/net/downloaders/src/bodies/bodies.rs | 5 +- crates/net/downloaders/src/bodies/request.rs | 5 +- crates/net/downloaders/src/test_utils/mod.rs | 6 +- crates/rpc/rpc-engine-api/src/engine_api.rs | 22 +++- .../rpc/rpc-types/src/eth/engine/payload.rs | 10 +- crates/stages/benches/setup/mod.rs | 28 +++-- crates/stages/src/pipeline/mod.rs | 6 +- crates/stages/src/stages/bodies.rs | 6 +- crates/stages/src/stages/finish.rs | 10 +- crates/stages/src/stages/hashing_account.rs | 11 +- crates/stages/src/stages/hashing_storage.rs | 24 ++-- crates/stages/src/stages/headers.rs | 20 +-- crates/stages/src/stages/merkle.rs | 16 ++- crates/stages/src/stages/sender_recovery.rs | 22 +++- crates/stages/src/stages/total_difficulty.rs | 6 +- crates/stages/src/stages/tx_lookup.rs | 21 +++- 23 files changed, 323 insertions(+), 175 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 992c447462ec..fcff216a8462 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -9,6 +9,7 @@ env: RUSTFLAGS: -D warnings CARGO_TERM_COLOR: always GETH_BUILD: 1.12.0-e501b3b0 + SEED: rustethereumethereumrust concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 2e495074ac3e..1204ffa5b742 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -8,6 +8,7 @@ on: env: RUSTFLAGS: -D warnings CARGO_TERM_COLOR: always + SEED: rustethereumethereumrust concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/README.md b/README.md index f15395a82dc8..dcb0ea323626 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,21 @@ cargo test --workspace # With Geth cargo test --workspace --features geth-tests + +# With Ethereum Foundation tests +# +# Note: Requires cloning https://github.com/ethereum/tests +# +# cd testing/ef-tests && git clone https://github.com/ethereum/tests ethereum-tests +cargo test --workspace --features ef-tests ``` We recommend using [`cargo nextest`](https://nexte.st/) to speed up testing. With nextest installed, simply substitute `cargo test` with `cargo nextest run`. +> **Note** +> +> Some tests use random number generators to generate test data. If you want to use a deterministic seed, you can set the `SEED` environment variable. + ## Getting Help If you have any questions, first see if the answer to your question can be found in the [book][book]. diff --git a/crates/blockchain-tree/src/block_buffer.rs b/crates/blockchain-tree/src/block_buffer.rs index a34427e0a75e..9ef9870d960d 100644 --- a/crates/blockchain-tree/src/block_buffer.rs +++ b/crates/blockchain-tree/src/block_buffer.rs @@ -215,21 +215,23 @@ impl BlockBuffer { #[cfg(test)] mod tests { + use reth_interfaces::test_utils::generators; use std::collections::HashMap; - use reth_interfaces::test_utils::generators::random_block; + use reth_interfaces::test_utils::generators::{random_block, Rng}; use reth_primitives::{BlockHash, BlockNumHash, SealedBlockWithSenders}; use crate::BlockBuffer; - fn create_block(number: u64, parent: BlockHash) -> SealedBlockWithSenders { - let block = random_block(number, Some(parent), None, None); + fn create_block(rng: &mut R, number: u64, parent: BlockHash) -> SealedBlockWithSenders { + let block = random_block(rng, number, Some(parent), None, None); block.seal_with_senders().unwrap() } #[test] fn simple_insertion() { - let block1 = create_block(10, BlockHash::random()); + let mut rng = generators::rng(); + let block1 = create_block(&mut rng, 10, BlockHash::random()); let mut buffer = BlockBuffer::new(3); buffer.insert_block(block1.clone()); @@ -240,11 +242,13 @@ mod tests { #[test] fn take_all_chain_of_childrens() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block3 = create_block(12, block2.hash); - let block4 = create_block(14, BlockHash::random()); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block3 = create_block(&mut rng, 12, block2.hash); + let block4 = create_block(&mut rng, 14, BlockHash::random()); let mut buffer = BlockBuffer::new(5); @@ -267,11 +271,13 @@ mod tests { #[test] fn take_all_multi_level_childrens() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block3 = create_block(11, block1.hash); - let block4 = create_block(12, block2.hash); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block3 = create_block(&mut rng, 11, block1.hash); + let block4 = create_block(&mut rng, 12, block2.hash); let mut buffer = BlockBuffer::new(5); @@ -299,11 +305,13 @@ mod tests { #[test] fn take_self_with_childs() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block3 = create_block(11, block1.hash); - let block4 = create_block(12, block2.hash); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block3 = create_block(&mut rng, 11, block1.hash); + let block4 = create_block(&mut rng, 12, block2.hash); let mut buffer = BlockBuffer::new(5); @@ -331,11 +339,13 @@ mod tests { #[test] fn clean_chain_of_children() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block3 = create_block(12, block2.hash); - let block4 = create_block(14, BlockHash::random()); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block3 = create_block(&mut rng, 12, block2.hash); + let block4 = create_block(&mut rng, 14, BlockHash::random()); let mut buffer = BlockBuffer::new(5); @@ -351,11 +361,13 @@ mod tests { #[test] fn clean_all_multi_level_childrens() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block3 = create_block(11, block1.hash); - let block4 = create_block(12, block2.hash); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block3 = create_block(&mut rng, 11, block1.hash); + let block4 = create_block(&mut rng, 12, block2.hash); let mut buffer = BlockBuffer::new(5); @@ -371,14 +383,16 @@ mod tests { #[test] fn clean_multi_chains() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block1a = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block2a = create_block(11, block1.hash); - let random_block1 = create_block(10, BlockHash::random()); - let random_block2 = create_block(11, BlockHash::random()); - let random_block3 = create_block(12, BlockHash::random()); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block1a = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block2a = create_block(&mut rng, 11, block1.hash); + let random_block1 = create_block(&mut rng, 10, BlockHash::random()); + let random_block2 = create_block(&mut rng, 11, BlockHash::random()); + let random_block3 = create_block(&mut rng, 12, BlockHash::random()); let mut buffer = BlockBuffer::new(10); @@ -420,11 +434,13 @@ mod tests { #[test] fn evict_with_gap() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block3 = create_block(12, block2.hash); - let block4 = create_block(13, BlockHash::random()); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block3 = create_block(&mut rng, 12, block2.hash); + let block4 = create_block(&mut rng, 13, BlockHash::random()); let mut buffer = BlockBuffer::new(3); @@ -454,11 +470,13 @@ mod tests { #[test] fn simple_eviction() { + let mut rng = generators::rng(); + let main_parent = BlockNumHash::new(9, BlockHash::random()); - let block1 = create_block(10, main_parent.hash); - let block2 = create_block(11, block1.hash); - let block3 = create_block(12, block2.hash); - let block4 = create_block(13, BlockHash::random()); + let block1 = create_block(&mut rng, 10, main_parent.hash); + let block2 = create_block(&mut rng, 11, block1.hash); + let block3 = create_block(&mut rng, 12, block2.hash); + let block4 = create_block(&mut rng, 13, BlockHash::random()); let mut buffer = BlockBuffer::new(3); diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index cb3bac489e96..3516572cc84f 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1751,7 +1751,7 @@ mod tests { mod fork_choice_updated { use super::*; use reth_db::{tables, transaction::DbTxMut}; - use reth_interfaces::test_utils::generators::random_block; + use reth_interfaces::test_utils::{generators, generators::random_block}; use reth_rpc_types::engine::ForkchoiceUpdateError; #[tokio::test] @@ -1786,6 +1786,7 @@ mod tests { #[tokio::test] async fn valid_forkchoice() { + let mut rng = generators::rng(); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -1801,8 +1802,8 @@ mod tests { })])) .build(); - let genesis = random_block(0, None, None, Some(0)); - let block1 = random_block(1, Some(genesis.hash), None, Some(0)); + let genesis = random_block(&mut rng, 0, None, None, Some(0)); + let block1 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); env.db .update(|tx| { @@ -1833,6 +1834,8 @@ mod tests { #[tokio::test] async fn unknown_head_hash() { + let mut rng = generators::rng(); + let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -1849,13 +1852,13 @@ mod tests { .disable_blockchain_tree_sync() .build(); - let genesis = random_block(0, None, None, Some(0)); - let block1 = random_block(1, Some(genesis.hash), None, Some(0)); + let genesis = random_block(&mut rng, 0, None, None, Some(0)); + let block1 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); let mut engine_rx = spawn_consensus_engine(consensus_engine); - let next_head = random_block(2, Some(block1.hash), None, Some(0)); + let next_head = random_block(&mut rng, 2, Some(block1.hash), None, Some(0)); let next_forkchoice_state = ForkchoiceState { head_block_hash: next_head.hash, finalized_block_hash: block1.hash, @@ -1882,6 +1885,7 @@ mod tests { #[tokio::test] async fn unknown_finalized_hash() { + let mut rng = generators::rng(); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -1898,8 +1902,8 @@ mod tests { .disable_blockchain_tree_sync() .build(); - let genesis = random_block(0, None, None, Some(0)); - let block1 = random_block(1, Some(genesis.hash), None, Some(0)); + let genesis = random_block(&mut rng, 0, None, None, Some(0)); + let block1 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); let engine = spawn_consensus_engine(consensus_engine); @@ -1918,6 +1922,7 @@ mod tests { #[tokio::test] async fn forkchoice_updated_pre_merge() { + let mut rng = generators::rng(); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -1934,16 +1939,16 @@ mod tests { ])) .build(); - let genesis = random_block(0, None, None, Some(0)); - let mut block1 = random_block(1, Some(genesis.hash), None, Some(0)); + let genesis = random_block(&mut rng, 0, None, None, Some(0)); + let mut block1 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); block1.header.difficulty = U256::from(1); // a second pre-merge block - let mut block2 = random_block(1, Some(genesis.hash), None, Some(0)); + let mut block2 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); block2.header.difficulty = U256::from(1); // a transition block - let mut block3 = random_block(1, Some(genesis.hash), None, Some(0)); + let mut block3 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); block3.header.difficulty = U256::from(1); insert_blocks( @@ -1971,6 +1976,7 @@ mod tests { #[tokio::test] async fn forkchoice_updated_invalid_pow() { + let mut rng = generators::rng(); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -1986,8 +1992,8 @@ mod tests { ])) .build(); - let genesis = random_block(0, None, None, Some(0)); - let block1 = random_block(1, Some(genesis.hash), None, Some(0)); + let genesis = random_block(&mut rng, 0, None, None, Some(0)); + let block1 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis, &block1].into_iter()); @@ -2011,12 +2017,13 @@ mod tests { mod new_payload { use super::*; - use reth_interfaces::test_utils::generators::random_block; + use reth_interfaces::test_utils::{generators, generators::random_block}; use reth_primitives::{Hardfork, U256}; use reth_provider::test_utils::blocks::BlockChainTestData; #[tokio::test] async fn new_payload_before_forkchoice() { + let mut rng = generators::rng(); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -2035,12 +2042,14 @@ mod tests { let mut engine_rx = spawn_consensus_engine(consensus_engine); // Send new payload - let res = env.send_new_payload(random_block(0, None, None, Some(0)).into()).await; + let res = + env.send_new_payload(random_block(&mut rng, 0, None, None, Some(0)).into()).await; // Invalid, because this is a genesis block assert_matches!(res, Ok(result) => assert_matches!(result.status, PayloadStatusEnum::Invalid { .. })); // Send new payload - let res = env.send_new_payload(random_block(1, None, None, Some(0)).into()).await; + let res = + env.send_new_payload(random_block(&mut rng, 1, None, None, Some(0)).into()).await; let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing); assert_matches!(res, Ok(result) => assert_eq!(result, expected_result)); @@ -2049,6 +2058,7 @@ mod tests { #[tokio::test] async fn payload_known() { + let mut rng = generators::rng(); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -2064,9 +2074,9 @@ mod tests { })])) .build(); - let genesis = random_block(0, None, None, Some(0)); - let block1 = random_block(1, Some(genesis.hash), None, Some(0)); - let block2 = random_block(2, Some(block1.hash), None, Some(0)); + let genesis = random_block(&mut rng, 0, None, None, Some(0)); + let block1 = random_block(&mut rng, 1, Some(genesis.hash), None, Some(0)); + let block2 = random_block(&mut rng, 2, Some(block1.hash), None, Some(0)); insert_blocks( env.db.as_ref(), chain_spec.clone(), @@ -2098,6 +2108,7 @@ mod tests { #[tokio::test] async fn payload_parent_unknown() { + let mut rng = generators::rng(); let chain_spec = Arc::new( ChainSpecBuilder::default() .chain(MAINNET.chain) @@ -2113,7 +2124,7 @@ mod tests { })])) .build(); - let genesis = random_block(0, None, None, Some(0)); + let genesis = random_block(&mut rng, 0, None, None, Some(0)); insert_blocks(env.db.as_ref(), chain_spec.clone(), [&genesis].into_iter()); @@ -2132,7 +2143,7 @@ mod tests { assert_matches!(res, Ok(ForkchoiceUpdated { payload_status, .. }) => assert_eq!(payload_status, expected_result)); // Send new payload - let block = random_block(2, Some(H256::random()), None, Some(0)); + let block = random_block(&mut rng, 2, Some(H256::random()), None, Some(0)); let res = env.send_new_payload(block.into()).await; let expected_result = PayloadStatus::from_status(PayloadStatusEnum::Syncing); assert_matches!(res, Ok(result) => assert_eq!(result, expected_result)); diff --git a/crates/interfaces/Cargo.toml b/crates/interfaces/Cargo.toml index 3fbd20672caf..d358cdef0074 100644 --- a/crates/interfaces/Cargo.toml +++ b/crates/interfaces/Cargo.toml @@ -52,4 +52,4 @@ secp256k1 = { workspace = true, features = [ ] } [features] -test-utils = ["tokio-stream/sync", "secp256k1"] +test-utils = ["tokio-stream/sync", "secp256k1", "rand/std_rng"] diff --git a/crates/interfaces/src/test_utils/generators.rs b/crates/interfaces/src/test_utils/generators.rs index 53733e21d751..137996046e61 100644 --- a/crates/interfaces/src/test_utils/generators.rs +++ b/crates/interfaces/src/test_utils/generators.rs @@ -1,4 +1,7 @@ -use rand::{distributions::uniform::SampleRange, seq::SliceRandom, thread_rng, Rng}; +pub use rand::Rng; +use rand::{ + distributions::uniform::SampleRange, rngs::StdRng, seq::SliceRandom, thread_rng, SeedableRng, +}; use reth_primitives::{ proofs, sign_message, Account, Address, BlockNumber, Bytes, Header, SealedBlock, SealedHeader, Signature, StorageEntry, Transaction, TransactionKind, TransactionSigned, TxLegacy, H160, H256, @@ -7,23 +10,42 @@ use reth_primitives::{ use secp256k1::{KeyPair, Message as SecpMessage, Secp256k1, SecretKey, SECP256K1}; use std::{ cmp::{max, min}, - collections::BTreeMap, + collections::{hash_map::DefaultHasher, BTreeMap}, + hash::Hasher, ops::{Range, RangeInclusive, Sub}, }; // TODO(onbjerg): Maybe we should split this off to its own crate, or move the helpers to the // relevant crates? +/// Returns a random number generator that can be seeded using the `SEED` environment variable. +/// +/// If `SEED` is not set, a random seed is used. +pub fn rng() -> StdRng { + if let Ok(seed) = std::env::var("SEED") { + let mut hasher = DefaultHasher::new(); + hasher.write(seed.as_bytes()); + StdRng::seed_from_u64(hasher.finish()) + } else { + StdRng::from_rng(thread_rng()).expect("could not build rng") + } +} + /// Generates a range of random [SealedHeader]s. /// /// The parent hash of the first header /// in the result will be equal to `head`. /// /// The headers are assumed to not be correct if validated. -pub fn random_header_range(rng: std::ops::Range, head: H256) -> Vec { - let mut headers = Vec::with_capacity(rng.end.saturating_sub(rng.start) as usize); - for idx in rng { +pub fn random_header_range( + rng: &mut R, + range: std::ops::Range, + head: H256, +) -> Vec { + let mut headers = Vec::with_capacity(range.end.saturating_sub(range.start) as usize); + for idx in range { headers.push(random_header( + rng, idx, Some(headers.last().map(|h: &SealedHeader| h.hash()).unwrap_or(head)), )); @@ -34,11 +56,11 @@ pub fn random_header_range(rng: std::ops::Range, head: H256) -> Vec) -> SealedHeader { +pub fn random_header(rng: &mut R, number: u64, parent: Option) -> SealedHeader { let header = reth_primitives::Header { number, - nonce: rand::random(), - difficulty: U256::from(rand::random::()), + nonce: rng.gen(), + difficulty: U256::from(rng.gen::()), parent_hash: parent.unwrap_or_default(), ..Default::default() }; @@ -51,14 +73,14 @@ pub fn random_header(number: u64, parent: Option) -> SealedHeader { /// /// - The chain ID, which is always 1 /// - The input, which is always nothing -pub fn random_tx() -> Transaction { +pub fn random_tx(rng: &mut R) -> Transaction { Transaction::Legacy(TxLegacy { chain_id: Some(1), - nonce: rand::random::().into(), - gas_price: rand::random::().into(), - gas_limit: rand::random::().into(), + nonce: rng.gen::().into(), + gas_price: rng.gen::().into(), + gas_limit: rng.gen::().into(), to: TransactionKind::Call(Address::random()), - value: rand::random::().into(), + value: rng.gen::().into(), input: Bytes::default(), }) } @@ -68,10 +90,10 @@ pub fn random_tx() -> Transaction { /// On top of the considerations of [random_tx], these apply as well: /// /// - There is no guarantee that the nonce is not used twice for the same account -pub fn random_signed_tx() -> TransactionSigned { +pub fn random_signed_tx(rng: &mut R) -> TransactionSigned { let secp = Secp256k1::new(); - let key_pair = KeyPair::new(&secp, &mut rand::thread_rng()); - let tx = random_tx(); + let key_pair = KeyPair::new(&secp, rng); + let tx = random_tx(rng); sign_tx_with_key_pair(key_pair, tx) } @@ -96,23 +118,23 @@ pub fn sign_tx_with_key_pair(key_pair: KeyPair, tx: Transaction) -> TransactionS /// transactions in the block. /// /// The ommer headers are not assumed to be valid. -pub fn random_block( +pub fn random_block( + rng: &mut R, number: u64, parent: Option, tx_count: Option, ommers_count: Option, ) -> SealedBlock { - let mut rng = thread_rng(); - // Generate transactions let tx_count = tx_count.unwrap_or_else(|| rng.gen::()); - let transactions: Vec = (0..tx_count).map(|_| random_signed_tx()).collect(); + let transactions: Vec = + (0..tx_count).map(|_| random_signed_tx(rng)).collect(); let total_gas = transactions.iter().fold(0, |sum, tx| sum + tx.transaction.gas_limit()); // Generate ommers let ommers_count = ommers_count.unwrap_or_else(|| rng.gen_range(0..2)); let ommers = - (0..ommers_count).map(|_| random_header(number, parent).unseal()).collect::>(); + (0..ommers_count).map(|_| random_header(rng, number, parent).unseal()).collect::>(); // Calculate roots let transactions_root = proofs::calculate_transaction_root(&transactions); @@ -142,19 +164,21 @@ pub fn random_block( /// in the result will be equal to `head`. /// /// See [random_block] for considerations when validating the generated blocks. -pub fn random_block_range( +pub fn random_block_range( + rng: &mut R, block_numbers: RangeInclusive, head: H256, tx_count: Range, ) -> Vec { - let mut rng = rand::thread_rng(); let mut blocks = Vec::with_capacity(block_numbers.end().saturating_sub(*block_numbers.start()) as usize); for idx in block_numbers { + let tx_count = tx_count.clone().sample_single(rng); blocks.push(random_block( + rng, idx, Some(blocks.last().map(|block: &SealedBlock| block.header.hash()).unwrap_or(head)), - Some(tx_count.clone().sample_single(&mut rng)), + Some(tx_count), None, )); } @@ -169,7 +193,8 @@ type AccountState = (Account, Vec); /// /// Returns a Vec of account and storage changes for each transition, /// along with the final state of all accounts and storages. -pub fn random_transition_range<'a, IBlk, IAcc>( +pub fn random_transition_range<'a, R: Rng, IBlk, IAcc>( + rng: &mut R, blocks: IBlk, accounts: IAcc, n_changes: std::ops::Range, @@ -179,7 +204,6 @@ where IBlk: IntoIterator, IAcc: IntoIterator))>, { - let mut rng = rand::thread_rng(); let mut state: BTreeMap<_, _> = accounts .into_iter() .map(|(addr, (acc, st))| (addr, (acc, st.into_iter().map(|e| (e.key, e.value)).collect()))) @@ -192,7 +216,7 @@ where blocks.into_iter().for_each(|block| { let mut transition = Vec::new(); let (from, to, mut transfer, new_entries) = - random_account_change(&valid_addresses, n_changes.clone(), key_range.clone()); + random_account_change(rng, &valid_addresses, n_changes.clone(), key_range.clone()); // extract from sending account let (prev_from, _) = state.get_mut(&from).unwrap(); @@ -239,61 +263,63 @@ where /// Generate a random account change. /// /// Returns two addresses, a balance_change, and a Vec of new storage entries. -pub fn random_account_change( +pub fn random_account_change( + rng: &mut R, valid_addresses: &Vec

, n_changes: std::ops::Range, key_range: std::ops::Range, ) -> (Address, Address, U256, Vec) { - let mut rng = rand::thread_rng(); - let mut addresses = valid_addresses.choose_multiple(&mut rng, 2).cloned(); + let mut addresses = valid_addresses.choose_multiple(rng, 2).cloned(); let addr_from = addresses.next().unwrap_or_else(Address::random); let addr_to = addresses.next().unwrap_or_else(Address::random); let balance_change = U256::from(rng.gen::()); - let storage_changes = (0..n_changes.sample_single(&mut rng)) - .map(|_| random_storage_entry(key_range.clone())) + let storage_changes = (0..n_changes.sample_single(rng)) + .map(|_| random_storage_entry(rng, key_range.clone())) .collect(); (addr_from, addr_to, balance_change, storage_changes) } /// Generate a random storage change. -pub fn random_storage_entry(key_range: std::ops::Range) -> StorageEntry { - let mut rng = rand::thread_rng(); - - let key = H256::from_low_u64_be(key_range.sample_single(&mut rng)); +pub fn random_storage_entry(rng: &mut R, key_range: std::ops::Range) -> StorageEntry { + let key = H256::from_low_u64_be(key_range.sample_single(rng)); let value = U256::from(rng.gen::()); StorageEntry { key, value } } /// Generate random Externally Owned Account (EOA account without contract). -pub fn random_eoa_account() -> (Address, Account) { - let nonce: u64 = rand::random(); - let balance = U256::from(rand::random::()); - let addr = H160::from(rand::random::()); +pub fn random_eoa_account(rng: &mut R) -> (Address, Account) { + let nonce: u64 = rng.gen(); + let balance = U256::from(rng.gen::()); + let addr = H160::from(rng.gen::()); (addr, Account { nonce, balance, bytecode_hash: None }) } /// Generate random Externally Owned Accounts -pub fn random_eoa_account_range(acc_range: std::ops::Range) -> Vec<(Address, Account)> { +pub fn random_eoa_account_range( + rng: &mut R, + acc_range: std::ops::Range, +) -> Vec<(Address, Account)> { let mut accounts = Vec::with_capacity(acc_range.end.saturating_sub(acc_range.start) as usize); for _ in acc_range { - accounts.push(random_eoa_account()) + accounts.push(random_eoa_account(rng)) } accounts } /// Generate random Contract Accounts -pub fn random_contract_account_range( +pub fn random_contract_account_range( + rng: &mut R, acc_range: &mut std::ops::Range, ) -> Vec<(Address, Account)> { let mut accounts = Vec::with_capacity(acc_range.end.saturating_sub(acc_range.start) as usize); for _ in acc_range { - let (address, eoa_account) = random_eoa_account(); + let (address, eoa_account) = random_eoa_account(rng); let account = Account { bytecode_hash: Some(H256::random()), ..eoa_account }; accounts.push((address, account)) } diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index acc61f65b0ca..e613f4977e84 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -595,7 +595,7 @@ mod tests { use assert_matches::assert_matches; use futures_util::stream::StreamExt; use reth_db::mdbx::{test_utils::create_test_db, EnvKind, WriteMap}; - use reth_interfaces::test_utils::{generators::random_block_range, TestConsensus}; + use reth_interfaces::test_utils::{generators, generators::random_block_range, TestConsensus}; use reth_primitives::{BlockBody, H256}; use std::{collections::HashMap, sync::Arc}; @@ -632,7 +632,8 @@ mod tests { async fn requests_correct_number_of_times() { // Generate some random blocks let db = create_test_db::(EnvKind::RW); - let blocks = random_block_range(0..=199, H256::zero(), 1..2); + let mut rng = generators::rng(); + let blocks = random_block_range(&mut rng, 0..=199, H256::zero(), 1..2); let headers = blocks.iter().map(|block| block.header.clone()).collect::>(); let bodies = blocks diff --git a/crates/net/downloaders/src/bodies/request.rs b/crates/net/downloaders/src/bodies/request.rs index e73836a4372b..a82b216cc32c 100644 --- a/crates/net/downloaders/src/bodies/request.rs +++ b/crates/net/downloaders/src/bodies/request.rs @@ -239,7 +239,7 @@ mod tests { }; use reth_interfaces::{ p2p::bodies::response::BlockResponse, - test_utils::{generators::random_header_range, TestConsensus}, + test_utils::{generators, generators::random_header_range, TestConsensus}, }; use reth_primitives::H256; use std::sync::Arc; @@ -247,7 +247,8 @@ mod tests { /// Check if future returns empty bodies without dispathing any requests. #[tokio::test] async fn request_returns_empty_bodies() { - let headers = random_header_range(0..20, H256::zero()); + let mut rng = generators::rng(); + let headers = random_header_range(&mut rng, 0..20, H256::zero()); let client = Arc::new(TestBodiesClient::default()); let fut = BodiesRequestFuture::new( diff --git a/crates/net/downloaders/src/test_utils/mod.rs b/crates/net/downloaders/src/test_utils/mod.rs index ad363ebef863..98c850ef1af0 100644 --- a/crates/net/downloaders/src/test_utils/mod.rs +++ b/crates/net/downloaders/src/test_utils/mod.rs @@ -18,15 +18,17 @@ mod file_codec; pub use bodies_client::TestBodiesClient; pub use file_client::{FileClient, FileClientError}; pub(crate) use file_codec::BlockFileCodec; +use reth_interfaces::test_utils::generators; /// Metrics scope used for testing. pub(crate) const TEST_SCOPE: &str = "downloaders.test"; /// Generate a set of bodies and their corresponding block hashes pub(crate) fn generate_bodies( - rng: RangeInclusive, + range: RangeInclusive, ) -> (Vec, HashMap) { - let blocks = random_block_range(rng, H256::zero(), 0..2); + let mut rng = generators::rng(); + let blocks = random_block_range(&mut rng, range, H256::zero(), 0..2); let headers = blocks.iter().map(|block| block.header.clone()).collect(); let bodies = blocks diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 20a085f0aa0b..159f2a250dc2 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -476,7 +476,7 @@ mod tests { // tests covering `engine_getPayloadBodiesByRange` and `engine_getPayloadBodiesByHash` mod get_payload_bodies { use super::*; - use reth_interfaces::test_utils::generators::random_block_range; + use reth_interfaces::test_utils::{generators, generators::random_block_range}; #[tokio::test] async fn invalid_params() { @@ -507,10 +507,12 @@ mod tests { #[tokio::test] async fn returns_payload_bodies() { + let mut rng = generators::rng(); let (handle, api) = setup_engine_api(); let (start, count) = (1, 10); - let blocks = random_block_range(start..=start + count - 1, H256::default(), 0..2); + let blocks = + random_block_range(&mut rng, start..=start + count - 1, H256::default(), 0..2); handle.provider.extend_blocks(blocks.iter().cloned().map(|b| (b.hash(), b.unseal()))); let expected = @@ -522,10 +524,12 @@ mod tests { #[tokio::test] async fn returns_payload_bodies_with_gaps() { + let mut rng = generators::rng(); let (handle, api) = setup_engine_api(); let (start, count) = (1, 100); - let blocks = random_block_range(start..=start + count - 1, H256::default(), 0..2); + let blocks = + random_block_range(&mut rng, start..=start + count - 1, H256::default(), 0..2); // Insert only blocks in ranges 1-25 and 50-75 let first_missing_range = 26..=50; @@ -566,6 +570,7 @@ mod tests { // https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#specification-3 mod exchange_transition_configuration { use super::*; + use reth_interfaces::test_utils::generators; use reth_primitives::U256; #[tokio::test] @@ -589,11 +594,15 @@ mod tests { #[tokio::test] async fn terminal_block_hash_mismatch() { + let mut rng = generators::rng(); + let (handle, api) = setup_engine_api(); let terminal_block_number = 1000; - let consensus_terminal_block = random_block(terminal_block_number, None, None, None); - let execution_terminal_block = random_block(terminal_block_number, None, None, None); + let consensus_terminal_block = + random_block(&mut rng, terminal_block_number, None, None, None); + let execution_terminal_block = + random_block(&mut rng, terminal_block_number, None, None, None); let transition_config = TransitionConfiguration { terminal_total_difficulty: handle.chain_spec.fork(Hardfork::Paris).ttd().unwrap(), @@ -630,7 +639,8 @@ mod tests { let (handle, api) = setup_engine_api(); let terminal_block_number = 1000; - let terminal_block = random_block(terminal_block_number, None, None, None); + let terminal_block = + random_block(&mut generators::rng(), terminal_block_number, None, None, None); let transition_config = TransitionConfiguration { terminal_total_difficulty: handle.chain_spec.fork(Hardfork::Paris).ttd().unwrap(), diff --git a/crates/rpc/rpc-types/src/eth/engine/payload.rs b/crates/rpc/rpc-types/src/eth/engine/payload.rs index 6f64e121def8..d60c81b5ae2d 100644 --- a/crates/rpc/rpc-types/src/eth/engine/payload.rs +++ b/crates/rpc/rpc-types/src/eth/engine/payload.rs @@ -427,7 +427,7 @@ mod tests { use super::*; use assert_matches::assert_matches; use reth_interfaces::test_utils::generators::{ - random_block, random_block_range, random_header, + self, random_block, random_block_range, random_header, }; use reth_primitives::{ bytes::{Bytes, BytesMut}, @@ -453,7 +453,8 @@ mod tests { #[test] fn payload_body_roundtrip() { - for block in random_block_range(0..=99, H256::default(), 0..2) { + let mut rng = generators::rng(); + for block in random_block_range(&mut rng, 0..=99, H256::default(), 0..2) { let unsealed = block.clone().unseal(); let payload_body: ExecutionPayloadBody = unsealed.into(); @@ -472,7 +473,8 @@ mod tests { #[test] fn payload_validation() { - let block = random_block(100, Some(H256::random()), Some(3), Some(0)); + let mut rng = generators::rng(); + let block = random_block(&mut rng, 100, Some(H256::random()), Some(3), Some(0)); // Valid extra data let block_with_valid_extra_data = transform_block(block.clone(), |mut b| { @@ -514,7 +516,7 @@ mod tests { // Non empty ommers let block_with_ommers = transform_block(block.clone(), |mut b| { - b.ommers.push(random_header(100, None).unseal()); + b.ommers.push(random_header(&mut rng, 100, None).unseal()); b }); assert_matches!( diff --git a/crates/stages/benches/setup/mod.rs b/crates/stages/benches/setup/mod.rs index 8fca814717cf..008957b66db8 100644 --- a/crates/stages/benches/setup/mod.rs +++ b/crates/stages/benches/setup/mod.rs @@ -5,9 +5,12 @@ use reth_db::{ tables, transaction::{DbTx, DbTxMut}, }; -use reth_interfaces::test_utils::generators::{ - random_block_range, random_contract_account_range, random_eoa_account_range, - random_transition_range, +use reth_interfaces::test_utils::{ + generators, + generators::{ + random_block_range, random_contract_account_range, random_eoa_account_range, + random_transition_range, + }, }; use reth_primitives::{Account, Address, SealedBlock, H256, MAINNET}; use reth_provider::ProviderFactory; @@ -98,6 +101,9 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> PathBuf { let n_eoa = 131; let n_contract = 31; + // rng + let mut rng = generators::rng(); + if !path.exists() { // create the dirs std::fs::create_dir_all(&path).unwrap(); @@ -105,15 +111,16 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> PathBuf { let tx = TestTransaction::new(&path); let accounts: BTreeMap = concat([ - random_eoa_account_range(0..n_eoa), - random_contract_account_range(&mut (0..n_contract)), + random_eoa_account_range(&mut rng, 0..n_eoa), + random_contract_account_range(&mut rng, &mut (0..n_contract)), ]) .into_iter() .collect(); - let mut blocks = random_block_range(0..=num_blocks, H256::zero(), txs_range); + let mut blocks = random_block_range(&mut rng, 0..=num_blocks, H256::zero(), txs_range); let (transitions, start_state) = random_transition_range( + &mut rng, blocks.iter().take(2), accounts.into_iter().map(|(addr, acc)| (addr, (acc, Vec::new()))), n_changes.clone(), @@ -135,8 +142,13 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> PathBuf { tx.insert_transitions(transitions, None).unwrap(); tx.commit(|tx| updates.flush(tx)).unwrap(); - let (transitions, final_state) = - random_transition_range(blocks.iter().skip(2), start_state, n_changes, key_range); + let (transitions, final_state) = random_transition_range( + &mut rng, + blocks.iter().skip(2), + start_state, + n_changes, + key_range, + ); tx.insert_transitions(transitions, Some(offset)).unwrap(); diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index ca9b7204e6d0..96fb9b34a239 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -482,7 +482,9 @@ mod tests { use assert_matches::assert_matches; use reth_db::mdbx::{self, test_utils, EnvKind}; use reth_interfaces::{ - consensus, provider::ProviderError, test_utils::generators::random_header, + consensus, + provider::ProviderError, + test_utils::{generators, generators::random_header}, }; use reth_primitives::{stage::StageCheckpoint, MAINNET}; use tokio_stream::StreamExt; @@ -787,7 +789,7 @@ mod tests { .add_stage( TestStage::new(StageId::Other("B")) .add_exec(Err(StageError::Validation { - block: random_header(5, Default::default()), + block: random_header(&mut generators::rng(), 5, Default::default()), error: consensus::ConsensusError::BaseFeeMissing, })) .add_unwind(Ok(UnwindOutput { checkpoint: StageCheckpoint::new(0) })) diff --git a/crates/stages/src/stages/bodies.rs b/crates/stages/src/stages/bodies.rs index 0108f782804d..579ba2f174c9 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -473,6 +473,7 @@ mod tests { priority::Priority, }, test_utils::{ + generators, generators::{random_block_range, random_signed_tx}, TestConsensus, }, @@ -556,7 +557,8 @@ mod tests { fn seed_execution(&mut self, input: ExecInput) -> Result { let start = input.checkpoint().block_number; let end = input.target(); - let blocks = random_block_range(start..=end, GENESIS_HASH, 0..2); + let mut rng = generators::rng(); + let blocks = random_block_range(&mut rng, start..=end, GENESIS_HASH, 0..2); self.tx.insert_headers_with_td(blocks.iter().map(|block| &block.header))?; if let Some(progress) = blocks.first() { // Insert last progress data @@ -566,7 +568,7 @@ mod tests { tx_count: progress.body.len() as u64, }; body.tx_num_range().try_for_each(|tx_num| { - let transaction = random_signed_tx(); + let transaction = random_signed_tx(&mut rng); tx.put::(tx_num, transaction.into()) })?; diff --git a/crates/stages/src/stages/finish.rs b/crates/stages/src/stages/finish.rs index bae21c8c76b8..53d23704902b 100644 --- a/crates/stages/src/stages/finish.rs +++ b/crates/stages/src/stages/finish.rs @@ -40,7 +40,10 @@ mod tests { stage_test_suite_ext, ExecuteStageTestRunner, StageTestRunner, TestRunnerError, TestTransaction, UnwindStageTestRunner, }; - use reth_interfaces::test_utils::generators::{random_header, random_header_range}; + use reth_interfaces::test_utils::{ + generators, + generators::{random_header, random_header_range}, + }; use reth_primitives::SealedHeader; stage_test_suite_ext!(FinishTestRunner, finish); @@ -67,7 +70,8 @@ mod tests { fn seed_execution(&mut self, input: ExecInput) -> Result { let start = input.checkpoint().block_number; - let head = random_header(start, None); + let mut rng = generators::rng(); + let head = random_header(&mut rng, start, None); self.tx.insert_headers_with_td(std::iter::once(&head))?; // use previous progress as seed size @@ -77,7 +81,7 @@ mod tests { return Ok(Vec::default()) } - let mut headers = random_header_range(start + 1..end, head.hash()); + let mut headers = random_header_range(&mut rng, start + 1..end, head.hash()); self.tx.insert_headers_with_td(headers.iter())?; headers.insert(0, head); Ok(headers) diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 255a09aa1445..84485c745433 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -83,18 +83,21 @@ impl AccountHashingStage { opts: SeedOpts, ) -> Result, StageError> { use reth_db::models::AccountBeforeTx; - use reth_interfaces::test_utils::generators::{ - random_block_range, random_eoa_account_range, + use reth_interfaces::test_utils::{ + generators, + generators::{random_block_range, random_eoa_account_range}, }; use reth_primitives::{Account, H256, U256}; use reth_provider::insert_canonical_block; - let blocks = random_block_range(opts.blocks.clone(), H256::zero(), opts.txs); + let mut rng = generators::rng(); + + let blocks = random_block_range(&mut rng, opts.blocks.clone(), H256::zero(), opts.txs); for block in blocks { insert_canonical_block(provider.tx_ref(), block, None).unwrap(); } - let mut accounts = random_eoa_account_range(opts.accounts); + let mut accounts = random_eoa_account_range(&mut rng, opts.accounts); { // Account State generator let mut account_cursor = diff --git a/crates/stages/src/stages/hashing_storage.rs b/crates/stages/src/stages/hashing_storage.rs index acb109b0e9d6..2c298de335f4 100644 --- a/crates/stages/src/stages/hashing_storage.rs +++ b/crates/stages/src/stages/hashing_storage.rs @@ -231,13 +231,15 @@ mod tests { TestTransaction, UnwindStageTestRunner, }; use assert_matches::assert_matches; + use rand::Rng; use reth_db::{ cursor::{DbCursorRO, DbCursorRW}, mdbx::{tx::Tx, WriteMap, RW}, models::{BlockNumberAddress, StoredBlockBodyIndices}, }; - use reth_interfaces::test_utils::generators::{ - random_block_range, random_contract_account_range, + use reth_interfaces::test_utils::{ + generators, + generators::{random_block_range, random_contract_account_range}, }; use reth_primitives::{ stage::StageUnitCheckpoint, Address, SealedBlock, StorageEntry, H256, U256, @@ -487,11 +489,12 @@ mod tests { fn seed_execution(&mut self, input: ExecInput) -> Result { let stage_progress = input.next_block(); let end = input.target(); + let mut rng = generators::rng(); let n_accounts = 31; - let mut accounts = random_contract_account_range(&mut (0..n_accounts)); + let mut accounts = random_contract_account_range(&mut rng, &mut (0..n_accounts)); - let blocks = random_block_range(stage_progress..=end, H256::zero(), 0..3); + let blocks = random_block_range(&mut rng, stage_progress..=end, H256::zero(), 0..3); self.tx.insert_headers(blocks.iter().map(|block| &block.header))?; @@ -510,14 +513,13 @@ mod tests { transaction.clone().into(), )?; - let (addr, _) = accounts - .get_mut(rand::random::() % n_accounts as usize) - .unwrap(); + let (addr, _) = + accounts.get_mut(rng.gen::() % n_accounts as usize).unwrap(); for _ in 0..2 { let new_entry = StorageEntry { - key: keccak256([rand::random::()]), - value: U256::from(rand::random::() % 30 + 1), + key: keccak256([rng.gen::()]), + value: U256::from(rng.gen::() % 30 + 1), }; self.insert_storage_entry( tx, @@ -533,14 +535,14 @@ mod tests { )?; // Randomize rewards - let has_reward: bool = rand::random(); + let has_reward: bool = rng.gen(); if has_reward { self.insert_storage_entry( tx, (block_number, Address::random()).into(), StorageEntry { key: keccak256("mining"), - value: U256::from(rand::random::()), + value: U256::from(rng.gen::()), }, progress.header.number == stage_progress, )?; diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index 7c38fecec4fc..1984e4d6f2b2 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -388,7 +388,7 @@ mod tests { stage_test_suite, ExecuteStageTestRunner, StageTestRunner, UnwindStageTestRunner, }; use assert_matches::assert_matches; - use reth_interfaces::test_utils::generators::random_header; + use reth_interfaces::test_utils::{generators, generators::random_header}; use reth_primitives::{stage::StageUnitCheckpoint, H256, MAINNET}; use reth_provider::ProviderFactory; use test_runner::HeadersTestRunner; @@ -400,7 +400,8 @@ mod tests { ReverseHeadersDownloader, ReverseHeadersDownloaderBuilder, }; use reth_interfaces::test_utils::{ - generators::random_header_range, TestConsensus, TestHeaderDownloader, TestHeadersClient, + generators, generators::random_header_range, TestConsensus, TestHeaderDownloader, + TestHeadersClient, }; use reth_primitives::U256; use reth_provider::{BlockHashProvider, BlockNumProvider, HeaderProvider}; @@ -452,8 +453,9 @@ mod tests { type Seed = Vec; fn seed_execution(&mut self, input: ExecInput) -> Result { + let mut rng = generators::rng(); let start = input.checkpoint().block_number; - let head = random_header(start, None); + let head = random_header(&mut rng, start, None); self.tx.insert_headers(std::iter::once(&head))?; // patch td table for `update_head` call self.tx.commit(|tx| tx.put::(head.number, U256::ZERO.into()))?; @@ -465,7 +467,7 @@ mod tests { return Ok(Vec::default()) } - let mut headers = random_header_range(start + 1..end, head.hash()); + let mut headers = random_header_range(&mut rng, start + 1..end, head.hash()); headers.insert(0, head); Ok(headers) } @@ -505,7 +507,7 @@ mod tests { let tip = if !headers.is_empty() { headers.last().unwrap().hash() } else { - let tip = random_header(0, None); + let tip = random_header(&mut generators::rng(), 0, None); self.tx.insert_headers(std::iter::once(&tip))?; tip.hash() }; @@ -603,14 +605,16 @@ mod tests { let tx = provider.tx_ref(); let mut stage = runner.stage(); + let mut rng = generators::rng(); + let consensus_tip = H256::random(); runner.send_tip(consensus_tip); // Genesis let checkpoint = 0; - let head = random_header(0, None); - let gap_fill = random_header(1, Some(head.hash())); - let gap_tip = random_header(2, Some(gap_fill.hash())); + let head = random_header(&mut rng, 0, None); + let gap_fill = random_header(&mut rng, 1, Some(head.hash())); + let gap_tip = random_header(&mut rng, 2, Some(gap_fill.hash())); // Empty database assert_matches!( diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index bf70559f0a35..56cd21194b38 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -346,8 +346,12 @@ mod tests { tables, transaction::{DbTx, DbTxMut}, }; - use reth_interfaces::test_utils::generators::{ - random_block, random_block_range, random_contract_account_range, random_transition_range, + use reth_interfaces::test_utils::{ + generators, + generators::{ + random_block, random_block_range, random_contract_account_range, + random_transition_range, + }, }; use reth_primitives::{ keccak256, stage::StageUnitCheckpoint, SealedBlock, StorageEntry, H256, U256, @@ -469,9 +473,10 @@ mod tests { let stage_progress = input.checkpoint().block_number; let start = stage_progress + 1; let end = input.target(); + let mut rng = generators::rng(); let num_of_accounts = 31; - let accounts = random_contract_account_range(&mut (0..num_of_accounts)) + let accounts = random_contract_account_range(&mut rng, &mut (0..num_of_accounts)) .into_iter() .collect::>(); @@ -480,7 +485,7 @@ mod tests { )?; let SealedBlock { header, body, ommers, withdrawals } = - random_block(stage_progress, None, Some(0), None); + random_block(&mut rng, stage_progress, None, Some(0), None); let mut header = header.unseal(); header.state_root = state_root( @@ -493,10 +498,11 @@ mod tests { let head_hash = sealed_head.hash(); let mut blocks = vec![sealed_head]; - blocks.extend(random_block_range(start..=end, head_hash, 0..3)); + blocks.extend(random_block_range(&mut rng, start..=end, head_hash, 0..3)); self.tx.insert_blocks(blocks.iter(), None)?; let (transitions, final_state) = random_transition_range( + &mut rng, blocks.iter(), accounts.into_iter().map(|(addr, acc)| (addr, (acc, Vec::new()))), 0..3, diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index ba60007ac7ae..5e012e3abfdf 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -231,7 +231,10 @@ struct FailedSenderRecoveryError { #[cfg(test)] mod tests { use assert_matches::assert_matches; - use reth_interfaces::test_utils::generators::{random_block, random_block_range}; + use reth_interfaces::test_utils::{ + generators, + generators::{random_block, random_block_range}, + }; use reth_primitives::{ stage::StageUnitCheckpoint, BlockNumber, SealedBlock, TransactionSigned, H256, }; @@ -249,6 +252,7 @@ mod tests { #[tokio::test] async fn execute_single_transaction() { let (previous_stage, stage_progress) = (500, 100); + let mut rng = generators::rng(); // Set up the runner let runner = SenderRecoveryTestRunner::default(); @@ -261,7 +265,13 @@ mod tests { let non_empty_block_number = stage_progress + 10; let blocks = (stage_progress..=input.target()) .map(|number| { - random_block(number, None, Some((number == non_empty_block_number) as u8), None) + random_block( + &mut rng, + number, + None, + Some((number == non_empty_block_number) as u8), + None, + ) }) .collect::>(); runner.tx.insert_blocks(blocks.iter(), None).expect("failed to insert blocks"); @@ -288,13 +298,16 @@ mod tests { /// Execute the stage twice with input range that exceeds the commit threshold #[tokio::test] async fn execute_intermediate_commit() { + let mut rng = generators::rng(); + let threshold = 10; let mut runner = SenderRecoveryTestRunner::default(); runner.set_threshold(threshold); let (stage_progress, previous_stage) = (1000, 1100); // input exceeds threshold // Manually seed once with full input range - let seed = random_block_range(stage_progress + 1..=previous_stage, H256::zero(), 0..4); // set tx count range high enough to hit the threshold + let seed = + random_block_range(&mut rng, stage_progress + 1..=previous_stage, H256::zero(), 0..4); // set tx count range high enough to hit the threshold runner.tx.insert_blocks(seed.iter(), None).expect("failed to seed execution"); let total_transactions = runner.tx.table::().unwrap().len() as u64; @@ -403,10 +416,11 @@ mod tests { type Seed = Vec; fn seed_execution(&mut self, input: ExecInput) -> Result { + let mut rng = generators::rng(); let stage_progress = input.checkpoint().block_number; let end = input.target(); - let blocks = random_block_range(stage_progress..=end, H256::zero(), 0..2); + let blocks = random_block_range(&mut rng, stage_progress..=end, H256::zero(), 0..2); self.tx.insert_blocks(blocks.iter(), None)?; Ok(blocks) } diff --git a/crates/stages/src/stages/total_difficulty.rs b/crates/stages/src/stages/total_difficulty.rs index 7175eb15f4f7..deb78f7c6a0e 100644 --- a/crates/stages/src/stages/total_difficulty.rs +++ b/crates/stages/src/stages/total_difficulty.rs @@ -126,6 +126,7 @@ mod tests { use assert_matches::assert_matches; use reth_db::transaction::DbTx; use reth_interfaces::test_utils::{ + generators, generators::{random_header, random_header_range}, TestConsensus, }; @@ -228,8 +229,9 @@ mod tests { type Seed = Vec; fn seed_execution(&mut self, input: ExecInput) -> Result { + let mut rng = generators::rng(); let start = input.checkpoint().block_number; - let head = random_header(start, None); + let head = random_header(&mut rng, start, None); self.tx.insert_headers(std::iter::once(&head))?; self.tx.commit(|tx| { let td: U256 = tx @@ -248,7 +250,7 @@ mod tests { return Ok(Vec::default()) } - let mut headers = random_header_range(start + 1..end, head.hash()); + let mut headers = random_header_range(&mut rng, start + 1..end, head.hash()); self.tx.insert_headers(headers.iter())?; headers.insert(0, head); Ok(headers) diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index d59209007efa..4afec7459b8b 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -198,7 +198,10 @@ mod tests { TestTransaction, UnwindStageTestRunner, }; use assert_matches::assert_matches; - use reth_interfaces::test_utils::generators::{random_block, random_block_range}; + use reth_interfaces::test_utils::{ + generators, + generators::{random_block, random_block_range}, + }; use reth_primitives::{stage::StageUnitCheckpoint, BlockNumber, SealedBlock, H256}; use reth_provider::TransactionsProvider; @@ -208,6 +211,7 @@ mod tests { #[tokio::test] async fn execute_single_transaction_lookup() { let (previous_stage, stage_progress) = (500, 100); + let mut rng = generators::rng(); // Set up the runner let runner = TransactionLookupTestRunner::default(); @@ -220,7 +224,13 @@ mod tests { let non_empty_block_number = stage_progress + 10; let blocks = (stage_progress..=input.target()) .map(|number| { - random_block(number, None, Some((number == non_empty_block_number) as u8), None) + random_block( + &mut rng, + number, + None, + Some((number == non_empty_block_number) as u8), + None, + ) }) .collect::>(); runner.tx.insert_blocks(blocks.iter(), None).expect("failed to insert blocks"); @@ -256,9 +266,11 @@ mod tests { target: Some(previous_stage), checkpoint: Some(StageCheckpoint::new(stage_progress)), }; + let mut rng = generators::rng(); // Seed only once with full input range - let seed = random_block_range(stage_progress + 1..=previous_stage, H256::zero(), 0..4); // set tx count range high enough to hit the threshold + let seed = + random_block_range(&mut rng, stage_progress + 1..=previous_stage, H256::zero(), 0..4); // set tx count range high enough to hit the threshold runner.tx.insert_blocks(seed.iter(), None).expect("failed to seed execution"); let total_txs = runner.tx.table::().unwrap().len() as u64; @@ -366,8 +378,9 @@ mod tests { fn seed_execution(&mut self, input: ExecInput) -> Result { let stage_progress = input.checkpoint().block_number; let end = input.target(); + let mut rng = generators::rng(); - let blocks = random_block_range(stage_progress + 1..=end, H256::zero(), 0..2); + let blocks = random_block_range(&mut rng, stage_progress + 1..=end, H256::zero(), 0..2); self.tx.insert_blocks(blocks.iter(), None)?; Ok(blocks) } From 48bdb63a9574d66d3140304849c05382de57e279 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 22 Jun 2023 03:33:20 +0200 Subject: [PATCH 134/216] chore: convert debug into warn (#3311) --- crates/consensus/beacon/src/engine/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 3516572cc84f..5bf34ad4ac36 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -840,7 +840,7 @@ where Ok(status) } Err(error) => { - debug!(target: "consensus::engine", ?error, "Error while processing payload"); + warn!(target: "consensus::engine", ?error, "Error while processing payload"); self.map_insert_error(error) } }; @@ -1087,7 +1087,7 @@ where } } Err(err) => { - debug!(target: "consensus::engine", ?err, "Failed to insert downloaded block"); + warn!(target: "consensus::engine", ?err, "Failed to insert downloaded block"); if !matches!(err.kind(), InsertBlockErrorKind::Internal(_)) { // non-internal error kinds occur if the payload is invalid self.invalid_headers.insert(err.into_block().header); From b2f4ba4491596adfbbbba7929ad8ea90eeb11d83 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:36:17 -0400 Subject: [PATCH 135/216] feat: add blockchain tree metric graphs (#3310) --- etc/grafana/dashboards/overview.json | 296 +++++++++++++++++++++++++++ 1 file changed, 296 insertions(+) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 271d2d984be7..f4fcb12716c5 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -2885,6 +2885,302 @@ ], "title": "Payload Builder", "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 117 + }, + "id": 79, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "The block number of the tip of the canonical chain from the blockchain tree.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 118 + }, + "id": 74, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_blockchain_tree_canonical_chain_height", + "hide": false, + "legendFormat": "Canonical chain height", + "range": true, + "refId": "B" + } + ], + "title": "Canonical chain height", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total number of blocks in the tree's block buffer", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 118 + }, + "id": 80, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_blockchain_tree_block_buffer_blocks", + "hide": false, + "legendFormat": "Buffered blocks", + "range": true, + "refId": "B" + } + ], + "title": "Block buffer blocks", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total number of sidechains in the blockchain tree", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 126 + }, + "id": 81, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_blockchain_tree_sidechains", + "hide": false, + "legendFormat": "Total number of sidechains", + "range": true, + "refId": "B" + } + ], + "title": "Sidechains", + "type": "timeseries" + } + ], + "title": "Blockchain tree", + "type": "row" } ], "refresh": "30s", From d7dd474a18451ce504c52790037ec7a6b0aef205 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 21 Jun 2023 21:52:00 -0400 Subject: [PATCH 136/216] chore: improve invalid block warnings (#3312) --- crates/consensus/beacon/src/engine/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 5bf34ad4ac36..7f1bc1749d05 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1368,8 +1368,7 @@ impl InvalidHeaderCache { /// Inserts an invalid block into the cache, with a given invalid ancestor. fn insert_with_invalid_ancestor(&mut self, header_hash: H256, invalid_ancestor: Arc
) { - warn!(target: "consensus::engine", "Bad block with header hash: {:?}, invalid ancestor: {:?}", - header_hash, invalid_ancestor); + warn!(target: "consensus::engine", hash=?header_hash, ?invalid_ancestor, "Bad block with existing invalid ancestor"); self.headers.insert(header_hash, invalid_ancestor); } @@ -1377,8 +1376,7 @@ impl InvalidHeaderCache { fn insert(&mut self, invalid_ancestor: SealedHeader) { let hash = invalid_ancestor.hash; let header = invalid_ancestor.unseal(); - warn!(target: "consensus::engine", "Bad block with header hash: {:?}, invalid ancestor: {:?}", - hash, header); + warn!(target: "consensus::engine", ?hash, ?header, "Bad block with hash"); self.headers.insert(hash, Arc::new(header)); } } From f6cd75a470f216f01f9215092c8d6348db7fe398 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Wed, 21 Jun 2023 22:22:43 -0400 Subject: [PATCH 137/216] feat: add engine api graphs (#3313) --- etc/grafana/dashboards/overview.json | 599 +++++++++++++++++++++++++++ 1 file changed, 599 insertions(+) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index f4fcb12716c5..f62432508f1d 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -2596,6 +2596,605 @@ "title": "Downloader buffer", "type": "timeseries" }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 101 + }, + "id": 79, + "panels": [], + "title": "Blockchain tree", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "description": "The block number of the tip of the canonical chain from the blockchain tree.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 102 + }, + "id": 74, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "builder", + "expr": "reth_blockchain_tree_canonical_chain_height", + "hide": false, + "legendFormat": "Canonical chain height", + "range": true, + "refId": "B" + } + ], + "title": "Canonical chain height", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "description": "Total number of blocks in the tree's block buffer", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 102 + }, + "id": 80, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "builder", + "expr": "reth_blockchain_tree_block_buffer_blocks", + "hide": false, + "legendFormat": "Buffered blocks", + "range": true, + "refId": "B" + } + ], + "title": "Block buffer blocks", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "description": "Total number of sidechains in the blockchain tree", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 110 + }, + "id": 81, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "Prometheus" + }, + "editorMode": "builder", + "expr": "reth_blockchain_tree_sidechains", + "hide": false, + "legendFormat": "Total number of sidechains", + "range": true, + "refId": "B" + } + ], + "title": "Sidechains", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 118 + }, + "id": 87, + "panels": [], + "title": "Engine API", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 119 + }, + "id": 83, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_consensus_engine_beacon_active_block_downloads", + "legendFormat": "Active block downloads", + "range": true, + "refId": "A" + } + ], + "title": "Active block downloads", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Engine API messages received by the CL, either engine_newPayload or engine_forkchoiceUpdated", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 119 + }, + "id": 84, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_consensus_engine_beacon_forkchoice_updated_messages", + "legendFormat": "Forkchoice updated messages", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_consensus_engine_beacon_new_payload_messages", + "hide": false, + "legendFormat": "New payload messages", + "range": true, + "refId": "B" + } + ], + "title": "Engine API messages", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total pipeline runs triggered by the sync controller", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 127 + }, + "id": 85, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_consensus_engine_beacon_pipeline_runs", + "legendFormat": "Pipeline runs", + "range": true, + "refId": "A" + } + ], + "title": "Pipeline runs", + "type": "timeseries" + }, { "collapsed": true, "gridPos": { From 1d3bab64ae9393eb28df57708b51b98c97cea9b1 Mon Sep 17 00:00:00 2001 From: Yuichiro Aoki <45054071+yuichiroaoki@users.noreply.github.com> Date: Thu, 22 Jun 2023 11:55:09 +0900 Subject: [PATCH 138/216] refactor: add total distance in log message (#3292) --- crates/net/downloaders/src/bodies/bodies.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/net/downloaders/src/bodies/bodies.rs b/crates/net/downloaders/src/bodies/bodies.rs index e613f4977e84..ddc00a48185e 100644 --- a/crates/net/downloaders/src/bodies/bodies.rs +++ b/crates/net/downloaders/src/bodies/bodies.rs @@ -329,10 +329,11 @@ where // Check if the provided range is the next expected range. let is_next_consecutive_range = *range.start() == *self.download_range.end() + 1; + let distance = *range.end() - *range.start(); if is_next_consecutive_range { // New range received. tracing::trace!(target: "downloaders::bodies", ?range, "New download range set"); - info!(target: "downloaders::bodies", "Downloading bodies {range:?}"); + info!(target: "downloaders::bodies", distance, "Downloading bodies {range:?}"); self.download_range = range; return Ok(()) } @@ -340,7 +341,7 @@ where // The block range is reset. This can happen either after unwind or after the bodies were // written by external services (e.g. BlockchainTree). tracing::trace!(target: "downloaders::bodies", ?range, prev_range = ?self.download_range, "Download range reset"); - info!(target: "downloaders::bodies", "Downloading bodies {range:?}"); + info!(target: "downloaders::bodies", distance, "Downloading bodies {range:?}"); self.clear(); self.download_range = range; Ok(()) From 5cd2148789f93a8561e8aecfdd4ea320c2344187 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 22 Jun 2023 15:02:36 +0300 Subject: [PATCH 139/216] chore(cli): display latest available block number in status logs (#3316) --- bin/reth/src/chain/import.rs | 12 +++++++++--- bin/reth/src/debug_cmd/execution.rs | 23 ++++++++++++----------- bin/reth/src/node/events.rs | 13 ++++++++----- bin/reth/src/node/mod.rs | 11 +++++++---- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/bin/reth/src/chain/import.rs b/bin/reth/src/chain/import.rs index ca4904fccb62..6a1a79e693d2 100644 --- a/bin/reth/src/chain/import.rs +++ b/bin/reth/src/chain/import.rs @@ -7,6 +7,7 @@ use clap::Parser; use eyre::Context; use futures::{Stream, StreamExt}; use reth_beacon_consensus::BeaconConsensus; +use reth_provider::{ProviderFactory, StageCheckpointReader}; use crate::args::utils::genesis_value_parser; use reth_config::Config; @@ -16,7 +17,7 @@ use reth_downloaders::{ headers::reverse_headers::ReverseHeadersDownloaderBuilder, test_utils::FileClient, }; use reth_interfaces::consensus::Consensus; -use reth_primitives::{ChainSpec, H256}; +use reth_primitives::{stage::StageId, ChainSpec, H256}; use reth_staged_sync::utils::init::{init_db, init_genesis}; use reth_stages::{ prelude::*, @@ -105,13 +106,18 @@ impl ImportCommand { info!(target: "reth::cli", "Chain file imported"); let (mut pipeline, events) = - self.build_import_pipeline(config, db, &consensus, file_client).await?; + self.build_import_pipeline(config, Arc::clone(&db), &consensus, file_client).await?; // override the tip pipeline.set_tip(tip); debug!(target: "reth::cli", ?tip, "Tip manually set"); - tokio::spawn(handle_events(None, events)); + let factory = ProviderFactory::new(&db, self.chain.clone()); + let provider = factory.provider().map_err(PipelineError::Interface)?; + + let latest_block_number = + provider.get_stage_checkpoint(StageId::Finish)?.map(|ch| ch.block_number); + tokio::spawn(handle_events(None, latest_block_number, events)); // Run pipeline info!(target: "reth::cli", "Starting sync pipeline"); diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index 0865f5abbb02..0533dd3daf55 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -234,26 +234,27 @@ impl Command { &ctx.task_executor, )?; - let pipeline_events = pipeline.events(); - let events = stream_select( - network.event_listener().map(Into::into), - pipeline_events.map(Into::into), - ); - ctx.task_executor - .spawn_critical("events task", events::handle_events(Some(network.clone()), events)); - let factory = ProviderFactory::new(&db, self.chain.clone()); let provider = factory.provider().map_err(PipelineError::Interface)?; let latest_block_number = - provider.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default().block_number; - if latest_block_number >= self.to { + provider.get_stage_checkpoint(StageId::Finish)?.map(|ch| ch.block_number); + if latest_block_number.unwrap_or_default() >= self.to { info!(target: "reth::cli", latest = latest_block_number, "Nothing to run"); return Ok(()) } - let mut current_max_block = latest_block_number; + let pipeline_events = pipeline.events(); + let events = stream_select( + network.event_listener().map(Into::into), + pipeline_events.map(Into::into), + ); + ctx.task_executor.spawn_critical( + "events task", + events::handle_events(Some(network.clone()), latest_block_number, events), + ); + let mut current_max_block = latest_block_number.unwrap_or_default(); while current_max_block < self.to { let next_block = current_max_block + 1; let target_block = self.to.min(current_max_block + self.interval); diff --git a/bin/reth/src/node/events.rs b/bin/reth/src/node/events.rs index 3187132b7e14..4f0c673e097a 100644 --- a/bin/reth/src/node/events.rs +++ b/bin/reth/src/node/events.rs @@ -38,13 +38,13 @@ struct NodeState { } impl NodeState { - fn new(network: Option) -> Self { + fn new(network: Option, latest_block_number: Option) -> Self { Self { network, current_stage: None, eta: Eta::default(), current_checkpoint: StageCheckpoint::new(0), - latest_canonical_engine_block: None, + latest_canonical_engine_block: latest_block_number, } } @@ -190,11 +190,14 @@ impl From for NodeEvent { /// Displays relevant information to the user from components of the node, and periodically /// displays the high-level status of the node. -pub async fn handle_events(network: Option, events: E) -where +pub async fn handle_events( + network: Option, + latest_block_number: Option, + events: E, +) where E: Stream + Unpin, { - let state = NodeState::new(network); + let state = NodeState::new(network, latest_block_number); let mut info_interval = tokio::time::interval(INFO_MESSAGE_INTERVAL); info_interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay); diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 3bd2609b2911..9aa3e8eab0ed 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -236,10 +236,12 @@ impl Command { debug!(target: "reth::cli", ?network_secret_path, "Loading p2p key file"); let secret_key = get_secret_key(&network_secret_path)?; let default_peers_path = data_dir.known_peers_path(); + let head = self.lookup_head(Arc::clone(&db)).expect("the head block is missing"); let network_config = self.load_network_config( &config, Arc::clone(&db), ctx.task_executor.clone(), + head, secret_key, default_peers_path.clone(), ); @@ -360,8 +362,10 @@ impl Command { Either::Right(stream::empty()) } ); - ctx.task_executor - .spawn_critical("events task", events::handle_events(Some(network.clone()), events)); + ctx.task_executor.spawn_critical( + "events task", + events::handle_events(Some(network.clone()), Some(head.number), events), + ); let engine_api = EngineApi::new( blockchain_db.clone(), @@ -600,11 +604,10 @@ impl Command { config: &Config, db: Arc>, executor: TaskExecutor, + head: Head, secret_key: SecretKey, default_peers_path: PathBuf, ) -> NetworkConfig>>> { - let head = self.lookup_head(Arc::clone(&db)).expect("the head block is missing"); - self.network .network_config(config, self.chain.clone(), secret_key, default_peers_path) .with_task_executor(Box::new(executor)) From b0f57bf97b044f880178dfcd80cff0080a83b0fe Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 22 Jun 2023 09:36:43 -0400 Subject: [PATCH 140/216] feat: add invalid headers metrics (#3314) Co-authored-by: Matthias Seitz --- .../beacon/src/engine/invalid_headers.rs | 70 +++++++++++++++++++ crates/consensus/beacon/src/engine/mod.rs | 36 +--------- 2 files changed, 73 insertions(+), 33 deletions(-) create mode 100644 crates/consensus/beacon/src/engine/invalid_headers.rs diff --git a/crates/consensus/beacon/src/engine/invalid_headers.rs b/crates/consensus/beacon/src/engine/invalid_headers.rs new file mode 100644 index 000000000000..129cfefa6f7a --- /dev/null +++ b/crates/consensus/beacon/src/engine/invalid_headers.rs @@ -0,0 +1,70 @@ +use std::sync::Arc; + +use reth_metrics::{ + metrics::{self, Counter, Gauge}, + Metrics, +}; +use reth_primitives::{Header, SealedHeader, H256}; +use schnellru::{ByLength, LruMap}; +use tracing::warn; + +/// Metrics for the invalid headers cache. +#[derive(Metrics)] +#[metrics(scope = "invalid_header_cache")] +struct InvalidHeaderCacheMetrics { + /// The total number of invalid headers in the cache. + invalid_headers: Gauge, + /// The number of inserts with a known ancestor. + known_ancestor_inserts: Counter, + /// The number of unique invalid header inserts (i.e. without a known ancestor). + unique_inserts: Counter, +} + +/// Keeps track of invalid headers. +pub(crate) struct InvalidHeaderCache { + /// This maps a header hash to a reference to its invalid ancestor. + headers: LruMap>, + /// Metrics for the cache. + metrics: InvalidHeaderCacheMetrics, +} + +impl InvalidHeaderCache { + pub(crate) fn new(max_length: u32) -> Self { + Self { headers: LruMap::new(ByLength::new(max_length)), metrics: Default::default() } + } + + /// Returns the invalid ancestor's header if it exists in the cache. + pub(crate) fn get(&mut self, hash: &H256) -> Option<&mut Arc
> { + self.headers.get(hash) + } + + /// Inserts an invalid block into the cache, with a given invalid ancestor. + pub(crate) fn insert_with_invalid_ancestor( + &mut self, + header_hash: H256, + invalid_ancestor: Arc
, + ) { + if self.headers.get(&header_hash).is_none() { + warn!(target: "consensus::engine", hash=?header_hash, ?invalid_ancestor, "Bad block with existing invalid ancestor"); + self.headers.insert(header_hash, invalid_ancestor); + + // update metrics + self.metrics.known_ancestor_inserts.increment(1); + self.metrics.invalid_headers.set(self.headers.len() as f64); + } + } + + /// Inserts an invalid ancestor into the map. + pub(crate) fn insert(&mut self, invalid_ancestor: SealedHeader) { + if self.headers.get(&invalid_ancestor.hash).is_none() { + let hash = invalid_ancestor.hash; + let header = invalid_ancestor.unseal(); + warn!(target: "consensus::engine", ?hash, ?header, "Bad block with hash"); + self.headers.insert(hash, Arc::new(header)); + + // update metrics + self.metrics.unique_inserts.increment(1); + self.metrics.invalid_headers.set(self.headers.len() as f64); + } + } +} diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 7f1bc1749d05..ceb022a4a923 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -18,7 +18,7 @@ use reth_interfaces::{ use reth_payload_builder::{PayloadBuilderAttributes, PayloadBuilderHandle}; use reth_primitives::{ listener::EventListeners, stage::StageId, BlockNumHash, BlockNumber, Head, Header, SealedBlock, - SealedHeader, H256, U256, + H256, U256, }; use reth_provider::{ BlockProvider, BlockSource, CanonChainTracker, ProviderError, StageCheckpointReader, @@ -29,7 +29,6 @@ use reth_rpc_types::engine::{ }; use reth_stages::{ControlFlow, Pipeline}; use reth_tasks::TaskSpawner; -use schnellru::{ByLength, LruMap}; use std::{ pin::Pin, sync::Arc, @@ -52,6 +51,8 @@ pub use error::{ BeaconOnNewPayloadError, }; +mod invalid_headers; +use invalid_headers::InvalidHeaderCache; mod metrics; mod event; @@ -1350,37 +1351,6 @@ where } } -/// Keeps track of invalid headers. -struct InvalidHeaderCache { - /// This maps a header hash to a reference to its invalid ancestor. - headers: LruMap>, -} - -impl InvalidHeaderCache { - fn new(max_length: u32) -> Self { - Self { headers: LruMap::new(ByLength::new(max_length)) } - } - - /// Returns the invalid ancestor's header if it exists in the cache. - fn get(&mut self, hash: &H256) -> Option<&mut Arc
> { - self.headers.get(hash) - } - - /// Inserts an invalid block into the cache, with a given invalid ancestor. - fn insert_with_invalid_ancestor(&mut self, header_hash: H256, invalid_ancestor: Arc
) { - warn!(target: "consensus::engine", hash=?header_hash, ?invalid_ancestor, "Bad block with existing invalid ancestor"); - self.headers.insert(header_hash, invalid_ancestor); - } - - /// Inserts an invalid ancestor into the map. - fn insert(&mut self, invalid_ancestor: SealedHeader) { - let hash = invalid_ancestor.hash; - let header = invalid_ancestor.unseal(); - warn!(target: "consensus::engine", ?hash, ?header, "Bad block with hash"); - self.headers.insert(hash, Arc::new(header)); - } -} - #[cfg(test)] mod tests { use super::*; From a9147ba2fc367b51e4bd4dc9ff8982ae24ff4d32 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:03:57 -0400 Subject: [PATCH 141/216] fix: remove duplicate tree graph (#3315) --- etc/grafana/dashboards/overview.json | 296 --------------------------- 1 file changed, 296 deletions(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index f62432508f1d..9d4ac8bea733 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -3484,302 +3484,6 @@ ], "title": "Payload Builder", "type": "row" - }, - { - "collapsed": true, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 117 - }, - "id": 79, - "panels": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "The block number of the tip of the canonical chain from the blockchain tree.", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 118 - }, - "id": 74, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "builder", - "expr": "reth_blockchain_tree_canonical_chain_height", - "hide": false, - "legendFormat": "Canonical chain height", - "range": true, - "refId": "B" - } - ], - "title": "Canonical chain height", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total number of blocks in the tree's block buffer", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 118 - }, - "id": 80, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "builder", - "expr": "reth_blockchain_tree_block_buffer_blocks", - "hide": false, - "legendFormat": "Buffered blocks", - "range": true, - "refId": "B" - } - ], - "title": "Block buffer blocks", - "type": "timeseries" - }, - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "description": "Total number of sidechains in the blockchain tree", - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 126 - }, - "id": 81, - "options": { - "legend": { - "calcs": [], - "displayMode": "list", - "placement": "bottom", - "showLegend": true - }, - "tooltip": { - "mode": "multi", - "sort": "none" - } - }, - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "${DS_PROMETHEUS}" - }, - "editorMode": "builder", - "expr": "reth_blockchain_tree_sidechains", - "hide": false, - "legendFormat": "Total number of sidechains", - "range": true, - "refId": "B" - } - ], - "title": "Sidechains", - "type": "timeseries" - } - ], - "title": "Blockchain tree", - "type": "row" } ], "refresh": "30s", From 68b93a88de91819ca5e86273168a7adac057b447 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Thu, 22 Jun 2023 15:08:27 +0100 Subject: [PATCH 142/216] refactor: adds `StorageReader` / `HashingWriter` / `HistoryWriter` (#3285) --- Cargo.lock | 1 - crates/interfaces/src/provider.rs | 26 + crates/staged-sync/src/utils/init.rs | 17 +- crates/stages/src/error.rs | 7 +- crates/stages/src/stages/hashing_storage.rs | 7 +- .../src/stages/index_storage_history.rs | 4 +- crates/storage/provider/Cargo.toml | 1 - crates/storage/provider/src/lib.rs | 11 +- .../src/providers/database/provider.rs | 630 +++++++++--------- crates/storage/provider/src/traits/hashing.rs | 31 + crates/storage/provider/src/traits/history.rs | 26 + crates/storage/provider/src/traits/mod.rs | 9 + crates/storage/provider/src/traits/storage.rs | 33 + crates/storage/provider/src/transaction.rs | 41 -- crates/transaction-pool/src/pool/txpool.rs | 5 +- 15 files changed, 442 insertions(+), 407 deletions(-) create mode 100644 crates/storage/provider/src/traits/hashing.rs create mode 100644 crates/storage/provider/src/traits/history.rs create mode 100644 crates/storage/provider/src/traits/storage.rs diff --git a/Cargo.lock b/Cargo.lock index 9929ef06af38..39b95fa1a0a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5558,7 +5558,6 @@ dependencies = [ "reth-revm-primitives", "reth-rlp", "reth-trie", - "thiserror", "tokio", "tokio-stream", "tracing", diff --git a/crates/interfaces/src/provider.rs b/crates/interfaces/src/provider.rs index 6ef7517d002f..f9ed2a8dc4a1 100644 --- a/crates/interfaces/src/provider.rs +++ b/crates/interfaces/src/provider.rs @@ -4,6 +4,8 @@ use reth_primitives::{Address, BlockHash, BlockHashOrNumber, BlockNumber, TxNumb #[allow(missing_docs)] #[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)] pub enum ProviderError { + #[error(transparent)] + Database(#[from] crate::db::DatabaseError), /// The header number was not found for the given block hash. #[error("Block hash {0:?} does not exist in Headers table")] BlockHashNotFound(BlockHash), @@ -68,4 +70,28 @@ pub enum ProviderError { /// Unable to find the block number for a given transaction index #[error("Unable to find the block number for a given transaction index")] BlockNumberForTransactionIndexNotFound, + /// Root mismatch + #[error("Merkle trie root mismatch at #{block_number} ({block_hash:?}). Got: {got:?}. Expected: {expected:?}")] + StateRootMismatch { + /// Expected root + expected: H256, + /// Calculated root + got: H256, + /// Block number + block_number: BlockNumber, + /// Block hash + block_hash: BlockHash, + }, + /// Root mismatch during unwind + #[error("Unwind merkle trie root mismatch at #{block_number} ({block_hash:?}). Got: {got:?}. Expected: {expected:?}")] + UnwindStateRootMismatch { + /// Expected root + expected: H256, + /// Calculated root + got: H256, + /// Target block number + block_number: BlockNumber, + /// Block hash + block_hash: BlockHash, + }, } diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index d04a11f0db6e..869de558b691 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -8,10 +8,8 @@ use reth_db::{ transaction::{DbTx, DbTxMut}, version::{check_db_version_file, create_db_version_file, DatabaseVersionError}, }; -use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, H256, U256}; -use reth_provider::{ - AccountWriter, DatabaseProviderRW, PostState, ProviderFactory, TransactionError, -}; +use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, StorageEntry, H256, U256}; +use reth_provider::{AccountWriter, DatabaseProviderRW, HashingWriter, PostState, ProviderFactory}; use std::{fs, path::Path, sync::Arc}; use tracing::debug; @@ -50,10 +48,6 @@ pub enum InitDatabaseError { database_hash: H256, }, - /// Higher level error encountered when using a Transaction. - #[error(transparent)] - TransactionError(#[from] TransactionError), - /// Low-level database error. #[error(transparent)] DBError(#[from] reth_db::DatabaseError), @@ -155,7 +149,12 @@ pub fn insert_genesis_hashes( let alloc_storage = genesis.alloc.clone().into_iter().filter_map(|(addr, account)| { // only return Some if there is storage - account.storage.map(|storage| (addr, storage.into_iter().map(|(k, v)| (k, v.into())))) + account.storage.map(|storage| { + ( + addr, + storage.into_iter().map(|(key, value)| StorageEntry { key, value: value.into() }), + ) + }) }); provider.insert_storage_for_hashing(alloc_storage)?; provider.commit()?; diff --git a/crates/stages/src/error.rs b/crates/stages/src/error.rs index b05b091db5ba..20310111ca9a 100644 --- a/crates/stages/src/error.rs +++ b/crates/stages/src/error.rs @@ -4,7 +4,6 @@ use reth_interfaces::{ provider::ProviderError, }; use reth_primitives::SealedHeader; -use reth_provider::TransactionError; use thiserror::Error; use tokio::sync::mpsc::error::SendError; @@ -59,9 +58,6 @@ pub enum StageError { /// The stage encountered a database integrity error. #[error("A database integrity error occurred: {0}")] DatabaseIntegrity(#[from] ProviderError), - /// The stage encountered an error related to the current database transaction. - #[error("A database transaction error occurred: {0}")] - Transaction(#[from] TransactionError), /// Invalid download response. Applicable for stages which /// rely on external downloaders #[error("Invalid download response: {0}")] @@ -92,8 +88,7 @@ impl StageError { StageError::DatabaseIntegrity(_) | StageError::StageCheckpoint(_) | StageError::ChannelClosed | - StageError::Fatal(_) | - StageError::Transaction(_) + StageError::Fatal(_) ) } } diff --git a/crates/stages/src/stages/hashing_storage.rs b/crates/stages/src/stages/hashing_storage.rs index 2c298de335f4..db4600a02bc4 100644 --- a/crates/stages/src/stages/hashing_storage.rs +++ b/crates/stages/src/stages/hashing_storage.rs @@ -16,7 +16,7 @@ use reth_primitives::{ }, StorageEntry, }; -use reth_provider::DatabaseProviderRW; +use reth_provider::{DatabaseProviderRW, HashingWriter, StorageReader}; use std::{collections::BTreeMap, fmt::Debug}; use tracing::*; @@ -171,12 +171,11 @@ impl Stage for StorageHashingStage { } else { // Aggregate all changesets and and make list of storages that have been // changed. - let lists = - provider.get_addresses_and_keys_of_changed_storages(from_block..=to_block)?; + let lists = provider.changed_storages_with_range(from_block..=to_block)?; // iterate over plain state and get newest storage value. // Assumption we are okay with is that plain state represent // `previous_stage_progress` state. - let storages = provider.get_plainstate_storages(lists)?; + let storages = provider.plainstate_storages(lists)?; provider.insert_storage_for_hashing(storages.into_iter())?; } diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index 1abb389b3f07..31d033173ca6 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -1,7 +1,7 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::{database::Database, models::BlockNumberAddress}; use reth_primitives::stage::{StageCheckpoint, StageId}; -use reth_provider::DatabaseProviderRW; +use reth_provider::{DatabaseProviderRW, HistoryWriter, StorageReader}; use std::fmt::Debug; /// Stage is indexing history the account changesets generated in @@ -46,7 +46,7 @@ impl Stage for IndexStorageHistoryStage { let (range, is_final_range) = input.next_block_range_with_threshold(self.commit_threshold); - let indices = provider.get_storage_block_numbers_from_changesets(range.clone())?; + let indices = provider.changed_storages_and_blocks_with_range(range.clone())?; provider.insert_storage_history_index(indices)?; Ok(ExecOutput { checkpoint: StageCheckpoint::new(*range.end()), done: is_final_range }) diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 22fcaba5e781..25b7e9f4c830 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -24,7 +24,6 @@ tokio-stream = { workspace = true, features = ["sync"] } tracing = { workspace = true } # misc -thiserror = { workspace = true } auto_impl = "1.0" itertools = "0.10" pin-project = { workspace = true } diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 5a988cf88cfa..a0c205568401 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -19,9 +19,10 @@ pub use traits::{ BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockSource, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, - ExecutorFactory, HeaderProvider, PostStateDataProvider, ReceiptProvider, ReceiptProviderIdExt, - StageCheckpointReader, StageCheckpointWriter, StateProvider, StateProviderBox, - StateProviderFactory, StateRootProvider, TransactionsProvider, WithdrawalsProvider, + ExecutorFactory, HashingWriter, HeaderProvider, HistoryWriter, PostStateDataProvider, + ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StageCheckpointWriter, + StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, StorageReader, + TransactionsProvider, WithdrawalsProvider, }; /// Provider trait implementations. @@ -35,10 +36,6 @@ pub use providers::{ pub mod post_state; pub use post_state::PostState; -/// Helper types for interacting with the database -mod transaction; -pub use transaction::TransactionError; - /// Common database utilities. mod utils; pub use utils::{insert_block, insert_canonical_block}; diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index b9b6ea5203d4..86e7429fe7b0 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -3,8 +3,8 @@ use crate::{ post_state::StorageChangeset, traits::{AccountExtReader, BlockSource, ReceiptProvider, StageCheckpointWriter}, AccountReader, AccountWriter, BlockHashProvider, BlockNumProvider, BlockProvider, - EvmEnvProvider, HeaderProvider, PostState, ProviderError, StageCheckpointReader, - TransactionError, TransactionsProvider, WithdrawalsProvider, + EvmEnvProvider, HashingWriter, HeaderProvider, HistoryWriter, PostState, ProviderError, + StageCheckpointReader, StorageReader, TransactionsProvider, WithdrawalsProvider, }; use itertools::{izip, Itertools}; use reth_db::{ @@ -145,7 +145,7 @@ fn unwind_storage_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( address: Address, storage_key: H256, block_number: BlockNumber, -) -> std::result::Result, TransactionError> { +) -> Result> { let mut item = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; while let Some((storage_sharded_key, list)) = item { @@ -205,77 +205,6 @@ impl<'this, TX: DbTx<'this>> DatabaseProvider<'this, TX> { .walk(Some(T::Key::default()))? .collect::, DatabaseError>>() } - - // TODO(joshie) TEMPORARY should be moved to trait providers - - /// Iterate over account changesets and return all account address that were changed. - pub fn get_addresses_and_keys_of_changed_storages( - &self, - range: RangeInclusive, - ) -> std::result::Result>, TransactionError> { - self.tx - .cursor_read::()? - .walk_range(BlockNumberAddress::range(range))? - // fold all storages and save its old state so we can remove it from HashedStorage - // it is needed as it is dup table. - .try_fold(BTreeMap::new(), |mut accounts: BTreeMap>, entry| { - let (BlockNumberAddress((_, address)), storage_entry) = entry?; - accounts.entry(address).or_default().insert(storage_entry.key); - Ok(accounts) - }) - } - - /// Get plainstate storages - #[allow(clippy::type_complexity)] - pub fn get_plainstate_storages( - &self, - iter: impl IntoIterator)>, - ) -> std::result::Result)>, TransactionError> { - let mut plain_storage = self.tx.cursor_dup_read::()?; - - iter.into_iter() - .map(|(address, storage)| { - storage - .into_iter() - .map(|key| -> std::result::Result<_, TransactionError> { - let ret = plain_storage - .seek_by_key_subkey(address, key)? - .filter(|v| v.key == key) - .unwrap_or_default(); - Ok((key, ret.value)) - }) - .collect::, _>>() - .map(|storage| (address, storage)) - }) - .collect::, _>>() - } - - /// Get all block numbers where account got changed. - /// - /// NOTE: Get inclusive range of blocks. - pub fn get_storage_block_numbers_from_changesets( - &self, - range: RangeInclusive, - ) -> std::result::Result>, TransactionError> { - let mut changeset_cursor = self.tx.cursor_read::()?; - - let storage_changeset_lists = - changeset_cursor.walk_range(BlockNumberAddress::range(range))?.try_fold( - BTreeMap::new(), - |mut storages: BTreeMap<(Address, H256), Vec>, - entry| - -> std::result::Result<_, TransactionError> { - let (index, storage) = entry?; - storages - .entry((index.address(), storage.key)) - .or_default() - .push(index.block_number()); - Ok(storages) - }, - )?; - - Ok(storage_changeset_lists) - } } impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { @@ -291,7 +220,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { &self, chain_spec: &ChainSpec, range: RangeInclusive, - ) -> std::result::Result, TransactionError> { + ) -> Result> { self.get_take_block_and_execution_range::(chain_spec, range) } @@ -300,107 +229,10 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { &self, chain_spec: &ChainSpec, range: RangeInclusive, - ) -> std::result::Result, TransactionError> { + ) -> Result> { self.get_take_block_and_execution_range::(chain_spec, range) } - /// Unwind and clear storage hashing - pub fn unwind_storage_hashing( - &self, - range: Range, - ) -> std::result::Result<(), TransactionError> { - let mut hashed_storage = self.tx.cursor_dup_write::()?; - - // Aggregate all block changesets and make list of accounts that have been changed. - self.tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()? - .into_iter() - .rev() - // fold all account to get the old balance/nonces and account that needs to be removed - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap<(Address, H256), U256>, - (BlockNumberAddress((_, address)), storage_entry)| { - accounts.insert((address, storage_entry.key), storage_entry.value); - accounts - }, - ) - .into_iter() - // hash addresses and collect it inside sorted BTreeMap. - // We are doing keccak only once per address. - .map(|((address, key), value)| ((keccak256(address), keccak256(key)), value)) - .collect::>() - .into_iter() - // Apply values to HashedStorage (if Value is zero just remove it); - .try_for_each( - |((hashed_address, key), value)| -> std::result::Result<(), TransactionError> { - if hashed_storage - .seek_by_key_subkey(hashed_address, key)? - .filter(|entry| entry.key == key) - .is_some() - { - hashed_storage.delete_current()?; - } - - if value != U256::ZERO { - hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; - } - Ok(()) - }, - )?; - - Ok(()) - } - - /// Unwind and clear storage history indices. - /// - /// Returns number of changesets walked. - pub fn unwind_storage_history_indices( - &self, - range: Range, - ) -> std::result::Result { - let storage_changesets = self - .tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; - let changesets = storage_changesets.len(); - - let last_indices = storage_changesets - .into_iter() - // reverse so we can get lowest block number where we need to unwind account. - .rev() - // fold all storages and get last block number - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap<(Address, H256), u64>, (index, storage)| { - // we just need address and lowest block number. - accounts.insert((index.address(), storage.key), index.block_number()); - accounts - }, - ); - - let mut cursor = self.tx.cursor_write::()?; - for ((address, storage_key), rem_index) in last_indices { - let shard_part = - unwind_storage_history_shards::(&mut cursor, address, storage_key, rem_index)?; - - // check last shard_part, if present, items needs to be reinserted. - if !shard_part.is_empty() { - // there are items in list - self.tx.put::( - StorageShardedKey::new(address, storage_key, u64::MAX), - BlockNumberList::new(shard_part) - .expect("There is at least one element in list and it is sorted."), - )?; - } - } - - Ok(changesets) - } - /// Traverse over changesets and plain state and recreate the [`PostState`]s for the given range /// of blocks. /// @@ -427,7 +259,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { fn get_take_block_execution_result_range( &self, range: RangeInclusive, - ) -> std::result::Result, TransactionError> { + ) -> Result> { if range.is_empty() { return Ok(Vec::new()) } @@ -587,7 +419,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { &self, chain_spec: &ChainSpec, range: RangeInclusive, - ) -> std::result::Result, TransactionError> { + ) -> Result> { if TAKE { let storage_range = BlockNumberAddress::range(range.clone()); @@ -598,7 +430,8 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { // merkle tree let (new_state_root, trie_updates) = - StateRoot::incremental_root_with_updates(&self.tx, range.clone())?; + StateRoot::incremental_root_with_updates(&self.tx, range.clone()) + .map_err(Into::::into)?; let parent_number = range.start().saturating_sub(1); let parent_state_root = self @@ -612,12 +445,13 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { let parent_hash = self .block_hash(parent_number)? .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))?; - return Err(TransactionError::UnwindStateRootMismatch { + return Err(ProviderError::UnwindStateRootMismatch { got: new_state_root, expected: parent_state_root, block_number: parent_number, block_hash: parent_hash, - }) + } + .into()) } trie_updates.flush(&self.tx)?; } @@ -675,8 +509,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { fn get_take_block_transaction_range( &self, range: impl RangeBounds + Clone, - ) -> std::result::Result)>, TransactionError> - { + ) -> Result)>> { // Raad range of block bodies to get all transactions id's of this range. let block_bodies = self.get_or_take::(range)?; @@ -755,7 +588,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { &self, chain_spec: &ChainSpec, range: impl RangeBounds + Clone, - ) -> std::result::Result, TransactionError> { + ) -> Result> { // For block we need Headers, Bodies, Uncles, withdrawals, Transactions, Signers let block_headers = self.get_or_take::(range.clone())?; @@ -837,51 +670,8 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(blocks) } - /// Insert storage change index to database. Used inside StorageHistoryIndex stage - pub fn insert_storage_history_index( - &self, - storage_transitions: BTreeMap<(Address, H256), Vec>, - ) -> std::result::Result<(), TransactionError> { - for ((address, storage_key), mut indices) in storage_transitions { - let mut last_shard = self.take_last_storage_shard(address, storage_key)?; - last_shard.append(&mut indices); - - // chunk indices and insert them in shards of N size. - let mut chunks = last_shard - .iter() - .chunks(storage_sharded_key::NUM_OF_INDICES_IN_SHARD) - .into_iter() - .map(|chunks| chunks.map(|i| *i as usize).collect::>()) - .collect::>(); - let last_chunk = chunks.pop(); - - // chunk indices and insert them in shards of N size. - chunks.into_iter().try_for_each(|list| { - self.tx.put::( - StorageShardedKey::new( - address, - storage_key, - *list.last().expect("Chuck does not return empty list") as BlockNumber, - ), - BlockNumberList::new(list).expect("Indices are presorted and not empty"), - ) - })?; - // Insert last list with u64::MAX - if let Some(last_list) = last_chunk { - self.tx.put::( - StorageShardedKey::new(address, storage_key, u64::MAX), - BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), - )?; - } - } - Ok(()) - } - /// Query the block body by number. - pub fn block_body_indices( - &self, - number: BlockNumber, - ) -> std::result::Result { + pub fn block_body_indices(&self, number: BlockNumber) -> Result { let body = self .tx .get::(number)? @@ -948,11 +738,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { /// Load last shard and check if it is full and remove if it is not. If list is empty, last /// shard was full or there is no shards at all. - pub fn take_last_storage_shard( - &self, - address: Address, - storage_key: H256, - ) -> std::result::Result, TransactionError> { + pub fn take_last_storage_shard(&self, address: Address, storage_key: H256) -> Result> { let mut cursor = self.tx.cursor_read::()?; let last = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; if let Some((storage_shard_key, list)) = last { @@ -963,44 +749,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { } Ok(Vec::new()) } - /// iterate over storages and insert them to hashing table - pub fn insert_storage_for_hashing( - &self, - storages: impl IntoIterator)>, - ) -> std::result::Result<(), TransactionError> { - // hash values - let hashed = storages.into_iter().fold(BTreeMap::new(), |mut map, (address, storage)| { - let storage = storage.into_iter().fold(BTreeMap::new(), |mut map, (key, value)| { - map.insert(keccak256(key), value); - map - }); - map.insert(keccak256(address), storage); - map - }); - - let mut hashed_storage = self.tx.cursor_dup_write::()?; - // Hash the address and key and apply them to HashedStorage (if Storage is None - // just remove it); - hashed.into_iter().try_for_each(|(hashed_address, storage)| { - storage.into_iter().try_for_each( - |(key, value)| -> std::result::Result<(), TransactionError> { - if hashed_storage - .seek_by_key_subkey(hashed_address, key)? - .filter(|entry| entry.key == key) - .is_some() - { - hashed_storage.delete_current()?; - } - - if value != U256::ZERO { - hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; - } - Ok(()) - }, - ) - })?; - Ok(()) - } /// Append blocks and insert its post state. /// This will insert block data to all related tables and will update pipeline progress. @@ -1008,7 +756,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { &mut self, blocks: Vec, state: PostState, - ) -> std::result::Result<(), TransactionError> { + ) -> Result<()> { if blocks.is_empty() { return Ok(()) } @@ -1047,73 +795,10 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { &mut self, block: SealedBlock, senders: Option>, - ) -> std::result::Result<(), TransactionError> { + ) -> Result<()> { insert_canonical_block(self.tx_mut(), block, senders)?; Ok(()) } - - /// Read account/storage changesets and update account/storage history indices. - pub fn calculate_history_indices( - &mut self, - range: RangeInclusive, - ) -> std::result::Result<(), TransactionError> { - // account history stage - { - let indices = self.changed_accounts_and_blocks_with_range(range.clone())?; - self.insert_account_history_index(indices)?; - } - - // storage history stage - { - let indices = self.get_storage_block_numbers_from_changesets(range)?; - self.insert_storage_history_index(indices)?; - } - - Ok(()) - } - - /// Calculate the hashes of all changed accounts and storages, and finally calculate the state - /// root. - /// - /// The hashes are calculated from `fork_block_number + 1` to `current_block_number`. - /// - /// The resulting state root is compared with `expected_state_root`. - pub fn insert_hashes( - &mut self, - range: RangeInclusive, - end_block_hash: H256, - expected_state_root: H256, - ) -> std::result::Result<(), TransactionError> { - // storage hashing stage - { - let lists = self.get_addresses_and_keys_of_changed_storages(range.clone())?; - let storages = self.get_plainstate_storages(lists.into_iter())?; - self.insert_storage_for_hashing(storages.into_iter())?; - } - - // account hashing stage - { - let lists = self.changed_accounts_with_range(range.clone())?; - let accounts = self.basic_accounts(lists.into_iter())?; - self.insert_account_for_hashing(accounts.into_iter())?; - } - - // merkle tree - { - let (state_root, trie_updates) = - StateRoot::incremental_root_with_updates(&self.tx, range.clone())?; - if state_root != expected_state_root { - return Err(TransactionError::StateRootMismatch { - got: state_root, - expected: expected_state_root, - block_number: *range.end(), - block_hash: end_block_hash, - }) - } - trie_updates.flush(&self.tx)?; - } - Ok(()) - } } impl<'this, TX: DbTx<'this>> AccountReader for DatabaseProvider<'this, TX> { @@ -1816,3 +1501,282 @@ impl<'this, TX: DbTxMut<'this>> StageCheckpointWriter for DatabaseProvider<'this Ok(()) } } + +impl<'this, TX: DbTx<'this>> StorageReader for DatabaseProvider<'this, TX> { + fn plainstate_storages( + &self, + addresses_with_keys: impl IntoIterator)>, + ) -> Result)>> { + let mut plain_storage = self.tx.cursor_dup_read::()?; + + addresses_with_keys + .into_iter() + .map(|(address, storage)| { + storage + .into_iter() + .map(|key| -> Result<_> { + Ok(plain_storage + .seek_by_key_subkey(address, key)? + .filter(|v| v.key == key) + .unwrap_or_else(|| StorageEntry { key, value: Default::default() })) + }) + .collect::>>() + .map(|storage| (address, storage)) + }) + .collect::>>() + } + + fn changed_storages_with_range( + &self, + range: RangeInclusive, + ) -> Result>> { + self.tx + .cursor_read::()? + .walk_range(BlockNumberAddress::range(range))? + // fold all storages and save its old state so we can remove it from HashedStorage + // it is needed as it is dup table. + .try_fold(BTreeMap::new(), |mut accounts: BTreeMap>, entry| { + let (BlockNumberAddress((_, address)), storage_entry) = entry?; + accounts.entry(address).or_default().insert(storage_entry.key); + Ok(accounts) + }) + } + + fn changed_storages_and_blocks_with_range( + &self, + range: RangeInclusive, + ) -> Result>> { + let mut changeset_cursor = self.tx.cursor_read::()?; + + let storage_changeset_lists = + changeset_cursor.walk_range(BlockNumberAddress::range(range))?.try_fold( + BTreeMap::new(), + |mut storages: BTreeMap<(Address, H256), Vec>, entry| -> Result<_> { + let (index, storage) = entry?; + storages + .entry((index.address(), storage.key)) + .or_default() + .push(index.block_number()); + Ok(storages) + }, + )?; + + Ok(storage_changeset_lists) + } +} + +impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HashingWriter for DatabaseProvider<'this, TX> { + fn insert_hashes( + &self, + range: RangeInclusive, + end_block_hash: H256, + expected_state_root: H256, + ) -> Result<()> { + // storage hashing stage + { + let lists = self.changed_storages_with_range(range.clone())?; + let storages = self.plainstate_storages(lists.into_iter())?; + self.insert_storage_for_hashing(storages.into_iter())?; + } + + // account hashing stage + { + let lists = self.changed_accounts_with_range(range.clone())?; + let accounts = self.basic_accounts(lists.into_iter())?; + self.insert_account_for_hashing(accounts.into_iter())?; + } + + // merkle tree + { + let (state_root, trie_updates) = + StateRoot::incremental_root_with_updates(&self.tx, range.clone()) + .map_err(Into::::into)?; + if state_root != expected_state_root { + return Err(ProviderError::StateRootMismatch { + got: state_root, + expected: expected_state_root, + block_number: *range.end(), + block_hash: end_block_hash, + } + .into()) + } + trie_updates.flush(&self.tx)?; + } + Ok(()) + } + + fn unwind_storage_hashing(&self, range: Range) -> Result<()> { + let mut hashed_storage = self.tx.cursor_dup_write::()?; + + // Aggregate all block changesets and make list of accounts that have been changed. + self.tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()? + .into_iter() + .rev() + // fold all account to get the old balance/nonces and account that needs to be removed + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap<(Address, H256), U256>, + (BlockNumberAddress((_, address)), storage_entry)| { + accounts.insert((address, storage_entry.key), storage_entry.value); + accounts + }, + ) + .into_iter() + // hash addresses and collect it inside sorted BTreeMap. + // We are doing keccak only once per address. + .map(|((address, key), value)| ((keccak256(address), keccak256(key)), value)) + .collect::>() + .into_iter() + // Apply values to HashedStorage (if Value is zero just remove it); + .try_for_each(|((hashed_address, key), value)| -> Result<()> { + if hashed_storage + .seek_by_key_subkey(hashed_address, key)? + .filter(|entry| entry.key == key) + .is_some() + { + hashed_storage.delete_current()?; + } + + if value != U256::ZERO { + hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; + } + Ok(()) + })?; + + Ok(()) + } + fn insert_storage_for_hashing( + &self, + storages: impl IntoIterator)>, + ) -> Result<()> { + // hash values + let hashed = storages.into_iter().fold(BTreeMap::new(), |mut map, (address, storage)| { + let storage = storage.into_iter().fold(BTreeMap::new(), |mut map, entry| { + map.insert(keccak256(entry.key), entry.value); + map + }); + map.insert(keccak256(address), storage); + map + }); + + let mut hashed_storage = self.tx.cursor_dup_write::()?; + // Hash the address and key and apply them to HashedStorage (if Storage is None + // just remove it); + hashed.into_iter().try_for_each(|(hashed_address, storage)| { + storage.into_iter().try_for_each(|(key, value)| -> Result<()> { + if hashed_storage + .seek_by_key_subkey(hashed_address, key)? + .filter(|entry| entry.key == key) + .is_some() + { + hashed_storage.delete_current()?; + } + + if value != U256::ZERO { + hashed_storage.upsert(hashed_address, StorageEntry { key, value })?; + } + Ok(()) + }) + })?; + Ok(()) + } +} + +impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider<'this, TX> { + fn calculate_history_indices(&self, range: RangeInclusive) -> Result<()> { + // account history stage + { + let indices = self.changed_accounts_and_blocks_with_range(range.clone())?; + self.insert_account_history_index(indices)?; + } + + // storage history stage + { + let indices = self.changed_storages_and_blocks_with_range(range)?; + self.insert_storage_history_index(indices)?; + } + + Ok(()) + } + fn insert_storage_history_index( + &self, + storage_transitions: BTreeMap<(Address, H256), Vec>, + ) -> Result<()> { + for ((address, storage_key), mut indices) in storage_transitions { + let mut last_shard = self.take_last_storage_shard(address, storage_key)?; + last_shard.append(&mut indices); + + // chunk indices and insert them in shards of N size. + let mut chunks = last_shard + .iter() + .chunks(storage_sharded_key::NUM_OF_INDICES_IN_SHARD) + .into_iter() + .map(|chunks| chunks.map(|i| *i as usize).collect::>()) + .collect::>(); + let last_chunk = chunks.pop(); + + // chunk indices and insert them in shards of N size. + chunks.into_iter().try_for_each(|list| { + self.tx.put::( + StorageShardedKey::new( + address, + storage_key, + *list.last().expect("Chuck does not return empty list") as BlockNumber, + ), + BlockNumberList::new(list).expect("Indices are presorted and not empty"), + ) + })?; + // Insert last list with u64::MAX + if let Some(last_list) = last_chunk { + self.tx.put::( + StorageShardedKey::new(address, storage_key, u64::MAX), + BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), + )?; + } + } + Ok(()) + } + fn unwind_storage_history_indices(&self, range: Range) -> Result { + let storage_changesets = self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()?; + let changesets = storage_changesets.len(); + + let last_indices = storage_changesets + .into_iter() + // reverse so we can get lowest block number where we need to unwind account. + .rev() + // fold all storages and get last block number + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap<(Address, H256), u64>, (index, storage)| { + // we just need address and lowest block number. + accounts.insert((index.address(), storage.key), index.block_number()); + accounts + }, + ); + + let mut cursor = self.tx.cursor_write::()?; + for ((address, storage_key), rem_index) in last_indices { + let shard_part = + unwind_storage_history_shards::(&mut cursor, address, storage_key, rem_index)?; + + // check last shard_part, if present, items needs to be reinserted. + if !shard_part.is_empty() { + // there are items in list + self.tx.put::( + StorageShardedKey::new(address, storage_key, u64::MAX), + BlockNumberList::new(shard_part) + .expect("There is at least one element in list and it is sorted."), + )?; + } + } + + Ok(changesets) + } +} diff --git a/crates/storage/provider/src/traits/hashing.rs b/crates/storage/provider/src/traits/hashing.rs new file mode 100644 index 000000000000..1ebe58bec72f --- /dev/null +++ b/crates/storage/provider/src/traits/hashing.rs @@ -0,0 +1,31 @@ +use auto_impl::auto_impl; +use reth_db::models::BlockNumberAddress; +use reth_interfaces::Result; +use reth_primitives::{Address, BlockNumber, StorageEntry, H256}; +use std::ops::{Range, RangeInclusive}; + +/// Hashing Writer +#[auto_impl(&, Arc, Box)] +pub trait HashingWriter: Send + Sync { + /// Unwind and clear storage hashing + fn unwind_storage_hashing(&self, range: Range) -> Result<()>; + + /// iterate over storages and insert them to hashing table + fn insert_storage_for_hashing( + &self, + storages: impl IntoIterator)>, + ) -> Result<()>; + + /// Calculate the hashes of all changed accounts and storages, and finally calculate the state + /// root. + /// + /// The hashes are calculated from `fork_block_number + 1` to `current_block_number`. + /// + /// The resulting state root is compared with `expected_state_root`. + fn insert_hashes( + &self, + range: RangeInclusive, + end_block_hash: H256, + expected_state_root: H256, + ) -> Result<()>; +} diff --git a/crates/storage/provider/src/traits/history.rs b/crates/storage/provider/src/traits/history.rs new file mode 100644 index 000000000000..d79bdd1c3fae --- /dev/null +++ b/crates/storage/provider/src/traits/history.rs @@ -0,0 +1,26 @@ +use auto_impl::auto_impl; +use reth_db::models::BlockNumberAddress; +use reth_interfaces::Result; +use reth_primitives::{Address, BlockNumber, H256}; +use std::{ + collections::BTreeMap, + ops::{Range, RangeInclusive}, +}; + +/// History Writer +#[auto_impl(&, Arc, Box)] +pub trait HistoryWriter: Send + Sync { + /// Unwind and clear storage history indices. + /// + /// Returns number of changesets walked. + fn unwind_storage_history_indices(&self, range: Range) -> Result; + + /// Insert storage change index to database. Used inside StorageHistoryIndex stage + fn insert_storage_history_index( + &self, + storage_transitions: BTreeMap<(Address, H256), Vec>, + ) -> Result<()>; + + /// Read account/storage changesets and update account/storage history indices. + fn calculate_history_indices(&self, range: RangeInclusive) -> Result<()>; +} diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index 07b918a01acf..c4805a9cd6f0 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -3,6 +3,9 @@ mod account; pub use account::{AccountExtReader, AccountReader, AccountWriter}; +mod storage; +pub use storage::StorageReader; + mod block; pub use block::{BlockProvider, BlockProviderIdExt, BlockSource}; @@ -47,3 +50,9 @@ pub use chain::{ mod stage_checkpoint; pub use stage_checkpoint::{StageCheckpointReader, StageCheckpointWriter}; + +mod hashing; +pub use hashing::HashingWriter; + +mod history; +pub use history::HistoryWriter; diff --git a/crates/storage/provider/src/traits/storage.rs b/crates/storage/provider/src/traits/storage.rs new file mode 100644 index 000000000000..87782ba9bf8f --- /dev/null +++ b/crates/storage/provider/src/traits/storage.rs @@ -0,0 +1,33 @@ +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::RangeInclusive, +}; + +use auto_impl::auto_impl; +use reth_interfaces::Result; +use reth_primitives::{Address, BlockNumber, StorageEntry, H256}; + +/// Storage reader +#[auto_impl(&, Arc, Box)] +pub trait StorageReader: Send + Sync { + /// Get plainstate storages for addresses and storage keys. + fn plainstate_storages( + &self, + addresses_with_keys: impl IntoIterator)>, + ) -> Result)>>; + + /// Iterate over storage changesets and return all storage slots that were changed. + fn changed_storages_with_range( + &self, + range: RangeInclusive, + ) -> Result>>; + + /// Iterate over storage changesets and return all storage slots that were changed alongside + /// each specific set of blocks. + /// + /// NOTE: Get inclusive range of blocks. + fn changed_storages_and_blocks_with_range( + &self, + range: RangeInclusive, + ) -> Result>>; +} diff --git a/crates/storage/provider/src/transaction.rs b/crates/storage/provider/src/transaction.rs index 9614f5e1f994..c64ae629f3a2 100644 --- a/crates/storage/provider/src/transaction.rs +++ b/crates/storage/provider/src/transaction.rs @@ -3,47 +3,6 @@ use reth_primitives::{BlockHash, BlockNumber, H256}; use reth_trie::StateRootError; use std::fmt::Debug; -/// An error that can occur when using the transaction container -#[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] -pub enum TransactionError { - /// The transaction encountered a database error. - #[error(transparent)] - Database(#[from] DbError), - /// The transaction encountered a database integrity error. - #[error(transparent)] - DatabaseIntegrity(#[from] ProviderError), - /// The trie error. - #[error(transparent)] - TrieError(#[from] StateRootError), - /// Root mismatch - #[error("Merkle trie root mismatch at #{block_number} ({block_hash:?}). Got: {got:?}. Expected: {expected:?}")] - StateRootMismatch { - /// Expected root - expected: H256, - /// Calculated root - got: H256, - /// Block number - block_number: BlockNumber, - /// Block hash - block_hash: BlockHash, - }, - /// Root mismatch during unwind - #[error("Unwind merkle trie root mismatch at #{block_number} ({block_hash:?}). Got: {got:?}. Expected: {expected:?}")] - UnwindStateRootMismatch { - /// Expected root - expected: H256, - /// Calculated root - got: H256, - /// Target block number - block_number: BlockNumber, - /// Block hash - block_hash: BlockHash, - }, - /// Internal interfaces error - #[error("Internal error")] - InternalError(#[from] reth_interfaces::Error), -} - #[cfg(test)] mod test { use crate::{ diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index e9d370c0ab2d..d32fe02e27d8 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -1443,11 +1443,10 @@ mod tests { let mut pool = AllTransactions::default(); let tx = MockTransaction::eip1559().inc_price().inc_limit(); let first = f.validated(tx.clone()); - let _res = pool.insert_tx(first.clone(), on_chain_balance, on_chain_nonce); + let _res = pool.insert_tx(first, on_chain_balance, on_chain_nonce); let mut replacement = f.validated(tx.rng_hash()); replacement.transaction = replacement.transaction.decr_price(); - let err = - pool.insert_tx(replacement.clone(), on_chain_balance, on_chain_nonce).unwrap_err(); + let err = pool.insert_tx(replacement, on_chain_balance, on_chain_nonce).unwrap_err(); assert!(matches!(err, InsertErr::Underpriced { .. })); } From 0ebdba6c642c69be3cce672052e25d370384875f Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 22 Jun 2023 15:21:35 +0100 Subject: [PATCH 143/216] feat(tasks): pass downcasted error from panicked task (#3319) --- crates/tasks/src/lib.rs | 60 +++++++++++++++++++++++++++++++---------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index 79ce940279b2..31e785979ff4 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -17,6 +17,8 @@ use futures_util::{ pin_mut, Future, FutureExt, TryFutureExt, }; use std::{ + any::Any, + fmt::{Display, Formatter}, pin::Pin, task::{ready, Context, Poll}, }; @@ -136,9 +138,9 @@ pub struct TaskManager { /// See [`Handle`] docs. handle: Handle, /// Sender half for sending panic signals to this type - panicked_tasks_tx: UnboundedSender<&'static str>, + panicked_tasks_tx: UnboundedSender, /// Listens for panicked tasks - panicked_tasks_rx: UnboundedReceiver<&'static str>, + panicked_tasks_rx: UnboundedReceiver, /// The [Signal] to fire when all tasks should be shutdown. /// /// This is fired on drop. @@ -177,14 +179,41 @@ impl Future for TaskManager { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let err = ready!(self.get_mut().panicked_tasks_rx.poll_recv(cx)); - Poll::Ready(err.map(PanickedTaskError).expect("stream can not end")) + Poll::Ready(err.expect("stream can not end")) } } -/// Error with the name of the task that panicked. +/// Error with the name of the task that panicked and an error downcasted to string, if possible. #[derive(Debug, thiserror::Error)] -#[error("Critical task panicked: `{0}`")] -pub struct PanickedTaskError(&'static str); +pub struct PanickedTaskError { + task_name: &'static str, + error: Option, +} + +impl Display for PanickedTaskError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let task_name = self.task_name; + if let Some(error) = &self.error { + write!(f, "Critical task `{task_name}` panicked: `{error}`") + } else { + write!(f, "Critical task `{task_name}` panicked") + } + } +} + +impl PanickedTaskError { + fn new(task_name: &'static str, error: Box) -> Self { + let error = match error.downcast::() { + Ok(value) => Some(*value), + Err(error) => match error.downcast::<&str>() { + Ok(value) => Some(value.to_string()), + Err(_) => None, + }, + }; + + Self { task_name, error } + } +} /// A type that can spawn new tokio tasks #[derive(Debug, Clone)] @@ -196,7 +225,7 @@ pub struct TaskExecutor { /// Receiver of the shutdown signal. on_shutdown: Shutdown, /// Sender half for sending panic signals to this type - panicked_tasks_tx: UnboundedSender<&'static str>, + panicked_tasks_tx: UnboundedSender, // Task Executor Metrics metrics: TaskExecutorMetrics, } @@ -298,9 +327,10 @@ impl TaskExecutor { // wrap the task in catch unwind let task = std::panic::AssertUnwindSafe(fut) .catch_unwind() - .inspect_err(move |res| { - error!("Critical task `{name}` panicked: {res:?}"); - let _ = panicked_tasks_tx.send(name); + .map_err(move |error| { + let task_error = PanickedTaskError::new(name, error); + error!("{task_error}"); + let _ = panicked_tasks_tx.send(task_error); }) .in_current_span(); @@ -352,9 +382,10 @@ impl TaskExecutor { // wrap the task in catch unwind let task = std::panic::AssertUnwindSafe(fut) .catch_unwind() - .inspect_err(move |res| { - error!("Critical task `{name}` panicked: {res:?}"); - let _ = panicked_tasks_tx.send(name); + .map_err(move |error| { + let task_error = PanickedTaskError::new(name, error); + error!("{task_error}"); + let _ = panicked_tasks_tx.send(task_error); }) .map(|_| ()) .in_current_span(); @@ -428,7 +459,8 @@ mod tests { runtime.block_on(async move { let err = manager.await; - assert_eq!(err.0, "this is a critical task"); + assert_eq!(err.task_name, "this is a critical task"); + assert_eq!(err.error, Some("intentionally panic".to_string())); }) } From 2aa1cec907b069afc727a486fa9da7bf0588b551 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 22 Jun 2023 15:50:16 +0100 Subject: [PATCH 144/216] feat(rpc): report JWT path in case of an IO read error (#3324) --- Cargo.lock | 2 ++ crates/rpc/rpc/Cargo.toml | 2 ++ crates/rpc/rpc/src/layers/jwt_secret.rs | 23 +++++++++++++++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39b95fa1a0a7..b474f250914e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5634,6 +5634,7 @@ dependencies = [ name = "reth-rpc" version = "0.1.0-alpha.1" dependencies = [ + "assert_matches", "async-trait", "bytes", "ethers-core", @@ -5663,6 +5664,7 @@ dependencies = [ "secp256k1", "serde", "serde_json", + "tempfile", "thiserror", "tokio", "tokio-stream", diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 30b94277f700..9fdf2c913fa6 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -61,3 +61,5 @@ futures = { workspace = true } [dev-dependencies] jsonrpsee = { version = "0.18", features = ["client"] } +assert_matches = "1.5.0" +tempfile = "3.5.0" \ No newline at end of file diff --git a/crates/rpc/rpc/src/layers/jwt_secret.rs b/crates/rpc/rpc/src/layers/jwt_secret.rs index abca0ff50a29..589308e9d30c 100644 --- a/crates/rpc/rpc/src/layers/jwt_secret.rs +++ b/crates/rpc/rpc/src/layers/jwt_secret.rs @@ -3,7 +3,7 @@ use jsonwebtoken::{decode, errors::ErrorKind, Algorithm, DecodingKey, Validation use rand::Rng; use serde::{Deserialize, Serialize}; use std::{ - path::Path, + path::{Path, PathBuf}, time::{Duration, SystemTime, UNIX_EPOCH}, }; use thiserror::Error; @@ -26,6 +26,10 @@ pub enum JwtError { MissingOrInvalidAuthorizationHeader, #[error("JWT decoding error {0}")] JwtDecodingError(String), + #[error("IO error occurred while reading {path}: {err}")] + IORead { err: std::io::Error, path: PathBuf }, + #[error("IO error occurred while writing {path}: {err}")] + IOWrite { err: std::io::Error, path: PathBuf }, #[error("An I/O error occurred: {0}")] IOError(#[from] std::io::Error), } @@ -76,7 +80,8 @@ impl JwtSecret { /// I/O or secret validation errors might occur during read operations in the form of /// a [`JwtError`]. pub fn from_file(fpath: &Path) -> Result { - let hex = std::fs::read_to_string(fpath)?; + let hex = std::fs::read_to_string(fpath) + .map_err(|err| JwtError::IORead { err, path: fpath.to_path_buf() })?; let secret = JwtSecret::from_hex(hex)?; Ok(secret) } @@ -92,7 +97,8 @@ impl JwtSecret { let secret = JwtSecret::random(); let bytes = &secret.0; let hex = hex::encode(bytes); - std::fs::write(fpath, hex)?; + std::fs::write(fpath, hex) + .map_err(|err| JwtError::IOWrite { err, path: fpath.to_path_buf() })?; Ok(secret) } } @@ -195,12 +201,14 @@ impl Claims { mod tests { use super::{Claims, JwtError, JwtSecret}; use crate::layers::jwt_secret::JWT_MAX_IAT_DIFF; + use assert_matches::assert_matches; use hex::encode as hex_encode; use jsonwebtoken::{encode, Algorithm, EncodingKey, Header}; use std::{ path::Path, time::{Duration, SystemTime, UNIX_EPOCH}, }; + use tempfile::tempdir; #[test] fn from_hex() { @@ -367,10 +375,17 @@ mod tests { fn provided_file_not_exists() { let fpath = Path::new("secret3.hex"); let result = JwtSecret::from_file(fpath); - assert!(result.is_err()); + assert_matches!(result, Err(JwtError::IORead {err: _, path}) if path == fpath.to_path_buf()); assert!(!exists(fpath)); } + #[test] + fn provided_file_is_a_directory() { + let dir = tempdir().unwrap(); + let result = JwtSecret::from_file(dir.path()); + assert_matches!(result, Err(JwtError::IORead {err: _, path}) if path == dir.into_path()); + } + fn hex(secret: &JwtSecret) -> String { hex::encode(secret.0) } From 9e72cbf6b44fd195738c6d85f461304578c8156c Mon Sep 17 00:00:00 2001 From: Waylon Jepsen <57912727+0xJepsen@users.noreply.github.com> Date: Thu, 22 Jun 2023 08:55:34 -0600 Subject: [PATCH 145/216] feat: js built in functions (#3161) Co-authored-by: Matthias Seitz --- .../src/tracing/js/bindings.rs | 22 +++- .../src/tracing/js/builtins.rs | 121 +++++++++++++++++- .../revm-inspectors/src/tracing/js/mod.rs | 23 ++-- 3 files changed, 148 insertions(+), 18 deletions(-) diff --git a/crates/revm/revm-inspectors/src/tracing/js/bindings.rs b/crates/revm/revm-inspectors/src/tracing/js/bindings.rs index bfcd9c086c16..a71720ef8294 100644 --- a/crates/revm/revm-inspectors/src/tracing/js/bindings.rs +++ b/crates/revm/revm-inspectors/src/tracing/js/bindings.rs @@ -208,12 +208,12 @@ impl MemoryObj { /// Represents the opcode object #[derive(Debug)] -pub(crate) struct OpObj(pub(crate) OpCode); +pub(crate) struct OpObj(pub(crate) u8); impl OpObj { pub(crate) fn into_js_object(self, context: &mut Context<'_>) -> JsResult { let obj = JsObject::default(); - let value = self.0.u8(); + let value = self.0; let is_push = (PUSH0..=PUSH32).contains(&value); let to_number = FunctionObjectBuilder::new( @@ -233,7 +233,17 @@ impl OpObj { let to_string = FunctionObjectBuilder::new( context, NativeFunction::from_copy_closure(move |_this, _args, _ctx| { - let s = OpCode::try_from_u8(value).expect("invalid opcode").to_string(); + let op = OpCode::try_from_u8(value) + .or_else(|| { + // if the opcode is invalid, we'll use the invalid opcode to represent it + // because this is invoked before the opcode is + // executed, the evm will eventually return a `Halt` + // with invalid/unknown opcode as result + let invalid_opcode = 0xfe; + OpCode::try_from_u8(invalid_opcode) + }) + .expect("is valid opcode;"); + let s = op.to_string(); Ok(JsValue::from(s)) }), ) @@ -247,6 +257,12 @@ impl OpObj { } } +impl From for OpObj { + fn from(op: u8) -> Self { + Self(op) + } +} + /// Represents the stack object #[derive(Debug, Clone)] pub(crate) struct StackObj(pub(crate) Stack); diff --git a/crates/revm/revm-inspectors/src/tracing/js/builtins.rs b/crates/revm/revm-inspectors/src/tracing/js/builtins.rs index f083ba5aab8c..bdba6945a64f 100644 --- a/crates/revm/revm-inspectors/src/tracing/js/builtins.rs +++ b/crates/revm/revm-inspectors/src/tracing/js/builtins.rs @@ -5,7 +5,12 @@ use boa_engine::{ property::Attribute, Context, JsArgs, JsError, JsNativeError, JsResult, JsString, JsValue, NativeFunction, Source, }; -use reth_primitives::{hex, Address, H256, U256}; +use boa_gc::{empty_trace, Finalize, Trace}; +use reth_primitives::{ + contract::{create2_address_from_code, create_address}, + hex, keccak256, Address, H256, U256, +}; +use std::collections::HashSet; /// bigIntegerJS is the minified version of . pub(crate) const BIG_INT_JS: &str = include_str!("bigint.js"); @@ -15,8 +20,12 @@ pub(crate) fn register_builtins(ctx: &mut Context<'_>) -> JsResult<()> { let big_int = ctx.eval(Source::from_bytes(BIG_INT_JS.as_bytes()))?; ctx.register_global_property("bigint", big_int, Attribute::all())?; ctx.register_global_builtin_callable("toHex", 1, NativeFunction::from_fn_ptr(to_hex))?; + ctx.register_global_callable("toWord", 1, NativeFunction::from_fn_ptr(to_word))?; + ctx.register_global_callable("toAddress", 1, NativeFunction::from_fn_ptr(to_address))?; + ctx.register_global_callable("toContract", 2, NativeFunction::from_fn_ptr(to_contract))?; + ctx.register_global_callable("toContract2", 3, NativeFunction::from_fn_ptr(to_contract2))?; - // TODO: register toWord, toAddress toContract toContract2 isPrecompiled slice + // TODO: isPrecompiled slice Ok(()) } @@ -122,6 +131,85 @@ pub(crate) fn to_bigint(value: U256, ctx: &mut Context<'_>) -> JsResult ctx, ) } +/// Takes three arguments: a JavaScript value that represents the sender's address, a string salt +/// value, and the initcode for the contract. Compute the address of a contract created by the +/// sender with the given salt and code hash, then converts the resulting address back into a byte +/// buffer for output. +pub(crate) fn to_contract2( + _: &JsValue, + args: &[JsValue], + ctx: &mut Context<'_>, +) -> JsResult { + // Extract the sender's address, salt and initcode from the arguments + let from = args.get_or_undefined(0).clone(); + let salt = match args.get_or_undefined(1).to_string(ctx) { + Ok(js_string) => { + let buf = hex_decode_js_string(js_string)?; + bytes_to_hash(buf) + } + Err(_) => { + return Err(JsError::from_native(JsNativeError::typ().with_message("invalid salt type"))) + } + }; + + let initcode = args.get_or_undefined(2).clone(); + + // Convert the sender's address to a byte buffer and then to an Address + let buf = from_buf(from, ctx)?; + let addr = bytes_to_address(buf); + + // Convert the initcode to a byte buffer + let code_buf = from_buf(initcode, ctx)?; + // Compute the code hash + let code_hash = keccak256(code_buf); + + // Compute the contract address + let contract_addr = create2_address_from_code(addr, salt, code_hash.into()); + + // Convert the contract address to a byte buffer and return it as an ArrayBuffer + to_buf_value(contract_addr.0.to_vec(), ctx) +} + +/// Converts the sender's address to a byte buffer +pub(crate) fn to_contract( + _: &JsValue, + args: &[JsValue], + ctx: &mut Context<'_>, +) -> JsResult { + // Extract the sender's address and nonce from the arguments + let from = args.get_or_undefined(0).clone(); + let nonce = args.get_or_undefined(1).to_number(ctx)? as u64; + + // Convert the sender's address to a byte buffer and then to an Address + let buf = from_buf(from, ctx)?; + let addr = bytes_to_address(buf); + + // Compute the contract address + let contract_addr = create_address(addr, nonce); + + // Convert the contract address to a byte buffer and return it as an ArrayBuffer + to_buf_value(contract_addr.0.to_vec(), ctx) +} + +/// Converts a buffer type to an address +pub(crate) fn to_address( + _: &JsValue, + args: &[JsValue], + ctx: &mut Context<'_>, +) -> JsResult { + let val = args.get_or_undefined(0).clone(); + let buf = from_buf(val, ctx)?; + let address = bytes_to_address(buf); + to_buf_value(address.0.to_vec(), ctx) +} + +/// Converts a buffer type to a word +pub(crate) fn to_word(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult { + let val = args.get_or_undefined(0).clone(); + let buf = from_buf(val, ctx)?; + let hash = bytes_to_hash(buf); + to_buf_value(hash.0.to_vec(), ctx) +} /// Converts a buffer type to a hex string pub(crate) fn to_hex(_: &JsValue, args: &[JsValue], ctx: &mut Context<'_>) -> JsResult { @@ -146,6 +234,35 @@ fn hex_decode_js_string(js_string: JsString) -> JsResult> { } } +/// A container for all precompile addresses used for the `isPrecompiled` global callable. +#[derive(Debug, Clone)] +pub(crate) struct PrecompileList(pub(crate) HashSet
); + +impl PrecompileList { + /// Registers the global callable `isPrecompiled` + pub(crate) fn register_callable(self, ctx: &mut Context<'_>) -> JsResult<()> { + let is_precompiled = NativeFunction::from_copy_closure_with_captures( + move |_this, args, precompiles, ctx| { + let val = args.get_or_undefined(0).clone(); + let buf = from_buf(val, ctx)?; + let addr = bytes_to_address(buf); + Ok(precompiles.0.contains(&addr).into()) + }, + self, + ); + + ctx.register_global_callable("isPrecompiled", 1, is_precompiled)?; + + Ok(()) + } +} + +impl Finalize for PrecompileList {} + +unsafe impl Trace for PrecompileList { + empty_trace!(); +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/revm/revm-inspectors/src/tracing/js/mod.rs b/crates/revm/revm-inspectors/src/tracing/js/mod.rs index 450cbb42821f..e9bc8f390046 100644 --- a/crates/revm/revm-inspectors/src/tracing/js/mod.rs +++ b/crates/revm/revm-inspectors/src/tracing/js/mod.rs @@ -3,10 +3,9 @@ use crate::tracing::{ js::{ bindings::{ - CallFrame, Contract, EvmContext, EvmDb, FrameResult, MemoryObj, OpObj, StackObj, - StepLog, + CallFrame, Contract, EvmContext, EvmDb, FrameResult, MemoryObj, StackObj, StepLog, }, - builtins::register_builtins, + builtins::{register_builtins, PrecompileList}, }, types::CallKind, utils::get_create_address, @@ -16,7 +15,6 @@ use reth_primitives::{bytes::Bytes, Account, Address, H256, U256}; use revm::{ interpreter::{ return_revert, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, - OpCode, }, primitives::{Env, ExecutionResult, Output, ResultAndState, TransactTo, B160, B256}, Database, EVMData, Inspector, @@ -275,9 +273,14 @@ where fn initialize_interp( &mut self, _interp: &mut Interpreter, - _data: &mut EVMData<'_, DB>, + data: &mut EVMData<'_, DB>, _is_static: bool, ) -> InstructionResult { + let precompiles = + PrecompileList(data.precompiles.addresses().into_iter().map(Into::into).collect()); + + let _ = precompiles.register_callable(&mut self.ctx); + InstructionResult::Continue } @@ -296,10 +299,7 @@ where let pc = interp.program_counter(); let step = StepLog { stack: StackObj(interp.stack.clone()), - op: OpObj( - OpCode::try_from_u8(interp.contract.bytecode.bytecode()[pc]) - .expect("is valid opcode;"), - ), + op: interp.contract.bytecode.bytecode()[pc].into(), memory: MemoryObj(interp.memory.clone()), pc: pc as u64, gas_remaining: interp.gas.remaining(), @@ -342,10 +342,7 @@ where let pc = interp.program_counter(); let step = StepLog { stack: StackObj(interp.stack.clone()), - op: OpObj( - OpCode::try_from_u8(interp.contract.bytecode.bytecode()[pc]) - .expect("is valid opcode;"), - ), + op: interp.contract.bytecode.bytecode()[pc].into(), memory: MemoryObj(interp.memory.clone()), pc: pc as u64, gas_remaining: interp.gas.remaining(), From cb298788df3eb02dd3f9ec2a851301311006d3de Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Thu, 22 Jun 2023 18:06:56 +0100 Subject: [PATCH 146/216] chore(ci): add missing Hive `rpc-compat` methods (#3331) --- .github/workflows/hive.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index b37a905b96e7..694e065451c0 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -90,7 +90,7 @@ jobs: experimental: true # eth_ rpc methods - sim: ethereum/rpc-compat - include: [eth_blockNumber, eth_call, eth_chainId, eth_createAccessList, eth_estimateGas, eth_feeHistory, eth_getBalance, eth_getBlockBy, eth_getBlockTransactionCountBy, eth_getCode, eth_getStorage, eth_getTransactionBy, eth_getTransactionCount, eth_sendRawTransaction, eth_syncing] + include: [eth_blockNumber, eth_call, eth_chainId, eth_createAccessList, eth_estimateGas, eth_feeHistory, eth_getBalance, eth_getBlockBy, eth_getBlockTransactionCountBy, eth_getCode, eth_getStorage, eth_getTransactionBy, eth_getTransactionCount, eth_getTransactionReceipt, eth_sendRawTransaction, eth_syncing] experimental: true # not running eth_getProof tests because we do not support # eth_getProof yet From 052f5cb97b4f6268efd3b2d7538bdfd99a0275dd Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 22 Jun 2023 19:15:22 +0200 Subject: [PATCH 147/216] test: add another genesis test (#3334) --- crates/primitives/src/chain/spec.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/chain/spec.rs b/crates/primitives/src/chain/spec.rs index 84f5901e3c68..4e888a5c10d1 100644 --- a/crates/primitives/src/chain/spec.rs +++ b/crates/primitives/src/chain/spec.rs @@ -866,8 +866,9 @@ where #[cfg(test)] mod tests { use crate::{ - AllGenesisFormats, Chain, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, - ForkHash, ForkId, Genesis, Hardfork, Head, GOERLI, H256, MAINNET, SEPOLIA, U256, + Address, AllGenesisFormats, Chain, ChainSpec, ChainSpecBuilder, DisplayHardforks, + ForkCondition, ForkHash, ForkId, Genesis, Hardfork, Head, GOERLI, H256, MAINNET, SEPOLIA, + U256, }; use bytes::BytesMut; use ethers_core::types as EtherType; @@ -1546,4 +1547,15 @@ Post-merge hard forks (timestamp based): let hash = chainspec.genesis_header().hash_slow(); assert_eq!(hash, expected_hash); } + + #[test] + fn test_parse_genesis_json() { + let s = r#"{"config":{"ethash":{},"chainId":1337,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"berlinBlock":0,"londonBlock":0,"terminalTotalDifficulty":0,"terminalTotalDifficultyPassed":true,"shanghaiTime":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x","gasLimit":"0x4c4b40","difficulty":"0x1","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"658bdf435d810c91414ec09147daa6db62406379":{"balance":"0x487a9a304539440000"},"aa00000000000000000000000000000000000000":{"code":"0x6042","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x1","nonce":"0x1"},"bb00000000000000000000000000000000000000":{"code":"0x600154600354","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000000","0x0100000000000000000000000000000000000000000000000000000000000000":"0x0100000000000000000000000000000000000000000000000000000000000000","0x0200000000000000000000000000000000000000000000000000000000000000":"0x0200000000000000000000000000000000000000000000000000000000000000","0x0300000000000000000000000000000000000000000000000000000000000000":"0x0000000000000000000000000000000000000000000000000000000000000303"},"balance":"0x2","nonce":"0x1"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":"0x3b9aca00"}"#; + let genesis: Genesis = serde_json::from_str(s).unwrap(); + let acc = genesis + .alloc + .get(&"0xaa00000000000000000000000000000000000000".parse::
().unwrap()) + .unwrap(); + assert_eq!(acc.balance, U256::from(1)); + } } From 559018f2cdd119e58c50b37ad5ec5f221c008e8b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 22 Jun 2023 19:30:59 +0200 Subject: [PATCH 148/216] feat: add U64HexOrNumber type (#3329) --- crates/primitives/src/serde_helper/num.rs | 105 +++++++++++++++++++++- 1 file changed, 103 insertions(+), 2 deletions(-) diff --git a/crates/primitives/src/serde_helper/num.rs b/crates/primitives/src/serde_helper/num.rs index beab749ab3b2..312f287124be 100644 --- a/crates/primitives/src/serde_helper/num.rs +++ b/crates/primitives/src/serde_helper/num.rs @@ -1,9 +1,93 @@ //! Numeric helpers -use crate::U256; -use serde::{de, Deserialize, Deserializer}; +use crate::{U256, U64}; +use serde::{de, Deserialize, Deserializer, Serialize}; use std::str::FromStr; +/// A `u64` wrapper type that deserializes from hex or a u64 and serializes as hex. +/// +/// +/// ```rust +/// use reth_primitives::serde_helper::num::U64HexOrNumber; +/// let number_json = "100"; +/// let hex_json = "\"0x64\""; +/// +/// let number: U64HexOrNumber = serde_json::from_str(number_json).unwrap(); +/// let hex: U64HexOrNumber = serde_json::from_str(hex_json).unwrap(); +/// assert_eq!(number, hex); +/// assert_eq!(hex.as_u64(), 100); +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)] +pub struct U64HexOrNumber(U64); + +impl U64HexOrNumber { + /// Returns the wrapped u64 + pub fn as_u64(self) -> u64 { + self.0.as_u64() + } +} + +impl From for U64HexOrNumber { + fn from(value: u64) -> Self { + Self(U64::from(value)) + } +} + +impl From for U64HexOrNumber { + fn from(value: U64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(value: U64HexOrNumber) -> Self { + value.as_u64() + } +} + +impl From for U64 { + fn from(value: U64HexOrNumber) -> Self { + value.0 + } +} + +impl<'de> Deserialize<'de> for U64HexOrNumber { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum NumberOrHexU64 { + Hex(U64), + Int(u64), + } + match NumberOrHexU64::deserialize(deserializer)? { + NumberOrHexU64::Int(val) => Ok(val.into()), + NumberOrHexU64::Hex(val) => Ok(val.into()), + } + } +} + +/// serde functions for handling primitive `u64` as [U64](crate::U64) +pub mod u64_hex_or_decimal { + use crate::serde_helper::num::U64HexOrNumber; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + /// Deserializes an `u64` accepting a hex quantity string with optional 0x prefix or + /// a number + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + U64HexOrNumber::deserialize(deserializer).map(Into::into) + } + + /// Serializes u64 as hex string + pub fn serialize(value: &u64, s: S) -> Result { + U64HexOrNumber::from(*value).serialize(s) + } +} /// Deserializes the input into an `Option`, using [`from_int_or_hex`] to deserialize the /// inner value. pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result, D::Error> @@ -85,4 +169,21 @@ mod tests { assert_eq!(int_val.0, Some(u256_val)); }); } + + #[test] + fn serde_hex_or_number_u64() { + #[derive(Debug, Deserialize, PartialEq, Eq)] + struct V(U64HexOrNumber); + + proptest::proptest!(|(value: u64)| { + let val = U64::from(value); + + let num_obj = serde_json::to_string(&value).unwrap(); + let hex_obj = serde_json::to_string(&val).unwrap(); + + let int_val:V = serde_json::from_str(&num_obj).unwrap(); + let hex_val = serde_json::from_str(&hex_obj).unwrap(); + assert_eq!(int_val, hex_val); + }); + } } From 164d3abddf461ab52c58d52eac15ea997467d525 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Fri, 23 Jun 2023 02:49:31 +0900 Subject: [PATCH 149/216] docs: fix typo in config.md (#3333) --- book/run/config.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/run/config.md b/book/run/config.md index 2935e418f3a8..fe9be5bf4a4b 100644 --- a/book/run/config.md +++ b/book/run/config.md @@ -142,7 +142,7 @@ max_changes = 5000000 Either one of `max_blocks` or `max_changes` must be specified, and both can also be specified at the same time: - If only `max_blocks` is specified, reth will execute (up to) that amount of blocks before writing to disk. -- If only `max_changes` is specified, reth will execute as many blocks as possible until the target amount of state transitions have occured before writing to disk. +- If only `max_changes` is specified, reth will execute as many blocks as possible until the target amount of state transitions have occurred before writing to disk. - If both are specified, then the first threshold to be hit will determine when the results are written to disk. Lower values correspond to more frequent disk writes, but also lower memory consumption. A lower value also negatively impacts sync speed, since reth keeps a cache around for the entire duration of blocks executed in the same range. @@ -328,4 +328,4 @@ secs = 120 nanos = 0 ``` -[TOML]: https://toml.io/ \ No newline at end of file +[TOML]: https://toml.io/ From 0dfb757a160f858fa4047d1e58b6c8ffe35194b4 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Thu, 22 Jun 2023 13:54:41 -0400 Subject: [PATCH 150/216] fix: reorder blockchain tree row (#3337) --- etc/grafana/dashboards/overview.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 9d4ac8bea733..05f53a780387 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -3201,7 +3201,7 @@ "h": 1, "w": 24, "x": 0, - "y": 101 + "y": 135 }, "id": 68, "panels": [ From 6810cd1295f99e2bec87c06023cb9cd45700793e Mon Sep 17 00:00:00 2001 From: Mateusz Date: Thu, 22 Jun 2023 20:04:01 +0200 Subject: [PATCH 151/216] feat(db): add helper enum for table name (#2935) Co-authored-by: Matthias Seitz --- bin/reth/src/db/get.rs | 94 ++++---- bin/reth/src/db/list.rs | 85 +++++++ bin/reth/src/db/mod.rs | 99 +-------- bin/reth/src/prometheus_exporter.rs | 10 +- bin/reth/src/utils.rs | 4 +- crates/storage/db/src/abstraction/table.rs | 6 +- .../storage/db/src/implementation/mdbx/mod.rs | 11 +- .../storage/db/src/implementation/mdbx/tx.rs | 12 +- crates/storage/db/src/tables/mod.rs | 210 +++++++++++++++--- crates/storage/db/src/tables/raw.rs | 4 +- 10 files changed, 334 insertions(+), 201 deletions(-) create mode 100644 bin/reth/src/db/list.rs diff --git a/bin/reth/src/db/get.rs b/bin/reth/src/db/get.rs index 32ede8c7a12c..5e4b127b37f6 100644 --- a/bin/reth/src/db/get.rs +++ b/bin/reth/src/db/get.rs @@ -1,8 +1,7 @@ use crate::utils::DbTool; use clap::Parser; -use eyre::WrapErr; -use reth_db::{database::Database, table::Table, tables}; -use serde::Deserialize; + +use reth_db::{database::Database, table::Table, TableType, TableViewer, Tables}; use tracing::error; /// The arguments for the `reth db get` command @@ -12,75 +11,60 @@ pub struct Command { /// /// NOTE: The dupsort tables are not supported now. #[arg()] - pub table: String, // TODO: Convert to enum + pub table: Tables, - /// The key to get content for + /// The key to get content for #[arg(value_parser = maybe_json_value_parser)] pub key: String, } impl Command { /// Execute `db get` command - pub fn execute(self, mut tool: DbTool<'_, DB>) -> eyre::Result<()> { - macro_rules! table_get { - ([$($table:ident),*]) => { - match self.table.as_str() { - $(stringify!($table) => { - let table_key = self.table_key::().wrap_err("Could not parse the given table key.")?; - - match tool.get::(table_key)? { - Some(content) => { - println!("{}", serde_json::to_string_pretty(&content)?); - } - None => { - error!(target: "reth::cli", "No content for the given table key."); - }, - }; - return Ok(()); - },)* - _ => { - error!(target: "reth::cli", "Unknown or unsupported table."); - return Ok(()); - } - } - } + pub fn execute(self, tool: &DbTool<'_, DB>) -> eyre::Result<()> { + if self.table.table_type() == TableType::DupSort { + error!(target: "reth::cli", "Unsupported table."); + + return Ok(()) } - table_get!([ - CanonicalHeaders, - HeaderTD, - HeaderNumbers, - Headers, - BlockBodyIndices, - BlockOmmers, - BlockWithdrawals, - TransactionBlock, - Transactions, - TxHashNumber, - Receipts, - PlainAccountState, - Bytecodes, - AccountHistory, - StorageHistory, - HashedAccount, - AccountsTrie, - TxSenders, - SyncStage, - SyncStageProgress - ]); + self.table.view(&GetValueViewer { tool, args: &self })?; + + Ok(()) } /// Get an instance of key for given table - fn table_key(&self) -> Result - where - for<'a> T::Key: Deserialize<'a>, - { - assert_eq!(T::NAME, self.table); + pub fn table_key(&self) -> Result { + assert_eq!(T::NAME, self.table.name()); serde_json::from_str::(&self.key).map_err(|e| eyre::eyre!(e)) } } +struct GetValueViewer<'a, DB: Database> { + tool: &'a DbTool<'a, DB>, + args: &'a Command, +} + +impl TableViewer<()> for GetValueViewer<'_, DB> { + type Error = eyre::Report; + + fn view(&self) -> Result<(), Self::Error> { + // get a key for given table + let key = self.args.table_key::()?; + + match self.tool.get::(key)? { + Some(content) => { + println!("{}", serde_json::to_string_pretty(&content)?); + } + None => { + error!(target: "reth::cli", "No content for the given table key."); + } + }; + + Ok(()) + } +} + /// Map the user input value to json fn maybe_json_value_parser(value: &str) -> Result { if serde_json::from_str::(value).is_ok() { diff --git a/bin/reth/src/db/list.rs b/bin/reth/src/db/list.rs new file mode 100644 index 000000000000..a4059fcf9cf5 --- /dev/null +++ b/bin/reth/src/db/list.rs @@ -0,0 +1,85 @@ +use crate::utils::DbTool; +use clap::Parser; + +use super::tui::DbListTUI; +use eyre::WrapErr; +use reth_db::{ + database::Database, + mdbx::{Env, WriteMap}, + table::Table, + TableType, TableViewer, Tables, +}; +use tracing::error; + +const DEFAULT_NUM_ITEMS: &str = "5"; + +#[derive(Parser, Debug)] +/// The arguments for the `reth db list` command +pub struct Command { + /// The table name + table: Tables, + /// Skip first N entries + #[arg(long, short, default_value = "0")] + skip: usize, + /// Reverse the order of the entries. If enabled last table entries are read. + #[arg(long, short, default_value = "false")] + reverse: bool, + /// How many items to take from the walker + #[arg(long, short, default_value = DEFAULT_NUM_ITEMS)] + len: usize, + /// Dump as JSON instead of using TUI. + #[arg(long, short)] + json: bool, +} + +impl Command { + /// Execute `db list` command + pub fn execute(self, tool: &DbTool<'_, Env>) -> eyre::Result<()> { + if self.table.table_type() == TableType::DupSort { + error!(target: "reth::cli", "Unsupported table."); + } + + self.table.view(&ListTableViewer { tool, args: &self })?; + + Ok(()) + } +} + +struct ListTableViewer<'a> { + tool: &'a DbTool<'a, Env>, + args: &'a Command, +} + +impl TableViewer<()> for ListTableViewer<'_> { + type Error = eyre::Report; + + fn view(&self) -> Result<(), Self::Error> { + self.tool.db.view(|tx| { + let table_db = tx.inner.open_db(Some(self.args.table.name())).wrap_err("Could not open db.")?; + let stats = tx.inner.db_stat(&table_db).wrap_err(format!("Could not find table: {}", stringify!($table)))?; + let total_entries = stats.entries(); + if self.args.skip > total_entries - 1 { + error!( + target: "reth::cli", + "Start index {start} is greater than the final entry index ({final_entry_idx}) in the table {table}", + start = self.args.skip, + final_entry_idx = total_entries - 1, + table = self.args.table.name() + ); + return Ok(()); + } + + if self.args.json { + let list_result = self.tool.list::(self.args.skip, self.args.len, self.args.reverse)?.into_iter().collect::>(); + println!("{}", serde_json::to_string_pretty(&list_result)?); + Ok(()) + } else { + DbListTUI::<_, T>::new(|skip, count| { + self.tool.list::(skip, count, self.args.reverse).unwrap() + }, self.args.skip, self.args.len, total_entries).run() + } + })??; + + Ok(()) + } +} diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index 43051d07b662..27f188c2a407 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -10,15 +10,15 @@ use eyre::WrapErr; use human_bytes::human_bytes; use reth_db::{ database::Database, - tables, version::{get_db_version, DatabaseVersionError, DB_VERSION}, + Tables, }; use reth_primitives::ChainSpec; use reth_staged_sync::utils::init::init_db; use std::sync::Arc; -use tracing::error; mod get; +mod list; /// DB List TUI mod tui; @@ -57,15 +57,13 @@ pub struct Command { command: Subcommands, } -const DEFAULT_NUM_ITEMS: &str = "5"; - #[derive(Subcommand, Debug)] /// `reth db` subcommands pub enum Subcommands { /// Lists all the tables, their entry count and their size Stats, /// Lists the contents of a table - List(ListArgs), + List(list::Command), /// Gets the content of a table for the given key Get(get::Command), /// Deletes all database entries @@ -76,25 +74,6 @@ pub enum Subcommands { Path, } -#[derive(Parser, Debug)] -/// The arguments for the `reth db list` command -pub struct ListArgs { - /// The table name - table: String, // TODO: Convert to enum - /// Skip first N entries - #[arg(long, short, default_value = "0")] - skip: usize, - /// Reverse the order of the entries. If enabled last table entries are read. - #[arg(long, short, default_value = "false")] - reverse: bool, - /// How many items to take from the walker - #[arg(long, short, default_value = DEFAULT_NUM_ITEMS)] - len: usize, - /// Dump as JSON instead of using TUI. - #[arg(long, short)] - json: bool, -} - impl Command { /// Execute `db` command pub async fn execute(self) -> eyre::Result<()> { @@ -122,7 +101,7 @@ impl Command { tool.db.view(|tx| { let mut tables = - tables::TABLES.iter().map(|(_, name)| name).collect::>(); + Tables::ALL.iter().map(|table| table.name()).collect::>(); tables.sort(); for table in tables { let table_db = @@ -157,75 +136,11 @@ impl Command { println!("{stats_table}"); } - Subcommands::List(args) => { - macro_rules! table_tui { - ($arg:expr, $start:expr, $len:expr => [$($table:ident),*]) => { - match $arg { - $(stringify!($table) => { - tool.db.view(|tx| { - let table_db = tx.inner.open_db(Some(stringify!($table))).wrap_err("Could not open db.")?; - let stats = tx.inner.db_stat(&table_db).wrap_err(format!("Could not find table: {}", stringify!($table)))?; - let total_entries = stats.entries(); - if $start > total_entries - 1 { - error!( - target: "reth::cli", - "Start index {start} is greater than the final entry index ({final_entry_idx}) in the table {table}", - start = $start, - final_entry_idx = total_entries - 1, - table = stringify!($table) - ); - return Ok(()); - } - - if args.json { - let list_result = tool.list::(args.skip, args.len,args.reverse)?.into_iter().collect::>(); - println!("{}", serde_json::to_string_pretty(&list_result)?); - Ok(()) - } else { - tui::DbListTUI::<_, tables::$table>::new(|skip, count| { - tool.list::(skip, count, args.reverse).unwrap() - }, $start, $len, total_entries).run() - } - })?? - },)* - _ => { - error!(target: "reth::cli", "Unknown table."); - return Ok(()); - } - } - } - } - - table_tui!(args.table.as_str(), args.skip, args.len => [ - CanonicalHeaders, - HeaderTD, - HeaderNumbers, - Headers, - BlockBodyIndices, - BlockOmmers, - BlockWithdrawals, - TransactionBlock, - Transactions, - TxHashNumber, - Receipts, - PlainStorageState, - PlainAccountState, - Bytecodes, - AccountHistory, - StorageHistory, - AccountChangeSet, - StorageChangeSet, - HashedAccount, - HashedStorage, - AccountsTrie, - StoragesTrie, - TxSenders, - SyncStage, - SyncStageProgress - ]); + Subcommands::List(command) => { + command.execute(&tool)?; } Subcommands::Get(command) => { - command.execute(tool)?; + command.execute(&tool)?; } Subcommands::Drop => { tool.drop(db_path)?; diff --git a/bin/reth/src/prometheus_exporter.rs b/bin/reth/src/prometheus_exporter.rs index 2896f0689af6..0c9c3bb48159 100644 --- a/bin/reth/src/prometheus_exporter.rs +++ b/bin/reth/src/prometheus_exporter.rs @@ -73,7 +73,7 @@ pub(crate) async fn initialize_with_db_metrics( // TODO: A generic stats abstraction for other DB types to deduplicate this and `reth db // stats` let _ = db.view(|tx| { - for table in tables::TABLES.iter().map(|(_, name)| name) { + for table in tables::Tables::ALL.iter().map(|table| table.name()) { let table_db = tx.inner.open_db(Some(table)).wrap_err("Could not open db.")?; @@ -89,10 +89,10 @@ pub(crate) async fn initialize_with_db_metrics( let num_pages = leaf_pages + branch_pages + overflow_pages; let table_size = page_size * num_pages; - absolute_counter!("db.table_size", table_size as u64, "table" => *table); - absolute_counter!("db.table_pages", leaf_pages as u64, "table" => *table, "type" => "leaf"); - absolute_counter!("db.table_pages", branch_pages as u64, "table" => *table, "type" => "branch"); - absolute_counter!("db.table_pages", overflow_pages as u64, "table" => *table, "type" => "overflow"); + absolute_counter!("db.table_size", table_size as u64, "table" => table); + absolute_counter!("db.table_pages", leaf_pages as u64, "table" => table, "type" => "leaf"); + absolute_counter!("db.table_pages", branch_pages as u64, "table" => table, "type" => "branch"); + absolute_counter!("db.table_pages", overflow_pages as u64, "table" => table, "type" => "overflow"); } Ok::<(), eyre::Report>(()) diff --git a/bin/reth/src/utils.rs b/bin/reth/src/utils.rs index fc659cec3c76..a6c1b6e0fac3 100644 --- a/bin/reth/src/utils.rs +++ b/bin/reth/src/utils.rs @@ -71,7 +71,7 @@ impl<'a, DB: Database> DbTool<'a, DB> { /// Grabs the contents of the table within a certain index range and places the /// entries into a [`HashMap`][std::collections::HashMap]. pub fn list( - &mut self, + &self, skip: usize, len: usize, reverse: bool, @@ -90,7 +90,7 @@ impl<'a, DB: Database> DbTool<'a, DB> { } /// Grabs the content of the table for the given key - pub fn get(&mut self, key: T::Key) -> Result> { + pub fn get(&self, key: T::Key) -> Result> { self.db.view(|tx| tx.get::(key))?.map_err(|e| eyre::eyre!(e)) } diff --git a/crates/storage/db/src/abstraction/table.rs b/crates/storage/db/src/abstraction/table.rs index be8af0a83608..65d611f86856 100644 --- a/crates/storage/db/src/abstraction/table.rs +++ b/crates/storage/db/src/abstraction/table.rs @@ -4,7 +4,7 @@ use crate::{ DatabaseError, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::{ fmt::Debug, marker::{Send, Sync}, @@ -53,9 +53,9 @@ pub trait Decode: Send + Sync + Sized + Debug { } /// Generic trait that enforces the database key to implement [`Encode`] and [`Decode`]. -pub trait Key: Encode + Decode + Ord + Clone {} +pub trait Key: Encode + Decode + Ord + Clone + Serialize + for<'a> Deserialize<'a> {} -impl Key for T where T: Encode + Decode + Ord + Clone {} +impl Key for T where T: Encode + Decode + Ord + Clone + Serialize + for<'a> Deserialize<'a> {} /// Generic trait that enforces the database value to implement [`Compress`] and [`Decompress`]. pub trait Value: Compress + Decompress + Serialize {} diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index 68f60b554f8d..616029acd574 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -2,7 +2,7 @@ use crate::{ database::{Database, DatabaseGAT}, - tables::{TableType, TABLES}, + tables::{TableType, Tables}, utils::default_page_size, DatabaseError, }; @@ -67,7 +67,7 @@ impl Env { let env = Env { inner: Environment::new() - .set_max_dbs(TABLES.len()) + .set_max_dbs(Tables::ALL.len()) .set_geometry(Geometry { // Maximum database size of 4 terabytes size: Some(0..(4 * TERABYTE)), @@ -96,13 +96,14 @@ impl Env { pub fn create_tables(&self) -> Result<(), DatabaseError> { let tx = self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTransaction(e.into()))?; - for (table_type, table) in TABLES { - let flags = match table_type { + for table in Tables::ALL { + let flags = match table.table_type() { TableType::Table => DatabaseFlags::default(), TableType::DupSort => DatabaseFlags::DUP_SORT, }; - tx.create_db(Some(table), flags).map_err(|e| DatabaseError::TableCreation(e.into()))?; + tx.create_db(Some(table.name()), flags) + .map_err(|e| DatabaseError::TableCreation(e.into()))?; } tx.commit().map_err(|e| DatabaseError::Commit(e.into()))?; diff --git a/crates/storage/db/src/implementation/mdbx/tx.rs b/crates/storage/db/src/implementation/mdbx/tx.rs index d6abdd712231..5644723b9d65 100644 --- a/crates/storage/db/src/implementation/mdbx/tx.rs +++ b/crates/storage/db/src/implementation/mdbx/tx.rs @@ -3,14 +3,14 @@ use super::cursor::Cursor; use crate::{ table::{Compress, DupSort, Encode, Table, TableImporter}, - tables::{utils::decode_one, NUM_TABLES, TABLES}, + tables::{utils::decode_one, Tables, NUM_TABLES}, transaction::{DbTx, DbTxGAT, DbTxMut, DbTxMutGAT}, DatabaseError, }; use parking_lot::RwLock; use reth_libmdbx::{EnvironmentKind, Transaction, TransactionKind, WriteFlags, DBI, RW}; use reth_metrics::metrics::{self, histogram}; -use std::{marker::PhantomData, sync::Arc, time::Instant}; +use std::{marker::PhantomData, str::FromStr, sync::Arc, time::Instant}; /// Wrapper for the libmdbx transaction. #[derive(Debug)] @@ -39,13 +39,9 @@ impl<'env, K: TransactionKind, E: EnvironmentKind> Tx<'env, K, E> { pub fn get_dbi(&self) -> Result { let mut handles = self.db_handles.write(); - let table_index = TABLES - .iter() - .enumerate() - .find_map(|(idx, (_, table))| (table == &T::NAME).then_some(idx)) - .expect("Requested table should be part of `TABLES`."); + let table = Tables::from_str(T::NAME).expect("Requested table should be part of `Tables`."); - let dbi_handle = handles.get_mut(table_index).expect("should exist"); + let dbi_handle = handles.get_mut(table as usize).expect("should exist"); if dbi_handle.is_none() { *dbi_handle = Some( self.inner diff --git a/crates/storage/db/src/tables/mod.rs b/crates/storage/db/src/tables/mod.rs index a33d69f84cf6..ba44ce1ea032 100644 --- a/crates/storage/db/src/tables/mod.rs +++ b/crates/storage/db/src/tables/mod.rs @@ -17,7 +17,9 @@ pub mod models; mod raw; pub(crate) mod utils; +use crate::abstraction::table::Table; pub use raw::{RawDupSort, RawKey, RawTable, RawValue}; +use std::{fmt::Display, str::FromStr}; /// Declaration of all Database tables. use crate::{ @@ -40,7 +42,7 @@ use reth_primitives::{ }; /// Enum for the types of tables present in libmdbx. -#[derive(Debug)] +#[derive(Debug, PartialEq, Copy, Clone)] pub enum TableType { /// key value table Table, @@ -51,34 +53,138 @@ pub enum TableType { /// Number of tables that should be present inside database. pub const NUM_TABLES: usize = 25; -/// Default tables that should be present inside database. -pub const TABLES: [(TableType, &str); NUM_TABLES] = [ - (TableType::Table, CanonicalHeaders::const_name()), - (TableType::Table, HeaderTD::const_name()), - (TableType::Table, HeaderNumbers::const_name()), - (TableType::Table, Headers::const_name()), - (TableType::Table, BlockBodyIndices::const_name()), - (TableType::Table, BlockOmmers::const_name()), - (TableType::Table, BlockWithdrawals::const_name()), - (TableType::Table, TransactionBlock::const_name()), - (TableType::Table, Transactions::const_name()), - (TableType::Table, TxHashNumber::const_name()), - (TableType::Table, Receipts::const_name()), - (TableType::Table, PlainAccountState::const_name()), - (TableType::DupSort, PlainStorageState::const_name()), - (TableType::Table, Bytecodes::const_name()), - (TableType::Table, AccountHistory::const_name()), - (TableType::Table, StorageHistory::const_name()), - (TableType::DupSort, AccountChangeSet::const_name()), - (TableType::DupSort, StorageChangeSet::const_name()), - (TableType::Table, HashedAccount::const_name()), - (TableType::DupSort, HashedStorage::const_name()), - (TableType::Table, AccountsTrie::const_name()), - (TableType::DupSort, StoragesTrie::const_name()), - (TableType::Table, TxSenders::const_name()), - (TableType::Table, SyncStage::const_name()), - (TableType::Table, SyncStageProgress::const_name()), -]; +/// The general purpose of this is to use with a combination of Tables enum, +/// by implementing a `TableViewer` trait you can operate on db tables in an abstract way. +/// +/// # Example +/// +/// ``` +/// use reth_db::{ table::Table, TableViewer, Tables }; +/// use std::str::FromStr; +/// +/// let headers = Tables::from_str("Headers").unwrap(); +/// let transactions = Tables::from_str("Transactions").unwrap(); +/// +/// struct MyTableViewer; +/// +/// impl TableViewer<()> for MyTableViewer { +/// type Error = &'static str; +/// +/// fn view(&self) -> Result<(), Self::Error> { +/// // operate on table in generic way +/// Ok(()) +/// } +/// } +/// +/// let viewer = MyTableViewer {}; +/// +/// let _ = headers.view(&viewer); +/// let _ = transactions.view(&viewer); +/// ``` +pub trait TableViewer { + /// type of error to return + type Error; + + /// operate on table in generic way + fn view(&self) -> Result; +} + +macro_rules! tables { + ([$(($table:ident, $type:expr)),*]) => { + #[derive(Debug, PartialEq, Copy, Clone)] + /// Default tables that should be present inside database. + pub enum Tables { + $( + #[doc = concat!("Represents a ", stringify!($table), " table")] + $table, + )* + } + + impl Tables { + /// Array of all tables in database + pub const ALL: [Tables; NUM_TABLES] = [$(Tables::$table,)*]; + + /// The name of the given table in database + pub const fn name(&self) -> &str { + match self { + $(Tables::$table => { + $table::NAME + },)* + } + } + + /// The type of the given table in database + pub const fn table_type(&self) -> TableType { + match self { + $(Tables::$table => { + $type + },)* + } + } + + /// Allows to operate on specific table type + pub fn view(&self, visitor: &T) -> Result + where + T: TableViewer, + { + match self { + $(Tables::$table => { + visitor.view::<$table>() + },)* + } + } + } + + impl Display for Tables { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name()) + } + } + + impl FromStr for Tables { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + $($table::NAME => { + return Ok(Tables::$table) + },)* + _ => { + return Err("Unknown table".to_string()) + } + } + } + } + }; +} + +tables!([ + (CanonicalHeaders, TableType::Table), + (HeaderTD, TableType::Table), + (HeaderNumbers, TableType::Table), + (Headers, TableType::Table), + (BlockBodyIndices, TableType::Table), + (BlockOmmers, TableType::Table), + (BlockWithdrawals, TableType::Table), + (TransactionBlock, TableType::Table), + (Transactions, TableType::Table), + (TxHashNumber, TableType::Table), + (Receipts, TableType::Table), + (PlainAccountState, TableType::Table), + (PlainStorageState, TableType::DupSort), + (Bytecodes, TableType::Table), + (AccountHistory, TableType::Table), + (StorageHistory, TableType::Table), + (AccountChangeSet, TableType::DupSort), + (StorageChangeSet, TableType::DupSort), + (HashedAccount, TableType::Table), + (HashedStorage, TableType::DupSort), + (AccountsTrie, TableType::Table), + (StoragesTrie, TableType::DupSort), + (TxSenders, TableType::Table), + (SyncStage, TableType::Table), + (SyncStageProgress, TableType::Table) +]); #[macro_export] /// Macro to declare key value table. @@ -315,3 +421,49 @@ table!( pub type BlockNumberList = IntegerList; /// Encoded stage id. pub type StageId = String; + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use crate::*; + + const TABLES: [(TableType, &str); NUM_TABLES] = [ + (TableType::Table, CanonicalHeaders::const_name()), + (TableType::Table, HeaderTD::const_name()), + (TableType::Table, HeaderNumbers::const_name()), + (TableType::Table, Headers::const_name()), + (TableType::Table, BlockBodyIndices::const_name()), + (TableType::Table, BlockOmmers::const_name()), + (TableType::Table, BlockWithdrawals::const_name()), + (TableType::Table, TransactionBlock::const_name()), + (TableType::Table, Transactions::const_name()), + (TableType::Table, TxHashNumber::const_name()), + (TableType::Table, Receipts::const_name()), + (TableType::Table, PlainAccountState::const_name()), + (TableType::DupSort, PlainStorageState::const_name()), + (TableType::Table, Bytecodes::const_name()), + (TableType::Table, AccountHistory::const_name()), + (TableType::Table, StorageHistory::const_name()), + (TableType::DupSort, AccountChangeSet::const_name()), + (TableType::DupSort, StorageChangeSet::const_name()), + (TableType::Table, HashedAccount::const_name()), + (TableType::DupSort, HashedStorage::const_name()), + (TableType::Table, AccountsTrie::const_name()), + (TableType::DupSort, StoragesTrie::const_name()), + (TableType::Table, TxSenders::const_name()), + (TableType::Table, SyncStage::const_name()), + (TableType::Table, SyncStageProgress::const_name()), + ]; + + #[test] + fn parse_table_from_str() { + for (table_index, &(table_type, table_name)) in TABLES.iter().enumerate() { + let table = Tables::from_str(table_name).unwrap(); + + assert_eq!(table as usize, table_index); + assert_eq!(table.table_type(), table_type); + assert_eq!(table.name(), table_name); + } + } +} diff --git a/crates/storage/db/src/tables/raw.rs b/crates/storage/db/src/tables/raw.rs index 883c759b62be..a1cf04ff3d4d 100644 --- a/crates/storage/db/src/tables/raw.rs +++ b/crates/storage/db/src/tables/raw.rs @@ -2,7 +2,7 @@ use crate::{ table::{Compress, Decode, Decompress, DupSort, Encode, Key, Table, Value}, DatabaseError, }; -use serde::Serialize; +use serde::{Deserialize, Serialize}; /// Raw table that can be used to access any table and its data in raw mode. /// This is useful for delayed decoding/encoding of data. @@ -39,7 +39,7 @@ impl DupSort for RawDupSort { } /// Raw table key. -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct RawKey { key: Vec, _phantom: std::marker::PhantomData, From d7467e86b48894b7bbcf6d3e928c08294b0fb1bb Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Thu, 22 Jun 2023 21:15:11 +0300 Subject: [PATCH 152/216] fix(sync): merkle current block logs (#3326) --- crates/stages/src/stages/merkle.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index 56cd21194b38..3485e7a66300 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -160,17 +160,17 @@ impl Stage for MerkleStage { let range = input.next_block_range(); let (from_block, to_block) = range.clone().into_inner(); - let current_block = input.target(); + let current_block_number = input.checkpoint().block_number; - let block = provider - .header_by_number(current_block)? - .ok_or_else(|| ProviderError::HeaderNotFound(current_block.into()))?; - let block_root = block.state_root; + let target_block = provider + .header_by_number(to_block)? + .ok_or_else(|| ProviderError::HeaderNotFound(to_block.into()))?; + let target_block_root = target_block.state_root; let mut checkpoint = self.get_execution_checkpoint(provider)?; let (trie_root, entities_checkpoint) = if range.is_empty() { - (block_root, input.checkpoint().entities_stage_checkpoint().unwrap_or_default()) + (target_block_root, input.checkpoint().entities_stage_checkpoint().unwrap_or_default()) } else if to_block - from_block > threshold || from_block == 1 { // if there are more blocks than threshold it is faster to rebuild the trie let mut entities_checkpoint = if let Some(checkpoint) = @@ -178,7 +178,7 @@ impl Stage for MerkleStage { { debug!( target: "sync::stages::merkle::exec", - current = ?current_block, + current = ?current_block_number, target = ?to_block, last_account_key = ?checkpoint.last_account_key, last_walker_key = ?hex::encode(&checkpoint.last_walker_key), @@ -189,7 +189,7 @@ impl Stage for MerkleStage { } else { debug!( target: "sync::stages::merkle::exec", - current = ?current_block, + current = ?current_block_number, target = ?to_block, previous_checkpoint = ?checkpoint, "Rebuilding trie" @@ -245,7 +245,7 @@ impl Stage for MerkleStage { } } } else { - debug!(target: "sync::stages::merkle::exec", current = ?current_block, target = ?to_block, "Updating trie"); + debug!(target: "sync::stages::merkle::exec", current = ?current_block_number, target = ?to_block, "Updating trie"); let (root, updates) = StateRoot::incremental_root_with_updates(provider.tx_ref(), range) .map_err(|e| StageError::Fatal(Box::new(e)))?; @@ -269,7 +269,7 @@ impl Stage for MerkleStage { // Reset the checkpoint self.save_execution_checkpoint(provider, None)?; - self.validate_state_root(trie_root, block.seal_slow(), to_block)?; + self.validate_state_root(trie_root, target_block.seal_slow(), to_block)?; Ok(ExecOutput { checkpoint: StageCheckpoint::new(to_block) From 3390671cb6b8dfed76c522026c50ac609bd66e40 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 22 Jun 2023 20:53:47 +0200 Subject: [PATCH 153/216] chore: add pending imports metrics (#3344) --- crates/net/network/src/metrics.rs | 2 ++ crates/net/network/src/transactions.rs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/crates/net/network/src/metrics.rs b/crates/net/network/src/metrics.rs index 273e77e73467..bea6d8cd4334 100644 --- a/crates/net/network/src/metrics.rs +++ b/crates/net/network/src/metrics.rs @@ -54,6 +54,8 @@ pub struct TransactionsManagerMetrics { pub(crate) messages_with_already_seen_hashes: Counter, /// Total number of messages with already seen full transactions pub(crate) messages_with_already_seen_transactions: Counter, + /// Number of transactions about to be imported into the pool. + pub(crate) pending_pool_imports: Gauge, } /// Metrics for Disconnection types diff --git a/crates/net/network/src/transactions.rs b/crates/net/network/src/transactions.rs index 8cf34a67aa5f..b15d8a23a183 100644 --- a/crates/net/network/src/transactions.rs +++ b/crates/net/network/src/transactions.rs @@ -158,6 +158,10 @@ where TransactionsHandle { manager_tx: self.command_tx.clone() } } + fn update_import_metrics(&self) { + self.metrics.pending_pool_imports.set(self.pool_imports.len() as f64); + } + /// Request handler for an incoming request for transactions fn on_get_pooled_transactions( &mut self, @@ -538,6 +542,8 @@ where } } + this.update_import_metrics(); + // Advance all imports while let Poll::Ready(Some(import_res)) = this.pool_imports.poll_next_unpin(cx) { match import_res { @@ -558,6 +564,8 @@ where } } + this.update_import_metrics(); + // handle and propagate new transactions let mut new_txs = Vec::new(); while let Poll::Ready(Some(hash)) = this.pending_transactions.poll_next_unpin(cx) { From a4c2f5f69c85b8ecf8f4362aa921a7a3cb8303e8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 22 Jun 2023 21:34:02 +0200 Subject: [PATCH 154/216] feat: add metrics for tx channel (#3345) --- Cargo.lock | 1 + crates/metrics/Cargo.toml | 3 ++- crates/metrics/src/common/mpsc.rs | 22 +++++++++++++++++++++- crates/net/network/src/manager.rs | 9 ++++++--- crates/net/network/src/metrics.rs | 3 +++ crates/net/network/src/transactions.rs | 10 +++++++--- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b474f250914e..96ccb4205b8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5374,6 +5374,7 @@ dependencies = [ name = "reth-metrics" version = "0.1.0-alpha.1" dependencies = [ + "futures", "metrics", "reth-metrics-derive", "tokio", diff --git a/crates/metrics/Cargo.toml b/crates/metrics/Cargo.toml index 75719a7aabb8..adbd26cb8029 100644 --- a/crates/metrics/Cargo.toml +++ b/crates/metrics/Cargo.toml @@ -17,6 +17,7 @@ metrics = "0.20.1" # async tokio = { workspace = true, features = ["full"], optional = true } +futures = { workspace = true, optional = true } [features] -common = ["tokio"] +common = ["tokio", "futures"] diff --git a/crates/metrics/src/common/mpsc.rs b/crates/metrics/src/common/mpsc.rs index fe52d8857f95..0f4688e21960 100644 --- a/crates/metrics/src/common/mpsc.rs +++ b/crates/metrics/src/common/mpsc.rs @@ -1,9 +1,13 @@ //! Support for metering senders. Facilitates debugging by exposing metrics for number of messages //! sent, number of errors, etc. +use futures::Stream; use metrics::Counter; use reth_metrics_derive::Metrics; -use std::task::{ready, Context, Poll}; +use std::{ + pin::Pin, + task::{ready, Context, Poll}, +}; use tokio::sync::mpsc::{ self, error::{SendError, TryRecvError, TrySendError}, @@ -115,6 +119,14 @@ impl UnboundedMeteredReceiver { } } +impl Stream for UnboundedMeteredReceiver { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.receiver.poll_recv(cx) + } +} + /// A wrapper type around [Sender](mpsc::Sender) that updates metrics on send. #[derive(Debug)] pub struct MeteredSender { @@ -215,6 +227,14 @@ impl MeteredReceiver { } } +impl Stream for MeteredReceiver { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.receiver.poll_recv(cx) + } +} + /// Throughput metrics for [MeteredSender] #[derive(Clone, Metrics)] #[metrics(dynamic = true)] diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 4c5a038732ba..8a70a9507be4 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -23,7 +23,7 @@ use crate::{ import::{BlockImport, BlockImportOutcome, BlockValidation}, listener::ConnectionListener, message::{NewBlockMessage, PeerMessage, PeerRequest, PeerRequestSender}, - metrics::{DisconnectMetrics, NetworkMetrics}, + metrics::{DisconnectMetrics, NetworkMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE}, network::{NetworkHandle, NetworkHandleMessage}, peers::{PeersHandle, PeersManager}, session::SessionManager, @@ -38,6 +38,7 @@ use reth_eth_wire::{ capability::{Capabilities, CapabilityMessage}, DisconnectReason, EthVersion, Status, }; +use reth_metrics::common::mpsc::UnboundedMeteredSender; use reth_net_common::bandwidth_meter::BandwidthMeter; use reth_network_api::ReputationChangeKind; use reth_primitives::{listener::EventListeners, NodeRecord, PeerId, H256}; @@ -55,6 +56,7 @@ use std::{ use tokio::sync::mpsc::{self, error::TrySendError}; use tokio_stream::wrappers::UnboundedReceiverStream; use tracing::{debug, error, info, trace, warn}; + /// Manages the _entire_ state of the network. /// /// This is an endless [`Future`] that consistently drives the state of the entire network forward. @@ -97,7 +99,7 @@ pub struct NetworkManager { event_listeners: EventListeners, /// Sender half to send events to the /// [`TransactionsManager`](crate::transactions::TransactionsManager) task, if configured. - to_transactions_manager: Option>, + to_transactions_manager: Option>, /// Sender half to send events to the /// [`EthRequestHandler`](crate::eth_requests::EthRequestHandler) task, if configured. /// @@ -128,7 +130,8 @@ impl NetworkManager { /// Sets the dedicated channel for events indented for the /// [`TransactionsManager`](crate::transactions::TransactionsManager). pub fn set_transactions(&mut self, tx: mpsc::UnboundedSender) { - self.to_transactions_manager = Some(tx); + self.to_transactions_manager = + Some(UnboundedMeteredSender::new(tx, NETWORK_POOL_TRANSACTIONS_SCOPE)); } /// Sets the dedicated channel for events indented for the diff --git a/crates/net/network/src/metrics.rs b/crates/net/network/src/metrics.rs index bea6d8cd4334..d7969da80af1 100644 --- a/crates/net/network/src/metrics.rs +++ b/crates/net/network/src/metrics.rs @@ -4,6 +4,9 @@ use reth_metrics::{ Metrics, }; +/// Scope for monitoring transactions sent from the manager to the tx manager +pub(crate) const NETWORK_POOL_TRANSACTIONS_SCOPE: &str = "network.pool.transactions"; + /// Metrics for the entire network, handled by NetworkManager #[derive(Metrics)] #[metrics(scope = "network")] diff --git a/crates/net/network/src/transactions.rs b/crates/net/network/src/transactions.rs index b15d8a23a183..c5d88c3e60b7 100644 --- a/crates/net/network/src/transactions.rs +++ b/crates/net/network/src/transactions.rs @@ -4,7 +4,7 @@ use crate::{ cache::LruCache, manager::NetworkEvent, message::{PeerRequest, PeerRequestSender}, - metrics::TransactionsManagerMetrics, + metrics::{TransactionsManagerMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE}, NetworkHandle, }; use futures::{stream::FuturesUnordered, FutureExt, StreamExt}; @@ -13,6 +13,7 @@ use reth_eth_wire::{ NewPooledTransactionHashes68, PooledTransactions, Transactions, }; use reth_interfaces::{p2p::error::RequestResult, sync::SyncStateProvider}; +use reth_metrics::common::mpsc::UnboundedMeteredReceiver; use reth_network_api::{Peers, ReputationChangeKind}; use reth_primitives::{ FromRecoveredTransaction, IntoRecoveredTransaction, PeerId, TransactionSigned, TxHash, H256, @@ -109,7 +110,7 @@ pub struct TransactionsManager { /// Incoming commands from [`TransactionsHandle`]. pending_transactions: ReceiverStream, /// Incoming events from the [`NetworkManager`](crate::NetworkManager). - transaction_events: UnboundedReceiverStream, + transaction_events: UnboundedMeteredReceiver, /// TransactionsManager metrics metrics: TransactionsManagerMetrics, } @@ -140,7 +141,10 @@ impl TransactionsManager { command_tx, command_rx: UnboundedReceiverStream::new(command_rx), pending_transactions: ReceiverStream::new(pending), - transaction_events: UnboundedReceiverStream::new(from_network), + transaction_events: UnboundedMeteredReceiver::new( + from_network, + NETWORK_POOL_TRANSACTIONS_SCOPE, + ), metrics: Default::default(), } } From e8b09cc59bf5b98bfae38ae38ba874155dd7e6e2 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 22 Jun 2023 22:00:16 +0200 Subject: [PATCH 155/216] feat: add pending receipts (#3320) --- crates/blockchain-tree/src/blockchain_tree.rs | 13 ++++++- crates/blockchain-tree/src/shareable.rs | 8 +++- crates/interfaces/src/blockchain_tree/mod.rs | 11 +++++- crates/rpc/rpc/src/debug.rs | 2 +- crates/storage/provider/src/chain.rs | 6 +++ crates/storage/provider/src/providers/mod.rs | 37 +++++++++++++++++-- .../storage/provider/src/test_utils/mock.rs | 6 ++- .../storage/provider/src/test_utils/noop.rs | 8 ++-- crates/storage/provider/src/traits/block.rs | 6 +-- .../storage/provider/src/traits/receipts.rs | 21 +++++++++-- 10 files changed, 98 insertions(+), 20 deletions(-) diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index efc7e82ce6d4..68b7d02c7943 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -16,8 +16,8 @@ use reth_interfaces::{ Error, }; use reth_primitives::{ - BlockHash, BlockNumHash, BlockNumber, ForkBlock, Hardfork, SealedBlock, SealedBlockWithSenders, - SealedHeader, U256, + BlockHash, BlockNumHash, BlockNumber, ForkBlock, Hardfork, Receipt, SealedBlock, + SealedBlockWithSenders, SealedHeader, U256, }; use reth_provider::{ chain::{ChainSplit, SplitAt}, @@ -217,6 +217,15 @@ impl BlockchainTree chain.block(block_hash) } + /// Returns the block's receipts with matching hash from any side-chain. + /// + /// Caution: This will not return blocks from the canonical chain. + pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<&[Receipt]> { + let id = self.block_indices.get_blocks_chain_id(&block_hash)?; + let chain = self.chains.get(&id)?; + chain.receipts_by_block_hash(block_hash) + } + /// Returns true if the block is included in a side-chain. fn is_block_hash_inside_chain(&self, block_hash: BlockHash) -> bool { self.block_by_hash(block_hash).is_some() diff --git a/crates/blockchain-tree/src/shareable.rs b/crates/blockchain-tree/src/shareable.rs index bd3eb3247797..759ca402a7bf 100644 --- a/crates/blockchain-tree/src/shareable.rs +++ b/crates/blockchain-tree/src/shareable.rs @@ -11,7 +11,8 @@ use reth_interfaces::{ Error, }; use reth_primitives::{ - BlockHash, BlockNumHash, BlockNumber, SealedBlock, SealedBlockWithSenders, SealedHeader, + BlockHash, BlockNumHash, BlockNumber, Receipt, SealedBlock, SealedBlockWithSenders, + SealedHeader, }; use reth_provider::{ BlockchainTreePendingStateProvider, CanonStateSubscriptions, ExecutorFactory, @@ -152,6 +153,11 @@ impl BlockchainTreeViewer trace!(target: "blockchain_tree", "Returning first pending block"); self.tree.read().pending_block().cloned() } + + fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option> { + let tree = self.tree.read(); + Some(tree.receipts_by_block_hash(block_hash)?.to_vec()) + } } impl BlockchainTreePendingStateProvider diff --git a/crates/interfaces/src/blockchain_tree/mod.rs b/crates/interfaces/src/blockchain_tree/mod.rs index 4e9e3b477189..c1b182b24cfa 100644 --- a/crates/interfaces/src/blockchain_tree/mod.rs +++ b/crates/interfaces/src/blockchain_tree/mod.rs @@ -1,6 +1,7 @@ use crate::{blockchain_tree::error::InsertBlockError, Error}; use reth_primitives::{ - BlockHash, BlockNumHash, BlockNumber, SealedBlock, SealedBlockWithSenders, SealedHeader, + BlockHash, BlockNumHash, BlockNumber, Receipt, SealedBlock, SealedBlockWithSenders, + SealedHeader, }; use std::collections::{BTreeMap, HashSet}; @@ -215,6 +216,14 @@ pub trait BlockchainTreeViewer: Send + Sync { self.block_by_hash(self.pending_block_num_hash()?.hash) } + /// Returns the pending receipts if there is one. + fn pending_receipts(&self) -> Option> { + self.receipts_by_block_hash(self.pending_block_num_hash()?.hash) + } + + /// Returns the pending receipts if there is one. + fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option>; + /// Returns the pending block if there is one. fn pending_header(&self) -> Option { self.header_by_hash(self.pending_block_num_hash()?.hash) diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 3cdf35b8b409..6adb46c4be1b 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -12,7 +12,7 @@ use crate::{ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_primitives::{Account, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSigned, H256}; -use reth_provider::{BlockProviderIdExt, HeaderProvider, ReceiptProviderIdExt, StateProviderBox}; +use reth_provider::{BlockProviderIdExt, HeaderProvider, StateProviderBox}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, diff --git a/crates/storage/provider/src/chain.rs b/crates/storage/provider/src/chain.rs index ff74d4cedbaa..605caadedd90 100644 --- a/crates/storage/provider/src/chain.rs +++ b/crates/storage/provider/src/chain.rs @@ -126,6 +126,12 @@ impl Chain { Self { state, blocks: block_num_hash } } + /// Get all receipts for the given block. + pub fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option<&[Receipt]> { + let num = self.block_number(block_hash)?; + Some(self.state.receipts(num)) + } + /// Get all receipts with attachment. /// /// Attachment includes block number, block hash, transaction hash and transaction index. diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index e45080d2bd8b..e3143c60bde4 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -2,8 +2,8 @@ use crate::{ BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, HeaderProvider, PostStateDataProvider, ProviderError, - ReceiptProvider, StageCheckpointReader, StateProviderBox, StateProviderFactory, - TransactionsProvider, WithdrawalsProvider, + ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateProviderBox, + StateProviderFactory, TransactionsProvider, WithdrawalsProvider, }; use reth_db::{database::Database, models::StoredBlockBodyIndices}; use reth_interfaces::{ @@ -327,6 +327,33 @@ where self.database.provider()?.receipts_by_block(block) } } +impl ReceiptProviderIdExt for BlockchainProvider +where + DB: Database, + Tree: BlockchainTreeViewer + Send + Sync, +{ + fn receipts_by_block_id(&self, block: BlockId) -> Result>> { + match block { + BlockId::Hash(rpc_block_hash) => { + let mut receipts = self.receipts_by_block(rpc_block_hash.block_hash.into())?; + if receipts.is_none() && !rpc_block_hash.require_canonical.unwrap_or(false) { + receipts = self.tree.receipts_by_block_hash(rpc_block_hash.block_hash); + } + Ok(receipts) + } + BlockId::Number(num_tag) => match num_tag { + BlockNumberOrTag::Pending => Ok(self.tree.pending_receipts()), + _ => { + if let Some(num) = self.convert_block_number(num_tag)? { + self.receipts_by_block(num.into()) + } else { + Ok(None) + } + } + }, + } + } +} impl WithdrawalsProvider for BlockchainProvider where @@ -600,6 +627,10 @@ where fn pending_block_num_hash(&self) -> Option { self.tree.pending_block_num_hash() } + + fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option> { + self.tree.receipts_by_block_hash(block_hash) + } } impl CanonChainTracker for BlockchainProvider @@ -640,7 +671,7 @@ where impl BlockProviderIdExt for BlockchainProvider where - Self: BlockProvider + BlockIdProvider, + Self: BlockProvider + BlockIdProvider + ReceiptProviderIdExt, Tree: BlockchainTreeEngine, { fn block_by_id(&self, id: BlockId) -> Result> { diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 4e09510e720f..0c6cd427ba43 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -2,8 +2,8 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, AccountReader, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, PostStateDataProvider, - StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, - WithdrawalsProvider, + ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, + TransactionsProvider, WithdrawalsProvider, }; use parking_lot::Mutex; use reth_db::models::StoredBlockBodyIndices; @@ -239,6 +239,8 @@ impl ReceiptProvider for MockEthProvider { } } +impl ReceiptProviderIdExt for MockEthProvider {} + impl BlockHashProvider for MockEthProvider { fn block_hash(&self, number: u64) -> Result> { let lock = self.blocks.lock(); diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 2edbde9ca58d..253e7982e89d 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -1,9 +1,9 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, AccountReader, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, - BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, StageCheckpointReader, - StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, - WithdrawalsProvider, + BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, ReceiptProviderIdExt, + StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, + StateRootProvider, TransactionsProvider, WithdrawalsProvider, }; use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::Result; @@ -179,6 +179,8 @@ impl ReceiptProvider for NoopProvider { } } +impl ReceiptProviderIdExt for NoopProvider {} + impl HeaderProvider for NoopProvider { fn header(&self, _block_hash: &BlockHash) -> Result> { Ok(None) diff --git a/crates/storage/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index f2ffe8767585..c8fbc16e8f3d 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -1,6 +1,6 @@ use crate::{ - BlockIdProvider, BlockNumProvider, HeaderProvider, ReceiptProvider, TransactionsProvider, - WithdrawalsProvider, + BlockIdProvider, BlockNumProvider, HeaderProvider, ReceiptProvider, ReceiptProviderIdExt, + TransactionsProvider, WithdrawalsProvider, }; use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::Result; @@ -113,7 +113,7 @@ pub trait BlockProvider: /// `BlockIdProvider` methods should be used to resolve `BlockId`s to block numbers or hashes, and /// retrieving the block should be done using the type's `BlockProvider` methods. #[auto_impl::auto_impl(&, Arc)] -pub trait BlockProviderIdExt: BlockProvider + BlockIdProvider { +pub trait BlockProviderIdExt: BlockProvider + BlockIdProvider + ReceiptProviderIdExt { /// Returns the block with matching tag from the database /// /// Returns `None` if block is not found. diff --git a/crates/storage/provider/src/traits/receipts.rs b/crates/storage/provider/src/traits/receipts.rs index 46cf992c74b1..3192679f6720 100644 --- a/crates/storage/provider/src/traits/receipts.rs +++ b/crates/storage/provider/src/traits/receipts.rs @@ -1,5 +1,5 @@ use reth_interfaces::Result; -use reth_primitives::{BlockHashOrNumber, BlockId, Receipt, TxHash, TxNumber}; +use reth_primitives::{BlockHashOrNumber, BlockId, BlockNumberOrTag, Receipt, TxHash, TxNumber}; use crate::BlockIdProvider; @@ -7,12 +7,18 @@ use crate::BlockIdProvider; #[auto_impl::auto_impl(&, Arc)] pub trait ReceiptProvider: Send + Sync { /// Get receipt by transaction number + /// + /// Returns `None` if the transaction is not found. fn receipt(&self, id: TxNumber) -> Result>; /// Get receipt by transaction hash. + /// + /// Returns `None` if the transaction is not found. fn receipt_by_hash(&self, hash: TxHash) -> Result>; /// Get receipts by block num or hash. + /// + /// Returns `None` if the block is not found. fn receipts_by_block(&self, block: BlockHashOrNumber) -> Result>>; } @@ -29,7 +35,6 @@ pub trait ReceiptProvider: Send + Sync { pub trait ReceiptProviderIdExt: ReceiptProvider + BlockIdProvider { /// Get receipt by block id fn receipts_by_block_id(&self, block: BlockId) -> Result>> { - // TODO: to implement EIP-1898 at the provider level or not let id = match block { BlockId::Hash(hash) => BlockHashOrNumber::Hash(hash.block_hash), BlockId::Number(num_tag) => { @@ -43,6 +48,14 @@ pub trait ReceiptProviderIdExt: ReceiptProvider + BlockIdProvider { self.receipts_by_block(id) } -} -impl ReceiptProviderIdExt for T where T: ReceiptProvider + BlockIdProvider {} + /// Returns the block with the matching `BlockId` from the database. + /// + /// Returns `None` if block is not found. + fn receipts_by_number_or_tag( + &self, + number_or_tag: BlockNumberOrTag, + ) -> Result>> { + self.receipts_by_block_id(number_or_tag.into()) + } +} From d60aef5a166cb4051da74af6f70a816431616c8b Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 23 Jun 2023 12:37:21 +0300 Subject: [PATCH 156/216] fix: insert genesis account & storage history (#3346) --- crates/primitives/src/chain/mod.rs | 4 +- crates/primitives/src/lib.rs | 2 +- crates/staged-sync/src/utils/init.rs | 107 ++++++++++++++++++++++++--- 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/crates/primitives/src/chain/mod.rs b/crates/primitives/src/chain/mod.rs index 36d2092ad9d4..8a5a5c7d338b 100644 --- a/crates/primitives/src/chain/mod.rs +++ b/crates/primitives/src/chain/mod.rs @@ -11,8 +11,8 @@ use std::{fmt, str::FromStr}; // The chain spec module. mod spec; pub use spec::{ - AllGenesisFormats, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, GOERLI, - MAINNET, SEPOLIA, + AllGenesisFormats, ChainSpec, ChainSpecBuilder, DisplayHardforks, ForkCondition, + ForkTimestamps, GOERLI, MAINNET, SEPOLIA, }; // The chain info module. diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 4fee46ffb4bb..b2ee21b88f17 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -52,7 +52,7 @@ pub use block::{ pub use bloom::Bloom; pub use chain::{ AllGenesisFormats, Chain, ChainInfo, ChainSpec, ChainSpecBuilder, DisplayHardforks, - ForkCondition, GOERLI, MAINNET, SEPOLIA, + ForkCondition, ForkTimestamps, GOERLI, MAINNET, SEPOLIA, }; pub use compression::*; pub use constants::{ diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index 869de558b691..e146aab745ac 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -9,8 +9,10 @@ use reth_db::{ version::{check_db_version_file, create_db_version_file, DatabaseVersionError}, }; use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, StorageEntry, H256, U256}; -use reth_provider::{AccountWriter, DatabaseProviderRW, HashingWriter, PostState, ProviderFactory}; -use std::{fs, path::Path, sync::Arc}; +use reth_provider::{ + AccountWriter, DatabaseProviderRW, HashingWriter, HistoryWriter, PostState, ProviderFactory, +}; +use std::{collections::BTreeMap, fs, path::Path, sync::Arc}; use tracing::debug; /// Opens up an existing database or creates a new one at the specified path. @@ -86,7 +88,9 @@ pub fn init_genesis( // use transaction to insert genesis header let factory = ProviderFactory::new(&db, chain.clone()); let provider_rw = factory.provider_rw()?; - insert_genesis_hashes(provider_rw, genesis)?; + insert_genesis_hashes(&provider_rw, genesis)?; + insert_genesis_history(&provider_rw, genesis)?; + provider_rw.commit()?; // Insert header let tx = db.tx_mut()?; @@ -139,7 +143,7 @@ pub fn insert_genesis_state( /// Inserts hashes for the genesis state. pub fn insert_genesis_hashes( - provider: DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, genesis: &reth_primitives::Genesis, ) -> Result<(), InitDatabaseError> { // insert and hash accounts to hashing table @@ -157,7 +161,26 @@ pub fn insert_genesis_hashes( }) }); provider.insert_storage_for_hashing(alloc_storage)?; - provider.commit()?; + + Ok(()) +} + +/// Inserts history indices for genesis accounts and storage. +pub fn insert_genesis_history( + provider: &DatabaseProviderRW<'_, &DB>, + genesis: &reth_primitives::Genesis, +) -> Result<(), InitDatabaseError> { + let account_transitions = + genesis.alloc.keys().map(|addr| (*addr, vec![0])).collect::>(); + provider.insert_account_history_index(account_transitions)?; + + let storage_transitions = genesis + .alloc + .iter() + .filter_map(|(addr, account)| account.storage.as_ref().map(|storage| (addr, storage))) + .flat_map(|(addr, storage)| storage.iter().map(|(key, _)| ((*addr, *key), vec![0]))) + .collect::>(); + provider.insert_storage_history_index(storage_transitions)?; Ok(()) } @@ -180,18 +203,31 @@ pub fn insert_genesis_header( #[cfg(test)] mod tests { - use super::{init_db, init_genesis, InitDatabaseError}; + use super::*; use assert_matches::assert_matches; use reth_db::{ mdbx::test_utils::create_test_rw_db, - version::{db_version_file_path, DatabaseVersionError}, + models::{storage_sharded_key::StorageShardedKey, ShardedKey}, + table::Table, + version::db_version_file_path, }; use reth_primitives::{ - GOERLI, GOERLI_GENESIS, MAINNET, MAINNET_GENESIS, SEPOLIA, SEPOLIA_GENESIS, + Address, Chain, ForkTimestamps, Genesis, GenesisAccount, IntegerList, GOERLI, + GOERLI_GENESIS, MAINNET, MAINNET_GENESIS, SEPOLIA, SEPOLIA_GENESIS, }; - use std::fs; + use std::collections::HashMap; use tempfile::tempdir; + fn collect_table_entries( + tx: &>::TX, + ) -> Result, InitDatabaseError> + where + DB: Database, + T: Table, + { + Ok(tx.cursor_read::()?.walk_range(..)?.collect::, _>>()?) + } + #[test] fn success_init_genesis_mainnet() { let db = create_test_rw_db(); @@ -236,6 +272,59 @@ mod tests { ) } + #[test] + fn init_genesis_history() { + let address_with_balance = Address::from_low_u64_be(1); + let address_with_storage = Address::from_low_u64_be(2); + let storage_key = H256::from_low_u64_be(1); + let chain_spec = Arc::new(ChainSpec { + chain: Chain::Id(1), + genesis: Genesis { + alloc: HashMap::from([ + ( + address_with_balance, + GenesisAccount { balance: U256::from(1), ..Default::default() }, + ), + ( + address_with_storage, + GenesisAccount { + storage: Some(HashMap::from([(storage_key, H256::random())])), + ..Default::default() + }, + ), + ]), + ..Default::default() + }, + hardforks: BTreeMap::default(), + fork_timestamps: ForkTimestamps { shanghai: None }, + genesis_hash: None, + paris_block_and_final_difficulty: None, + }); + + let db = create_test_rw_db(); + init_genesis(db.clone(), chain_spec).unwrap(); + + let tx = db.tx().expect("failed to init tx"); + + assert_eq!( + collect_table_entries::>, tables::AccountHistory>(&tx) + .expect("failed to collect"), + vec![ + (ShardedKey::new(address_with_balance, u64::MAX), IntegerList::new([0]).unwrap()), + (ShardedKey::new(address_with_storage, u64::MAX), IntegerList::new([0]).unwrap()) + ], + ); + + assert_eq!( + collect_table_entries::>, tables::StorageHistory>(&tx) + .expect("failed to collect"), + vec![( + StorageShardedKey::new(address_with_storage, storage_key, u64::MAX), + IntegerList::new([0]).unwrap() + )], + ); + } + #[test] fn db_version() { let path = tempdir().unwrap(); From f7d47564395ebc8dec392fd4242e831850b63315 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 23 Jun 2023 12:37:46 +0300 Subject: [PATCH 157/216] feat(cli): `jemalloc-prof` feature (#3322) --- Makefile | 2 +- bin/reth/Cargo.toml | 1 + bin/reth/src/lib.rs | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b355991fec66..260dcc95c92a 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ build-native-%: # The resulting binaries will be created in the `target/` directory. # No jemalloc on Windows -build-x86_64-pc-windows-gnu: FEATURES := $(filter-out jemalloc,$(FEATURES)) +build-x86_64-pc-windows-gnu: FEATURES := $(filter-out jemalloc jemalloc-prof,$(FEATURES)) # Note: The additional rustc compiler flags are for intrinsics needed by MDBX. # See: https://github.com/cross-rs/cross/wiki/FAQ#undefined-reference-with-build-std diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 59a7b2651e93..c6b3560530bb 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -87,6 +87,7 @@ humantime = "2.1.0" [features] jemalloc = ["dep:jemallocator"] +jemalloc-prof = ["jemalloc", "jemallocator?/profiling"] only-info-logs = ["tracing/release_max_level_info"] [build-dependencies] diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index a3ba783bfdf2..94a940828312 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -12,6 +12,10 @@ //! - `jemalloc`: Uses [jemallocator](https://github.com/tikv/jemallocator) as the global allocator. //! This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) //! for more info. +//! - `jemalloc-prof`: Enables [jemallocator's](https://github.com/tikv/jemallocator) heap profiling +//! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) +//! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) +//! for more info. //! - `only-info-logs`: Disables all logs below `info` level. This can speed up the node, since //! fewer calls to the logging component is made. From 3e07a5d508bce530b8aa3ecb35f07a1306676234 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Fri, 23 Jun 2023 11:55:07 +0100 Subject: [PATCH 158/216] chore: move `AccountWriter` methods to `HashingWriter` and `HistoryWriter` (#3332) Co-authored-by: Roman Krasiuk --- crates/staged-sync/src/utils/init.rs | 4 +- crates/stages/src/stages/hashing_account.rs | 2 +- .../src/stages/index_account_history.rs | 2 +- crates/storage/provider/src/lib.rs | 4 +- .../src/providers/database/provider.rs | 313 +++++++++--------- crates/storage/provider/src/traits/account.rs | 24 -- crates/storage/provider/src/traits/hashing.rs | 11 +- crates/storage/provider/src/traits/history.rs | 11 + crates/storage/provider/src/traits/mod.rs | 2 +- 9 files changed, 184 insertions(+), 189 deletions(-) diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index e146aab745ac..aadf2d6d90d4 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -9,9 +9,7 @@ use reth_db::{ version::{check_db_version_file, create_db_version_file, DatabaseVersionError}, }; use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, StorageEntry, H256, U256}; -use reth_provider::{ - AccountWriter, DatabaseProviderRW, HashingWriter, HistoryWriter, PostState, ProviderFactory, -}; +use reth_provider::{DatabaseProviderRW, HashingWriter, HistoryWriter, PostState, ProviderFactory}; use std::{collections::BTreeMap, fs, path::Path, sync::Arc}; use tracing::debug; diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 84485c745433..79b93cb2da4b 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -16,7 +16,7 @@ use reth_primitives::{ StageId, }, }; -use reth_provider::{AccountExtReader, AccountWriter, DatabaseProviderRW}; +use reth_provider::{AccountExtReader, DatabaseProviderRW, HashingWriter}; use std::{ cmp::max, fmt::Debug, diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index 30bc39aec83a..34848e71f0fb 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -1,7 +1,7 @@ use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; use reth_db::database::Database; use reth_primitives::stage::{StageCheckpoint, StageId}; -use reth_provider::{AccountExtReader, AccountWriter, DatabaseProviderRW}; +use reth_provider::{AccountExtReader, DatabaseProviderRW, HistoryWriter}; use std::fmt::Debug; /// Stage is indexing history the account changesets generated in diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index a0c205568401..4e6485fb2adb 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -15,8 +15,8 @@ /// Various provider traits. mod traits; pub use traits::{ - AccountExtReader, AccountReader, AccountWriter, BlockExecutor, BlockHashProvider, - BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockSource, + AccountExtReader, AccountReader, BlockExecutor, BlockHashProvider, BlockIdProvider, + BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockSource, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, ExecutorFactory, HashingWriter, HeaderProvider, HistoryWriter, PostStateDataProvider, diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 86e7429fe7b0..766975fbae71 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -2,9 +2,9 @@ use crate::{ insert_canonical_block, post_state::StorageChangeset, traits::{AccountExtReader, BlockSource, ReceiptProvider, StageCheckpointWriter}, - AccountReader, AccountWriter, BlockHashProvider, BlockNumProvider, BlockProvider, - EvmEnvProvider, HashingWriter, HeaderProvider, HistoryWriter, PostState, ProviderError, - StageCheckpointReader, StorageReader, TransactionsProvider, WithdrawalsProvider, + AccountReader, BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, + HashingWriter, HeaderProvider, HistoryWriter, PostState, ProviderError, StageCheckpointReader, + StorageReader, TransactionsProvider, WithdrawalsProvider, }; use itertools::{izip, Itertools}; use reth_db::{ @@ -851,159 +851,6 @@ impl<'this, TX: DbTx<'this>> AccountExtReader for DatabaseProvider<'this, TX> { } } -impl<'this, TX: DbTxMut<'this> + DbTx<'this>> AccountWriter for DatabaseProvider<'this, TX> { - fn unwind_account_hashing(&self, range: RangeInclusive) -> Result<()> { - let mut hashed_accounts = self.tx.cursor_write::()?; - - // Aggregate all block changesets and make a list of accounts that have been changed. - self.tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()? - .into_iter() - .rev() - // fold all account to get the old balance/nonces and account that needs to be removed - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap>, (_, account_before)| { - accounts.insert(account_before.address, account_before.info); - accounts - }, - ) - .into_iter() - // hash addresses and collect it inside sorted BTreeMap. - // We are doing keccak only once per address. - .map(|(address, account)| (keccak256(address), account)) - .collect::>() - .into_iter() - // Apply values to HashedState (if Account is None remove it); - .try_for_each(|(hashed_address, account)| -> Result<()> { - if let Some(account) = account { - hashed_accounts.upsert(hashed_address, account)?; - } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { - hashed_accounts.delete_current()?; - } - Ok(()) - })?; - - Ok(()) - } - - fn unwind_account_history_indices(&self, range: RangeInclusive) -> Result { - let account_changeset = self - .tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; - let changesets = account_changeset.len(); - - let last_indices = account_changeset - .into_iter() - // reverse so we can get lowest block number where we need to unwind account. - .rev() - // fold all account and get last block number - .fold(BTreeMap::new(), |mut accounts: BTreeMap, (index, account)| { - // we just need address and lowest block number. - accounts.insert(account.address, index); - accounts - }); - // try to unwind the index - let mut cursor = self.tx.cursor_write::()?; - for (address, rem_index) in last_indices { - let shard_part = unwind_account_history_shards::(&mut cursor, address, rem_index)?; - - // check last shard_part, if present, items needs to be reinserted. - if !shard_part.is_empty() { - // there are items in list - self.tx.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(shard_part) - .expect("There is at least one element in list and it is sorted."), - )?; - } - } - - Ok(changesets) - } - - fn insert_account_history_index( - &self, - account_transitions: BTreeMap>, - ) -> Result<()> { - // insert indexes to AccountHistory. - for (address, mut indices) in account_transitions { - // Load last shard and check if it is full and remove if it is not. If list is empty, - // last shard was full or there is no shards at all. - let mut last_shard = { - let mut cursor = self.tx.cursor_read::()?; - let last = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; - if let Some((shard_key, list)) = last { - // delete old shard so new one can be inserted. - self.tx.delete::(shard_key, None)?; - let list = list.iter(0).map(|i| i as u64).collect::>(); - list - } else { - Vec::new() - } - }; - - last_shard.append(&mut indices); - // chunk indices and insert them in shards of N size. - let mut chunks = last_shard - .iter() - .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) - .into_iter() - .map(|chunks| chunks.map(|i| *i as usize).collect::>()) - .collect::>(); - let last_chunk = chunks.pop(); - - chunks.into_iter().try_for_each(|list| { - self.tx.put::( - ShardedKey::new( - address, - *list.last().expect("Chuck does not return empty list") as BlockNumber, - ), - BlockNumberList::new(list).expect("Indices are presorted and not empty"), - ) - })?; - - // Insert last list with u64::MAX - if let Some(last_list) = last_chunk { - self.tx.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), - )? - } - } - Ok(()) - } - - fn insert_account_for_hashing( - &self, - accounts: impl IntoIterator)>, - ) -> Result<()> { - let mut hashed_accounts = self.tx.cursor_write::()?; - - let hashes_accounts = accounts.into_iter().fold( - BTreeMap::new(), - |mut map: BTreeMap>, (address, account)| { - map.insert(keccak256(address), account); - map - }, - ); - - hashes_accounts.into_iter().try_for_each(|(hashed_address, account)| -> Result<()> { - if let Some(account) = account { - hashed_accounts.upsert(hashed_address, account)? - } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { - hashed_accounts.delete_current()?; - } - Ok(()) - })?; - Ok(()) - } -} - impl<'this, TX: DbTx<'this>> HeaderProvider for DatabaseProvider<'this, TX> { fn header(&self, block_hash: &BlockHash) -> Result> { if let Some(num) = self.block_number(*block_hash)? { @@ -1648,6 +1495,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HashingWriter for DatabaseProvider Ok(()) } + fn insert_storage_for_hashing( &self, storages: impl IntoIterator)>, @@ -1683,6 +1531,68 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HashingWriter for DatabaseProvider })?; Ok(()) } + + fn unwind_account_hashing(&self, range: RangeInclusive) -> Result<()> { + let mut hashed_accounts = self.tx.cursor_write::()?; + + // Aggregate all block changesets and make a list of accounts that have been changed. + self.tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()? + .into_iter() + .rev() + // fold all account to get the old balance/nonces and account that needs to be removed + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap>, (_, account_before)| { + accounts.insert(account_before.address, account_before.info); + accounts + }, + ) + .into_iter() + // hash addresses and collect it inside sorted BTreeMap. + // We are doing keccak only once per address. + .map(|(address, account)| (keccak256(address), account)) + .collect::>() + .into_iter() + // Apply values to HashedState (if Account is None remove it); + .try_for_each(|(hashed_address, account)| -> Result<()> { + if let Some(account) = account { + hashed_accounts.upsert(hashed_address, account)?; + } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { + hashed_accounts.delete_current()?; + } + Ok(()) + })?; + + Ok(()) + } + + fn insert_account_for_hashing( + &self, + accounts: impl IntoIterator)>, + ) -> Result<()> { + let mut hashed_accounts = self.tx.cursor_write::()?; + + let hashes_accounts = accounts.into_iter().fold( + BTreeMap::new(), + |mut map: BTreeMap>, (address, account)| { + map.insert(keccak256(address), account); + map + }, + ); + + hashes_accounts.into_iter().try_for_each(|(hashed_address, account)| -> Result<()> { + if let Some(account) = account { + hashed_accounts.upsert(hashed_address, account)? + } else if hashed_accounts.seek_exact(hashed_address)?.is_some() { + hashed_accounts.delete_current()?; + } + Ok(()) + })?; + Ok(()) + } } impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider<'this, TX> { @@ -1701,6 +1611,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider Ok(()) } + fn insert_storage_history_index( &self, storage_transitions: BTreeMap<(Address, H256), Vec>, @@ -1739,6 +1650,7 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider } Ok(()) } + fn unwind_storage_history_indices(&self, range: Range) -> Result { let storage_changesets = self .tx @@ -1779,4 +1691,93 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider Ok(changesets) } + + fn insert_account_history_index( + &self, + account_transitions: BTreeMap>, + ) -> Result<()> { + // insert indexes to AccountHistory. + for (address, mut indices) in account_transitions { + // Load last shard and check if it is full and remove if it is not. If list is empty, + // last shard was full or there is no shards at all. + let mut last_shard = { + let mut cursor = self.tx.cursor_read::()?; + let last = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; + if let Some((shard_key, list)) = last { + // delete old shard so new one can be inserted. + self.tx.delete::(shard_key, None)?; + let list = list.iter(0).map(|i| i as u64).collect::>(); + list + } else { + Vec::new() + } + }; + + last_shard.append(&mut indices); + // chunk indices and insert them in shards of N size. + let mut chunks = last_shard + .iter() + .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) + .into_iter() + .map(|chunks| chunks.map(|i| *i as usize).collect::>()) + .collect::>(); + let last_chunk = chunks.pop(); + + chunks.into_iter().try_for_each(|list| { + self.tx.put::( + ShardedKey::new( + address, + *list.last().expect("Chuck does not return empty list") as BlockNumber, + ), + BlockNumberList::new(list).expect("Indices are presorted and not empty"), + ) + })?; + + // Insert last list with u64::MAX + if let Some(last_list) = last_chunk { + self.tx.put::( + ShardedKey::new(address, u64::MAX), + BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), + )? + } + } + Ok(()) + } + + fn unwind_account_history_indices(&self, range: RangeInclusive) -> Result { + let account_changeset = self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()?; + let changesets = account_changeset.len(); + + let last_indices = account_changeset + .into_iter() + // reverse so we can get lowest block number where we need to unwind account. + .rev() + // fold all account and get last block number + .fold(BTreeMap::new(), |mut accounts: BTreeMap, (index, account)| { + // we just need address and lowest block number. + accounts.insert(account.address, index); + accounts + }); + // try to unwind the index + let mut cursor = self.tx.cursor_write::()?; + for (address, rem_index) in last_indices { + let shard_part = unwind_account_history_shards::(&mut cursor, address, rem_index)?; + + // check last shard_part, if present, items needs to be reinserted. + if !shard_part.is_empty() { + // there are items in list + self.tx.put::( + ShardedKey::new(address, u64::MAX), + BlockNumberList::new(shard_part) + .expect("There is at least one element in list and it is sorted."), + )?; + } + } + + Ok(changesets) + } } diff --git a/crates/storage/provider/src/traits/account.rs b/crates/storage/provider/src/traits/account.rs index ceafaec2458b..d08d15a1218e 100644 --- a/crates/storage/provider/src/traits/account.rs +++ b/crates/storage/provider/src/traits/account.rs @@ -42,27 +42,3 @@ pub trait AccountExtReader: Send + Sync { range: RangeInclusive, ) -> Result>>; } - -/// Account reader -#[auto_impl(&, Arc, Box)] -pub trait AccountWriter: Send + Sync { - /// Unwind and clear account hashing - fn unwind_account_hashing(&self, range: RangeInclusive) -> Result<()>; - - /// Unwind and clear account history indices. - /// - /// Returns number of changesets walked. - fn unwind_account_history_indices(&self, range: RangeInclusive) -> Result; - - /// Insert account change index to database. Used inside AccountHistoryIndex stage - fn insert_account_history_index( - &self, - account_transitions: BTreeMap>, - ) -> Result<()>; - - /// iterate over accounts and insert them to hashing table - fn insert_account_for_hashing( - &self, - accounts: impl IntoIterator)>, - ) -> Result<()>; -} diff --git a/crates/storage/provider/src/traits/hashing.rs b/crates/storage/provider/src/traits/hashing.rs index 1ebe58bec72f..dc327ca09cca 100644 --- a/crates/storage/provider/src/traits/hashing.rs +++ b/crates/storage/provider/src/traits/hashing.rs @@ -1,12 +1,21 @@ use auto_impl::auto_impl; use reth_db::models::BlockNumberAddress; use reth_interfaces::Result; -use reth_primitives::{Address, BlockNumber, StorageEntry, H256}; +use reth_primitives::{Account, Address, BlockNumber, StorageEntry, H256}; use std::ops::{Range, RangeInclusive}; /// Hashing Writer #[auto_impl(&, Arc, Box)] pub trait HashingWriter: Send + Sync { + /// Unwind and clear account hashing + fn unwind_account_hashing(&self, range: RangeInclusive) -> Result<()>; + + /// Inserts all accounts into [reth_db::tables::AccountHistory] table. + fn insert_account_for_hashing( + &self, + accounts: impl IntoIterator)>, + ) -> Result<()>; + /// Unwind and clear storage hashing fn unwind_storage_hashing(&self, range: Range) -> Result<()>; diff --git a/crates/storage/provider/src/traits/history.rs b/crates/storage/provider/src/traits/history.rs index d79bdd1c3fae..af391c4a05e1 100644 --- a/crates/storage/provider/src/traits/history.rs +++ b/crates/storage/provider/src/traits/history.rs @@ -10,6 +10,17 @@ use std::{ /// History Writer #[auto_impl(&, Arc, Box)] pub trait HistoryWriter: Send + Sync { + /// Unwind and clear account history indices. + /// + /// Returns number of changesets walked. + fn unwind_account_history_indices(&self, range: RangeInclusive) -> Result; + + /// Insert account change index to database. Used inside AccountHistoryIndex stage + fn insert_account_history_index( + &self, + account_transitions: BTreeMap>, + ) -> Result<()>; + /// Unwind and clear storage history indices. /// /// Returns number of changesets walked. diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index c4805a9cd6f0..81ad694a5733 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -1,7 +1,7 @@ //! Collection of common provider traits. mod account; -pub use account::{AccountExtReader, AccountReader, AccountWriter}; +pub use account::{AccountExtReader, AccountReader}; mod storage; pub use storage::StorageReader; From a66a350cf02a018f19f51cae614300618a447006 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 23 Jun 2023 14:11:08 +0300 Subject: [PATCH 159/216] chore: simplify usage of `IntegerList` (#3351) --- crates/primitives/src/integer_list.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/primitives/src/integer_list.rs b/crates/primitives/src/integer_list.rs index f4dd5a6ae15c..fe65ad588c60 100644 --- a/crates/primitives/src/integer_list.rs +++ b/crates/primitives/src/integer_list.rs @@ -30,11 +30,26 @@ impl IntegerList { /// Creates an IntegerList from a list of integers. `usize` is safe to use since /// [`sucds::EliasFano`] restricts its compilation to 64bits. /// - /// List should be pre-sorted and not empty. + /// # Returns + /// + /// Returns an error if the list is empty or not pre-sorted. pub fn new>(list: T) -> Result { Ok(Self(EliasFano::from_ints(list.as_ref()).map_err(|_| EliasFanoError::InvalidInput)?)) } + // Creates an IntegerList from a pre-sorted list of integers. `usize` is safe to use since + /// [`sucds::EliasFano`] restricts its compilation to 64bits. + /// + /// # Panics + /// + /// Panics if the list is empty or not pre-sorted. + pub fn new_pre_sorted>(list: T) -> Self { + Self( + EliasFano::from_ints(list.as_ref()) + .expect("IntegerList must be pre-sorted and non-empty."), + ) + } + /// Serializes a [`IntegerList`] into a sequence of bytes. pub fn to_bytes(&self) -> Vec { let mut vec = Vec::with_capacity(self.0.size_in_bytes()); From f4d7a6a369fa510bc9594f986947d91def06256f Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 23 Jun 2023 14:15:50 +0300 Subject: [PATCH 160/216] chore: `ShardedKey::last` helper methods (#3352) --- crates/storage/db/src/tables/models/sharded_key.rs | 6 ++++++ .../db/src/tables/models/storage_sharded_key.rs | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/storage/db/src/tables/models/sharded_key.rs b/crates/storage/db/src/tables/models/sharded_key.rs index eefc175e3ee6..71985f1f8e32 100644 --- a/crates/storage/db/src/tables/models/sharded_key.rs +++ b/crates/storage/db/src/tables/models/sharded_key.rs @@ -29,6 +29,12 @@ impl ShardedKey { pub fn new(key: T, highest_block_number: BlockNumber) -> Self { ShardedKey { key, highest_block_number } } + + /// Creates a new key with the highest block number set to maximum. + /// This is useful when we want to search the last value for a given key. + pub fn last(key: T) -> Self { + Self { key, highest_block_number: u64::MAX } + } } impl Encode for ShardedKey diff --git a/crates/storage/db/src/tables/models/storage_sharded_key.rs b/crates/storage/db/src/tables/models/storage_sharded_key.rs index e8f3d55a41f6..2b8025a8f3aa 100644 --- a/crates/storage/db/src/tables/models/storage_sharded_key.rs +++ b/crates/storage/db/src/tables/models/storage_sharded_key.rs @@ -1,4 +1,4 @@ -//! Sharded key +//! Storage sharded key use crate::{ table::{Decode, Encode}, @@ -32,6 +32,15 @@ impl StorageShardedKey { pub fn new(address: H160, storage_key: H256, highest_block_number: BlockNumber) -> Self { Self { address, sharded_key: ShardedKey { key: storage_key, highest_block_number } } } + + /// Creates a new key with the highest block number set to maximum. + /// This is useful when we want to search the last value for a given key. + pub fn last(address: H160, storage_key: H256) -> Self { + Self { + address, + sharded_key: ShardedKey { key: storage_key, highest_block_number: u64::MAX }, + } + } } impl Encode for StorageShardedKey { From be58d1c28606f15c24cca0907ffaacf1b93bfbea Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 23 Jun 2023 16:58:51 +0200 Subject: [PATCH 161/216] fix: use metered poll fns (#3353) --- crates/metrics/src/common/mpsc.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/metrics/src/common/mpsc.rs b/crates/metrics/src/common/mpsc.rs index 0f4688e21960..6148e5a876a7 100644 --- a/crates/metrics/src/common/mpsc.rs +++ b/crates/metrics/src/common/mpsc.rs @@ -123,7 +123,7 @@ impl Stream for UnboundedMeteredReceiver { type Item = T; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.receiver.poll_recv(cx) + self.poll_recv(cx) } } @@ -231,7 +231,7 @@ impl Stream for MeteredReceiver { type Item = T; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.receiver.poll_recv(cx) + self.poll_recv(cx) } } From 2b1a34116df2731a4e4abc28e4a05133558bc1a0 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 23 Jun 2023 18:47:55 +0300 Subject: [PATCH 162/216] chore(provider): simplify history unwind (#3355) --- Cargo.lock | 1 + crates/storage/db/Cargo.toml | 1 + .../db/src/tables/models/sharded_key.rs | 6 + .../src/tables/models/storage_sharded_key.rs | 5 +- .../src/providers/database/provider.rs | 199 ++++++++---------- 5 files changed, 104 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96ccb4205b8c..847fc94405c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5128,6 +5128,7 @@ dependencies = [ "async-trait", "bytes", "criterion", + "derive_more", "futures", "heapless", "iai", diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 818c22738b74..5254d69bb6a9 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -37,6 +37,7 @@ page_size = "0.4.2" thiserror = { workspace = true } tempfile = { version = "3.3.0", optional = true } parking_lot = "0.12" +derive_more = "0.99" # arbitrary utils arbitrary = { version = "1.1.7", features = ["derive"], optional = true } diff --git a/crates/storage/db/src/tables/models/sharded_key.rs b/crates/storage/db/src/tables/models/sharded_key.rs index 71985f1f8e32..a38c3af3a315 100644 --- a/crates/storage/db/src/tables/models/sharded_key.rs +++ b/crates/storage/db/src/tables/models/sharded_key.rs @@ -24,6 +24,12 @@ pub struct ShardedKey { pub highest_block_number: BlockNumber, } +impl AsRef> for ShardedKey { + fn as_ref(&self) -> &ShardedKey { + self + } +} + impl ShardedKey { /// Creates a new `ShardedKey`. pub fn new(key: T, highest_block_number: BlockNumber) -> Self { diff --git a/crates/storage/db/src/tables/models/storage_sharded_key.rs b/crates/storage/db/src/tables/models/storage_sharded_key.rs index 2b8025a8f3aa..984933d1f172 100644 --- a/crates/storage/db/src/tables/models/storage_sharded_key.rs +++ b/crates/storage/db/src/tables/models/storage_sharded_key.rs @@ -4,7 +4,7 @@ use crate::{ table::{Decode, Encode}, DatabaseError, }; - +use derive_more::AsRef; use reth_primitives::{BlockNumber, H160, H256}; use serde::{Deserialize, Serialize}; @@ -19,11 +19,12 @@ pub const NUM_OF_INDICES_IN_SHARD: usize = 2_000; /// `Address | Storagekey | 200` -> data is from transition 0 to 200. /// /// `Address | StorageKey | 300` -> data is from transition 201 to 300. -#[derive(Debug, Default, Clone, Eq, Ord, PartialOrd, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Default, Clone, Eq, Ord, PartialOrd, PartialEq, AsRef, Serialize, Deserialize)] pub struct StorageShardedKey { /// Storage account address. pub address: H160, /// Storage slot with highest transition id. + #[as_ref] pub sharded_key: ShardedKey, } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 766975fbae71..66a72d21d47f 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -18,7 +18,7 @@ use reth_db::{ }, table::Table, tables, - transaction::{DbTx, DbTxMut, DbTxMutGAT}, + transaction::{DbTx, DbTxMut}, BlockNumberList, DatabaseError, }; use reth_interfaces::Result; @@ -102,75 +102,51 @@ impl<'this, TX: DbTxMut<'this>> DatabaseProvider<'this, TX> { } } -/// Unwind all history shards. For boundary shard, remove it from database and -/// return last part of shard with still valid items. If all full shard were removed, return list -/// would be empty. -fn unwind_account_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( - cursor: &mut >::CursorMut, - address: Address, +/// For a given key, unwind all history shards that are below the given block number. +/// +/// S - Sharded key subtype. +/// T - Table to walk over. +/// C - Cursor implementation. +/// +/// This function walks the entries from the given start key and deletes all shards that belong to +/// the key and are below the given block number. +/// +/// The boundary shard (the shard is split by the block number) is removed from the database. Any +/// indices that are above the block number are filtered out. The boundary shard is returned for +/// reinsertion (if it's not empty). +fn unwind_history_shards<'a, S, T, C>( + cursor: &mut C, + start_key: T::Key, block_number: BlockNumber, -) -> Result> { - let mut item = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; - + mut shard_belongs_to_key: impl FnMut(&T::Key) -> bool, +) -> Result> +where + T: Table, + T::Key: AsRef>, + C: DbCursorRO<'a, T> + DbCursorRW<'a, T>, +{ + let mut item = cursor.seek_exact(start_key)?; while let Some((sharded_key, list)) = item { - // there is no more shard for address - if sharded_key.key != address { + // If the shard does not belong to the key, break. + if !shard_belongs_to_key(&sharded_key) { break } cursor.delete_current()?; - // check first item and if it is more and eq than `block_number` delete current - // item. - let first = list.iter(0).next().expect("List can't empty"); - if first >= block_number as usize { - item = cursor.prev()?; - continue - } else if block_number <= sharded_key.highest_block_number { - // if first element is in scope whole list would be removed. - // so at least this first element is present. - return Ok(list.iter(0).take_while(|i| *i < block_number as usize).collect::>()) - } else { - let new_list = list.iter(0).collect::>(); - return Ok(new_list) - } - } - Ok(Vec::new()) -} -/// Unwind all history shards. For boundary shard, remove it from database and -/// return last part of shard with still valid items. If all full shard were removed, return list -/// would be empty but this does not mean that there is none shard left but that there is no -/// split shards. -fn unwind_storage_history_shards<'a, TX: reth_db::transaction::DbTxMutGAT<'a>>( - cursor: &mut >::CursorMut, - address: Address, - storage_key: H256, - block_number: BlockNumber, -) -> Result> { - let mut item = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; - - while let Some((storage_sharded_key, list)) = item { - // there is no more shard for address - if storage_sharded_key.address != address || - storage_sharded_key.sharded_key.key != storage_key - { - // there is no more shard for address and storage_key. - break - } - cursor.delete_current()?; - // check first item and if it is more and eq than `block_number` delete current - // item. - let first = list.iter(0).next().expect("List can't empty"); + // Check the first item. + // If it is greater or eq to the block number, delete it. + let first = list.iter(0).next().expect("List can't be empty"); if first >= block_number as usize { item = cursor.prev()?; continue - } else if block_number <= storage_sharded_key.sharded_key.highest_block_number { - // if first element is in scope whole list would be removed. - // so at least this first element is present. + } else if block_number <= sharded_key.as_ref().highest_block_number { + // Filter out all elements greater than block number. return Ok(list.iter(0).take_while(|i| *i < block_number as usize).collect::>()) } else { return Ok(list.iter(0).collect::>()) } } + Ok(Vec::new()) } @@ -1651,47 +1627,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider Ok(()) } - fn unwind_storage_history_indices(&self, range: Range) -> Result { - let storage_changesets = self - .tx - .cursor_read::()? - .walk_range(range)? - .collect::, _>>()?; - let changesets = storage_changesets.len(); - - let last_indices = storage_changesets - .into_iter() - // reverse so we can get lowest block number where we need to unwind account. - .rev() - // fold all storages and get last block number - .fold( - BTreeMap::new(), - |mut accounts: BTreeMap<(Address, H256), u64>, (index, storage)| { - // we just need address and lowest block number. - accounts.insert((index.address(), storage.key), index.block_number()); - accounts - }, - ); - - let mut cursor = self.tx.cursor_write::()?; - for ((address, storage_key), rem_index) in last_indices { - let shard_part = - unwind_storage_history_shards::(&mut cursor, address, storage_key, rem_index)?; - - // check last shard_part, if present, items needs to be reinserted. - if !shard_part.is_empty() { - // there are items in list - self.tx.put::( - StorageShardedKey::new(address, storage_key, u64::MAX), - BlockNumberList::new(shard_part) - .expect("There is at least one element in list and it is sorted."), - )?; - } - } - - Ok(changesets) - } - fn insert_account_history_index( &self, account_transitions: BTreeMap>, @@ -1744,6 +1679,53 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider Ok(()) } + fn unwind_storage_history_indices(&self, range: Range) -> Result { + let storage_changesets = self + .tx + .cursor_read::()? + .walk_range(range)? + .collect::, _>>()?; + let changesets = storage_changesets.len(); + + let last_indices = storage_changesets + .into_iter() + // reverse so we can get lowest block number where we need to unwind account. + .rev() + // fold all storages and get last block number + .fold( + BTreeMap::new(), + |mut accounts: BTreeMap<(Address, H256), u64>, (index, storage)| { + // we just need address and lowest block number. + accounts.insert((index.address(), storage.key), index.block_number()); + accounts + }, + ); + + let mut cursor = self.tx.cursor_write::()?; + for ((address, storage_key), rem_index) in last_indices { + let partial_shard = unwind_history_shards::<_, tables::StorageHistory, _>( + &mut cursor, + StorageShardedKey::last(address, storage_key), + rem_index, + |storage_sharded_key| { + storage_sharded_key.address == address && + storage_sharded_key.sharded_key.key == storage_key + }, + )?; + + // Check the last returned partial shard. + // If it's not empty, the shard needs to be reinserted. + if !partial_shard.is_empty() { + cursor.insert( + StorageShardedKey::last(address, storage_key), + BlockNumberList::new_pre_sorted(partial_shard), + )?; + } + } + + Ok(changesets) + } + fn unwind_account_history_indices(&self, range: RangeInclusive) -> Result { let account_changeset = self .tx @@ -1762,18 +1744,23 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider accounts.insert(account.address, index); accounts }); - // try to unwind the index + + // Unwind the account history index. let mut cursor = self.tx.cursor_write::()?; for (address, rem_index) in last_indices { - let shard_part = unwind_account_history_shards::(&mut cursor, address, rem_index)?; + let partial_shard = unwind_history_shards::<_, tables::AccountHistory, _>( + &mut cursor, + ShardedKey::last(address), + rem_index, + |sharded_key| sharded_key.key == address, + )?; - // check last shard_part, if present, items needs to be reinserted. - if !shard_part.is_empty() { - // there are items in list - self.tx.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(shard_part) - .expect("There is at least one element in list and it is sorted."), + // Check the last returned partial shard. + // If it's not empty, the shard needs to be reinserted. + if !partial_shard.is_empty() { + cursor.insert( + ShardedKey::last(address), + BlockNumberList::new_pre_sorted(partial_shard), )?; } } From c2ad5a222f514a51252b5303cf8f1b3dbda210eb Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Fri, 23 Jun 2023 12:06:34 -0400 Subject: [PATCH 163/216] feat: add transaction pool metrics graphs (#3348) --- etc/grafana/dashboards/overview.json | 575 ++++++++++++++++++++++++++- 1 file changed, 564 insertions(+), 11 deletions(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 05f53a780387..f6fcec21d147 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -2604,6 +2604,559 @@ "x": 0, "y": 101 }, + "id": 89, + "panels": [], + "title": "Transaction Pool", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Tracks a heuristic of the memory footprint of the various transaction pool sub-pools", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 102 + }, + "id": 91, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_basefee_pool_size_bytes", + "legendFormat": "Base fee pool size (bytes)", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_pending_pool_size_bytes", + "hide": false, + "legendFormat": "Pending pool size (bytes)", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_queued_pool_size_bytes", + "hide": false, + "legendFormat": "Queued pool size (bytes)", + "range": true, + "refId": "C" + } + ], + "title": "Subpool sizes in bytes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Tracks the number of transactions in the various transaction pool sub-pools", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 102 + }, + "id": 92, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_basefee_pool_transactions", + "legendFormat": "Base fee pool transactions", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_pending_pool_transactions", + "hide": false, + "legendFormat": "Pending pool transactions", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_queued_pool_transactions", + "hide": false, + "legendFormat": "Queued pool transactions", + "range": true, + "refId": "C" + } + ], + "title": "Subpool transaction count", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Tracks the number of transactions inserted and removed from the transaction pool, as well as the number of invalid transactions", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 110 + }, + "id": 93, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_inserted_transactions", + "legendFormat": "Inserted transactions", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_removed_transactions", + "hide": false, + "legendFormat": "Removed transactions", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_transaction_pool_invalid_transactions", + "hide": false, + "legendFormat": "Invalid transactions", + "range": true, + "refId": "C" + } + ], + "title": "Inserted transactions", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Number of transactions about to be imported into the pool.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 110 + }, + "id": 94, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_network_pending_pool_imports", + "hide": false, + "legendFormat": "Transactions pending import", + "range": true, + "refId": "C" + } + ], + "title": "Pending pool imports", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Tracks the number of transaction messages in the channel from the network to the transaction pool", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 118 + }, + "id": 95, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_network_pool_transactions_messages_sent - reth_network_pool_transactions_messages_received", + "hide": false, + "instant": false, + "legendFormat": "Total events in the channel", + "range": true, + "refId": "A" + } + ], + "title": "Network transaction channel", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 126 + }, "id": 79, "panels": [], "title": "Blockchain tree", @@ -2671,7 +3224,7 @@ "h": 8, "w": 12, "x": 0, - "y": 102 + "y": 127 }, "id": 74, "options": { @@ -2765,7 +3318,7 @@ "h": 8, "w": 12, "x": 12, - "y": 102 + "y": 127 }, "id": 80, "options": { @@ -2859,7 +3412,7 @@ "h": 8, "w": 12, "x": 0, - "y": 110 + "y": 135 }, "id": 81, "options": { @@ -2897,7 +3450,7 @@ "h": 1, "w": 24, "x": 0, - "y": 118 + "y": 143 }, "id": 87, "panels": [], @@ -2966,7 +3519,7 @@ "h": 8, "w": 12, "x": 0, - "y": 119 + "y": 144 }, "id": 83, "options": { @@ -3059,7 +3612,7 @@ "h": 8, "w": 12, "x": 12, - "y": 119 + "y": 144 }, "id": 84, "options": { @@ -3164,7 +3717,7 @@ "h": 8, "w": 12, "x": 0, - "y": 127 + "y": 152 }, "id": 85, "options": { @@ -3201,7 +3754,7 @@ "h": 1, "w": 24, "x": 0, - "y": 135 + "y": 160 }, "id": 68, "panels": [ @@ -3266,7 +3819,7 @@ "h": 8, "w": 11, "x": 0, - "y": 9 + "y": 17 }, "id": 60, "options": { @@ -3358,7 +3911,7 @@ "h": 8, "w": 13, "x": 11, - "y": 9 + "y": 17 }, "id": 62, "options": { @@ -3450,7 +4003,7 @@ "h": 7, "w": 11, "x": 0, - "y": 17 + "y": 25 }, "id": 64, "options": { From faeb6125535e47eb1abbf70d71d68e55eb799526 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 23 Jun 2023 19:32:58 +0200 Subject: [PATCH 164/216] fix: flaky addr in use test (#3364) --- crates/net/network/tests/it/startup.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/net/network/tests/it/startup.rs b/crates/net/network/tests/it/startup.rs index 4994a7365b83..0d36867fc8fd 100644 --- a/crates/net/network/tests/it/startup.rs +++ b/crates/net/network/tests/it/startup.rs @@ -11,10 +11,10 @@ use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4}, }; -fn is_addr_in_use_kind(err: NetworkError, kind: ServiceKind) -> bool { +fn is_addr_in_use_kind(err: &NetworkError, kind: ServiceKind) -> bool { match err { NetworkError::AddressAlreadyInUse { kind: k, error } => { - k == kind && error.kind() == io::ErrorKind::AddrInUse + *k == kind && error.kind() == io::ErrorKind::AddrInUse } _ => false, } @@ -34,12 +34,17 @@ async fn test_is_default_syncing() { #[tokio::test(flavor = "multi_thread")] async fn test_listener_addr_in_use() { let secret_key = SecretKey::new(&mut rand::thread_rng()); - let config = NetworkConfigBuilder::new(secret_key).build(NoopProvider::default()); - let _network = NetworkManager::new(config).await.unwrap(); - let config = NetworkConfigBuilder::new(secret_key).build(NoopProvider::default()); - let addr = config.discovery_addr; + let config = + NetworkConfigBuilder::new(secret_key).listener_port(0).build(NoopProvider::default()); + let network = NetworkManager::new(config).await.unwrap(); + let listener_port = network.local_addr().port(); + let config = NetworkConfigBuilder::new(secret_key) + .listener_port(listener_port) + .build(NoopProvider::default()); + let addr = config.listener_addr; let result = NetworkManager::new(config).await; - assert!(is_addr_in_use_kind(result.err().unwrap(), ServiceKind::Listener(addr))); + let err = result.err().unwrap(); + assert!(is_addr_in_use_kind(&err, ServiceKind::Listener(addr)), "{:?}", err); } #[tokio::test(flavor = "multi_thread")] @@ -50,5 +55,5 @@ async fn test_discovery_addr_in_use() { let _discovery = Discovery::new(addr, secret_key, Some(disc_config), None).await.unwrap(); let disc_config = Discv4Config::default(); let result = Discovery::new(addr, secret_key, Some(disc_config), None).await; - assert!(is_addr_in_use_kind(result.err().unwrap(), ServiceKind::Discovery(addr))); + assert!(is_addr_in_use_kind(&result.err().unwrap(), ServiceKind::Discovery(addr))); } From cff4aa7b2b2fb33376f27f62c3317e2d94661021 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 23 Jun 2023 19:33:27 +0200 Subject: [PATCH 165/216] chore: shrink session buffers (#3360) --- crates/net/network/src/session/active.rs | 29 +++++++++++++++--------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 36c5b6892267..4b2ba780e82f 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -109,6 +109,12 @@ impl ActiveSession { id } + /// Shrinks the capacity of the internal buffers. + pub fn shrink_to_fit(&mut self) { + self.received_requests_from_remote.shrink_to_fit(); + self.queued_outgoing.shrink_to_fit(); + } + /// Handle a message read from the connection. /// /// Returns an error if the message is considered to be in violation of the protocol. @@ -437,17 +443,6 @@ impl ActiveSession { } } -/// Calculates a new timeout using an updated estimation of the RTT -#[inline] -fn calculate_new_timeout(current_timeout: Duration, estimated_rtt: Duration) -> Duration { - let new_timeout = estimated_rtt.mul_f64(SAMPLE_IMPACT) * TIMEOUT_SCALING; - - // this dampens sudden changes by taking a weighted mean of the old and new values - let smoothened_timeout = current_timeout.mul_f64(1.0 - SAMPLE_IMPACT) + new_timeout; - - smoothened_timeout.clamp(MINIMUM_TIMEOUT, MAXIMUM_TIMEOUT) -} - impl Future for ActiveSession { type Output = (); @@ -605,6 +600,8 @@ impl Future for ActiveSession { } } + this.shrink_to_fit(); + return Poll::Pending } } @@ -703,6 +700,16 @@ impl From for OutgoingMessage { } } +/// Calculates a new timeout using an updated estimation of the RTT +#[inline] +fn calculate_new_timeout(current_timeout: Duration, estimated_rtt: Duration) -> Duration { + let new_timeout = estimated_rtt.mul_f64(SAMPLE_IMPACT) * TIMEOUT_SCALING; + + // this dampens sudden changes by taking a weighted mean of the old and new values + let smoothened_timeout = current_timeout.mul_f64(1.0 - SAMPLE_IMPACT) + new_timeout; + + smoothened_timeout.clamp(MINIMUM_TIMEOUT, MAXIMUM_TIMEOUT) +} #[cfg(test)] mod tests { #![allow(dead_code)] From a3c66f36b4aded99d28bf1009e7b321c16499a6b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 23 Jun 2023 20:31:58 +0200 Subject: [PATCH 166/216] chore: shrink request buffer (#3363) --- crates/net/network/src/transactions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/net/network/src/transactions.rs b/crates/net/network/src/transactions.rs index c5d88c3e60b7..51d023d072cb 100644 --- a/crates/net/network/src/transactions.rs +++ b/crates/net/network/src/transactions.rs @@ -546,6 +546,8 @@ where } } + this.inflight_requests.shrink_to_fit(); + this.update_import_metrics(); // Advance all imports From db05e42ffd1572ed2e05f4f7500b858f1f47bcc6 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Fri, 23 Jun 2023 21:58:15 +0300 Subject: [PATCH 167/216] docs(trie): fix typo (#3366) --- crates/trie/src/trie.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index 6348e8f1a332..2dc257ca2092 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -54,7 +54,7 @@ impl<'a, 'b, TX, H> StateRoot<'a, 'b, TX, H> { self } - /// Set the threshold to maximum value so that itermediate progress is not returned. + /// Set the threshold to maximum value so that intermediate progress is not returned. pub fn with_no_threshold(mut self) -> Self { self.threshold = u64::MAX; self From 02673f2301b000433d4ad81ad25a4edf4682d75a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 23 Jun 2023 21:28:36 +0200 Subject: [PATCH 168/216] fix: use RO database for read only commands (#3368) --- bin/reth/src/db/list.rs | 6 +++--- bin/reth/src/db/mod.rs | 26 ++++++++++++++++++++------ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/bin/reth/src/db/list.rs b/bin/reth/src/db/list.rs index a4059fcf9cf5..ee9de7eeb448 100644 --- a/bin/reth/src/db/list.rs +++ b/bin/reth/src/db/list.rs @@ -5,7 +5,7 @@ use super::tui::DbListTUI; use eyre::WrapErr; use reth_db::{ database::Database, - mdbx::{Env, WriteMap}, + mdbx::{Env, NoWriteMap}, table::Table, TableType, TableViewer, Tables, }; @@ -34,7 +34,7 @@ pub struct Command { impl Command { /// Execute `db list` command - pub fn execute(self, tool: &DbTool<'_, Env>) -> eyre::Result<()> { + pub fn execute(self, tool: &DbTool<'_, Env>) -> eyre::Result<()> { if self.table.table_type() == TableType::DupSort { error!(target: "reth::cli", "Unsupported table."); } @@ -46,7 +46,7 @@ impl Command { } struct ListTableViewer<'a> { - tool: &'a DbTool<'a, Env>, + tool: &'a DbTool<'a, Env>, args: &'a Command, } diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index 27f188c2a407..8940a1b54496 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -10,12 +10,12 @@ use eyre::WrapErr; use human_bytes::human_bytes; use reth_db::{ database::Database, + mdbx::{Env, NoWriteMap, WriteMap}, version::{get_db_version, DatabaseVersionError, DB_VERSION}, Tables, }; use reth_primitives::ChainSpec; -use reth_staged_sync::utils::init::init_db; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; mod get; mod list; @@ -81,13 +81,11 @@ impl Command { let data_dir = self.datadir.unwrap_or_chain_default(self.chain.chain); let db_path = data_dir.db_path(); - let db = init_db(&db_path)?; - - let mut tool = DbTool::new(&db, self.chain.clone())?; - match self.command { // TODO: We'll need to add this on the DB trait. Subcommands::Stats { .. } => { + let db = read_only_db(&db_path)?; + let tool = DbTool::new(&db, self.chain.clone())?; let mut stats_table = ComfyTable::new(); stats_table.load_preset(comfy_table::presets::ASCII_MARKDOWN); stats_table.set_header([ @@ -137,12 +135,18 @@ impl Command { println!("{stats_table}"); } Subcommands::List(command) => { + let db = read_only_db(&db_path)?; + let tool = DbTool::new(&db, self.chain.clone())?; command.execute(&tool)?; } Subcommands::Get(command) => { + let db = read_only_db(&db_path)?; + let tool = DbTool::new(&db, self.chain.clone())?; command.execute(&tool)?; } Subcommands::Drop => { + let db = read_write_db(&db_path)?; + let mut tool = DbTool::new(&db, self.chain.clone())?; tool.drop(db_path)?; } Subcommands::Version => { @@ -169,6 +173,16 @@ impl Command { } } +fn read_only_db(path: &Path) -> eyre::Result> { + Env::::open(path, reth_db::mdbx::EnvKind::RO) + .with_context(|| format!("Could not open database at path: {}", path.display())) +} + +fn read_write_db(path: &Path) -> eyre::Result> { + Env::::open(path, reth_db::mdbx::EnvKind::RW) + .with_context(|| format!("Could not open database at path: {}", path.display())) +} + #[cfg(test)] mod tests { use super::*; From 4c84d22e36f383e9c411ecdd36c47a4e367fc504 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Fri, 23 Jun 2023 21:56:55 +0200 Subject: [PATCH 169/216] chore: rename to_session variable (#3359) --- crates/net/network/src/session/active.rs | 28 +++++++++++++----------- crates/net/network/src/session/mod.rs | 2 +- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 4b2ba780e82f..21e305467fe5 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -76,7 +76,7 @@ pub(crate) struct ActiveSession { /// Incoming commands from the manager pub(crate) commands_rx: ReceiverStream, /// Sink to send messages to the [`SessionManager`](super::SessionManager). - pub(crate) to_session: MeteredSender, + pub(crate) to_session_manager: MeteredSender, /// A message that needs to be delivered to the session manager pub(crate) pending_message_to_session: Option, /// Incoming request to send to delegate to the remote peer. @@ -300,7 +300,7 @@ impl ActiveSession { #[allow(clippy::result_large_err)] fn try_emit_broadcast(&self, message: PeerMessage) -> Result<(), ActiveSessionMessage> { match self - .to_session + .to_session_manager .try_send(ActiveSessionMessage::ValidMessage { peer_id: self.remote_peer_id, message }) { Ok(_) => Ok(()), @@ -325,7 +325,7 @@ impl ActiveSession { #[allow(clippy::result_large_err)] fn try_emit_request(&self, message: PeerMessage) -> Result<(), ActiveSessionMessage> { match self - .to_session + .to_session_manager .try_send(ActiveSessionMessage::ValidMessage { peer_id: self.remote_peer_id, message }) { Ok(_) => Ok(()), @@ -350,7 +350,7 @@ impl ActiveSession { /// Notify the manager that the peer sent a bad message fn on_bad_message(&self) { let _ = self - .to_session + .to_session_manager .try_send(ActiveSessionMessage::BadMessage { peer_id: self.remote_peer_id }); } @@ -358,7 +358,7 @@ impl ActiveSession { fn emit_disconnect(&self) { trace!(target: "net::session", remote_peer_id=?self.remote_peer_id, "emitting disconnect"); // NOTE: we clone here so there's enough capacity to deliver this message - let _ = self.to_session.clone().try_send(ActiveSessionMessage::Disconnected { + let _ = self.to_session_manager.clone().try_send(ActiveSessionMessage::Disconnected { peer_id: self.remote_peer_id, remote_addr: self.remote_addr, }); @@ -367,11 +367,13 @@ impl ActiveSession { /// Report back that this session has been closed due to an error fn close_on_error(&self, error: EthStreamError) { // NOTE: we clone here so there's enough capacity to deliver this message - let _ = self.to_session.clone().try_send(ActiveSessionMessage::ClosedOnConnectionError { - peer_id: self.remote_peer_id, - remote_addr: self.remote_addr, - error, - }); + let _ = self.to_session_manager.clone().try_send( + ActiveSessionMessage::ClosedOnConnectionError { + peer_id: self.remote_peer_id, + remote_addr: self.remote_addr, + error, + }, + ); } /// Starts the disconnect process @@ -530,7 +532,7 @@ impl Future for ActiveSession { // try to resend the pending message that we could not send because the channel was // full. if let Some(msg) = this.pending_message_to_session.take() { - match this.to_session.try_send(msg) { + match this.to_session_manager.try_send(msg) { Ok(_) => {} Err(err) => { match err { @@ -594,7 +596,7 @@ impl Future for ActiveSession { let _ = this.internal_request_timeout_interval.poll_tick(cx); // check for timed out requests if this.check_timed_out_requests(Instant::now()) { - let _ = this.to_session.clone().try_send( + let _ = this.to_session_manager.clone().try_send( ActiveSessionMessage::ProtocolBreach { peer_id: this.remote_peer_id }, ); } @@ -829,7 +831,7 @@ mod tests { remote_capabilities: Arc::clone(&capabilities), session_id, commands_rx: ReceiverStream::new(commands_rx), - to_session: MeteredSender::new( + to_session_manager: MeteredSender::new( self.active_session_tx.clone(), "network_active_session", ), diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index a7de7aed668f..7dda07cdd9c9 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -441,7 +441,7 @@ impl SessionManager { remote_capabilities: Arc::clone(&capabilities), session_id, commands_rx: ReceiverStream::new(commands_rx), - to_session: self.active_session_tx.clone(), + to_session_manager: self.active_session_tx.clone(), pending_message_to_session: None, internal_request_tx: ReceiverStream::new(messages_rx).fuse(), inflight_requests: Default::default(), From b787d09993ddd2d34a199a962bd4a98365a8df76 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 25 Jun 2023 15:11:24 +0200 Subject: [PATCH 170/216] perf: reduce p2p message capacity (#3371) --- crates/net/eth-wire/src/p2pstream.rs | 37 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/crates/net/eth-wire/src/p2pstream.rs b/crates/net/eth-wire/src/p2pstream.rs index 2faf4c2e8aef..55b70877f51c 100644 --- a/crates/net/eth-wire/src/p2pstream.rs +++ b/crates/net/eth-wire/src/p2pstream.rs @@ -56,7 +56,11 @@ const GRACE_PERIOD: Duration = Duration::from_secs(2); /// [`MAX_P2P_CAPACITY`] is the maximum number of messages that can be buffered to be sent in the /// `p2p` stream. -const MAX_P2P_CAPACITY: usize = 64; +/// +/// Note: this default is rather low because it is expected that the [P2PStream] wraps an +/// [ECIESStream](reth_ecies::stream::ECIESStream) which internally already buffers a few MB of +/// encoded data. +const MAX_P2P_CAPACITY: usize = 2; /// An un-authenticated [`P2PStream`]. This is consumed and returns a [`P2PStream`] after the /// `Hello` handshake is completed. @@ -212,6 +216,10 @@ pub struct P2PStream { /// Outgoing messages buffered for sending to the underlying stream. outgoing_messages: VecDeque, + /// Maximum number of messages that we can buffer here before the [Sink] impl returns + /// [Poll::Pending]. + outgoing_message_buffer_capacity: usize, + /// Whether this stream is currently in the process of disconnecting by sending a disconnect /// message. disconnecting: bool, @@ -229,10 +237,20 @@ impl P2PStream { pinger: Pinger::new(PING_INTERVAL, PING_TIMEOUT), shared_capability: capability, outgoing_messages: VecDeque::new(), + outgoing_message_buffer_capacity: MAX_P2P_CAPACITY, disconnecting: false, } } + /// Sets a custom outgoing message buffer capacity. + /// + /// # Panics + /// + /// If the provided capacity is `0`. + pub fn set_outgoing_message_buffer_capacity(&mut self, capacity: usize) { + self.outgoing_message_buffer_capacity = capacity; + } + /// Returns the shared capability for this stream. pub fn shared_capability(&self) -> &SharedCapability { &self.shared_capability @@ -243,6 +261,11 @@ impl P2PStream { self.disconnecting } + /// Returns `true` if the stream has outgoing capacity. + fn has_outgoing_capacity(&self) -> bool { + self.outgoing_messages.len() < self.outgoing_message_buffer_capacity + } + /// Queues in a _snappy_ encoded [`P2PMessage::Pong`] message. fn send_pong(&mut self) { let pong = P2PMessage::Pong; @@ -366,10 +389,6 @@ where let id = *bytes.first().ok_or(P2PStreamError::EmptyProtocolMessage)?; match id { _ if id == P2PMessageID::Ping as u8 => { - if this.outgoing_messages.len() > MAX_P2P_CAPACITY { - return Poll::Ready(Some(Err(P2PStreamError::SendBufferFull))) - } - tracing::trace!("Received Ping, Sending Pong"); this.send_pong(); } @@ -467,7 +486,7 @@ where } } - if self.outgoing_messages.len() < MAX_P2P_CAPACITY { + if self.has_outgoing_capacity() { // still has capacity Poll::Ready(Ok(())) } else { @@ -476,13 +495,13 @@ where } fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> { - let this = self.project(); - // ensure we have free capacity - if this.outgoing_messages.len() >= MAX_P2P_CAPACITY { + if !self.has_outgoing_capacity() { return Err(P2PStreamError::SendBufferFull) } + let this = self.project(); + let mut compressed = BytesMut::zeroed(1 + snap::raw::max_compress_len(item.len() - 1)); let compressed_size = this.encoder.compress(&item[1..], &mut compressed[1..]).map_err(|err| { From 8dd7a78356cea0b0ea7dd45281020806c27a429c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 25 Jun 2023 15:12:00 +0200 Subject: [PATCH 171/216] feat: add transaction validation task (#3358) --- Cargo.lock | 367 +++++------------ Cargo.toml | 4 +- bin/reth/src/node/mod.rs | 20 +- crates/transaction-pool/Cargo.toml | 2 + crates/transaction-pool/src/lib.rs | 4 +- crates/transaction-pool/src/maintain.rs | 28 +- crates/transaction-pool/src/pool/mod.rs | 6 +- crates/transaction-pool/src/validate.rs | 411 ------------------- crates/transaction-pool/src/validate/eth.rs | 306 ++++++++++++++ crates/transaction-pool/src/validate/mod.rs | 225 ++++++++++ crates/transaction-pool/src/validate/task.rs | 62 +++ 11 files changed, 744 insertions(+), 691 deletions(-) delete mode 100644 crates/transaction-pool/src/validate.rs create mode 100644 crates/transaction-pool/src/validate/eth.rs create mode 100644 crates/transaction-pool/src/validate/mod.rs create mode 100644 crates/transaction-pool/src/validate/task.rs diff --git a/Cargo.lock b/Cargo.lock index 847fc94405c6..6d8e731b52a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -324,12 +324,6 @@ dependencies = [ "rustc-demangle", ] -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - [[package]] name = "base16ct" version = "0.2.0" @@ -437,9 +431,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6776fc96284a0bb647b615056fc496d1fe1644a7ab01829818a6d91cae888b84" +checksum = "6dbe3c979c178231552ecba20214a8272df4e09f232a87aef4320cf06539aded" [[package]] name = "bitvec" @@ -494,9 +488,9 @@ dependencies = [ [[package]] name = "boa_ast" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "boa_interner", "boa_macros", "indexmap", @@ -507,9 +501,9 @@ dependencies = [ [[package]] name = "boa_engine" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "boa_ast", "boa_gc", "boa_icu_provider", @@ -545,7 +539,7 @@ dependencies = [ [[package]] name = "boa_gc" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "boa_macros", "boa_profiler", @@ -555,7 +549,7 @@ dependencies = [ [[package]] name = "boa_icu_provider" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "icu_collections", "icu_normalizer", @@ -568,7 +562,7 @@ dependencies = [ [[package]] name = "boa_interner" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "boa_gc", "boa_macros", @@ -583,7 +577,7 @@ dependencies = [ [[package]] name = "boa_macros" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ "proc-macro2 1.0.60", "quote 1.0.28", @@ -594,9 +588,9 @@ dependencies = [ [[package]] name = "boa_parser" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" dependencies = [ - "bitflags 2.3.1", + "bitflags 2.3.2", "boa_ast", "boa_icu_provider", "boa_interner", @@ -614,7 +608,7 @@ dependencies = [ [[package]] name = "boa_profiler" version = "0.16.0" -source = "git+https://github.com/boa-dev/boa#da4a3315d75680e63ce2a2dba66000ef2e0e50b9" +source = "git+https://github.com/boa-dev/boa#0e1b32a232109fc0e192c1297a7274091af2ac61" [[package]] name = "brotli" @@ -936,7 +930,7 @@ dependencies = [ "digest 0.10.6", "getrandom 0.2.9", "hmac", - "k256 0.13.1", + "k256", "lazy_static", "serde", "sha2 0.10.6", @@ -1212,18 +1206,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" -[[package]] -name = "crypto-bigint" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - [[package]] name = "crypto-bigint" version = "0.5.1" @@ -1276,13 +1258,15 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "4.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "03d928d978dbec61a1167414f5ec534f24bea0d7a0d24dd9b6233d3d8223e585" dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", + "cfg-if", + "digest 0.10.6", + "fiat-crypto", + "packed_simd_2", + "platforms", "subtle", "zeroize", ] @@ -1471,17 +1455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4355c25cbf99edcb6b4a0e906f6bdc6956eda149e84455bea49696429b2f8e8" dependencies = [ "futures", - "tokio-util 0.7.7", -] - -[[package]] -name = "der" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" -dependencies = [ - "const-oid", - "zeroize", + "tokio-util", ] [[package]] @@ -1628,14 +1602,14 @@ dependencies = [ [[package]] name = "discv5" -version = "0.2.2" -source = "git+https://github.com/sigp/discv5#d86707d79c1183b14b8cf31ef62a8a74ef9cd3e4" +version = "0.3.0" +source = "git+https://github.com/sigp/discv5#47844ca54e8d22f4fd3db4594645e65afb288bb6" dependencies = [ "aes 0.7.5", "aes-gcm", "arrayvec", "delay_map", - "enr 0.7.0", + "enr", "fnv", "futures", "hashlink", @@ -1650,8 +1624,6 @@ dependencies = [ "smallvec", "socket2", "tokio", - "tokio-stream", - "tokio-util 0.6.10", "tracing", "tracing-subscriber", "uint", @@ -1699,51 +1671,40 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" -[[package]] -name = "ecdsa" -version = "0.14.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" -dependencies = [ - "der 0.6.1", - "elliptic-curve 0.12.3", - "rfc6979 0.3.1", - "signature 1.6.4", -] - [[package]] name = "ecdsa" version = "0.16.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a48e5d537b8a30c0b023116d981b16334be1485af7ca68db3a2b7024cbc957fd" dependencies = [ - "der 0.7.3", + "der", "digest 0.10.6", - "elliptic-curve 0.13.4", - "rfc6979 0.4.0", - "signature 2.1.0", + "elliptic-curve", + "rfc6979", + "signature", ] [[package]] name = "ed25519" -version = "1.5.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +checksum = "5fb04eee5d9d907f29e80ee6b0e78f7e2c82342c63e3580d8c4f69d9d5aad963" dependencies = [ - "signature 1.6.4", + "pkcs8", + "signature", ] [[package]] name = "ed25519-dalek" -version = "1.0.1" +version = "2.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +checksum = "798f704d128510932661a3489b08e3f4c934a01d61c5def59ae7b8e48f19665a" dependencies = [ "curve25519-dalek", "ed25519", - "rand 0.7.3", + "rand_core 0.6.4", "serde", - "sha2 0.9.9", + "sha2 0.10.6", "zeroize", ] @@ -1784,41 +1745,21 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -[[package]] -name = "elliptic-curve" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" -dependencies = [ - "base16ct 0.1.1", - "crypto-bigint 0.4.9", - "der 0.6.1", - "digest 0.10.6", - "ff 0.12.1", - "generic-array", - "group 0.12.1", - "pkcs8 0.9.0", - "rand_core 0.6.4", - "sec1 0.3.0", - "subtle", - "zeroize", -] - [[package]] name = "elliptic-curve" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c71eaa367f2e5d556414a8eea812bc62985c879748d6403edabd9cb03f16e7" dependencies = [ - "base16ct 0.2.0", - "crypto-bigint 0.5.1", + "base16ct", + "crypto-bigint", "digest 0.10.6", - "ff 0.13.0", + "ff", "generic-array", - "group 0.13.0", - "pkcs8 0.10.2", + "group", + "pkcs8", "rand_core 0.6.4", - "sec1 0.7.2", + "sec1", "subtle", "zeroize", ] @@ -1844,26 +1785,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" -[[package]] -name = "enr" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "492a7e5fc2504d5fdce8e124d3e263b244a68b283cac67a69eda0cd43e0aebad" -dependencies = [ - "base64 0.13.1", - "bs58", - "bytes", - "ed25519-dalek", - "hex", - "k256 0.11.6", - "log", - "rand 0.8.5", - "rlp", - "serde", - "sha3", - "zeroize", -] - [[package]] name = "enr" version = "0.8.1" @@ -1872,8 +1793,9 @@ checksum = "cf56acd72bb22d2824e66ae8e9e5ada4d0de17a69c7fd35569dde2ada8ec9116" dependencies = [ "base64 0.13.1", "bytes", + "ed25519-dalek", "hex", - "k256 0.13.1", + "k256", "log", "rand 0.8.5", "rlp", @@ -2101,11 +2023,11 @@ dependencies = [ "bytes", "cargo_metadata", "chrono", - "elliptic-curve 0.13.4", + "elliptic-curve", "ethabi", "generic-array", "hex", - "k256 0.13.1", + "k256", "num_enum", "once_cell", "open-fastrlp", @@ -2173,7 +2095,7 @@ dependencies = [ "auto_impl", "base64 0.21.0", "bytes", - "enr 0.8.1", + "enr", "ethers-core", "futures-channel", "futures-core", @@ -2209,7 +2131,7 @@ dependencies = [ "async-trait", "coins-bip32", "coins-bip39", - "elliptic-curve 0.13.4", + "elliptic-curve", "eth-keystore", "ethers-core", "hex", @@ -2267,23 +2189,19 @@ dependencies = [ [[package]] name = "ff" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", "subtle", ] [[package]] -name = "ff" -version = "0.13.0" +name = "fiat-crypto" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "findshlibs" @@ -2588,24 +2506,13 @@ dependencies = [ "web-sys", ] -[[package]] -name = "group" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" -dependencies = [ - "ff 0.12.1", - "rand_core 0.6.4", - "subtle", -] - [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "ff 0.13.0", + "ff", "rand_core 0.6.4", "subtle", ] @@ -2625,7 +2532,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -3322,7 +3229,7 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util 0.7.7", + "tokio-util", "tracing", "webpki-roots", ] @@ -3402,7 +3309,7 @@ dependencies = [ "soketto", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tower", "tracing", ] @@ -3458,18 +3365,6 @@ dependencies = [ "simple_asn1", ] -[[package]] -name = "k256" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" -dependencies = [ - "cfg-if", - "ecdsa 0.14.8", - "elliptic-curve 0.12.3", - "sha2 0.10.6", -] - [[package]] name = "k256" version = "0.13.1" @@ -3477,11 +3372,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", - "ecdsa 0.16.6", - "elliptic-curve 0.13.4", + "ecdsa", + "elliptic-curve", "once_cell", "sha2 0.10.6", - "signature 2.1.0", + "signature", ] [[package]] @@ -3524,6 +3419,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "libm" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" + [[package]] name = "libm" version = "0.2.6" @@ -3981,7 +3882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm", + "libm 0.2.6", ] [[package]] @@ -4107,6 +4008,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "packed_simd_2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1914cd452d8fccd6f9db48147b29fd4ae05bea9dc5d9ad578509f72415de282" +dependencies = [ + "cfg-if", + "libm 0.1.4", +] + [[package]] name = "page_size" version = "0.4.2" @@ -4336,24 +4247,14 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" -dependencies = [ - "der 0.6.1", - "spki 0.6.0", -] - [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.3", - "spki 0.7.1", + "der", + "spki", ] [[package]] @@ -4371,6 +4272,12 @@ dependencies = [ "crunchy", ] +[[package]] +name = "platforms" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" + [[package]] name = "plotters" version = "0.3.4" @@ -5164,7 +5071,7 @@ name = "reth-discv4" version = "0.1.0-alpha.1" dependencies = [ "discv5", - "enr 0.8.1", + "enr", "generic-array", "hex", "rand 0.8.5", @@ -5188,7 +5095,7 @@ version = "0.1.0-alpha.1" dependencies = [ "async-trait", "data-encoding", - "enr 0.8.1", + "enr", "linked_hash_set", "parking_lot 0.12.1", "reth-net-common", @@ -5227,7 +5134,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -5257,7 +5164,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", "typenum", ] @@ -5291,7 +5198,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -5336,7 +5243,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tower", "tracing", "tracing-test", @@ -5425,7 +5332,7 @@ dependencies = [ "aquamarine", "async-trait", "auto_impl", - "enr 0.8.1", + "enr", "ethers-core", "ethers-middleware", "ethers-providers", @@ -5464,7 +5371,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tracing", ] @@ -5670,7 +5577,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tower", "tracing", "tracing-futures", @@ -5775,7 +5682,7 @@ dependencies = [ "assert_matches", "async-trait", "confy", - "enr 0.8.1", + "enr", "ethers-core", "ethers-middleware", "ethers-providers", @@ -5882,9 +5789,11 @@ dependencies = [ "reth-primitives", "reth-provider", "reth-rlp", + "reth-tasks", "serde", "thiserror", "tokio", + "tokio-stream", "tracing", ] @@ -5934,7 +5843,7 @@ name = "revm-precompile" version = "2.0.3" source = "git+https://github.com/bluealloy/revm/?branch=release/v25#88337924f4d16ed1f5e4cde12a03d0cb755cd658" dependencies = [ - "k256 0.13.1", + "k256", "num", "once_cell", "revm-primitives", @@ -5969,17 +5878,6 @@ dependencies = [ "sha3", ] -[[package]] -name = "rfc6979" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" -dependencies = [ - "crypto-bigint 0.4.9", - "hmac", - "zeroize", -] - [[package]] name = "rfc6979" version = "0.4.0" @@ -6290,30 +6188,16 @@ dependencies = [ "untrusted", ] -[[package]] -name = "sec1" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" -dependencies = [ - "base16ct 0.1.1", - "der 0.6.1", - "generic-array", - "pkcs8 0.9.0", - "subtle", - "zeroize", -] - [[package]] name = "sec1" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" dependencies = [ - "base16ct 0.2.0", - "der 0.7.3", + "base16ct", + "der", "generic-array", - "pkcs8 0.10.2", + "pkcs8", "subtle", "zeroize", ] @@ -6619,16 +6503,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" -dependencies = [ - "digest 0.10.6", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "2.1.0" @@ -6754,16 +6628,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" -dependencies = [ - "base64ct", - "der 0.6.1", -] - [[package]] name = "spki" version = "0.7.1" @@ -6771,7 +6635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a5be806ab6f127c3da44b7378837ebf01dadca8510a0e572460216b228bd0e" dependencies = [ "base64ct", - "der 0.7.3", + "der", ] [[package]] @@ -7203,7 +7067,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", - "tokio-util 0.7.7", + "tokio-util", ] [[package]] @@ -7218,21 +7082,6 @@ dependencies = [ "tungstenite", ] -[[package]] -name = "tokio-util" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "slab", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.7" @@ -7320,7 +7169,7 @@ dependencies = [ "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.7", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -7348,7 +7197,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tokio", - "tokio-util 0.7.7", + "tokio-util", "tower", "tower-layer", "tower-service", @@ -7682,9 +7531,9 @@ checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" diff --git a/Cargo.toml b/Cargo.toml index 09bf2988a245..6b94e8db06f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,10 +114,10 @@ tokio = { version = "1.21", default-features = false } tokio-util = { version = "0.7.4", features = ["codec"] } ## async -async-trait = "0.1.58" +async-trait = "0.1.68" futures = "0.3.26" pin-project = "1.0.12" futures-util = "0.3.25" ## crypto -secp256k1 = { version = "0.27.0", default-features = false, features = ["global-context", "rand-std", "recovery"] } \ No newline at end of file +secp256k1 = { version = "0.27.0", default-features = false, features = ["global-context", "rand-std", "recovery"] } diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 9aa3e8eab0ed..3ee507720948 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -206,7 +206,12 @@ impl Command { let blockchain_db = BlockchainProvider::new(factory, blockchain_tree.clone())?; let transaction_pool = reth_transaction_pool::Pool::eth_pool( - EthTransactionValidator::new(blockchain_db.clone(), Arc::clone(&self.chain)), + EthTransactionValidator::new( + blockchain_db.clone(), + Arc::clone(&self.chain), + ctx.task_executor.clone(), + 1, + ), Default::default(), ); info!(target: "reth::cli", "Transaction pool initialized"); @@ -218,14 +223,11 @@ impl Command { let client = blockchain_db.clone(); ctx.task_executor.spawn_critical( "txpool maintenance task", - Box::pin(async move { - reth_transaction_pool::maintain::maintain_transaction_pool( - client, - pool, - chain_events, - ) - .await - }), + reth_transaction_pool::maintain::maintain_transaction_pool_future( + client, + pool, + chain_events, + ), ); debug!(target: "reth::cli", "Spawned txpool maintenance task"); } diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index efc0cda13337..c562df71cf5e 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -23,12 +23,14 @@ reth-provider = { workspace = true } reth-interfaces = { workspace = true } reth-rlp = { workspace = true } reth-metrics = { workspace = true } +reth-tasks = { workspace = true } # async/futures async-trait = { workspace = true} futures-util = { workspace = true } parking_lot = "0.12" tokio = { workspace = true, default-features = false, features = ["sync"] } +tokio-stream.workspace = true # misc aquamarine = "0.3.0" diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index d0054c9cc407..598cb6714d94 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -116,7 +116,7 @@ pub mod metrics; mod ordering; pub mod pool; mod traits; -mod validate; +pub mod validate; #[cfg(any(test, feature = "test-utils"))] /// Common test helpers for mocking A pool @@ -222,7 +222,7 @@ where impl Pool, CostOrdering> where - Client: StateProviderFactory, + Client: StateProviderFactory + Clone + 'static, { /// Returns a new [Pool] that uses the default [EthTransactionValidator] when validating /// [PooledTransaction]s and ords via [CostOrdering] diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index dd2cfb111244..5805e715ae8a 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -4,7 +4,7 @@ use crate::{ traits::{CanonicalStateUpdate, ChangedAccount}, BlockInfo, Pool, TransactionOrdering, TransactionPool, TransactionValidator, }; -use futures_util::{Stream, StreamExt}; +use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt}; use reth_primitives::{Address, BlockHash, BlockNumberOrTag, FromRecoveredTransaction}; use reth_provider::{BlockProviderIdExt, CanonStateNotification, PostState, StateProviderFactory}; use std::{ @@ -18,6 +18,24 @@ use tracing::debug; /// last_seen.number` const MAX_UPDATE_DEPTH: u64 = 64; +/// Returns a spawnable future for maintaining the state of the transaction pool. +pub fn maintain_transaction_pool_future( + client: Client, + pool: Pool, + events: St, +) -> BoxFuture<'static, ()> +where + Client: StateProviderFactory + BlockProviderIdExt + Send + 'static, + V: TransactionValidator + Send + 'static, + T: TransactionOrdering::Transaction> + Send + 'static, + St: Stream + Send + Unpin + 'static, +{ + async move { + maintain_transaction_pool(client, pool, events).await; + } + .boxed() +} + /// Maintains the state of the transaction pool by handling new blocks and reorgs. /// /// This listens for any new blocks and reorgs and updates the transaction pool's state accordingly @@ -27,10 +45,10 @@ pub async fn maintain_transaction_pool( pool: Pool, mut events: St, ) where - Client: StateProviderFactory + BlockProviderIdExt, - V: TransactionValidator, - T: TransactionOrdering::Transaction>, - St: Stream + Unpin, + Client: StateProviderFactory + BlockProviderIdExt + Send + 'static, + V: TransactionValidator + Send + 'static, + T: TransactionOrdering::Transaction> + Send + 'static, + St: Stream + Send + Unpin + 'static, { // ensure the pool points to latest state if let Ok(Some(latest)) = client.block_by_number_or_tag(BlockNumberOrTag::Latest) { diff --git a/crates/transaction-pool/src/pool/mod.rs b/crates/transaction-pool/src/pool/mod.rs index e4744a296b11..293e7af22a36 100644 --- a/crates/transaction-pool/src/pool/mod.rs +++ b/crates/transaction-pool/src/pool/mod.rs @@ -285,10 +285,10 @@ where listener.discarded(tx.hash()); Err(PoolError::InvalidTransaction(*tx.hash(), err)) } - TransactionValidationOutcome::Error(tx, err) => { + TransactionValidationOutcome::Error(tx_hash, err) => { let mut listener = self.event_listener.write(); - listener.discarded(tx.hash()); - Err(PoolError::Other(*tx.hash(), err)) + listener.discarded(&tx_hash); + Err(PoolError::Other(tx_hash, err)) } } } diff --git a/crates/transaction-pool/src/validate.rs b/crates/transaction-pool/src/validate.rs deleted file mode 100644 index 959d5958bc8b..000000000000 --- a/crates/transaction-pool/src/validate.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! Transaction validation abstractions. - -use crate::{ - error::InvalidPoolTransactionError, - identifier::{SenderId, TransactionId}, - traits::{PoolTransaction, TransactionOrigin}, - MAX_INIT_CODE_SIZE, TX_MAX_SIZE, -}; -use reth_primitives::{ - constants::ETHEREUM_BLOCK_GAS_LIMIT, Address, ChainSpec, IntoRecoveredTransaction, - InvalidTransactionError, TransactionKind, TransactionSignedEcRecovered, TxHash, - EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, U256, -}; -use reth_provider::{AccountReader, StateProviderFactory}; -use std::{fmt, marker::PhantomData, sync::Arc, time::Instant}; - -/// A Result type returned after checking a transaction's validity. -#[derive(Debug)] -pub enum TransactionValidationOutcome { - /// The transaction is considered _currently_ valid and can be inserted into the pool. - Valid { - /// Balance of the sender at the current point. - balance: U256, - /// Current nonce of the sender. - state_nonce: u64, - /// Validated transaction. - transaction: T, - }, - /// The transaction is considered invalid indefinitely: It violates constraints that prevent - /// this transaction from ever becoming valid. - Invalid(T, InvalidPoolTransactionError), - /// An error occurred while trying to validate the transaction - Error(T, Box), -} - -impl TransactionValidationOutcome { - /// Returns the transaction that was validated. - pub fn transaction(&self) -> &T { - match self { - Self::Valid { transaction, .. } => transaction, - Self::Invalid(transaction, ..) => transaction, - Self::Error(transaction, ..) => transaction, - } - } - - /// Returns the hash of the transactions - pub fn tx_hash(&self) -> TxHash { - *self.transaction().hash() - } -} - -/// Provides support for validating transaction at any given state of the chain -#[async_trait::async_trait] -pub trait TransactionValidator: Send + Sync { - /// The transaction type to validate. - type Transaction: PoolTransaction; - - /// Validates the transaction and returns a [`TransactionValidationOutcome`] describing the - /// validity of the given transaction. - /// - /// This will be used by the transaction-pool to check whether the transaction should be - /// inserted into the pool or discarded right away. - /// - /// Implementers of this trait must ensure that the transaction is well-formed, i.e. that it - /// complies at least all static constraints, which includes checking for: - /// - /// * chain id - /// * gas limit - /// * max cost - /// * nonce >= next nonce of the sender - /// * ... - /// - /// See [InvalidTransactionError](InvalidTransactionError) for common errors variants. - /// - /// The transaction pool makes no additional assumptions about the validity of the transaction - /// at the time of this call before it inserts it into the pool. However, the validity of - /// this transaction is still subject to future (dynamic) changes enforced by the pool, for - /// example nonce or balance changes. Hence, any validation checks must be applied in this - /// function. - /// - /// See [EthTransactionValidator] for a reference implementation. - async fn validate_transaction( - &self, - origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> TransactionValidationOutcome; - - /// Ensure that the code size is not greater than `max_init_code_size`. - /// `max_init_code_size` should be configurable so this will take it as an argument. - fn ensure_max_init_code_size( - &self, - transaction: &Self::Transaction, - max_init_code_size: usize, - ) -> Result<(), InvalidPoolTransactionError> { - if *transaction.kind() == TransactionKind::Create && transaction.size() > max_init_code_size - { - Err(InvalidPoolTransactionError::ExceedsMaxInitCodeSize( - transaction.size(), - max_init_code_size, - )) - } else { - Ok(()) - } - } -} - -/// A [TransactionValidator] implementation that validates ethereum transaction. -#[derive(Debug, Clone)] -pub struct EthTransactionValidator { - /// Spec of the chain - chain_spec: Arc, - /// This type fetches account info from the db - client: Client, - /// Fork indicator whether we are in the Shanghai stage. - shanghai: bool, - /// Fork indicator whether we are using EIP-2718 type transactions. - eip2718: bool, - /// Fork indicator whether we are using EIP-1559 type transactions. - eip1559: bool, - /// The current max gas limit - block_gas_limit: u64, - /// Minimum priority fee to enforce for acceptance into the pool. - minimum_priority_fee: Option, - /// Marker for the transaction type - _marker: PhantomData, -} - -// === impl EthTransactionValidator === - -impl EthTransactionValidator { - /// Creates a new instance for the given [ChainSpec] - pub fn new(client: Client, chain_spec: Arc) -> Self { - // TODO(mattsse): improve these settings by checking against hardfork - // See [reth_consensus::validation::validate_transaction_regarding_header] - Self { - chain_spec, - client, - shanghai: true, - eip2718: true, - eip1559: true, - block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, - minimum_priority_fee: None, - _marker: Default::default(), - } - } - - /// Returns the configured chain id - pub fn chain_id(&self) -> u64 { - self.chain_spec.chain().id() - } -} - -#[async_trait::async_trait] -impl TransactionValidator for EthTransactionValidator -where - Client: StateProviderFactory, - Tx: PoolTransaction, -{ - type Transaction = Tx; - - async fn validate_transaction( - &self, - origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> TransactionValidationOutcome { - // Checks for tx_type - match transaction.tx_type() { - LEGACY_TX_TYPE_ID => { - // Accept legacy transactions - } - EIP2930_TX_TYPE_ID => { - // Accept only legacy transactions until EIP-2718/2930 activates - if !self.eip2718 { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::Eip1559Disabled.into(), - ) - } - } - - EIP1559_TX_TYPE_ID => { - // Reject dynamic fee transactions until EIP-1559 activates. - if !self.eip1559 { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::Eip1559Disabled.into(), - ) - } - } - - _ => { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::TxTypeNotSupported.into(), - ) - } - }; - - // Reject transactions over defined size to prevent DOS attacks - if transaction.size() > TX_MAX_SIZE { - let size = transaction.size(); - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::OversizedData(size, TX_MAX_SIZE), - ) - } - - // Check whether the init code size has been exceeded. - if self.shanghai { - if let Err(err) = self.ensure_max_init_code_size(&transaction, MAX_INIT_CODE_SIZE) { - return TransactionValidationOutcome::Invalid(transaction, err) - } - } - - // Checks for gas limit - if transaction.gas_limit() > self.block_gas_limit { - let gas_limit = transaction.gas_limit(); - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::ExceedsGasLimit(gas_limit, self.block_gas_limit), - ) - } - - // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any. - if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::TipAboveFeeCap.into(), - ) - } - - // Drop non-local transactions with a fee lower than the configured fee for acceptance into - // the pool. - if !origin.is_local() && - transaction.is_eip1559() && - transaction.max_priority_fee_per_gas() < self.minimum_priority_fee - { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Underpriced, - ) - } - - // Checks for chainid - if let Some(chain_id) = transaction.chain_id() { - if chain_id != self.chain_id() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::ChainIdMismatch.into(), - ) - } - } - - let account = match self - .client - .latest() - .and_then(|state| state.basic_account(transaction.sender())) - { - Ok(account) => account.unwrap_or_default(), - Err(err) => return TransactionValidationOutcome::Error(transaction, Box::new(err)), - }; - - // Signer account shouldn't have bytecode. Presence of bytecode means this is a - // smartcontract. - if account.has_bytecode() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::SignerAccountHasBytecode.into(), - ) - } - - // Checks for nonce - if transaction.nonce() < account.nonce { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::NonceNotConsistent.into(), - ) - } - - // Checks for max cost - if transaction.cost() > account.balance { - let cost = transaction.cost(); - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::InsufficientFunds { - cost, - available_funds: account.balance, - } - .into(), - ) - } - - // Return the valid transaction - TransactionValidationOutcome::Valid { - balance: account.balance, - state_nonce: account.nonce, - transaction, - } - } -} - -/// A valid transaction in the pool. -pub struct ValidPoolTransaction { - /// The transaction - pub transaction: T, - /// The identifier for this transaction. - pub transaction_id: TransactionId, - /// Whether to propagate the transaction. - pub propagate: bool, - /// Total cost of the transaction: `feeCap x gasLimit + transferredValue`. - pub cost: U256, - /// Timestamp when this was added to the pool. - pub timestamp: Instant, - /// Where this transaction originated from. - pub origin: TransactionOrigin, - /// The length of the rlp encoded transaction (cached) - pub encoded_length: usize, -} - -// === impl ValidPoolTransaction === - -impl ValidPoolTransaction { - /// Returns the hash of the transaction. - pub fn hash(&self) -> &TxHash { - self.transaction.hash() - } - - /// Returns the type identifier of the transaction - pub fn tx_type(&self) -> u8 { - self.transaction.tx_type() - } - - /// Returns the address of the sender - pub fn sender(&self) -> Address { - self.transaction.sender() - } - - /// Returns the internal identifier for the sender of this transaction - pub(crate) fn sender_id(&self) -> SenderId { - self.transaction_id.sender - } - - /// Returns the internal identifier for this transaction. - pub(crate) fn id(&self) -> &TransactionId { - &self.transaction_id - } - - /// Returns the nonce set for this transaction. - pub fn nonce(&self) -> u64 { - self.transaction.nonce() - } - - /// Returns the EIP-1559 Max base fee the caller is willing to pay. - /// - /// For legacy transactions this is gas_price. - pub fn max_fee_per_gas(&self) -> u128 { - self.transaction.max_fee_per_gas() - } - - /// Returns the EIP-1559 Max base fee the caller is willing to pay. - pub fn effective_gas_price(&self) -> u128 { - self.transaction.effective_gas_price() - } - - /// Amount of gas that should be used in executing this transaction. This is paid up-front. - pub fn gas_limit(&self) -> u64 { - self.transaction.gas_limit() - } - - /// Whether the transaction originated locally. - pub fn is_local(&self) -> bool { - self.origin.is_local() - } - - /// The heap allocated size of this transaction. - pub(crate) fn size(&self) -> usize { - self.transaction.size() - } -} - -impl IntoRecoveredTransaction for ValidPoolTransaction { - fn to_recovered_transaction(&self) -> TransactionSignedEcRecovered { - self.transaction.to_recovered_transaction() - } -} - -#[cfg(test)] -impl Clone for ValidPoolTransaction { - fn clone(&self) -> Self { - Self { - transaction: self.transaction.clone(), - transaction_id: self.transaction_id, - propagate: self.propagate, - cost: self.cost, - timestamp: self.timestamp, - origin: self.origin, - encoded_length: self.encoded_length, - } - } -} - -impl fmt::Debug for ValidPoolTransaction { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(fmt, "Transaction {{ ")?; - write!(fmt, "hash: {:?}, ", &self.transaction.hash())?; - write!(fmt, "provides: {:?}, ", &self.transaction_id)?; - write!(fmt, "raw tx: {:?}", &self.transaction)?; - write!(fmt, "}}")?; - Ok(()) - } -} diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs new file mode 100644 index 000000000000..6abe0cb5237b --- /dev/null +++ b/crates/transaction-pool/src/validate/eth.rs @@ -0,0 +1,306 @@ +//! Ethereum transaction validator. + +use crate::{ + error::InvalidPoolTransactionError, + traits::{PoolTransaction, TransactionOrigin}, + validate::{task::ValidationJobSender, TransactionValidatorError, ValidationTask}, + TransactionValidationOutcome, TransactionValidator, MAX_INIT_CODE_SIZE, TX_MAX_SIZE, +}; +use reth_primitives::{ + constants::ETHEREUM_BLOCK_GAS_LIMIT, ChainSpec, InvalidTransactionError, EIP1559_TX_TYPE_ID, + EIP2930_TX_TYPE_ID, LEGACY_TX_TYPE_ID, +}; +use reth_provider::{AccountReader, StateProviderFactory}; +use reth_tasks::TaskSpawner; +use std::{marker::PhantomData, sync::Arc}; +use tokio::sync::{oneshot, Mutex}; + +/// A [TransactionValidator] implementation that validates ethereum transaction. +/// +/// This validator is non-blocking, all validation work is done in a separate task. +#[derive(Debug, Clone)] +pub struct EthTransactionValidator { + /// The type that performs the actual validation. + inner: Arc>, + /// The sender half to validation tasks that perform the actual validation. + to_validation_task: Arc>, +} + +// === impl EthTransactionValidator === + +impl EthTransactionValidator { + /// Creates a new instance for the given [ChainSpec] + /// + /// This will always spawn a validation tasks that perform the actual validation. A will spawn + /// `num_additional_tasks` additional tasks. + pub fn new( + client: Client, + chain_spec: Arc, + tasks: T, + num_additional_tasks: usize, + ) -> Self + where + T: TaskSpawner, + { + let inner = EthTransactionValidatorInner { + chain_spec, + client, + shanghai: true, + eip2718: true, + eip1559: true, + block_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT, + minimum_priority_fee: None, + _marker: Default::default(), + }; + + let (tx, task) = ValidationTask::new(); + + // Spawn validation tasks, they are blocking because they perform db lookups + for _ in 0..num_additional_tasks { + let task = task.clone(); + tasks.spawn_blocking(Box::pin(async move { + task.run().await; + })); + } + + tasks.spawn_critical_blocking( + "transaction-validation-service", + Box::pin(async move { + task.run().await; + }), + ); + + let to_validation_task = Arc::new(Mutex::new(tx)); + + Self { inner: Arc::new(inner), to_validation_task } + } + + /// Returns the configured chain id + pub fn chain_id(&self) -> u64 { + self.inner.chain_id() + } +} + +#[async_trait::async_trait] +impl TransactionValidator for EthTransactionValidator +where + Client: StateProviderFactory + Clone + 'static, + Tx: PoolTransaction + 'static, +{ + type Transaction = Tx; + + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome { + let hash = *transaction.hash(); + let (tx, rx) = oneshot::channel(); + { + let to_validation_task = self.to_validation_task.clone(); + let to_validation_task = to_validation_task.lock().await; + let validator = Arc::clone(&self.inner); + let res = to_validation_task + .send(Box::pin(async move { + let res = validator.validate_transaction(origin, transaction).await; + let _ = tx.send(res); + })) + .await; + if res.is_err() { + return TransactionValidationOutcome::Error( + hash, + Box::new(TransactionValidatorError::ValidationServiceUnreachable), + ) + } + } + + match rx.await { + Ok(res) => res, + Err(_) => TransactionValidationOutcome::Error( + hash, + Box::new(TransactionValidatorError::ValidationServiceUnreachable), + ), + } + } +} + +/// A [TransactionValidator] implementation that validates ethereum transaction. +#[derive(Debug, Clone)] +struct EthTransactionValidatorInner { + /// Spec of the chain + chain_spec: Arc, + /// This type fetches account info from the db + client: Client, + /// Fork indicator whether we are in the Shanghai stage. + shanghai: bool, + /// Fork indicator whether we are using EIP-2718 type transactions. + eip2718: bool, + /// Fork indicator whether we are using EIP-1559 type transactions. + eip1559: bool, + /// The current max gas limit + block_gas_limit: u64, + /// Minimum priority fee to enforce for acceptance into the pool. + minimum_priority_fee: Option, + /// Marker for the transaction type + _marker: PhantomData, +} + +// === impl EthTransactionValidatorInner === + +impl EthTransactionValidatorInner { + /// Returns the configured chain id + fn chain_id(&self) -> u64 { + self.chain_spec.chain().id() + } +} + +#[async_trait::async_trait] +impl TransactionValidator for EthTransactionValidatorInner +where + Client: StateProviderFactory, + Tx: PoolTransaction, +{ + type Transaction = Tx; + + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome { + // Checks for tx_type + match transaction.tx_type() { + LEGACY_TX_TYPE_ID => { + // Accept legacy transactions + } + EIP2930_TX_TYPE_ID => { + // Accept only legacy transactions until EIP-2718/2930 activates + if !self.eip2718 { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::Eip1559Disabled.into(), + ) + } + } + + EIP1559_TX_TYPE_ID => { + // Reject dynamic fee transactions until EIP-1559 activates. + if !self.eip1559 { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::Eip1559Disabled.into(), + ) + } + } + + _ => { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::TxTypeNotSupported.into(), + ) + } + }; + + // Reject transactions over defined size to prevent DOS attacks + if transaction.size() > TX_MAX_SIZE { + let size = transaction.size(); + return TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::OversizedData(size, TX_MAX_SIZE), + ) + } + + // Check whether the init code size has been exceeded. + if self.shanghai { + if let Err(err) = self.ensure_max_init_code_size(&transaction, MAX_INIT_CODE_SIZE) { + return TransactionValidationOutcome::Invalid(transaction, err) + } + } + + // Checks for gas limit + if transaction.gas_limit() > self.block_gas_limit { + let gas_limit = transaction.gas_limit(); + return TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::ExceedsGasLimit(gas_limit, self.block_gas_limit), + ) + } + + // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any. + if transaction.max_priority_fee_per_gas() > Some(transaction.max_fee_per_gas()) { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::TipAboveFeeCap.into(), + ) + } + + // Drop non-local transactions with a fee lower than the configured fee for acceptance into + // the pool. + if !origin.is_local() && + transaction.is_eip1559() && + transaction.max_priority_fee_per_gas() < self.minimum_priority_fee + { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::Underpriced, + ) + } + + // Checks for chainid + if let Some(chain_id) = transaction.chain_id() { + if chain_id != self.chain_id() { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::ChainIdMismatch.into(), + ) + } + } + + let account = match self + .client + .latest() + .and_then(|state| state.basic_account(transaction.sender())) + { + Ok(account) => account.unwrap_or_default(), + Err(err) => { + return TransactionValidationOutcome::Error(*transaction.hash(), Box::new(err)) + } + }; + + // Signer account shouldn't have bytecode. Presence of bytecode means this is a + // smartcontract. + if account.has_bytecode() { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::SignerAccountHasBytecode.into(), + ) + } + + // Checks for nonce + if transaction.nonce() < account.nonce { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::NonceNotConsistent.into(), + ) + } + + // Checks for max cost + if transaction.cost() > account.balance { + let cost = transaction.cost(); + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::InsufficientFunds { + cost, + available_funds: account.balance, + } + .into(), + ) + } + + // Return the valid transaction + TransactionValidationOutcome::Valid { + balance: account.balance, + state_nonce: account.nonce, + transaction, + } + } +} diff --git a/crates/transaction-pool/src/validate/mod.rs b/crates/transaction-pool/src/validate/mod.rs new file mode 100644 index 000000000000..db414530f647 --- /dev/null +++ b/crates/transaction-pool/src/validate/mod.rs @@ -0,0 +1,225 @@ +//! Transaction validation abstractions. + +use crate::{ + error::InvalidPoolTransactionError, + identifier::{SenderId, TransactionId}, + traits::{PoolTransaction, TransactionOrigin}, +}; +use reth_primitives::{ + Address, IntoRecoveredTransaction, TransactionKind, TransactionSignedEcRecovered, TxHash, U256, +}; +use std::{fmt, time::Instant}; + +mod eth; +mod task; + +/// A [TransactionValidator] implementation that validates ethereum transaction. +pub use eth::EthTransactionValidator; + +/// A spawnable task that performs transaction validation. +pub use task::ValidationTask; + +/// A Result type returned after checking a transaction's validity. +#[derive(Debug)] +pub enum TransactionValidationOutcome { + /// The transaction is considered _currently_ valid and can be inserted into the pool. + Valid { + /// Balance of the sender at the current point. + balance: U256, + /// Current nonce of the sender. + state_nonce: u64, + /// Validated transaction. + transaction: T, + }, + /// The transaction is considered invalid indefinitely: It violates constraints that prevent + /// this transaction from ever becoming valid. + Invalid(T, InvalidPoolTransactionError), + /// An error occurred while trying to validate the transaction + Error(TxHash, Box), +} + +impl TransactionValidationOutcome { + /// Returns the hash of the transactions + pub fn tx_hash(&self) -> TxHash { + match self { + Self::Valid { transaction, .. } => *transaction.hash(), + Self::Invalid(transaction, ..) => *transaction.hash(), + Self::Error(hash, ..) => *hash, + } + } +} + +/// Provides support for validating transaction at any given state of the chain +#[async_trait::async_trait] +pub trait TransactionValidator: Send + Sync { + /// The transaction type to validate. + type Transaction: PoolTransaction; + + /// Validates the transaction and returns a [`TransactionValidationOutcome`] describing the + /// validity of the given transaction. + /// + /// This will be used by the transaction-pool to check whether the transaction should be + /// inserted into the pool or discarded right away. + /// + /// Implementers of this trait must ensure that the transaction is well-formed, i.e. that it + /// complies at least all static constraints, which includes checking for: + /// + /// * chain id + /// * gas limit + /// * max cost + /// * nonce >= next nonce of the sender + /// * ... + /// + /// See [InvalidTransactionError](reth_primitives::InvalidTransactionError) for common errors + /// variants. + /// + /// The transaction pool makes no additional assumptions about the validity of the transaction + /// at the time of this call before it inserts it into the pool. However, the validity of + /// this transaction is still subject to future (dynamic) changes enforced by the pool, for + /// example nonce or balance changes. Hence, any validation checks must be applied in this + /// function. + /// + /// See [EthTransactionValidator] for a reference implementation. + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome; + + /// Ensure that the code size is not greater than `max_init_code_size`. + /// `max_init_code_size` should be configurable so this will take it as an argument. + fn ensure_max_init_code_size( + &self, + transaction: &Self::Transaction, + max_init_code_size: usize, + ) -> Result<(), InvalidPoolTransactionError> { + if *transaction.kind() == TransactionKind::Create && transaction.size() > max_init_code_size + { + Err(InvalidPoolTransactionError::ExceedsMaxInitCodeSize( + transaction.size(), + max_init_code_size, + )) + } else { + Ok(()) + } + } +} + +/// A valid transaction in the pool. +pub struct ValidPoolTransaction { + /// The transaction + pub transaction: T, + /// The identifier for this transaction. + pub transaction_id: TransactionId, + /// Whether to propagate the transaction. + pub propagate: bool, + /// Total cost of the transaction: `feeCap x gasLimit + transferredValue`. + pub cost: U256, + /// Timestamp when this was added to the pool. + pub timestamp: Instant, + /// Where this transaction originated from. + pub origin: TransactionOrigin, + /// The length of the rlp encoded transaction (cached) + pub encoded_length: usize, +} + +// === impl ValidPoolTransaction === + +impl ValidPoolTransaction { + /// Returns the hash of the transaction. + pub fn hash(&self) -> &TxHash { + self.transaction.hash() + } + + /// Returns the type identifier of the transaction + pub fn tx_type(&self) -> u8 { + self.transaction.tx_type() + } + + /// Returns the address of the sender + pub fn sender(&self) -> Address { + self.transaction.sender() + } + + /// Returns the internal identifier for the sender of this transaction + pub(crate) fn sender_id(&self) -> SenderId { + self.transaction_id.sender + } + + /// Returns the internal identifier for this transaction. + pub(crate) fn id(&self) -> &TransactionId { + &self.transaction_id + } + + /// Returns the nonce set for this transaction. + pub fn nonce(&self) -> u64 { + self.transaction.nonce() + } + + /// Returns the EIP-1559 Max base fee the caller is willing to pay. + /// + /// For legacy transactions this is gas_price. + pub fn max_fee_per_gas(&self) -> u128 { + self.transaction.max_fee_per_gas() + } + + /// Returns the EIP-1559 Max base fee the caller is willing to pay. + pub fn effective_gas_price(&self) -> u128 { + self.transaction.effective_gas_price() + } + + /// Amount of gas that should be used in executing this transaction. This is paid up-front. + pub fn gas_limit(&self) -> u64 { + self.transaction.gas_limit() + } + + /// Whether the transaction originated locally. + pub fn is_local(&self) -> bool { + self.origin.is_local() + } + + /// The heap allocated size of this transaction. + pub(crate) fn size(&self) -> usize { + self.transaction.size() + } +} + +impl IntoRecoveredTransaction for ValidPoolTransaction { + fn to_recovered_transaction(&self) -> TransactionSignedEcRecovered { + self.transaction.to_recovered_transaction() + } +} + +#[cfg(test)] +impl Clone for ValidPoolTransaction { + fn clone(&self) -> Self { + Self { + transaction: self.transaction.clone(), + transaction_id: self.transaction_id, + propagate: self.propagate, + cost: self.cost, + timestamp: self.timestamp, + origin: self.origin, + encoded_length: self.encoded_length, + } + } +} + +impl fmt::Debug for ValidPoolTransaction { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "Transaction {{ ")?; + write!(fmt, "hash: {:?}, ", &self.transaction.hash())?; + write!(fmt, "provides: {:?}, ", &self.transaction_id)?; + write!(fmt, "raw tx: {:?}", &self.transaction)?; + write!(fmt, "}}")?; + Ok(()) + } +} + +/// Validation Errors that can occur during transaction validation. +#[derive(thiserror::Error, Debug)] +pub enum TransactionValidatorError { + /// Failed to communicate with the validation service. + #[error("Validation service unreachable")] + ValidationServiceUnreachable, +} diff --git a/crates/transaction-pool/src/validate/task.rs b/crates/transaction-pool/src/validate/task.rs new file mode 100644 index 000000000000..80c477802f2d --- /dev/null +++ b/crates/transaction-pool/src/validate/task.rs @@ -0,0 +1,62 @@ +//! A validation service for transactions. + +use crate::validate::TransactionValidatorError; +use futures_util::{lock::Mutex, StreamExt}; +use std::{future::Future, pin::Pin, sync::Arc}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; + +/// A service that performs validation jobs. +#[derive(Clone)] +pub struct ValidationTask { + #[allow(clippy::type_complexity)] + validation_jobs: Arc + Send>>>>>, +} + +impl ValidationTask { + /// Creates a new clonable task pair + pub fn new() -> (ValidationJobSender, Self) { + let (tx, rx) = mpsc::channel(1); + (ValidationJobSender { tx }, Self::with_receiver(rx)) + } + + /// Creates a new task with the given receiver. + pub fn with_receiver(jobs: mpsc::Receiver + Send>>>) -> Self { + ValidationTask { validation_jobs: Arc::new(Mutex::new(ReceiverStream::new(jobs))) } + } + + /// Executes all new validation jobs that come in. + /// + /// This will run as long as the channel is alive and is expected to be spawned as a task. + pub async fn run(self) { + loop { + let task = self.validation_jobs.lock().await.next().await; + match task { + None => return, + Some(task) => task.await, + } + } + } +} + +impl std::fmt::Debug for ValidationTask { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ValidationTask").field("validation_jobs", &"...").finish() + } +} + +/// A sender new type for sending validation jobs to [ValidationTask]. +#[derive(Debug)] +pub struct ValidationJobSender { + tx: mpsc::Sender + Send>>>, +} + +impl ValidationJobSender { + /// Sends the given job to the validation task. + pub async fn send( + &self, + job: Pin + Send>>, + ) -> Result<(), TransactionValidatorError> { + self.tx.send(job).await.map_err(|_| TransactionValidatorError::ValidationServiceUnreachable) + } +} From 05eada7aa20ac6a3d57c9ba534fa37fbe3c6fccc Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 25 Jun 2023 15:12:39 +0200 Subject: [PATCH 172/216] fix: add receive budget (#3361) --- crates/net/network/src/session/active.rs | 44 +++++++++++++++++------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 21e305467fe5..180096241a5c 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -455,7 +455,15 @@ impl Future for ActiveSession { return this.poll_disconnect(cx) } - loop { + // The receive loop can be CPU intensive since it involves message decoding which could take + // up a lot of resources and increase latencies for other sessions if not yielded manually. + // If the budget is exhausted we manually yield back control to the (coop) scheduler. This + // manual yield point should prevent situations where polling appears to be frozen. See also + // And tokio's docs on cooperative scheduling + let mut budget = 4; + + // The main poll loop that drives the session + 'main: loop { let mut progress = false; // we prioritize incoming commands sent from the session manager @@ -529,6 +537,14 @@ impl Future for ActiveSession { // read incoming messages from the wire 'receive: loop { + // ensure we still have enough budget for another iteration + budget -= 1; + if budget == 0 { + // make sure we're woken up again + cx.waker().wake_by_ref(); + break 'main + } + // try to resend the pending message that we could not send because the channel was // full. if let Some(msg) = this.pending_message_to_session.take() { @@ -592,21 +608,23 @@ impl Future for ActiveSession { } if !progress { - if this.internal_request_timeout_interval.poll_tick(cx).is_ready() { - let _ = this.internal_request_timeout_interval.poll_tick(cx); - // check for timed out requests - if this.check_timed_out_requests(Instant::now()) { - let _ = this.to_session_manager.clone().try_send( - ActiveSessionMessage::ProtocolBreach { peer_id: this.remote_peer_id }, - ); - } - } - - this.shrink_to_fit(); + break 'main + } + } - return Poll::Pending + if this.internal_request_timeout_interval.poll_tick(cx).is_ready() { + let _ = this.internal_request_timeout_interval.poll_tick(cx); + // check for timed out requests + if this.check_timed_out_requests(Instant::now()) { + let _ = this.to_session_manager.clone().try_send( + ActiveSessionMessage::ProtocolBreach { peer_id: this.remote_peer_id }, + ); } } + + this.shrink_to_fit(); + + Poll::Pending } } From 1777a20d2492e5eeac8741ed295b43e47c832643 Mon Sep 17 00:00:00 2001 From: W Date: Sun, 25 Jun 2023 15:20:03 +0200 Subject: [PATCH 173/216] doc: minor fix to mainnet run book (#3374) --- book/run/mainnet.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/book/run/mainnet.md b/book/run/mainnet.md index 8b9c87cc66c4..bc1826d30971 100644 --- a/book/run/mainnet.md +++ b/book/run/mainnet.md @@ -41,7 +41,13 @@ Assuming you have done that, run: RUST_LOG=info lighthouse bn \ --checkpoint-sync-url https://mainnet.checkpoint.sigp.io \ --execution-endpoint http://localhost:8551 \ - --execution-jwt ~/.local/share/reth/jwtsecret/jwt.hex + --execution-jwt ~/.local/share/reth/mainnet/jwt.hex +``` + +If you don't intend on running validators on your node you can add : + +``` bash + --disable-deposit-contract-sync ``` Your Reth node should start receiving "fork choice updated" messages, and begin syncing the chain. From be9aed489f559ede5fa452a1a0bf3fa15bbf2128 Mon Sep 17 00:00:00 2001 From: Volky Date: Sun, 25 Jun 2023 12:59:30 -0300 Subject: [PATCH 174/216] feat: add link to Grafana dashboard config file on docs (#3373) Co-authored-by: Matthias Seitz --- book/run/observability.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/run/observability.md b/book/run/observability.md index 9e951532d4cb..43a241999175 100644 --- a/book/run/observability.md +++ b/book/run/observability.md @@ -51,7 +51,7 @@ Once you've logged in, click on the gear icon in the lower left, and select "Dat As this might be a point of confusion, `localhost:9001`, which we supplied to `--metrics`, is the endpoint that Reth exposes, from which Prometheus collects metrics. Prometheus then exposes `localhost:9090` (by default) for other services (such as Grafana) to consume Prometheus metrics. -To configure the dashboard in Grafana, click on the squares icon in the upper left, and click on "New", then "Import". From there, click on "Upload JSON file", and select the example file in `reth/etc/grafana/overview.json`. Finally, select the Prometheus data source you just created, and click "Import". +To configure the dashboard in Grafana, click on the squares icon in the upper left, and click on "New", then "Import". From there, click on "Upload JSON file", and select the example file in [`reth/etc/grafana/overview.json`](https://github.com/paradigmxyz/reth/blob/main/etc/grafana/dashboards/overview.json). Finally, select the Prometheus data source you just created, and click "Import". And voilá, you should see your dashboard! If you're not yet connected to any peers, the dashboard will look like it's in an empty state, but once you are, you should see it start populating with data. @@ -64,4 +64,4 @@ This will all be very useful to you, whether you're simply running a home node a [installation]: ../installation/installation.md [release-profile]: https://doc.rust-lang.org/cargo/reference/profiles.html#release [docs]: https://github.com/paradigmxyz/reth/tree/main/docs -[metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics.md#current-metrics \ No newline at end of file +[metrics]: https://github.com/paradigmxyz/reth/blob/main/docs/design/metrics.md#current-metrics From 48ee8e096044d3cb562a0849765b7ccc99217283 Mon Sep 17 00:00:00 2001 From: Madhav Goyal <88841339+hydrogenbond007@users.noreply.github.com> Date: Sun, 25 Jun 2023 21:59:40 +0530 Subject: [PATCH 175/216] editing some code comments in the payload.rs (#3372) --- crates/payload/builder/src/payload.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/payload/builder/src/payload.rs b/crates/payload/builder/src/payload.rs index 8adc3c1246f9..83cfe75ec1ed 100644 --- a/crates/payload/builder/src/payload.rs +++ b/crates/payload/builder/src/payload.rs @@ -36,7 +36,7 @@ impl BuiltPayload { self.id } - /// Returns the identifier of the payload. + /// Returns the built block(sealed) pub fn block(&self) -> &SealedBlock { &self.block } @@ -148,7 +148,7 @@ impl PayloadBuilderAttributes { /// Generates the payload id for the configured payload /// -/// Returns an 8-byte identifier by hashing the payload components. +/// Returns an 8-byte identifier by hashing the payload components with sha256 hash. pub(crate) fn payload_id(parent: &H256, attributes: &PayloadAttributes) -> PayloadId { use sha2::Digest; let mut hasher = sha2::Sha256::new(); From 63fbc48c60111c2971a774994fd3485a0f0603b1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 25 Jun 2023 18:45:58 +0200 Subject: [PATCH 176/216] chore: Error -> Failed for failed make_canonical call (#3376) --- crates/consensus/beacon/src/engine/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index ceb022a4a923..43f5b7f0bd28 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -671,7 +671,7 @@ where error: Error, ) -> PayloadStatus { debug_assert!(self.sync.is_pipeline_idle(), "pipeline must be idle"); - warn!(target: "consensus::engine", ?error, ?state, "Error canonicalizing the head hash"); + warn!(target: "consensus::engine", ?error, ?state, "Failed to canonicalize the head hash"); // check if the new head was previously invalidated, if so then we deem this FCU // as invalid From add1abfeb2ce0f7cd4babcfa4762006e79df310e Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 25 Jun 2023 18:46:05 +0200 Subject: [PATCH 177/216] perf: lower pipeline run threshold to 1 epoch (#3375) --- crates/consensus/beacon/src/engine/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 43f5b7f0bd28..f206229c04cd 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -69,7 +69,7 @@ const MAX_INVALID_HEADERS: u32 = 512u32; /// The largest gap for which the tree will be used for sync. See docs for `pipeline_run_threshold` /// for more information. -pub const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = 2 * EPOCH_SLOTS; +pub const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS; /// A _shareable_ beacon consensus frontend. Used to interact with the spawned beacon consensus /// engine. From 46d4795f41905f2474575ac50a6f47e157518fe5 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 26 Jun 2023 10:13:09 +0300 Subject: [PATCH 178/216] feat: log & receipt test generators (#3357) --- .../interfaces/src/test_utils/generators.rs | 37 +++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/crates/interfaces/src/test_utils/generators.rs b/crates/interfaces/src/test_utils/generators.rs index 137996046e61..dfcdf39a0c51 100644 --- a/crates/interfaces/src/test_utils/generators.rs +++ b/crates/interfaces/src/test_utils/generators.rs @@ -3,9 +3,9 @@ use rand::{ distributions::uniform::SampleRange, rngs::StdRng, seq::SliceRandom, thread_rng, SeedableRng, }; use reth_primitives::{ - proofs, sign_message, Account, Address, BlockNumber, Bytes, Header, SealedBlock, SealedHeader, - Signature, StorageEntry, Transaction, TransactionKind, TransactionSigned, TxLegacy, H160, H256, - U256, + proofs, sign_message, Account, Address, BlockNumber, Bytes, Header, Log, Receipt, SealedBlock, + SealedHeader, Signature, StorageEntry, Transaction, TransactionKind, TransactionSigned, + TxLegacy, H160, H256, U256, }; use secp256k1::{KeyPair, Message as SecpMessage, Secp256k1, SecretKey, SECP256K1}; use std::{ @@ -326,6 +326,37 @@ pub fn random_contract_account_range( accounts } +/// Generate random receipt for transaction +pub fn random_receipt( + rng: &mut R, + transaction: &TransactionSigned, + logs_count: Option, +) -> Receipt { + let success = rng.gen::(); + let logs_count = logs_count.unwrap_or_else(|| rng.gen::()); + Receipt { + tx_type: transaction.tx_type(), + success, + cumulative_gas_used: rng.gen_range(0..=transaction.gas_limit()), + logs: if success { + (0..logs_count).map(|_| random_log(rng, None, None)).collect() + } else { + vec![] + }, + } +} + +/// Generate random log +pub fn random_log(rng: &mut R, address: Option
, topics_count: Option) -> Log { + let data_byte_count = rng.gen::(); + let topics_count = topics_count.unwrap_or_else(|| rng.gen::()); + Log { + address: address.unwrap_or_else(|| rng.gen()), + topics: (0..topics_count).map(|_| rng.gen()).collect(), + data: Bytes::from((0..data_byte_count).map(|_| rng.gen::()).collect::>()), + } +} + #[cfg(test)] mod test { use std::str::FromStr; From 9b2a2fbca9a50cddc291747b6396088f591ed427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Vincent?= <28714795+leovct@users.noreply.github.com> Date: Mon, 26 Jun 2023 13:25:35 +0200 Subject: [PATCH 179/216] doc: self-documented `Makefile` (#3380) --- Makefile | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 260dcc95c92a..f024ccf556f0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ # Heavily inspired by Lighthouse: https://github.com/sigp/lighthouse/blob/693886b94176faa4cb450f024696cb69cda2fe58/Makefile +.DEFAULT_GOAL := help GIT_TAG ?= $(shell git describe --tags --abbrev=0) BIN_DIR = "dist/bin" @@ -31,10 +32,16 @@ EF_TESTS_DIR := ./testing/ef-tests/ethereum-tests # The docker image name DOCKER_IMAGE_NAME ?= ghcr.io/paradigmxyz/reth -# Builds and installs the reth binary. -# -# The binary will most likely be in `~/.cargo/bin` -install: +##@ Help + +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "Usage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Build + +.PHONY: install +install: ## Build and install the reth binary under `~/.cargo/bin`. cargo install --path bin/reth --bin reth --force --locked \ --features "$(FEATURES)" \ --profile "$(PROFILE)" \ @@ -82,14 +89,12 @@ define tarball_release_binary rm $(2) endef -# Create a series of `.tar.gz` files in the BIN_DIR directory, each containing -# a `reth` binary for a different target. -# # The current git tag will be used as the version in the output file names. You # will likely need to use `git tag` and create a semver tag (e.g., `v0.2.3`). # # Note: This excludes macOS tarballs because of SDK licensing issues. -build-release-tarballs: +.PHONY: build-release-tarballs +build-release-tarballs: ## Create a series of `.tar.gz` files in the BIN_DIR directory, each containing a `reth` binary for a different target. [ -d $(BIN_DIR) ] || mkdir -p $(BIN_DIR) $(MAKE) build-x86_64-unknown-linux-gnu $(call tarball_release_binary,"x86_64-unknown-linux-gnu","reth","") @@ -98,6 +103,8 @@ build-release-tarballs: $(MAKE) build-x86_64-pc-windows-gnu $(call tarball_release_binary,"x86_64-pc-windows-gnu","reth.exe","") +##@ Test + # Downloads and unpacks Ethereum Foundation tests in the `$(EF_TESTS_DIR)` directory. # # Requires `wget` and `tar` @@ -107,26 +114,26 @@ $(EF_TESTS_DIR): tar -xzf ethereum-tests.tar.gz --strip-components=1 -C $(EF_TESTS_DIR) rm ethereum-tests.tar.gz -# Runs Ethereum Foundation tests -ef-tests: $(EF_TESTS_DIR) +.PHONY: ef-tests +ef-tests: $(EF_TESTS_DIR) ## Runs Ethereum Foundation tests. cargo nextest run -p ef-tests --features ef-tests -# Builds and pushes a cross-arch Docker image tagged with the latest git tag and `latest` -# +##@ Docker + # Note: This requires a buildx builder with emulation support. For example: # # `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64` # `docker buildx create --use --driver docker-container --name cross-builder` -docker-build-latest: +.PHONY: docker-build-latest +docker-build-latest: ## Build and push a cross-arch Docker image tagged with the latest git tag and `latest`. $(call build_docker_image,$(GIT_TAG),latest) -# Builds and pushes cross-arch Docker image tagged with the latest git tag with a `-nightly` suffix, and `latest-nightly` -# # Note: This requires a buildx builder with emulation support. For example: # # `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64` # `docker buildx create --use --name cross-builder` -docker-build-nightly: +.PHONY: docker-build-nightly +docker-build-nightly: ## Build and push cross-arch Docker image tagged with the latest git tag with a `-nightly` suffix, and `latest-nightly`. $(call build_docker_image,$(GIT_TAG)-nightly,latest-nightly) # Create a cross-arch Docker image with the given tags and push it @@ -147,15 +154,16 @@ define build_docker_image --push endef -# Performs a `cargo` clean and removes the binary and test vectors directories -clean: +##@ Other + +.PHONY: clean +clean: ## Perform a `cargo` clean and remove the binary and test vectors directories. cargo clean rm -rf $(BIN_DIR) rm -rf $(EF_TESTS_DIR) -# Compile MDBX debugging tools .PHONY: db-tools -db-tools: +db-tools: ## Compile MDBX debugging tools. @echo "Building MDBX debugging tools..." # `IOARENA=1` silences benchmarking info message that is printed to stderr @$(MAKE) -C $(MDBX_PATH) IOARENA=1 tools > /dev/null From 30a1ad25863972f519b1b0a564c1ef64bb35af84 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 26 Jun 2023 12:33:20 +0100 Subject: [PATCH 180/216] feat(ci): validate Grafana dashboard JSON (#3382) --- .github/workflows/ci.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4dc36d318c45..ce139eb29da0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,11 +52,21 @@ jobs: - name: Check if documentation builds run: RUSTDOCFLAGS="-D warnings" cargo doc --all --no-deps --all-features --document-private-items + grafana-lint: + name: grafana lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check dashboard JSON with jq + uses: sergeysova/jq-action@v2 + with: + cmd: jq empty etc/grafana/dashboards/overview.json + lint-success: if: always() name: lint success runs-on: ubuntu-latest - needs: [lint, doc-lint] + needs: [lint, doc-lint, grafana-lint] steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 From 9c1b48b9837518aca76356770c8fe0f0b1f66ed1 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 26 Jun 2023 16:40:54 +0300 Subject: [PATCH 181/216] chore(provider): simplify history inserts (#3356) --- .../src/providers/database/provider.rs | 151 +++++++----------- 1 file changed, 62 insertions(+), 89 deletions(-) diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 66a72d21d47f..f941f5ae9ac5 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -12,9 +12,8 @@ use reth_db::{ cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, database::{Database, DatabaseGAT}, models::{ - sharded_key, - storage_sharded_key::{self, StorageShardedKey}, - AccountBeforeTx, BlockNumberAddress, ShardedKey, StoredBlockBodyIndices, + sharded_key, storage_sharded_key::StorageShardedKey, AccountBeforeTx, BlockNumberAddress, + ShardedKey, StoredBlockBodyIndices, }, table::Table, tables, @@ -712,20 +711,66 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(()) } - /// Load last shard and check if it is full and remove if it is not. If list is empty, last - /// shard was full or there is no shards at all. - pub fn take_last_storage_shard(&self, address: Address, storage_key: H256) -> Result> { - let mut cursor = self.tx.cursor_read::()?; - let last = cursor.seek_exact(StorageShardedKey::new(address, storage_key, u64::MAX))?; - if let Some((storage_shard_key, list)) = last { + /// Load shard and remove it. If list is empty, last shard was full or + /// there are no shards at all. + fn take_shard(&self, key: T::Key) -> Result> + where + T: Table, + { + let mut cursor = self.tx.cursor_read::()?; + let shard = cursor.seek_exact(key)?; + if let Some((shard_key, list)) = shard { // delete old shard so new one can be inserted. - self.tx.delete::(storage_shard_key, None)?; + self.tx.delete::(shard_key, None)?; let list = list.iter(0).map(|i| i as u64).collect::>(); return Ok(list) } Ok(Vec::new()) } + /// Insert history index to the database. + /// + /// For each updated partial key, this function removes the last shard from + /// the database (if any), appends the new indices to it, chunks the resulting integer list and + /// inserts the new shards back into the database. + /// + /// This function is used by history indexing stages. + fn append_history_index( + &self, + index_updates: BTreeMap>, + mut sharded_key_factory: impl FnMut(P, BlockNumber) -> T::Key, + ) -> Result<()> + where + P: Copy, + T: Table, + { + for (partial_key, indices) in index_updates { + let last_shard = self.take_shard::(sharded_key_factory(partial_key, u64::MAX))?; + // chunk indices and insert them in shards of N size. + let indices = last_shard.iter().chain(indices.iter()); + let chunks = indices + .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) + .into_iter() + .map(|chunks| chunks.map(|i| *i as usize).collect::>()) + .collect::>(); + + let mut chunks = chunks.into_iter().peekable(); + while let Some(list) = chunks.next() { + let highest_block_number = if chunks.peek().is_some() { + *list.last().expect("`chunks` does not return empty list") as u64 + } else { + // Insert last list with u64::MAX + u64::MAX + }; + self.tx.put::( + sharded_key_factory(partial_key, highest_block_number), + BlockNumberList::new_pre_sorted(list), + )?; + } + } + Ok(()) + } + /// Append blocks and insert its post state. /// This will insert block data to all related tables and will update pipeline progress. pub fn append_blocks_with_post_state( @@ -1592,91 +1637,19 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider &self, storage_transitions: BTreeMap<(Address, H256), Vec>, ) -> Result<()> { - for ((address, storage_key), mut indices) in storage_transitions { - let mut last_shard = self.take_last_storage_shard(address, storage_key)?; - last_shard.append(&mut indices); - - // chunk indices and insert them in shards of N size. - let mut chunks = last_shard - .iter() - .chunks(storage_sharded_key::NUM_OF_INDICES_IN_SHARD) - .into_iter() - .map(|chunks| chunks.map(|i| *i as usize).collect::>()) - .collect::>(); - let last_chunk = chunks.pop(); - - // chunk indices and insert them in shards of N size. - chunks.into_iter().try_for_each(|list| { - self.tx.put::( - StorageShardedKey::new( - address, - storage_key, - *list.last().expect("Chuck does not return empty list") as BlockNumber, - ), - BlockNumberList::new(list).expect("Indices are presorted and not empty"), - ) - })?; - // Insert last list with u64::MAX - if let Some(last_list) = last_chunk { - self.tx.put::( - StorageShardedKey::new(address, storage_key, u64::MAX), - BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), - )?; - } - } - Ok(()) + self.append_history_index::<_, tables::StorageHistory>( + storage_transitions, + |(address, storage_key), highest_block_number| { + StorageShardedKey::new(address, storage_key, highest_block_number) + }, + ) } fn insert_account_history_index( &self, account_transitions: BTreeMap>, ) -> Result<()> { - // insert indexes to AccountHistory. - for (address, mut indices) in account_transitions { - // Load last shard and check if it is full and remove if it is not. If list is empty, - // last shard was full or there is no shards at all. - let mut last_shard = { - let mut cursor = self.tx.cursor_read::()?; - let last = cursor.seek_exact(ShardedKey::new(address, u64::MAX))?; - if let Some((shard_key, list)) = last { - // delete old shard so new one can be inserted. - self.tx.delete::(shard_key, None)?; - let list = list.iter(0).map(|i| i as u64).collect::>(); - list - } else { - Vec::new() - } - }; - - last_shard.append(&mut indices); - // chunk indices and insert them in shards of N size. - let mut chunks = last_shard - .iter() - .chunks(sharded_key::NUM_OF_INDICES_IN_SHARD) - .into_iter() - .map(|chunks| chunks.map(|i| *i as usize).collect::>()) - .collect::>(); - let last_chunk = chunks.pop(); - - chunks.into_iter().try_for_each(|list| { - self.tx.put::( - ShardedKey::new( - address, - *list.last().expect("Chuck does not return empty list") as BlockNumber, - ), - BlockNumberList::new(list).expect("Indices are presorted and not empty"), - ) - })?; - - // Insert last list with u64::MAX - if let Some(last_list) = last_chunk { - self.tx.put::( - ShardedKey::new(address, u64::MAX), - BlockNumberList::new(last_list).expect("Indices are presorted and not empty"), - )? - } - } - Ok(()) + self.append_history_index::<_, tables::AccountHistory>(account_transitions, ShardedKey::new) } fn unwind_storage_history_indices(&self, range: Range) -> Result { From 054f30f43c943999abcb438f8ba51771b72f4472 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Mon, 26 Jun 2023 15:13:45 +0100 Subject: [PATCH 182/216] chore: rename `Block*Provider` for `Block*Reader` (#3385) --- bin/reth/src/args/rpc_server_args.rs | 9 ++++---- bin/reth/src/node/mod.rs | 6 +++--- crates/blockchain-tree/src/blockchain_tree.rs | 5 ++--- crates/consensus/auto-seal/src/lib.rs | 4 ++-- crates/consensus/beacon/src/engine/mod.rs | 8 +++---- crates/net/network/src/config.rs | 4 ++-- crates/net/network/src/eth_requests.rs | 6 +++--- crates/net/network/src/lib.rs | 2 +- crates/net/network/src/manager.rs | 6 +++--- crates/net/network/src/state.rs | 4 ++-- crates/net/network/src/swarm.rs | 6 +++--- crates/net/network/src/test_utils/testnet.rs | 14 ++++++------- crates/payload/basic/src/lib.rs | 4 ++-- crates/revm/src/executor.rs | 4 ++-- crates/rpc/rpc-builder/src/auth.rs | 6 +++--- crates/rpc/rpc-builder/src/lib.rs | 21 +++++++++---------- crates/rpc/rpc-engine-api/src/engine_api.rs | 6 +++--- crates/rpc/rpc/src/debug.rs | 6 +++--- crates/rpc/rpc/src/eth/api/block.rs | 4 ++-- crates/rpc/rpc/src/eth/api/call.rs | 4 ++-- crates/rpc/rpc/src/eth/api/fees.rs | 4 ++-- crates/rpc/rpc/src/eth/api/mod.rs | 8 +++---- crates/rpc/rpc/src/eth/api/server.rs | 8 +++---- crates/rpc/rpc/src/eth/api/state.rs | 4 ++-- crates/rpc/rpc/src/eth/api/transactions.rs | 6 +++--- crates/rpc/rpc/src/eth/cache.rs | 10 ++++----- crates/rpc/rpc/src/eth/filter.rs | 8 +++---- crates/rpc/rpc/src/eth/gas_oracle.rs | 4 ++-- crates/rpc/rpc/src/eth/pubsub.rs | 10 ++++----- crates/rpc/rpc/src/trace.rs | 6 +++--- crates/stages/src/stages/execution.rs | 2 +- crates/stages/src/stages/headers.rs | 2 +- crates/storage/provider/src/lib.rs | 16 +++++++------- .../provider/src/providers/database/mod.rs | 17 +++++++-------- .../src/providers/database/provider.rs | 14 ++++++------- crates/storage/provider/src/providers/mod.rs | 16 +++++++------- .../src/providers/post_state_provider.rs | 4 ++-- .../src/providers/state/historical.rs | 4 ++-- .../provider/src/providers/state/latest.rs | 4 ++-- .../provider/src/providers/state/macros.rs | 4 ++-- .../storage/provider/src/test_utils/mock.rs | 18 ++++++++-------- .../storage/provider/src/test_utils/noop.rs | 18 ++++++++-------- crates/storage/provider/src/traits/block.rs | 18 ++++++++-------- .../storage/provider/src/traits/block_hash.rs | 2 +- .../storage/provider/src/traits/block_id.rs | 8 +++---- crates/storage/provider/src/traits/mod.rs | 6 +++--- .../storage/provider/src/traits/receipts.rs | 8 +++---- crates/storage/provider/src/traits/state.rs | 8 +++---- .../provider/src/traits/transactions.rs | 4 ++-- crates/transaction-pool/src/maintain.rs | 6 +++--- docs/crates/discv4.md | 4 ++-- docs/crates/network.md | 2 +- 52 files changed, 188 insertions(+), 194 deletions(-) diff --git a/bin/reth/src/args/rpc_server_args.rs b/bin/reth/src/args/rpc_server_args.rs index b7e7e2b8e933..8a6d9162fc6d 100644 --- a/bin/reth/src/args/rpc_server_args.rs +++ b/bin/reth/src/args/rpc_server_args.rs @@ -8,8 +8,7 @@ use clap::{ use futures::{FutureExt, TryFutureExt}; use reth_network_api::{NetworkInfo, Peers}; use reth_provider::{ - BlockProviderIdExt, CanonStateSubscriptions, EvmEnvProvider, HeaderProvider, - StateProviderFactory, + BlockReaderIdExt, CanonStateSubscriptions, EvmEnvProvider, HeaderProvider, StateProviderFactory, }; use reth_rpc::{ eth::{ @@ -248,7 +247,7 @@ impl RpcServerArgs { jwt_secret: JwtSecret, ) -> Result<(RpcServerHandle, AuthServerHandle), RpcError> where - Provider: BlockProviderIdExt + Provider: BlockReaderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider @@ -306,7 +305,7 @@ impl RpcServerArgs { events: Events, ) -> Result where - Provider: BlockProviderIdExt + Provider: BlockReaderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider @@ -341,7 +340,7 @@ impl RpcServerArgs { jwt_secret: JwtSecret, ) -> Result where - Provider: BlockProviderIdExt + Provider: BlockReaderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 3ee507720948..02c160f3aec3 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -41,7 +41,7 @@ use reth_network::{error::NetworkError, NetworkConfig, NetworkHandle, NetworkMan use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, ChainSpec, Head, SealedHeader, H256}; use reth_provider::{ - BlockHashProvider, BlockProvider, CanonStateSubscriptions, HeaderProvider, ProviderFactory, + BlockHashReader, BlockReader, CanonStateSubscriptions, HeaderProvider, ProviderFactory, StageCheckpointReader, }; use reth_revm::Factory; @@ -498,7 +498,7 @@ impl Command { default_peers_path: PathBuf, ) -> Result where - C: BlockProvider + HeaderProvider + Clone + Unpin + 'static, + C: BlockReader + HeaderProvider + Clone + Unpin + 'static, Pool: TransactionPool + Unpin + 'static, { let client = config.client.clone(); @@ -725,7 +725,7 @@ async fn run_network_until_shutdown( network: NetworkManager, persistent_peers_file: Option, ) where - C: BlockProvider + HeaderProvider + Clone + Unpin + 'static, + C: BlockReader + HeaderProvider + Clone + Unpin + 'static, { pin_mut!(network, shutdown); diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 68b7d02c7943..920e5ddb016b 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -22,9 +22,8 @@ use reth_primitives::{ use reth_provider::{ chain::{ChainSplit, SplitAt}, post_state::PostState, - BlockNumProvider, CanonStateNotification, CanonStateNotificationSender, - CanonStateNotifications, Chain, DatabaseProvider, DisplayBlocksChain, ExecutorFactory, - HeaderProvider, + BlockNumReader, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, + Chain, DatabaseProvider, DisplayBlocksChain, ExecutorFactory, HeaderProvider, }; use std::{ collections::{BTreeMap, HashMap}, diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index 2d6d66da74df..54b583457939 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -20,7 +20,7 @@ use reth_primitives::{ BlockBody, BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Header, SealedBlock, SealedHeader, H256, U256, }; -use reth_provider::{BlockProviderIdExt, CanonStateNotificationSender}; +use reth_provider::{BlockReaderIdExt, CanonStateNotificationSender}; use reth_transaction_pool::TransactionPool; use std::{collections::HashMap, sync::Arc}; use tokio::sync::{mpsc::UnboundedSender, RwLock, RwLockReadGuard, RwLockWriteGuard}; @@ -90,7 +90,7 @@ pub struct AutoSealBuilder { impl AutoSealBuilder where - Client: BlockProviderIdExt, + Client: BlockReaderIdExt, { /// Creates a new builder instance to configure all parts. pub fn new( diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index f206229c04cd..fd7a90047f79 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -21,7 +21,7 @@ use reth_primitives::{ H256, U256, }; use reth_provider::{ - BlockProvider, BlockSource, CanonChainTracker, ProviderError, StageCheckpointReader, + BlockReader, BlockSource, CanonChainTracker, ProviderError, StageCheckpointReader, }; use reth_rpc_types::engine::{ ExecutionPayload, ForkchoiceUpdated, PayloadAttributes, PayloadStatus, PayloadStatusEnum, @@ -217,7 +217,7 @@ pub struct BeaconConsensusEngine where DB: Database, Client: HeadersClient + BodiesClient, - BT: BlockchainTreeEngine + BlockProvider + CanonChainTracker + StageCheckpointReader, + BT: BlockchainTreeEngine + BlockReader + CanonChainTracker + StageCheckpointReader, { /// Controls syncing triggered by engine updates. sync: EngineSyncController, @@ -257,7 +257,7 @@ where impl BeaconConsensusEngine where DB: Database + Unpin + 'static, - BT: BlockchainTreeEngine + BlockProvider + CanonChainTracker + StageCheckpointReader + 'static, + BT: BlockchainTreeEngine + BlockReader + CanonChainTracker + StageCheckpointReader + 'static, Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, { /// Create a new instance of the [BeaconConsensusEngine]. @@ -1288,7 +1288,7 @@ where DB: Database + Unpin + 'static, Client: HeadersClient + BodiesClient + Clone + Unpin + 'static, BT: BlockchainTreeEngine - + BlockProvider + + BlockReader + CanonChainTracker + StageCheckpointReader + Unpin diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index 9cd2dd58405d..dbfc570873a5 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -12,7 +12,7 @@ use reth_dns_discovery::DnsDiscoveryConfig; use reth_ecies::util::pk2id; use reth_eth_wire::{HelloMessage, Status}; use reth_primitives::{ChainSpec, ForkFilter, Head, NodeRecord, PeerId, MAINNET}; -use reth_provider::{BlockProvider, HeaderProvider}; +use reth_provider::{BlockReader, HeaderProvider}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use secp256k1::SECP256K1; use std::{ @@ -97,7 +97,7 @@ impl NetworkConfig { impl NetworkConfig where - C: BlockProvider + HeaderProvider + Clone + Unpin + 'static, + C: BlockReader + HeaderProvider + Clone + Unpin + 'static, { /// Starts the networking stack given a [NetworkConfig] and returns a handle to the network. pub async fn start_network(self) -> Result { diff --git a/crates/net/network/src/eth_requests.rs b/crates/net/network/src/eth_requests.rs index 7216a5cf3b26..09fc31129c31 100644 --- a/crates/net/network/src/eth_requests.rs +++ b/crates/net/network/src/eth_requests.rs @@ -8,7 +8,7 @@ use reth_eth_wire::{ }; use reth_interfaces::p2p::error::RequestResult; use reth_primitives::{BlockBody, BlockHashOrNumber, Header, HeadersDirection, PeerId}; -use reth_provider::{BlockProvider, HeaderProvider}; +use reth_provider::{BlockReader, HeaderProvider}; use std::{ borrow::Borrow, future::Future, @@ -70,7 +70,7 @@ impl EthRequestHandler { impl EthRequestHandler where - C: BlockProvider + HeaderProvider, + C: BlockReader + HeaderProvider, { /// Returns the list of requested headers fn get_headers_response(&self, request: GetBlockHeaders) -> Vec
{ @@ -190,7 +190,7 @@ where /// This should be spawned or used as part of `tokio::select!`. impl Future for EthRequestHandler where - C: BlockProvider + HeaderProvider + Unpin, + C: BlockReader + HeaderProvider + Unpin, { type Output = (); diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index acf5f408459d..35d6dac79436 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -50,7 +50,7 @@ //! ### Configure and launch a standalone network //! //! The [`NetworkConfig`] is used to configure the network. -//! It requires an instance of [`BlockProvider`](reth_provider::BlockProvider). +//! It requires an instance of [`BlockReader`](reth_provider::BlockReader). //! //! ``` //! # async fn launch() { diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index 8a70a9507be4..0a997d85a60e 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -42,7 +42,7 @@ use reth_metrics::common::mpsc::UnboundedMeteredSender; use reth_net_common::bandwidth_meter::BandwidthMeter; use reth_network_api::ReputationChangeKind; use reth_primitives::{listener::EventListeners, NodeRecord, PeerId, H256}; -use reth_provider::BlockProvider; +use reth_provider::BlockReader; use reth_rpc_types::{EthProtocolInfo, NetworkStatus}; use std::{ net::SocketAddr, @@ -156,7 +156,7 @@ impl NetworkManager { impl NetworkManager where - C: BlockProvider, + C: BlockReader, { /// Creates the manager of a new network. /// @@ -586,7 +586,7 @@ where impl Future for NetworkManager where - C: BlockProvider + Unpin, + C: BlockReader + Unpin, { type Output = (); diff --git a/crates/net/network/src/state.rs b/crates/net/network/src/state.rs index 3eb74dda256e..05f423a73843 100644 --- a/crates/net/network/src/state.rs +++ b/crates/net/network/src/state.rs @@ -16,7 +16,7 @@ use reth_eth_wire::{ }; use reth_network_api::PeerKind; use reth_primitives::{ForkId, PeerId, H256}; -use reth_provider::BlockProvider; +use reth_provider::BlockReader; use std::{ collections::{HashMap, VecDeque}, net::{IpAddr, SocketAddr}, @@ -65,7 +65,7 @@ pub struct NetworkState { impl NetworkState where - C: BlockProvider, + C: BlockReader, { /// Create a new state instance with the given params pub(crate) fn new( diff --git a/crates/net/network/src/swarm.rs b/crates/net/network/src/swarm.rs index 189795926b91..f296ebe277bf 100644 --- a/crates/net/network/src/swarm.rs +++ b/crates/net/network/src/swarm.rs @@ -12,7 +12,7 @@ use reth_eth_wire::{ DisconnectReason, EthVersion, Status, }; use reth_primitives::PeerId; -use reth_provider::BlockProvider; +use reth_provider::BlockReader; use std::{ io, net::SocketAddr, @@ -77,7 +77,7 @@ pub(crate) struct Swarm { impl Swarm where - C: BlockProvider, + C: BlockReader, { /// Configures a new swarm instance. pub(crate) fn new( @@ -291,7 +291,7 @@ where impl Stream for Swarm where - C: BlockProvider + Unpin, + C: BlockReader + Unpin, { type Item = SwarmEvent; diff --git a/crates/net/network/src/test_utils/testnet.rs b/crates/net/network/src/test_utils/testnet.rs index d2b149294c85..e2cbeaca27e8 100644 --- a/crates/net/network/src/test_utils/testnet.rs +++ b/crates/net/network/src/test_utils/testnet.rs @@ -8,7 +8,7 @@ use futures::{FutureExt, StreamExt}; use pin_project::pin_project; use reth_eth_wire::{capability::Capability, DisconnectReason, HelloBuilder}; use reth_primitives::PeerId; -use reth_provider::{test_utils::NoopProvider, BlockProvider, HeaderProvider}; +use reth_provider::{test_utils::NoopProvider, BlockReader, HeaderProvider}; use secp256k1::SecretKey; use std::{ fmt, @@ -34,7 +34,7 @@ pub struct Testnet { impl Testnet where - C: BlockProvider + HeaderProvider + Clone, + C: BlockReader + HeaderProvider + Clone, { /// Same as [`Self::try_create_with`] but panics on error pub async fn create_with(num_peers: usize, provider: C) -> Self { @@ -122,7 +122,7 @@ where impl Testnet where - C: BlockProvider + HeaderProvider + Unpin + 'static, + C: BlockReader + HeaderProvider + Unpin + 'static, { /// Spawns the testnet to a separate task pub fn spawn(self) -> TestnetHandle { @@ -176,7 +176,7 @@ impl fmt::Debug for Testnet { impl Future for Testnet where - C: BlockProvider + HeaderProvider + Unpin, + C: BlockReader + HeaderProvider + Unpin, { type Output = (); @@ -220,7 +220,7 @@ pub struct Peer { impl Peer where - C: BlockProvider + HeaderProvider + Clone, + C: BlockReader + HeaderProvider + Clone, { /// Returns the number of connected peers. pub fn num_peers(&self) -> usize { @@ -249,7 +249,7 @@ where impl Future for Peer where - C: BlockProvider + HeaderProvider + Unpin, + C: BlockReader + HeaderProvider + Unpin, { type Output = (); @@ -275,7 +275,7 @@ pub struct PeerConfig { impl PeerConfig where - C: BlockProvider + HeaderProvider + Clone, + C: BlockReader + HeaderProvider + Clone, { /// Launches the network and returns the [Peer] that manages it pub async fn launch(self) -> Result, NetworkError> { diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index a16e730abc01..f136d92cb26c 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -23,7 +23,7 @@ use reth_primitives::{ proofs, Block, BlockNumberOrTag, ChainSpec, Header, IntoRecoveredTransaction, Receipt, SealedBlock, Withdrawal, EMPTY_OMMER_ROOT, H256, U256, }; -use reth_provider::{BlockProviderIdExt, BlockSource, PostState, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, BlockSource, PostState, StateProviderFactory}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, @@ -98,7 +98,7 @@ impl BasicPayloadJobGenerator {} impl PayloadJobGenerator for BasicPayloadJobGenerator where - Client: StateProviderFactory + BlockProviderIdExt + Clone + Unpin + 'static, + Client: StateProviderFactory + BlockReaderIdExt + Clone + Unpin + 'static, Pool: TransactionPool + Unpin + 'static, Tasks: TaskSpawner + Clone + Unpin + 'static, { diff --git a/crates/revm/src/executor.rs b/crates/revm/src/executor.rs index 8f5fcc3e4200..dc456d61cfe4 100644 --- a/crates/revm/src/executor.rs +++ b/crates/revm/src/executor.rs @@ -654,7 +654,7 @@ mod tests { }; use reth_provider::{ post_state::{AccountChanges, Storage, StorageTransition, StorageWipe}, - AccountReader, BlockHashProvider, StateProvider, StateRootProvider, + AccountReader, BlockHashReader, StateProvider, StateRootProvider, }; use reth_rlp::Decodable; use std::{collections::HashMap, str::FromStr}; @@ -700,7 +700,7 @@ mod tests { } } - impl BlockHashProvider for StateProviderTest { + impl BlockHashReader for StateProviderTest { fn block_hash(&self, number: u64) -> reth_interfaces::Result> { Ok(self.block_hash.get(&number).cloned()) } diff --git a/crates/rpc/rpc-builder/src/auth.rs b/crates/rpc/rpc-builder/src/auth.rs index edf398117e85..e44911d4bbf2 100644 --- a/crates/rpc/rpc-builder/src/auth.rs +++ b/crates/rpc/rpc-builder/src/auth.rs @@ -11,7 +11,7 @@ use jsonrpsee::{ }; use reth_network_api::{NetworkInfo, Peers}; use reth_provider::{ - BlockProviderIdExt, EvmEnvProvider, HeaderProvider, ReceiptProviderIdExt, StateProviderFactory, + BlockReaderIdExt, EvmEnvProvider, HeaderProvider, ReceiptProviderIdExt, StateProviderFactory, }; use reth_rpc::{ eth::{cache::EthStateCache, gas_oracle::GasPriceOracle}, @@ -38,7 +38,7 @@ pub async fn launch( secret: JwtSecret, ) -> Result where - Provider: BlockProviderIdExt + Provider: BlockReaderIdExt + ReceiptProviderIdExt + HeaderProvider + StateProviderFactory @@ -82,7 +82,7 @@ pub async fn launch_with_eth_api( secret: JwtSecret, ) -> Result where - Provider: BlockProviderIdExt + Provider: BlockReaderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index fb73bddf67d6..1c48b6d6ca09 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -25,13 +25,13 @@ //! //! ``` //! use reth_network_api::{NetworkInfo, Peers}; -//! use reth_provider::{BlockProviderIdExt, CanonStateSubscriptions, StateProviderFactory, EvmEnvProvider}; +//! use reth_provider::{BlockReaderIdExt, CanonStateSubscriptions, StateProviderFactory, EvmEnvProvider}; //! use reth_rpc_builder::{RethRpcModule, RpcModuleBuilder, RpcServerConfig, ServerBuilder, TransportRpcModuleConfig}; //! use reth_tasks::TokioTaskExecutor; //! use reth_transaction_pool::TransactionPool; //! pub async fn launch(provider: Provider, pool: Pool, network: Network, events: Events) //! where -//! Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, +//! Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, //! Pool: TransactionPool + Clone + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static, @@ -58,7 +58,7 @@ //! ``` //! use tokio::try_join; //! use reth_network_api::{NetworkInfo, Peers}; -//! use reth_provider::{BlockProviderIdExt, CanonStateSubscriptions, StateProviderFactory, EvmEnvProvider}; +//! use reth_provider::{BlockReaderIdExt, CanonStateSubscriptions, StateProviderFactory, EvmEnvProvider}; //! use reth_rpc::JwtSecret; //! use reth_rpc_builder::{RethRpcModule, RpcModuleBuilder, RpcServerConfig, TransportRpcModuleConfig}; //! use reth_tasks::TokioTaskExecutor; @@ -67,7 +67,7 @@ //! use reth_rpc_builder::auth::AuthServerConfig; //! pub async fn launch(provider: Provider, pool: Pool, network: Network, events: Events, engine_api: EngineApi) //! where -//! Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, +//! Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, //! Pool: TransactionPool + Clone + 'static, //! Network: NetworkInfo + Peers + Clone + 'static, //! Events: CanonStateSubscriptions + Clone + 'static, @@ -107,8 +107,7 @@ use jsonrpsee::{ use reth_ipc::server::IpcServer; use reth_network_api::{NetworkInfo, Peers}; use reth_provider::{ - BlockProvider, BlockProviderIdExt, CanonStateSubscriptions, EvmEnvProvider, - StateProviderFactory, + BlockReader, BlockReaderIdExt, CanonStateSubscriptions, EvmEnvProvider, StateProviderFactory, }; use reth_rpc::{ eth::{ @@ -164,7 +163,7 @@ pub async fn launch( events: Events, ) -> Result where - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, @@ -214,7 +213,7 @@ impl /// Configure the provider instance. pub fn with_provider

(self, provider: P) -> RpcModuleBuilder where - P: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + P: BlockReader + StateProviderFactory + EvmEnvProvider + 'static, { let Self { pool, network, executor, events, .. } = self; RpcModuleBuilder { provider, network, pool, executor, events } @@ -263,7 +262,7 @@ impl impl RpcModuleBuilder where - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, @@ -481,7 +480,7 @@ impl RpcModuleSelection { ) -> RpcModule<()> where Provider: - BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, + BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, @@ -687,7 +686,7 @@ where impl RethModuleRegistry where - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + Clone + Unpin + 'static, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, Tasks: TaskSpawner + Clone + 'static, diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 159f2a250dc2..55e631328c5d 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -5,7 +5,7 @@ use reth_beacon_consensus::BeaconConsensusEngineHandle; use reth_interfaces::consensus::ForkchoiceState; use reth_payload_builder::PayloadStore; use reth_primitives::{BlockHash, BlockHashOrNumber, BlockNumber, ChainSpec, Hardfork, U64}; -use reth_provider::{BlockProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory}; +use reth_provider::{BlockReader, EvmEnvProvider, HeaderProvider, StateProviderFactory}; use reth_rpc_api::EngineApiServer; use reth_rpc_types::engine::{ ExecutionPayload, ExecutionPayloadBodies, ExecutionPayloadEnvelope, ForkchoiceUpdated, @@ -36,7 +36,7 @@ pub struct EngineApi { impl EngineApi where - Provider: HeaderProvider + BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: HeaderProvider + BlockReader + StateProviderFactory + EvmEnvProvider + 'static, { /// Create new instance of [EngineApi]. pub fn new( @@ -304,7 +304,7 @@ where #[async_trait] impl EngineApiServer for EngineApi where - Provider: HeaderProvider + BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: HeaderProvider + BlockReader + StateProviderFactory + EvmEnvProvider + 'static, { /// Handler for `engine_newPayloadV1` /// See also diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 6adb46c4be1b..6f997172c14d 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -12,7 +12,7 @@ use crate::{ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_primitives::{Account, Block, BlockId, BlockNumberOrTag, Bytes, TransactionSigned, H256}; -use reth_provider::{BlockProviderIdExt, HeaderProvider, StateProviderBox}; +use reth_provider::{BlockReaderIdExt, HeaderProvider, StateProviderBox}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, @@ -70,7 +70,7 @@ impl DebugApi { impl DebugApi where - Provider: BlockProviderIdExt + HeaderProvider + 'static, + Provider: BlockReaderIdExt + HeaderProvider + 'static, Eth: EthTransactions + 'static, { /// Executes the future on a new blocking task. @@ -516,7 +516,7 @@ where #[async_trait] impl DebugApiServer for DebugApi where - Provider: BlockProviderIdExt + HeaderProvider + 'static, + Provider: BlockReaderIdExt + HeaderProvider + 'static, Eth: EthApiSpec + 'static, { /// Handler for `debug_getRawHeader` diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 1aa3108ee166..472ac9bd2a89 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -5,12 +5,12 @@ use crate::{ EthApi, }; use reth_primitives::BlockId; -use reth_provider::{BlockProviderIdExt, EvmEnvProvider, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderFactory}; use reth_rpc_types::{Block, Index, RichBlock}; impl EthApi where - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, { /// Returns the uncle headers of the given block /// diff --git a/crates/rpc/rpc/src/eth/api/call.rs b/crates/rpc/rpc/src/eth/api/call.rs index f7039b3ea9ca..cca5d4b26e6e 100644 --- a/crates/rpc/rpc/src/eth/api/call.rs +++ b/crates/rpc/rpc/src/eth/api/call.rs @@ -14,7 +14,7 @@ use crate::{ use ethers_core::utils::get_contract_address; use reth_network_api::NetworkInfo; use reth_primitives::{AccessList, BlockId, BlockNumberOrTag, Bytes, U256}; -use reth_provider::{BlockProviderIdExt, EvmEnvProvider, StateProvider, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProvider, StateProviderFactory}; use reth_revm::{ access_list::AccessListInspector, database::{State, SubState}, @@ -34,7 +34,7 @@ const MIN_CREATE_GAS: u64 = 53_000u64; impl EthApi where Pool: TransactionPool + Clone + 'static, - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + Send + Sync + 'static, { /// Estimate gas needed for execution of the `request` at the [BlockId]. diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs index ba6885240ead..6077475fce37 100644 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ b/crates/rpc/rpc/src/eth/api/fees.rs @@ -6,7 +6,7 @@ use crate::{ }; use reth_network_api::NetworkInfo; use reth_primitives::{BlockId, BlockNumberOrTag, U256}; -use reth_provider::{BlockProviderIdExt, EvmEnvProvider, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderFactory}; use reth_rpc_types::{FeeHistory, FeeHistoryCacheItem, TxGasAndReward}; use reth_transaction_pool::TransactionPool; use std::collections::BTreeMap; @@ -14,7 +14,7 @@ use std::collections::BTreeMap; impl EthApi where Pool: TransactionPool + Clone + 'static, - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + Send + Sync + 'static, { /// Returns a suggestion for a gas price for legacy transactions. diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index 29aebdb6a576..f611034fd58e 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -13,7 +13,7 @@ use async_trait::async_trait; use reth_interfaces::Result; use reth_network_api::NetworkInfo; use reth_primitives::{Address, BlockId, BlockNumberOrTag, ChainInfo, H256, U256, U64}; -use reth_provider::{BlockProviderIdExt, EvmEnvProvider, StateProviderBox, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderBox, StateProviderFactory}; use reth_rpc_types::{FeeHistoryCache, SyncInfo, SyncStatus}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::TransactionPool; @@ -72,7 +72,7 @@ pub struct EthApi { impl EthApi where - Provider: BlockProviderIdExt, + Provider: BlockReaderIdExt, { /// Creates a new, shareable instance using the default tokio task spawner. pub fn new( @@ -175,7 +175,7 @@ where impl EthApi where - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, { /// Returns the state at the given [BlockId] enum. pub fn state_at_block_id(&self, at: BlockId) -> EthResult> { @@ -223,7 +223,7 @@ impl Clone for EthApi { impl EthApiSpec for EthApi where Pool: TransactionPool + Clone + 'static, - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + 'static, { /// Returns the current ethereum protocol version. diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 6d093cb01fb3..f8244dcf53d2 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -16,7 +16,7 @@ use reth_primitives::{ H256, H64, U256, U64, }; use reth_provider::{ - BlockIdProvider, BlockProvider, BlockProviderIdExt, EvmEnvProvider, HeaderProvider, + BlockIdReader, BlockReader, BlockReaderIdExt, EvmEnvProvider, HeaderProvider, StateProviderFactory, }; use reth_rpc_api::EthApiServer; @@ -33,9 +33,9 @@ impl EthApiServer for EthApi where Self: EthApiSpec + EthTransactions, Pool: TransactionPool + 'static, - Provider: BlockProvider - + BlockIdProvider - + BlockProviderIdExt + Provider: BlockReader + + BlockIdReader + + BlockReaderIdExt + HeaderProvider + StateProviderFactory + EvmEnvProvider diff --git a/crates/rpc/rpc/src/eth/api/state.rs b/crates/rpc/rpc/src/eth/api/state.rs index 4785ea3846ba..281198be0796 100644 --- a/crates/rpc/rpc/src/eth/api/state.rs +++ b/crates/rpc/rpc/src/eth/api/state.rs @@ -9,14 +9,14 @@ use reth_primitives::{ U256, }; use reth_provider::{ - AccountReader, BlockProviderIdExt, EvmEnvProvider, StateProvider, StateProviderFactory, + AccountReader, BlockReaderIdExt, EvmEnvProvider, StateProvider, StateProviderFactory, }; use reth_rpc_types::{EIP1186AccountProofResponse, StorageProof}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; impl EthApi where - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Pool: TransactionPool + Clone + 'static, Network: Send + Sync + 'static, { diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 1c5a512c5668..ece5457e565d 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -18,7 +18,7 @@ use reth_primitives::{ TransactionKind::{Call, Create}, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, H256, U128, U256, U64, }; -use reth_provider::{BlockProviderIdExt, EvmEnvProvider, StateProviderBox, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderBox, StateProviderFactory}; use reth_revm::{ database::{State, SubState}, env::{fill_block_env_with_coinbase, tx_env_with_recovered}, @@ -222,7 +222,7 @@ pub trait EthTransactions: Send + Sync { impl EthTransactions for EthApi where Pool: TransactionPool + Clone + 'static, - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: NetworkInfo + Send + Sync + 'static, { fn state_at(&self, at: BlockId) -> EthResult> { @@ -652,7 +652,7 @@ where impl EthApi where Pool: TransactionPool + 'static, - Provider: BlockProviderIdExt + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, Network: 'static, { pub(crate) fn sign_request( diff --git a/crates/rpc/rpc/src/eth/cache.rs b/crates/rpc/rpc/src/eth/cache.rs index 717ef540254f..8a580f36e290 100644 --- a/crates/rpc/rpc/src/eth/cache.rs +++ b/crates/rpc/rpc/src/eth/cache.rs @@ -3,7 +3,7 @@ use futures::{future::Either, Stream, StreamExt}; use reth_interfaces::{provider::ProviderError, Result}; use reth_primitives::{Block, Receipt, SealedBlock, TransactionSigned, H256}; -use reth_provider::{BlockProvider, CanonStateNotification, EvmEnvProvider, StateProviderFactory}; +use reth_provider::{BlockReader, CanonStateNotification, EvmEnvProvider, StateProviderFactory}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use revm::primitives::{BlockEnv, CfgEnv}; use schnellru::{ByMemoryUsage, Limiter, LruMap}; @@ -121,7 +121,7 @@ impl EthStateCache { /// See also [Self::spawn_with] pub fn spawn(provider: Provider, config: EthStateCacheConfig) -> Self where - Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, { Self::spawn_with(provider, config, TokioTaskExecutor::default()) } @@ -136,7 +136,7 @@ impl EthStateCache { executor: Tasks, ) -> Self where - Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, { let EthStateCacheConfig { max_block_bytes, max_receipt_bytes, max_env_bytes } = config; @@ -244,7 +244,7 @@ pub(crate) struct EthStateCacheService< impl EthStateCacheService where - Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, { fn on_new_block(&mut self, block_hash: H256, res: Result>) { @@ -287,7 +287,7 @@ where impl Future for EthStateCacheService where - Provider: StateProviderFactory + BlockProvider + EvmEnvProvider + Clone + Unpin + 'static, + Provider: StateProviderFactory + BlockReader + EvmEnvProvider + Clone + Unpin + 'static, Tasks: TaskSpawner + Clone + 'static, { type Output = (); diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index c6eea1fbe193..891f24f4758e 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -10,7 +10,7 @@ use crate::{ use async_trait::async_trait; use jsonrpsee::{core::RpcResult, server::IdProvider}; use reth_primitives::{BlockHashOrNumber, Receipt, SealedBlock, H256}; -use reth_provider::{BlockIdProvider, BlockProvider, EvmEnvProvider}; +use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider}; use reth_rpc_api::EthFilterApiServer; use reth_rpc_types::{Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log}; use reth_tasks::TaskSpawner; @@ -66,7 +66,7 @@ impl EthFilter { impl EthFilter where - Provider: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, + Provider: BlockReader + BlockIdReader + EvmEnvProvider + 'static, Pool: TransactionPool + 'static, { /// Executes the given filter on a new task. @@ -178,7 +178,7 @@ where #[async_trait] impl EthFilterApiServer for EthFilter where - Provider: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, + Provider: BlockReader + BlockIdReader + EvmEnvProvider + 'static, Pool: TransactionPool + 'static, { /// Handler for `eth_newFilter` @@ -274,7 +274,7 @@ struct EthFilterInner { impl EthFilterInner where - Provider: BlockProvider + BlockIdProvider + EvmEnvProvider + 'static, + Provider: BlockReader + BlockIdReader + EvmEnvProvider + 'static, Pool: TransactionPool + 'static, { /// Returns logs matching given filter object. diff --git a/crates/rpc/rpc/src/eth/gas_oracle.rs b/crates/rpc/rpc/src/eth/gas_oracle.rs index fd5ab1942877..8a364088d13a 100644 --- a/crates/rpc/rpc/src/eth/gas_oracle.rs +++ b/crates/rpc/rpc/src/eth/gas_oracle.rs @@ -5,7 +5,7 @@ use crate::eth::{ error::{EthApiError, EthResult, RpcInvalidTransactionError}, }; use reth_primitives::{constants::GWEI_TO_WEI, BlockNumberOrTag, H256, U256}; -use reth_provider::BlockProviderIdExt; +use reth_provider::BlockReaderIdExt; use serde::{Deserialize, Serialize}; use tokio::sync::Mutex; use tracing::warn; @@ -94,7 +94,7 @@ pub struct GasPriceOracle { impl GasPriceOracle where - Provider: BlockProviderIdExt + 'static, + Provider: BlockReaderIdExt + 'static, { /// Creates and returns the [GasPriceOracle]. pub fn new( diff --git a/crates/rpc/rpc/src/eth/pubsub.rs b/crates/rpc/rpc/src/eth/pubsub.rs index f4a4ccebb1b5..ac76d4fabbe1 100644 --- a/crates/rpc/rpc/src/eth/pubsub.rs +++ b/crates/rpc/rpc/src/eth/pubsub.rs @@ -4,7 +4,7 @@ use futures::StreamExt; use jsonrpsee::{server::SubscriptionMessage, PendingSubscriptionSink, SubscriptionSink}; use reth_network_api::NetworkInfo; use reth_primitives::TxHash; -use reth_provider::{BlockProvider, CanonStateSubscriptions, EvmEnvProvider}; +use reth_provider::{BlockReader, CanonStateSubscriptions, EvmEnvProvider}; use reth_rpc_api::EthPubSubApiServer; use reth_rpc_types::FilteredParams; @@ -67,7 +67,7 @@ impl EthPubSub impl EthPubSubApiServer for EthPubSub where - Provider: BlockProvider + EvmEnvProvider + Clone + 'static, + Provider: BlockReader + EvmEnvProvider + Clone + 'static, Pool: TransactionPool + 'static, Events: CanonStateSubscriptions + Clone + 'static, Network: NetworkInfo + Clone + 'static, @@ -97,7 +97,7 @@ async fn handle_accepted( params: Option, ) -> Result<(), jsonrpsee::core::Error> where - Provider: BlockProvider + EvmEnvProvider + Clone + 'static, + Provider: BlockReader + EvmEnvProvider + Clone + 'static, Pool: TransactionPool + 'static, Events: CanonStateSubscriptions + Clone + 'static, Network: NetworkInfo + Clone + 'static, @@ -217,7 +217,7 @@ struct EthPubSubInner { impl EthPubSubInner where - Provider: BlockProvider + 'static, + Provider: BlockReader + 'static, { /// Returns the current sync status for the `syncing` subscription async fn sync_status(&self, is_syncing: bool) -> EthSubscriptionResult { @@ -248,7 +248,7 @@ where impl EthPubSubInner where - Provider: BlockProvider + EvmEnvProvider + 'static, + Provider: BlockReader + EvmEnvProvider + 'static, Events: CanonStateSubscriptions + 'static, Network: NetworkInfo + 'static, Pool: 'static, diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 8b61a9f1087f..ae0c1193f7a5 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -12,7 +12,7 @@ use crate::{ use async_trait::async_trait; use jsonrpsee::core::RpcResult as Result; use reth_primitives::{BlockId, BlockNumberOrTag, Bytes, H256}; -use reth_provider::{BlockProvider, EvmEnvProvider, StateProviderBox, StateProviderFactory}; +use reth_provider::{BlockReader, EvmEnvProvider, StateProviderBox, StateProviderFactory}; use reth_revm::{ database::{State, SubState}, env::tx_env_with_recovered, @@ -77,7 +77,7 @@ impl TraceApi { impl TraceApi where - Provider: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReader + StateProviderFactory + EvmEnvProvider + 'static, Eth: EthTransactions + 'static, { /// Executes the future on a new blocking task. @@ -425,7 +425,7 @@ where #[async_trait] impl TraceApiServer for TraceApi where - Provider: BlockProvider + StateProviderFactory + EvmEnvProvider + 'static, + Provider: BlockReader + StateProviderFactory + EvmEnvProvider + 'static, Eth: EthTransactions + 'static, { /// Executes the given call and returns a number of possible traces for it. diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 4dae22d19324..259c91b445eb 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -19,7 +19,7 @@ use reth_primitives::{ BlockNumber, Header, U256, }; use reth_provider::{ - post_state::PostState, BlockExecutor, BlockProvider, DatabaseProviderRW, ExecutorFactory, + post_state::PostState, BlockExecutor, BlockReader, DatabaseProviderRW, ExecutorFactory, HeaderProvider, LatestStateProviderRef, ProviderError, }; use std::{ops::RangeInclusive, time::Instant}; diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index 1984e4d6f2b2..4dd7620cc88b 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -404,7 +404,7 @@ mod tests { TestHeadersClient, }; use reth_primitives::U256; - use reth_provider::{BlockHashProvider, BlockNumProvider, HeaderProvider}; + use reth_provider::{BlockHashReader, BlockNumReader, HeaderProvider}; use std::sync::Arc; pub(crate) struct HeadersTestRunner { diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 4e6485fb2adb..47929cc4f0ea 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -15,14 +15,14 @@ /// Various provider traits. mod traits; pub use traits::{ - AccountExtReader, AccountReader, BlockExecutor, BlockHashProvider, BlockIdProvider, - BlockNumProvider, BlockProvider, BlockProviderIdExt, BlockSource, - BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotification, - CanonStateNotificationSender, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, - ExecutorFactory, HashingWriter, HeaderProvider, HistoryWriter, PostStateDataProvider, - ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StageCheckpointWriter, - StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, StorageReader, - TransactionsProvider, WithdrawalsProvider, + AccountExtReader, AccountReader, BlockExecutor, BlockHashReader, BlockIdReader, BlockNumReader, + BlockReader, BlockReaderIdExt, BlockSource, BlockchainTreePendingStateProvider, + CanonChainTracker, CanonStateNotification, CanonStateNotificationSender, + CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, ExecutorFactory, + HashingWriter, HeaderProvider, HistoryWriter, PostStateDataProvider, ReceiptProvider, + ReceiptProviderIdExt, StageCheckpointReader, StageCheckpointWriter, StateProvider, + StateProviderBox, StateProviderFactory, StateRootProvider, StorageReader, TransactionsProvider, + WithdrawalsProvider, }; /// Provider trait implementations. diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index f04fd9c6832f..6109a6e6384b 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -1,9 +1,8 @@ use crate::{ providers::state::{historical::HistoricalStateProvider, latest::LatestStateProvider}, traits::{BlockSource, ReceiptProvider}, - BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, HeaderProvider, - ProviderError, StageCheckpointReader, StateProviderBox, TransactionsProvider, - WithdrawalsProvider, + BlockHashReader, BlockNumReader, BlockReader, EvmEnvProvider, HeaderProvider, ProviderError, + StageCheckpointReader, StateProviderBox, TransactionsProvider, WithdrawalsProvider, }; use reth_db::{database::Database, models::StoredBlockBodyIndices}; use reth_interfaces::Result; @@ -34,14 +33,14 @@ pub struct ProviderFactory { impl ProviderFactory { /// Returns a provider with a created `DbTx` inside, which allows fetching data from the /// database using different types of providers. Example: [`HeaderProvider`] - /// [`BlockHashProvider`]. This may fail if the inner read database transaction fails to open. + /// [`BlockHashReader`]. This may fail if the inner read database transaction fails to open. pub fn provider(&self) -> Result> { Ok(DatabaseProvider::new(self.db.tx()?, self.chain_spec.clone())) } /// Returns a provider with a created `DbTxMut` inside, which allows fetching and updating /// data from the database using different types of providers. Example: [`HeaderProvider`] - /// [`BlockHashProvider`]. This may fail if the inner read/write database transaction fails to + /// [`BlockHashReader`]. This may fail if the inner read/write database transaction fails to /// open. pub fn provider_rw(&self) -> Result> { Ok(DatabaseProviderRW(DatabaseProvider::new_rw(self.db.tx_mut()?, self.chain_spec.clone()))) @@ -144,7 +143,7 @@ impl HeaderProvider for ProviderFactory { } } -impl BlockHashProvider for ProviderFactory { +impl BlockHashReader for ProviderFactory { fn block_hash(&self, number: u64) -> Result> { self.provider()?.block_hash(number) } @@ -154,7 +153,7 @@ impl BlockHashProvider for ProviderFactory { } } -impl BlockNumProvider for ProviderFactory { +impl BlockNumReader for ProviderFactory { fn chain_info(&self) -> Result { self.provider()?.chain_info() } @@ -172,7 +171,7 @@ impl BlockNumProvider for ProviderFactory { } } -impl BlockProvider for ProviderFactory { +impl BlockReader for ProviderFactory { fn find_block_by_hash(&self, hash: H256, source: BlockSource) -> Result> { self.provider()?.find_block_by_hash(hash, source) } @@ -329,7 +328,7 @@ impl EvmEnvProvider for ProviderFactory { #[cfg(test)] mod tests { use super::ProviderFactory; - use crate::{BlockHashProvider, BlockNumProvider}; + use crate::{BlockHashReader, BlockNumReader}; use reth_db::mdbx::{test_utils::create_test_db, EnvKind, WriteMap}; use reth_primitives::{ChainSpecBuilder, H256}; use std::sync::Arc; diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index f941f5ae9ac5..7d6b389c4250 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -2,9 +2,9 @@ use crate::{ insert_canonical_block, post_state::StorageChangeset, traits::{AccountExtReader, BlockSource, ReceiptProvider, StageCheckpointWriter}, - AccountReader, BlockHashProvider, BlockNumProvider, BlockProvider, EvmEnvProvider, - HashingWriter, HeaderProvider, HistoryWriter, PostState, ProviderError, StageCheckpointReader, - StorageReader, TransactionsProvider, WithdrawalsProvider, + AccountReader, BlockHashReader, BlockNumReader, BlockReader, EvmEnvProvider, HashingWriter, + HeaderProvider, HistoryWriter, PostState, ProviderError, StageCheckpointReader, StorageReader, + TransactionsProvider, WithdrawalsProvider, }; use itertools::{izip, Itertools}; use reth_db::{ @@ -81,7 +81,7 @@ impl<'this, DB: Database> DatabaseProviderRW<'this, DB> { } /// A provider struct that fetchs data from the database. -/// Wrapper around [`DbTx`] and [`DbTxMut`]. Example: [`HeaderProvider`] [`BlockHashProvider`] +/// Wrapper around [`DbTx`] and [`DbTxMut`]. Example: [`HeaderProvider`] [`BlockHashReader`] #[derive(Debug)] pub struct DatabaseProvider<'this, TX> where @@ -938,7 +938,7 @@ impl<'this, TX: DbTx<'this>> HeaderProvider for DatabaseProvider<'this, TX> { } } -impl<'this, TX: DbTx<'this>> BlockHashProvider for DatabaseProvider<'this, TX> { +impl<'this, TX: DbTx<'this>> BlockHashReader for DatabaseProvider<'this, TX> { fn block_hash(&self, number: u64) -> Result> { Ok(self.tx.get::(number)?) } @@ -953,7 +953,7 @@ impl<'this, TX: DbTx<'this>> BlockHashProvider for DatabaseProvider<'this, TX> { } } -impl<'this, TX: DbTx<'this>> BlockNumProvider for DatabaseProvider<'this, TX> { +impl<'this, TX: DbTx<'this>> BlockNumReader for DatabaseProvider<'this, TX> { fn chain_info(&self) -> Result { let best_number = self.best_block_number()?; let best_hash = self.block_hash(best_number)?.unwrap_or_default(); @@ -976,7 +976,7 @@ impl<'this, TX: DbTx<'this>> BlockNumProvider for DatabaseProvider<'this, TX> { } } -impl<'this, TX: DbTx<'this>> BlockProvider for DatabaseProvider<'this, TX> { +impl<'this, TX: DbTx<'this>> BlockReader for DatabaseProvider<'this, TX> { fn find_block_by_hash(&self, hash: H256, source: BlockSource) -> Result> { if source.is_database() { self.block(hash.into()) diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index e3143c60bde4..bdb9bc4fc4f4 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -1,5 +1,5 @@ use crate::{ - BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, BlockProviderIdExt, + BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, HeaderProvider, PostStateDataProvider, ProviderError, ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StateProviderBox, @@ -144,7 +144,7 @@ where } } -impl BlockHashProvider for BlockchainProvider +impl BlockHashReader for BlockchainProvider where DB: Database, Tree: Send + Sync, @@ -158,7 +158,7 @@ where } } -impl BlockNumProvider for BlockchainProvider +impl BlockNumReader for BlockchainProvider where DB: Database, Tree: BlockchainTreeViewer + Send + Sync, @@ -180,7 +180,7 @@ where } } -impl BlockIdProvider for BlockchainProvider +impl BlockIdReader for BlockchainProvider where DB: Database, Tree: BlockchainTreeViewer + Send + Sync, @@ -198,7 +198,7 @@ where } } -impl BlockProvider for BlockchainProvider +impl BlockReader for BlockchainProvider where DB: Database, Tree: BlockchainTreeViewer + Send + Sync, @@ -637,7 +637,7 @@ impl CanonChainTracker for BlockchainProvider where DB: Send + Sync, Tree: Send + Sync, - Self: BlockProvider, + Self: BlockReader, { fn on_forkchoice_update_received(&self, _update: &ForkchoiceState) { // update timestamp @@ -669,9 +669,9 @@ where } } -impl BlockProviderIdExt for BlockchainProvider +impl BlockReaderIdExt for BlockchainProvider where - Self: BlockProvider + BlockIdProvider + ReceiptProviderIdExt, + Self: BlockReader + BlockIdReader + ReceiptProviderIdExt, Tree: BlockchainTreeEngine, { fn block_by_id(&self, id: BlockId) -> Result> { diff --git a/crates/storage/provider/src/providers/post_state_provider.rs b/crates/storage/provider/src/providers/post_state_provider.rs index 065e5a1efea2..517ff50af114 100644 --- a/crates/storage/provider/src/providers/post_state_provider.rs +++ b/crates/storage/provider/src/providers/post_state_provider.rs @@ -1,5 +1,5 @@ use crate::{ - AccountReader, BlockHashProvider, PostState, PostStateDataProvider, StateProvider, + AccountReader, BlockHashReader, PostState, PostStateDataProvider, StateProvider, StateRootProvider, }; use reth_interfaces::{provider::ProviderError, Result}; @@ -23,7 +23,7 @@ impl PostStateProvider /* Implement StateProvider traits */ -impl BlockHashProvider +impl BlockHashReader for PostStateProvider { fn block_hash(&self, block_number: BlockNumber) -> Result> { diff --git a/crates/storage/provider/src/providers/state/historical.rs b/crates/storage/provider/src/providers/state/historical.rs index 686dd0469c2f..048d523297ce 100644 --- a/crates/storage/provider/src/providers/state/historical.rs +++ b/crates/storage/provider/src/providers/state/historical.rs @@ -1,5 +1,5 @@ use crate::{ - providers::state::macros::delegate_provider_impls, AccountReader, BlockHashProvider, PostState, + providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, PostState, ProviderError, StateProvider, StateRootProvider, }; use reth_db::{ @@ -122,7 +122,7 @@ impl<'a, 'b, TX: DbTx<'a>> AccountReader for HistoricalStateProviderRef<'a, 'b, } } -impl<'a, 'b, TX: DbTx<'a>> BlockHashProvider for HistoricalStateProviderRef<'a, 'b, TX> { +impl<'a, 'b, TX: DbTx<'a>> BlockHashReader for HistoricalStateProviderRef<'a, 'b, TX> { /// Get block hash by number. fn block_hash(&self, number: u64) -> Result> { self.tx.get::(number).map_err(Into::into) diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 4d53351cf18e..77a8692b51d7 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -1,5 +1,5 @@ use crate::{ - providers::state::macros::delegate_provider_impls, AccountReader, BlockHashProvider, PostState, + providers::state::macros::delegate_provider_impls, AccountReader, BlockHashReader, PostState, StateProvider, StateRootProvider, }; use reth_db::{ @@ -35,7 +35,7 @@ impl<'a, 'b, TX: DbTx<'a>> AccountReader for LatestStateProviderRef<'a, 'b, TX> } } -impl<'a, 'b, TX: DbTx<'a>> BlockHashProvider for LatestStateProviderRef<'a, 'b, TX> { +impl<'a, 'b, TX: DbTx<'a>> BlockHashReader for LatestStateProviderRef<'a, 'b, TX> { /// Get block hash by number. fn block_hash(&self, number: u64) -> Result> { self.db.get::(number).map_err(Into::into) diff --git a/crates/storage/provider/src/providers/state/macros.rs b/crates/storage/provider/src/providers/state/macros.rs index 299032e99b85..8c8c5f709d58 100644 --- a/crates/storage/provider/src/providers/state/macros.rs +++ b/crates/storage/provider/src/providers/state/macros.rs @@ -24,7 +24,7 @@ pub(crate) use delegate_impls_to_as_ref; /// Delegates the provider trait implementations to the `as_ref` function of the type: /// /// [AccountReader](crate::AccountReader) -/// [BlockHashProvider](crate::BlockHashProvider) +/// [BlockHashReader](crate::BlockHashReader) /// [StateProvider](crate::StateProvider) macro_rules! delegate_provider_impls { ($target:ty $(where [$($generics:tt)*])?) => { @@ -36,7 +36,7 @@ macro_rules! delegate_provider_impls { AccountReader $(where [$($generics)*])? { fn basic_account(&self, address: reth_primitives::Address) -> reth_interfaces::Result>; } - BlockHashProvider $(where [$($generics)*])? { + BlockHashReader $(where [$($generics)*])? { fn block_hash(&self, number: u64) -> reth_interfaces::Result>; fn canonical_hashes_range(&self, start: reth_primitives::BlockNumber, end: reth_primitives::BlockNumber) -> reth_interfaces::Result>; } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 0c6cd427ba43..825bee816c7e 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, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, - BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, PostStateDataProvider, - ReceiptProviderIdExt, StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, - TransactionsProvider, WithdrawalsProvider, + AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, + EvmEnvProvider, HeaderProvider, PostState, PostStateDataProvider, ReceiptProviderIdExt, + StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, + WithdrawalsProvider, }; use parking_lot::Mutex; use reth_db::models::StoredBlockBodyIndices; @@ -241,7 +241,7 @@ impl ReceiptProvider for MockEthProvider { impl ReceiptProviderIdExt for MockEthProvider {} -impl BlockHashProvider for MockEthProvider { +impl BlockHashReader for MockEthProvider { fn block_hash(&self, number: u64) -> Result> { let lock = self.blocks.lock(); @@ -261,7 +261,7 @@ impl BlockHashProvider for MockEthProvider { } } -impl BlockNumProvider for MockEthProvider { +impl BlockNumReader for MockEthProvider { fn chain_info(&self) -> Result { let best_block_number = self.best_block_number()?; let lock = self.headers.lock(); @@ -293,7 +293,7 @@ impl BlockNumProvider for MockEthProvider { } } -impl BlockIdProvider for MockEthProvider { +impl BlockIdReader for MockEthProvider { fn pending_block_num_hash(&self) -> Result> { Ok(None) } @@ -307,7 +307,7 @@ impl BlockIdProvider for MockEthProvider { } } -impl BlockProvider for MockEthProvider { +impl BlockReader for MockEthProvider { fn find_block_by_hash(&self, hash: H256, _source: BlockSource) -> Result> { self.block(hash.into()) } @@ -337,7 +337,7 @@ impl BlockProvider for MockEthProvider { } } -impl BlockProviderIdExt for MockEthProvider { +impl BlockReaderIdExt for MockEthProvider { fn block_by_id(&self, id: BlockId) -> Result> { match id { BlockId::Number(num) => self.block_by_number_or_tag(num), diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 253e7982e89d..9e7f629fb414 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -1,9 +1,9 @@ use crate::{ traits::{BlockSource, ReceiptProvider}, - AccountReader, BlockHashProvider, BlockIdProvider, BlockNumProvider, BlockProvider, - BlockProviderIdExt, EvmEnvProvider, HeaderProvider, PostState, ReceiptProviderIdExt, - StageCheckpointReader, StateProvider, StateProviderBox, StateProviderFactory, - StateRootProvider, TransactionsProvider, WithdrawalsProvider, + AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, + EvmEnvProvider, HeaderProvider, PostState, ReceiptProviderIdExt, StageCheckpointReader, + StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, TransactionsProvider, + WithdrawalsProvider, }; use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::Result; @@ -22,7 +22,7 @@ use std::ops::RangeBounds; pub struct NoopProvider; /// Noop implementation for testing purposes -impl BlockHashProvider for NoopProvider { +impl BlockHashReader for NoopProvider { fn block_hash(&self, _number: u64) -> Result> { Ok(None) } @@ -32,7 +32,7 @@ impl BlockHashProvider for NoopProvider { } } -impl BlockNumProvider for NoopProvider { +impl BlockNumReader for NoopProvider { fn chain_info(&self) -> Result { Ok(ChainInfo::default()) } @@ -50,7 +50,7 @@ impl BlockNumProvider for NoopProvider { } } -impl BlockProvider for NoopProvider { +impl BlockReader for NoopProvider { fn find_block_by_hash(&self, hash: H256, _source: BlockSource) -> Result> { self.block(hash.into()) } @@ -79,7 +79,7 @@ impl BlockProvider for NoopProvider { } } -impl BlockProviderIdExt for NoopProvider { +impl BlockReaderIdExt for NoopProvider { fn block_by_id(&self, _id: BlockId) -> Result> { Ok(None) } @@ -97,7 +97,7 @@ impl BlockProviderIdExt for NoopProvider { } } -impl BlockIdProvider for NoopProvider { +impl BlockIdReader for NoopProvider { fn pending_block_num_hash(&self) -> Result> { Ok(None) } diff --git a/crates/storage/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index c8fbc16e8f3d..079e12a086be 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -1,5 +1,5 @@ use crate::{ - BlockIdProvider, BlockNumProvider, HeaderProvider, ReceiptProvider, ReceiptProviderIdExt, + BlockIdReader, BlockNumReader, HeaderProvider, ReceiptProvider, ReceiptProviderIdExt, TransactionsProvider, WithdrawalsProvider, }; use reth_db::models::StoredBlockBodyIndices; @@ -45,8 +45,8 @@ impl BlockSource { /// If not requested otherwise, implementers of this trait should prioritize fetching blocks from /// the database. #[auto_impl::auto_impl(&, Arc)] -pub trait BlockProvider: - BlockNumProvider +pub trait BlockReader: + BlockNumReader + HeaderProvider + TransactionsProvider + ReceiptProvider @@ -102,18 +102,18 @@ pub trait BlockProvider: fn block_with_senders(&self, number: BlockNumber) -> Result>; } -/// Trait extension for `BlockProvider`, for types that implement `BlockId` conversion. +/// Trait extension for `BlockReader`, for types that implement `BlockId` conversion. /// -/// The `BlockProvider` trait should be implemented on types that can retrieve a block from either +/// The `BlockReader` trait should be implemented on types that can retrieve a block from either /// a block number or hash. However, it might be desirable to fetch a block from a `BlockId` type, /// which can be a number, hash, or tag such as `BlockNumberOrTag::Safe`. /// /// Resolving tags requires keeping track of block hashes or block numbers associated with the tag, -/// so this trait can only be implemented for types that implement `BlockIdProvider`. The -/// `BlockIdProvider` methods should be used to resolve `BlockId`s to block numbers or hashes, and -/// retrieving the block should be done using the type's `BlockProvider` methods. +/// so this trait can only be implemented for types that implement `BlockIdReader`. The +/// `BlockIdReader` methods should be used to resolve `BlockId`s to block numbers or hashes, and +/// retrieving the block should be done using the type's `BlockReader` methods. #[auto_impl::auto_impl(&, Arc)] -pub trait BlockProviderIdExt: BlockProvider + BlockIdProvider + ReceiptProviderIdExt { +pub trait BlockReaderIdExt: BlockReader + BlockIdReader + ReceiptProviderIdExt { /// Returns the block with matching tag from the database /// /// Returns `None` if block is not found. diff --git a/crates/storage/provider/src/traits/block_hash.rs b/crates/storage/provider/src/traits/block_hash.rs index ed8249cdd5da..24cf932841af 100644 --- a/crates/storage/provider/src/traits/block_hash.rs +++ b/crates/storage/provider/src/traits/block_hash.rs @@ -4,7 +4,7 @@ use reth_primitives::{BlockHashOrNumber, BlockNumber, H256}; /// Client trait for fetching block hashes by number. #[auto_impl(&, Arc, Box)] -pub trait BlockHashProvider: Send + Sync { +pub trait BlockHashReader: Send + Sync { /// Get the hash of the block with the given number. Returns `None` if no block with this number /// exists. fn block_hash(&self, number: BlockNumber) -> Result>; diff --git a/crates/storage/provider/src/traits/block_id.rs b/crates/storage/provider/src/traits/block_id.rs index f83929acbc28..4fd3ae4a1fd7 100644 --- a/crates/storage/provider/src/traits/block_id.rs +++ b/crates/storage/provider/src/traits/block_id.rs @@ -1,4 +1,4 @@ -use super::BlockHashProvider; +use super::BlockHashReader; use reth_interfaces::Result; use reth_primitives::{BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, ChainInfo, H256}; @@ -7,7 +7,7 @@ use reth_primitives::{BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, /// /// This trait also supports fetching block hashes and block numbers from a [BlockHashOrNumber]. #[auto_impl::auto_impl(&, Arc)] -pub trait BlockNumProvider: BlockHashProvider + Send + Sync { +pub trait BlockNumReader: BlockHashReader + Send + Sync { /// Returns the current info for the chain. fn chain_info(&self) -> Result; @@ -46,10 +46,10 @@ pub trait BlockNumProvider: BlockHashProvider + Send + Sync { /// are provided if the type implements the `pending_block_num_hash`, `finalized_block_num`, and /// `safe_block_num` methods. /// -/// The resulting block numbers can be converted to hashes using the underlying [BlockNumProvider] +/// The resulting block numbers can be converted to hashes using the underlying [BlockNumReader] /// methods, and vice versa. #[auto_impl::auto_impl(&, Arc)] -pub trait BlockIdProvider: BlockNumProvider + Send + Sync { +pub trait BlockIdReader: BlockNumReader + Send + Sync { /// Converts the `BlockNumberOrTag` variants to a block number. fn convert_block_number(&self, num: BlockNumberOrTag) -> Result> { let num = match num { diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index 81ad694a5733..88b7358f4ba1 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -7,13 +7,13 @@ mod storage; pub use storage::StorageReader; mod block; -pub use block::{BlockProvider, BlockProviderIdExt, BlockSource}; +pub use block::{BlockReader, BlockReaderIdExt, BlockSource}; mod block_hash; -pub use block_hash::BlockHashProvider; +pub use block_hash::BlockHashReader; mod block_id; -pub use block_id::{BlockIdProvider, BlockNumProvider}; +pub use block_id::{BlockIdReader, BlockNumReader}; mod evm_env; pub use evm_env::EvmEnvProvider; diff --git a/crates/storage/provider/src/traits/receipts.rs b/crates/storage/provider/src/traits/receipts.rs index 3192679f6720..3166b620804a 100644 --- a/crates/storage/provider/src/traits/receipts.rs +++ b/crates/storage/provider/src/traits/receipts.rs @@ -1,7 +1,7 @@ use reth_interfaces::Result; use reth_primitives::{BlockHashOrNumber, BlockId, BlockNumberOrTag, Receipt, TxHash, TxNumber}; -use crate::BlockIdProvider; +use crate::BlockIdReader; /// Client trait for fetching [Receipt] data . #[auto_impl::auto_impl(&, Arc)] @@ -29,10 +29,10 @@ pub trait ReceiptProvider: Send + Sync { /// which can be a number, hash, or tag such as `BlockNumberOrTag::Safe`. /// /// Resolving tags requires keeping track of block hashes or block numbers associated with the tag, -/// so this trait can only be implemented for types that implement `BlockIdProvider`. The -/// `BlockIdProvider` methods should be used to resolve `BlockId`s to block numbers or hashes, and +/// so this trait can only be implemented for types that implement `BlockIdReader`. The +/// `BlockIdReader` methods should be used to resolve `BlockId`s to block numbers or hashes, and /// retrieving the receipts should be done using the type's `ReceiptProvider` methods. -pub trait ReceiptProviderIdExt: ReceiptProvider + BlockIdProvider { +pub trait ReceiptProviderIdExt: ReceiptProvider + BlockIdReader { /// Get receipt by block id fn receipts_by_block_id(&self, block: BlockId) -> Result>> { let id = match block { diff --git a/crates/storage/provider/src/traits/state.rs b/crates/storage/provider/src/traits/state.rs index 5e9dca9959f0..e09b135223d8 100644 --- a/crates/storage/provider/src/traits/state.rs +++ b/crates/storage/provider/src/traits/state.rs @@ -1,5 +1,5 @@ use super::AccountReader; -use crate::{post_state::PostState, BlockHashProvider, BlockIdProvider}; +use crate::{post_state::PostState, BlockHashReader, BlockIdReader}; use auto_impl::auto_impl; use reth_interfaces::{provider::ProviderError, Result}; use reth_primitives::{ @@ -12,9 +12,7 @@ pub type StateProviderBox<'a> = Box; /// An abstraction for a type that provides state data. #[auto_impl(&, Arc, Box)] -pub trait StateProvider: - BlockHashProvider + AccountReader + StateRootProvider + Send + Sync -{ +pub trait StateProvider: BlockHashReader + AccountReader + StateRootProvider + Send + Sync { /// Get storage of given account. fn storage(&self, account: Address, storage_key: StorageKey) -> Result>; @@ -96,7 +94,7 @@ pub trait StateProvider: /// This affects tracing, or replaying blocks, which will need to be executed on top of the state of /// the parent block. For example, in order to trace block `n`, the state after block `n - 1` needs /// to be used, since block `n` was executed on its parent block's state. -pub trait StateProviderFactory: BlockIdProvider + Send + Sync { +pub trait StateProviderFactory: BlockIdReader + Send + Sync { /// Storage provider for latest block. fn latest(&self) -> Result>; diff --git a/crates/storage/provider/src/traits/transactions.rs b/crates/storage/provider/src/traits/transactions.rs index e53a03cbf375..6bbd17454443 100644 --- a/crates/storage/provider/src/traits/transactions.rs +++ b/crates/storage/provider/src/traits/transactions.rs @@ -1,4 +1,4 @@ -use crate::BlockNumProvider; +use crate::BlockNumReader; use reth_interfaces::Result; use reth_primitives::{ Address, BlockHashOrNumber, BlockNumber, TransactionMeta, TransactionSigned, @@ -8,7 +8,7 @@ use std::ops::RangeBounds; /// Client trait for fetching [TransactionSigned] related data. #[auto_impl::auto_impl(&, Arc)] -pub trait TransactionsProvider: BlockNumProvider + Send + Sync { +pub trait TransactionsProvider: BlockNumReader + Send + Sync { /// Get internal transaction identifier by transaction hash. /// /// This is the inverse of [TransactionsProvider::transaction_by_id]. diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 5805e715ae8a..8fd87e0669bb 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -6,7 +6,7 @@ use crate::{ }; use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt}; use reth_primitives::{Address, BlockHash, BlockNumberOrTag, FromRecoveredTransaction}; -use reth_provider::{BlockProviderIdExt, CanonStateNotification, PostState, StateProviderFactory}; +use reth_provider::{BlockReaderIdExt, CanonStateNotification, PostState, StateProviderFactory}; use std::{ borrow::Borrow, collections::HashSet, @@ -25,7 +25,7 @@ pub fn maintain_transaction_pool_future( events: St, ) -> BoxFuture<'static, ()> where - Client: StateProviderFactory + BlockProviderIdExt + Send + 'static, + Client: StateProviderFactory + BlockReaderIdExt + Send + 'static, V: TransactionValidator + Send + 'static, T: TransactionOrdering::Transaction> + Send + 'static, St: Stream + Send + Unpin + 'static, @@ -45,7 +45,7 @@ pub async fn maintain_transaction_pool( pool: Pool, mut events: St, ) where - Client: StateProviderFactory + BlockProviderIdExt + Send + 'static, + Client: StateProviderFactory + BlockReaderIdExt + Send + 'static, V: TransactionValidator + Send + 'static, T: TransactionOrdering::Transaction> + Send + 'static, St: Stream + Send + Unpin + 'static, diff --git a/docs/crates/discv4.md b/docs/crates/discv4.md index 083ea1b170ef..fe37cf463db2 100644 --- a/docs/crates/discv4.md +++ b/docs/crates/discv4.md @@ -26,7 +26,7 @@ During this process, a new `NetworkManager` is created through the `NetworkManag ```rust ignore impl NetworkManager where - C: BlockProvider, + C: BlockReader, { //--snip-- @@ -267,7 +267,7 @@ In Reth, once a new `NetworkState` is initialized as the node starts up and a ne ```rust ignore impl NetworkState where - C: BlockProvider, + C: BlockReader, { /// Advances the state pub(crate) fn poll(&mut self, cx: &mut Context<'_>) -> Poll { diff --git a/docs/crates/network.md b/docs/crates/network.md index c67b7b6ee87c..f78a5f5d33b0 100644 --- a/docs/crates/network.md +++ b/docs/crates/network.md @@ -88,7 +88,7 @@ Let's begin by taking a look at the line where the network is started, with the ```rust,ignore async fn start_network(config: NetworkConfig) -> Result where - C: BlockProvider + HeaderProvider + 'static, + C: BlockReader + HeaderProvider + 'static, { let client = config.client.clone(); let (handle, network, _txpool, eth) = From 49922bfd897a16b3750db4c20aa0f40b073d6f05 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Mon, 26 Jun 2023 15:18:33 +0100 Subject: [PATCH 183/216] feat(revm): store access list storage keys sorted (#3386) --- crates/revm/revm-inspectors/src/access_list.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/revm/revm-inspectors/src/access_list.rs b/crates/revm/revm-inspectors/src/access_list.rs index 5aa7ef32e94b..873ab3ebc47e 100644 --- a/crates/revm/revm-inspectors/src/access_list.rs +++ b/crates/revm/revm-inspectors/src/access_list.rs @@ -4,6 +4,7 @@ use revm::{ interpreter::{opcode, InstructionResult, Interpreter}, Database, EVMData, Inspector, }; +use std::collections::BTreeSet; /// An [Inspector] that collects touched accounts and storage slots. /// @@ -13,7 +14,7 @@ pub struct AccessListInspector { /// All addresses that should be excluded from the final accesslist excluded: HashSet

, /// All addresses and touched slots - access_list: HashMap>, + access_list: HashMap>, } impl AccessListInspector { From 23a99d41643d3a432cae0f717c90451738ec2d51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Vincent?= <28714795+leovct@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:17:05 +0200 Subject: [PATCH 184/216] feat: support multiple nodes in grafana dashboard (#3336) --- etc/grafana/dashboards/overview.json | 353 ++++++++++++++++----------- 1 file changed, 207 insertions(+), 146 deletions(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index f6fcec21d147..5a7824ed943a 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -27,7 +27,7 @@ "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "9.5.2" + "version": "10.0.0" }, { "type": "panel", @@ -89,6 +89,20 @@ "links": [], "liveNow": false, "panels": [ + { + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 96, + "panels": [], + "repeat": "instance", + "repeatDirection": "h", + "title": "Overview ($instance)", + "type": "row" + }, { "datasource": { "type": "prometheus", @@ -130,7 +144,7 @@ "h": 8, "w": 12, "x": 0, - "y": 0 + "y": 1 }, "id": 22, "options": { @@ -145,7 +159,7 @@ "showThresholdLabels": false, "showThresholdMarkers": true }, - "pluginVersion": "9.5.2", + "pluginVersion": "10.0.0", "targets": [ { "datasource": { @@ -154,7 +168,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_network_connected_peers", + "expr": "reth_network_connected_peers{instance=~\"$instance\"}", "instant": true, "legendFormat": "__auto", "range": false, @@ -194,7 +208,7 @@ "h": 8, "w": 12, "x": 12, - "y": 0 + "y": 1 }, "id": 20, "options": { @@ -212,7 +226,7 @@ "showUnfilled": true, "valueMode": "color" }, - "pluginVersion": "9.5.2", + "pluginVersion": "10.0.0", "targets": [ { "datasource": { @@ -221,7 +235,7 @@ }, "editorMode": "builder", "exemplar": false, - "expr": "reth_sync_checkpoint", + "expr": "reth_sync_checkpoint{instance=~\"$instance\"}", "instant": true, "legendFormat": "{{stage}}", "range": false, @@ -292,7 +306,7 @@ "h": 8, "w": 12, "x": 0, - "y": 8 + "y": 9 }, "id": 69, "options": { @@ -314,7 +328,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_sync_entities_processed / reth_sync_entities_total", + "expr": "reth_sync_entities_processed{instance=~\"$instance\"} / reth_sync_entities_total{instance=~\"$instance\"}", "legendFormat": "{{stage}}", "range": true, "refId": "A" @@ -384,7 +398,7 @@ "h": 8, "w": 12, "x": 12, - "y": 8 + "y": 9 }, "id": 12, "options": { @@ -406,7 +420,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_sync_checkpoint", + "expr": "reth_sync_checkpoint{instance=~\"$instance\"}", "legendFormat": "{{stage}}", "range": true, "refId": "A" @@ -421,11 +435,13 @@ "h": 1, "w": 24, "x": 0, - "y": 16 + "y": 34 }, "id": 38, "panels": [], - "title": "Database", + "repeat": "instance", + "repeatDirection": "h", + "title": "Database ($instance)", "type": "row" }, { @@ -489,7 +505,7 @@ "h": 8, "w": 12, "x": 0, - "y": 17 + "y": 35 }, "id": 40, "options": { @@ -513,7 +529,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "rate(reth_tx_commit_sum[$__rate_interval]) / rate(reth_tx_commit_count[$__rate_interval])", + "expr": "rate(reth_tx_commit_sum{instance=~\"$instance\"}[$__rate_interval]) / rate(reth_tx_commit_count{instance=~\"$instance\"}[$__rate_interval])", "format": "time_series", "instant": false, "legendFormat": "Commit time", @@ -549,7 +565,7 @@ "h": 8, "w": 12, "x": 12, - "y": 17 + "y": 35 }, "id": 42, "maxDataPoints": 25, @@ -593,7 +609,7 @@ "unit": "percentunit" } }, - "pluginVersion": "9.5.2", + "pluginVersion": "10.0.0", "targets": [ { "datasource": { @@ -602,7 +618,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sum(increase(reth_tx_commit[$__interval])) by (quantile)", + "expr": "sum(increase(reth_tx_commit{instance=~\"$instance\"}[$__interval])) by (quantile)", "format": "time_series", "instant": false, "legendFormat": "{{quantile}}", @@ -640,7 +656,7 @@ "h": 8, "w": 12, "x": 0, - "y": 25 + "y": 43 }, "id": 48, "options": { @@ -676,7 +692,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_db_table_size", + "expr": "reth_db_table_size{instance=~\"$instance\"}", "interval": "", "legendFormat": "{{table}}", "range": true, @@ -749,7 +765,7 @@ "h": 8, "w": 12, "x": 12, - "y": 25 + "y": 43 }, "id": 52, "options": { @@ -771,7 +787,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "sum by (job) ( reth_db_table_size )", + "expr": "sum by (job) ( reth_db_table_size{instance=~\"$instance\"} )", "legendFormat": "Size ({{job}})", "range": true, "refId": "A" @@ -807,7 +823,7 @@ "h": 8, "w": 12, "x": 0, - "y": 33 + "y": 51 }, "id": 50, "options": { @@ -839,7 +855,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "sum by (type) ( reth_db_table_pages )", + "expr": "sum by (type) ( reth_db_table_pages{instance=~\"$instance\"} )", "legendFormat": "__auto", "range": true, "refId": "A" @@ -975,7 +991,7 @@ "h": 8, "w": 12, "x": 12, - "y": 33 + "y": 51 }, "id": 58, "options": { @@ -990,7 +1006,7 @@ }, "showHeader": true }, - "pluginVersion": "9.5.2", + "pluginVersion": "10.0.0", "targets": [ { "datasource": { @@ -999,7 +1015,7 @@ }, "editorMode": "code", "exemplar": false, - "expr": "sort_desc(reth_db_table_pages{type=\"overflow\"} != 0)", + "expr": "sort_desc(reth_db_table_pages{instance=~\"$instance\", type=\"overflow\"} != 0)", "format": "table", "instant": true, "legendFormat": "__auto", @@ -1016,11 +1032,13 @@ "h": 1, "w": 24, "x": 0, - "y": 41 + "y": 84 }, "id": 46, "panels": [], - "title": "Stage: Execution", + "repeat": "instance", + "repeatDirection": "h", + "title": "Stage: Execution ($instance)", "type": "row" }, { @@ -1082,7 +1100,7 @@ "h": 8, "w": 24, "x": 0, - "y": 42 + "y": 85 }, "id": 56, "options": { @@ -1104,7 +1122,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(reth_sync_execution_mgas_processed_total[30s])", + "expr": "rate(reth_sync_execution_mgas_processed_total{instance=~\"$instance\"}[30s])", "legendFormat": "Gas/s (30s)", "range": true, "refId": "A" @@ -1115,7 +1133,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_sync_execution_mgas_processed_total[1m])", + "expr": "rate(reth_sync_execution_mgas_processed_total{instance=~\"$instance\"}[1m])", "hide": false, "legendFormat": "Gas/s (1m)", "range": true, @@ -1127,7 +1145,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_sync_execution_mgas_processed_total[5m])", + "expr": "rate(reth_sync_execution_mgas_processed_total{instance=~\"$instance\"}[5m])", "hide": false, "legendFormat": "Gas/s (5m)", "range": true, @@ -1139,7 +1157,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_sync_execution_mgas_processed_total[10m])", + "expr": "rate(reth_sync_execution_mgas_processed_total{instance=~\"$instance\"}[10m])", "hide": false, "legendFormat": "Gas/s (10m)", "range": true, @@ -1155,11 +1173,13 @@ "h": 1, "w": 24, "x": 0, - "y": 50 + "y": 102 }, "id": 6, "panels": [], - "title": "Networking", + "repeat": "instance", + "repeatDirection": "h", + "title": "Networking ($instance)", "type": "row" }, { @@ -1224,7 +1244,7 @@ "h": 8, "w": 8, "x": 0, - "y": 51 + "y": 103 }, "id": 18, "options": { @@ -1246,7 +1266,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_tracked_peers", + "expr": "reth_network_tracked_peers{instance=~\"$instance\"}", "legendFormat": "Tracked peers", "range": true, "refId": "A" @@ -1317,7 +1337,7 @@ "h": 8, "w": 8, "x": 8, - "y": 51 + "y": 103 }, "id": 16, "options": { @@ -1339,7 +1359,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_outgoing_connections", + "expr": "reth_network_outgoing_connections{instance=~\"$instance\"}", "legendFormat": "Outgoing connections", "range": true, "refId": "A" @@ -1350,7 +1370,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_incoming_connections", + "expr": "reth_network_incoming_connections{instance=~\"$instance\"}", "hide": false, "legendFormat": "Incoming connections", "range": true, @@ -1362,7 +1382,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_connected_peers", + "expr": "reth_network_connected_peers{instance=~\"$instance\"}", "hide": false, "legendFormat": "Connected peers", "range": true, @@ -1435,7 +1455,7 @@ "h": 8, "w": 8, "x": 16, - "y": 51 + "y": 103 }, "id": 8, "options": { @@ -1460,7 +1480,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_p2pstream_disconnected_errors", + "expr": "reth_p2pstream_disconnected_errors{instance=~\"$instance\"}", "legendFormat": "P2P stream disconnected", "range": true, "refId": "A" @@ -1471,7 +1491,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_pending_session_failures", + "expr": "reth_network_pending_session_failures{instance=~\"$instance\"}", "hide": false, "legendFormat": "Failed pending sessions", "range": true, @@ -1483,7 +1503,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_invalid_messages_received", + "expr": "reth_network_invalid_messages_received{instance=~\"$instance\"}", "hide": false, "legendFormat": "Invalid messages", "range": true, @@ -1514,7 +1534,7 @@ "h": 8, "w": 8, "x": 0, - "y": 59 + "y": 111 }, "id": 54, "options": { @@ -1543,7 +1563,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_useless_peer", + "expr": "reth_network_useless_peer{instance=~\"$instance\"}", "legendFormat": "UselessPeer", "range": true, "refId": "A" @@ -1554,7 +1574,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_subprotocol_specific", + "expr": "reth_network_subprotocol_specific{instance=~\"$instance\"}", "hide": false, "legendFormat": "SubprotocolSpecific", "range": true, @@ -1566,7 +1586,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_already_connected", + "expr": "reth_network_already_connected{instance=~\"$instance\"}", "hide": false, "legendFormat": "AlreadyConnected", "range": true, @@ -1578,7 +1598,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_client_quitting", + "expr": "reth_network_client_quitting{instance=~\"$instance\"}", "hide": false, "legendFormat": "ClientQuitting", "range": true, @@ -1590,7 +1610,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_unexpected_identity", + "expr": "reth_network_unexpected_identity{instance=~\"$instance\"}", "hide": false, "legendFormat": "UnexpectedHandshakeIdentity", "range": true, @@ -1602,7 +1622,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_disconnect_requested", + "expr": "reth_network_disconnect_requested{instance=~\"$instance\"}", "hide": false, "legendFormat": "DisconnectRequested", "range": true, @@ -1614,7 +1634,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_null_node_identity", + "expr": "reth_network_null_node_identity{instance=~\"$instance\"}", "hide": false, "legendFormat": "NullNodeIdentity", "range": true, @@ -1626,7 +1646,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_tcp_subsystem_error", + "expr": "reth_network_tcp_subsystem_error{instance=~\"$instance\"}", "hide": false, "legendFormat": "TCPSubsystemError", "range": true, @@ -1638,7 +1658,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_incompatible", + "expr": "reth_network_incompatible{instance=~\"$instance\"}", "hide": false, "legendFormat": "IncompatibleP2PVersion", "range": true, @@ -1650,7 +1670,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_protocol_breach", + "expr": "reth_network_protocol_breach{instance=~\"$instance\"}", "hide": false, "legendFormat": "ProtocolBreach", "range": true, @@ -1662,7 +1682,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_too_many_peers", + "expr": "reth_network_too_many_peers{instance=~\"$instance\"}", "hide": false, "legendFormat": "TooManyPeers", "range": true, @@ -1678,11 +1698,13 @@ "h": 1, "w": 24, "x": 0, - "y": 67 + "y": 136 }, "id": 24, "panels": [], - "title": "Downloader: Headers", + "repeat": "instance", + "repeatDirection": "h", + "title": "Downloader: Headers ($instance)", "type": "row" }, { @@ -1730,7 +1752,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -1770,7 +1793,7 @@ "h": 8, "w": 12, "x": 0, - "y": 68 + "y": 137 }, "id": 26, "options": { @@ -1792,7 +1815,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_total_downloaded", + "expr": "reth_downloaders_headers_total_downloaded{instance=~\"$instance\"}", "legendFormat": "Downloaded", "range": true, "refId": "A" @@ -1803,7 +1826,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_total_flushed", + "expr": "reth_downloaders_headers_total_flushed{instance=~\"$instance\"}", "hide": false, "legendFormat": "Flushed", "range": true, @@ -1815,7 +1838,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_headers_total_downloaded[$__rate_interval])", + "expr": "rate(reth_downloaders_headers_total_downloaded{instance=~\"$instance\"}[$__rate_interval])", "hide": false, "instant": false, "legendFormat": "Downloaded/s", @@ -1828,7 +1851,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_headers_total_flushed[$__rate_interval])", + "expr": "rate(reth_downloaders_headers_total_flushed{instance=~\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Flushed/s", "range": true, @@ -1884,7 +1907,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -1899,7 +1923,7 @@ "h": 8, "w": 12, "x": 12, - "y": 68 + "y": 137 }, "id": 33, "options": { @@ -1921,7 +1945,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_timeout_errors", + "expr": "reth_downloaders_headers_timeout_errors{instance=~\"$instance\"}", "legendFormat": "Request timed out", "range": true, "refId": "A" @@ -1932,7 +1956,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_unexpected_errors", + "expr": "reth_downloaders_headers_unexpected_errors{instance=~\"$instance\"}", "hide": false, "legendFormat": "Unexpected error", "range": true, @@ -1944,7 +1968,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_validation_errors", + "expr": "reth_downloaders_headers_validation_errors{instance=~\"$instance\"}", "hide": false, "legendFormat": "Invalid response", "range": true, @@ -2000,7 +2024,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -2015,7 +2040,7 @@ "h": 8, "w": 12, "x": 0, - "y": 76 + "y": 145 }, "id": 36, "options": { @@ -2037,7 +2062,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_headers_in_flight_requests", + "expr": "reth_downloaders_headers_in_flight_requests{instance=~\"$instance\"}", "legendFormat": "In flight requests", "range": true, "refId": "A" @@ -2048,7 +2073,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_connected_peers", + "expr": "reth_network_connected_peers{instance=~\"$instance\"}", "hide": false, "legendFormat": "Connected peers", "range": true, @@ -2064,11 +2089,13 @@ "h": 1, "w": 24, "x": 0, - "y": 84 + "y": 170 }, "id": 32, "panels": [], - "title": "Downloader: Bodies", + "repeat": "instance", + "repeatDirection": "h", + "title": "Downloader: Bodies ($instance)", "type": "row" }, { @@ -2117,7 +2144,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -2157,7 +2185,7 @@ "h": 8, "w": 12, "x": 0, - "y": 85 + "y": 171 }, "id": 30, "options": { @@ -2179,7 +2207,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_total_downloaded", + "expr": "reth_downloaders_bodies_total_downloaded{instance=~\"$instance\"}", "legendFormat": "Downloaded", "range": true, "refId": "A" @@ -2190,7 +2218,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_total_flushed", + "expr": "reth_downloaders_bodies_total_flushed{instance=~\"$instance\"}", "hide": false, "legendFormat": "Flushed", "range": true, @@ -2202,7 +2230,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_bodies_total_flushed[$__rate_interval])", + "expr": "rate(reth_downloaders_bodies_total_flushed{instance=~\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Flushed/s", "range": true, @@ -2214,7 +2242,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "rate(reth_downloaders_bodies_total_downloaded[$__rate_interval])", + "expr": "rate(reth_downloaders_bodies_total_downloaded{instance=~\"$instance\"}[$__rate_interval])", "hide": false, "legendFormat": "Downloaded/s", "range": true, @@ -2226,7 +2254,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_responses", + "expr": "reth_downloaders_bodies_buffered_responses{instance=~\"$instance\"}", "hide": false, "legendFormat": "Buffered responses", "range": true, @@ -2238,7 +2266,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_blocks", + "expr": "reth_downloaders_bodies_buffered_blocks{instance=~\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks", "range": true, @@ -2250,7 +2278,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_queued_blocks", + "expr": "reth_downloaders_bodies_queued_blocks{instance=~\"$instance\"}", "hide": false, "legendFormat": "Queued blocks", "range": true, @@ -2307,7 +2335,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null } ] } @@ -2318,7 +2347,7 @@ "h": 8, "w": 12, "x": 12, - "y": 85 + "y": 171 }, "id": 28, "options": { @@ -2340,7 +2369,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_timeout_errors", + "expr": "reth_downloaders_bodies_timeout_errors{instance=~\"$instance\"}", "legendFormat": "Request timed out", "range": true, "refId": "A" @@ -2351,7 +2380,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_unexpected_errors", + "expr": "reth_downloaders_bodies_unexpected_errors{instance=~\"$instance\"}", "hide": false, "legendFormat": "Unexpected error", "range": true, @@ -2363,7 +2392,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_validation_errors", + "expr": "reth_downloaders_bodies_validation_errors{instance=~\"$instance\"}", "hide": false, "legendFormat": "Invalid response", "range": true, @@ -2419,7 +2448,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": null }, { "color": "red", @@ -2434,7 +2464,7 @@ "h": 8, "w": 12, "x": 0, - "y": 93 + "y": 179 }, "id": 35, "options": { @@ -2456,7 +2486,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_in_flight_requests", + "expr": "reth_downloaders_bodies_in_flight_requests{instance=~\"$instance\"}", "legendFormat": "In flight requests", "range": true, "refId": "A" @@ -2467,7 +2497,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_connected_peers", + "expr": "reth_network_connected_peers{instance=~\"$instance\"}", "hide": false, "legendFormat": "Connected peers", "range": true, @@ -2552,7 +2582,7 @@ "h": 8, "w": 12, "x": 12, - "y": 93 + "y": 179 }, "id": 73, "options": { @@ -2574,7 +2604,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_blocks_size_bytes", + "expr": "reth_downloaders_bodies_buffered_blocks_size_bytes{instance=~\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks size (bytes)", "range": true, @@ -2586,7 +2616,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_downloaders_bodies_buffered_blocks", + "expr": "reth_downloaders_bodies_buffered_blocks{instance=~\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks", "range": true, @@ -2602,11 +2632,13 @@ "h": 1, "w": 24, "x": 0, - "y": 101 + "y": 204 }, "id": 89, "panels": [], - "title": "Transaction Pool", + "repeat": "instance", + "repeatDirection": "h", + "title": "Transaction Pool ($instance)", "type": "row" }, { @@ -2671,7 +2703,7 @@ "h": 8, "w": 12, "x": 0, - "y": 102 + "y": 205 }, "id": 91, "options": { @@ -2693,7 +2725,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_basefee_pool_size_bytes", + "expr": "reth_transaction_pool_basefee_pool_size_bytes{instance=~\"$instance\"}", "legendFormat": "Base fee pool size (bytes)", "range": true, "refId": "A" @@ -2704,7 +2736,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_pending_pool_size_bytes", + "expr": "reth_transaction_pool_pending_pool_size_bytes{instance=~\"$instance\"}", "hide": false, "legendFormat": "Pending pool size (bytes)", "range": true, @@ -2716,7 +2748,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_queued_pool_size_bytes", + "expr": "reth_transaction_pool_queued_pool_size_bytes{instance=~\"$instance\"}", "hide": false, "legendFormat": "Queued pool size (bytes)", "range": true, @@ -2788,7 +2820,7 @@ "h": 8, "w": 12, "x": 12, - "y": 102 + "y": 205 }, "id": 92, "options": { @@ -2810,7 +2842,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_basefee_pool_transactions", + "expr": "reth_transaction_pool_basefee_pool_transactions{instance=~\"$instance\"}", "legendFormat": "Base fee pool transactions", "range": true, "refId": "A" @@ -2821,7 +2853,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_pending_pool_transactions", + "expr": "reth_transaction_pool_pending_pool_transactions{instance=~\"$instance\"}", "hide": false, "legendFormat": "Pending pool transactions", "range": true, @@ -2833,7 +2865,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_queued_pool_transactions", + "expr": "reth_transaction_pool_queued_pool_transactions{instance=~\"$instance\"}", "hide": false, "legendFormat": "Queued pool transactions", "range": true, @@ -2905,7 +2937,7 @@ "h": 8, "w": 12, "x": 0, - "y": 110 + "y": 213 }, "id": 93, "options": { @@ -2927,7 +2959,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_inserted_transactions", + "expr": "reth_transaction_pool_inserted_transactions{instance=~\"$instance\"}", "legendFormat": "Inserted transactions", "range": true, "refId": "A" @@ -2938,7 +2970,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_removed_transactions", + "expr": "reth_transaction_pool_removed_transactions{instance=~\"$instance\"}", "hide": false, "legendFormat": "Removed transactions", "range": true, @@ -2950,7 +2982,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_transaction_pool_invalid_transactions", + "expr": "reth_transaction_pool_invalid_transactions{instance=~\"$instance\"}", "hide": false, "legendFormat": "Invalid transactions", "range": true, @@ -3022,7 +3054,7 @@ "h": 8, "w": 12, "x": 12, - "y": 110 + "y": 213 }, "id": 94, "options": { @@ -3044,7 +3076,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_network_pending_pool_imports", + "expr": "reth_network_pending_pool_imports{instance=~\"$instance\"}", "hide": false, "legendFormat": "Transactions pending import", "range": true, @@ -3116,7 +3148,7 @@ "h": 8, "w": 12, "x": 0, - "y": 118 + "y": 221 }, "id": 95, "options": { @@ -3138,7 +3170,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_network_pool_transactions_messages_sent - reth_network_pool_transactions_messages_received", + "expr": "reth_network_pool_transactions_messages_sent{instance=~\"$instance\"} - reth_network_pool_transactions_messages_received{instance=~\"$instance\"}", "hide": false, "instant": false, "legendFormat": "Total events in the channel", @@ -3155,17 +3187,19 @@ "h": 1, "w": 24, "x": 0, - "y": 126 + "y": 254 }, "id": 79, "panels": [], - "title": "Blockchain tree", + "repeat": "instance", + "repeatDirection": "h", + "title": "Blockchain Tree ($instance)", "type": "row" }, { "datasource": { "type": "prometheus", - "uid": "Prometheus" + "uid": "${DS_PROMETHEUS}" }, "description": "The block number of the tip of the canonical chain from the blockchain tree.", "fieldConfig": { @@ -3224,7 +3258,7 @@ "h": 8, "w": 12, "x": 0, - "y": 127 + "y": 255 }, "id": 74, "options": { @@ -3243,10 +3277,10 @@ { "datasource": { "type": "prometheus", - "uid": "Prometheus" + "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_blockchain_tree_canonical_chain_height", + "expr": "reth_blockchain_tree_canonical_chain_height{instance=~\"$instance\"}", "hide": false, "legendFormat": "Canonical chain height", "range": true, @@ -3259,7 +3293,7 @@ { "datasource": { "type": "prometheus", - "uid": "Prometheus" + "uid": "${DS_PROMETHEUS}" }, "description": "Total number of blocks in the tree's block buffer", "fieldConfig": { @@ -3318,7 +3352,7 @@ "h": 8, "w": 12, "x": 12, - "y": 127 + "y": 255 }, "id": 80, "options": { @@ -3337,10 +3371,10 @@ { "datasource": { "type": "prometheus", - "uid": "Prometheus" + "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_blockchain_tree_block_buffer_blocks", + "expr": "reth_blockchain_tree_block_buffer_blocks{instance=~\"$instance\"}", "hide": false, "legendFormat": "Buffered blocks", "range": true, @@ -3353,7 +3387,7 @@ { "datasource": { "type": "prometheus", - "uid": "Prometheus" + "uid": "${DS_PROMETHEUS}" }, "description": "Total number of sidechains in the blockchain tree", "fieldConfig": { @@ -3412,7 +3446,7 @@ "h": 8, "w": 12, "x": 0, - "y": 135 + "y": 263 }, "id": 81, "options": { @@ -3431,10 +3465,10 @@ { "datasource": { "type": "prometheus", - "uid": "Prometheus" + "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_blockchain_tree_sidechains", + "expr": "reth_blockchain_tree_sidechains{instance=~\"$instance\"}", "hide": false, "legendFormat": "Total number of sidechains", "range": true, @@ -3450,11 +3484,13 @@ "h": 1, "w": 24, "x": 0, - "y": 143 + "y": 288 }, "id": 87, "panels": [], - "title": "Engine API", + "repeat": "instance", + "repeatDirection": "h", + "title": "Engine API ($instance)", "type": "row" }, { @@ -3519,7 +3555,7 @@ "h": 8, "w": 12, "x": 0, - "y": 144 + "y": 289 }, "id": 83, "options": { @@ -3541,7 +3577,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_consensus_engine_beacon_active_block_downloads", + "expr": "reth_consensus_engine_beacon_active_block_downloads{instance=~\"$instance\"}", "legendFormat": "Active block downloads", "range": true, "refId": "A" @@ -3612,7 +3648,7 @@ "h": 8, "w": 12, "x": 12, - "y": 144 + "y": 289 }, "id": 84, "options": { @@ -3634,7 +3670,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_consensus_engine_beacon_forkchoice_updated_messages", + "expr": "reth_consensus_engine_beacon_forkchoice_updated_messages{instance=~\"$instance\"}", "legendFormat": "Forkchoice updated messages", "range": true, "refId": "A" @@ -3645,7 +3681,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_consensus_engine_beacon_new_payload_messages", + "expr": "reth_consensus_engine_beacon_new_payload_messages{instance=~\"$instance\"}", "hide": false, "legendFormat": "New payload messages", "range": true, @@ -3717,7 +3753,7 @@ "h": 8, "w": 12, "x": 0, - "y": 152 + "y": 297 }, "id": 85, "options": { @@ -3739,7 +3775,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_consensus_engine_beacon_pipeline_runs", + "expr": "reth_consensus_engine_beacon_pipeline_runs{instance=~\"$instance\"}", "legendFormat": "Pipeline runs", "range": true, "refId": "A" @@ -3754,7 +3790,7 @@ "h": 1, "w": 24, "x": 0, - "y": 160 + "y": 322 }, "id": 68, "panels": [ @@ -3841,7 +3877,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_payloads_active_jobs", + "expr": "reth_payloads_active_jobs{instance=~\"$instance\"}", "legendFormat": "Active Jobs", "range": true, "refId": "A" @@ -3933,7 +3969,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_payloads_initiated_jobs", + "expr": "reth_payloads_initiated_jobs{instance=~\"$instance\"}", "legendFormat": "Initiated Jobs", "range": true, "refId": "A" @@ -4025,7 +4061,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "builder", - "expr": "reth_payloads_failed_jobs", + "expr": "reth_payloads_failed_jobs{instance=~\"$instance\"}", "legendFormat": "Failed Jobs", "range": true, "refId": "A" @@ -4035,7 +4071,9 @@ "type": "timeseries" } ], - "title": "Payload Builder", + "repeat": "instance", + "repeatDirection": "h", + "title": "Payload Builder ($instance)", "type": "row" } ], @@ -4045,7 +4083,30 @@ "style": "dark", "tags": [], "templating": { - "list": [] + "list": [ + { + "current": {}, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "definition": "query_result(up)", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "instance", + "options": [], + "query": { + "query": "query_result(up)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "/.*instance=\\\"([^\\\"]*).*/", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] }, "time": { "from": "now-1h", @@ -4055,6 +4116,6 @@ "timezone": "", "title": "reth", "uid": "2k8BXz24x", - "version": 6, + "version": 7, "weekStart": "" } \ No newline at end of file From 0096739dbb192b419e1a3aa89d34c202c7a554af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Vincent?= <28714795+leovct@users.noreply.github.com> Date: Mon, 26 Jun 2023 17:41:11 +0200 Subject: [PATCH 185/216] doc: add reth logo to docs (#3317) Co-authored-by: Oliver Nordbjerg --- assets/reth-docs.png | Bin 0 -> 8251 bytes crates/blockchain-tree/src/lib.rs | 6 ++++++ crates/config/src/lib.rs | 6 ++++++ crates/consensus/auto-seal/src/lib.rs | 6 ++++++ crates/consensus/beacon/src/lib.rs | 6 ++++++ crates/consensus/common/src/lib.rs | 6 ++++++ crates/interfaces/src/lib.rs | 6 ++++++ crates/metrics/metrics-derive/src/lib.rs | 6 ++++++ crates/metrics/src/lib.rs | 6 ++++++ crates/net/common/src/lib.rs | 6 ++++++ crates/net/discv4/src/lib.rs | 6 ++++++ crates/net/dns/src/lib.rs | 6 ++++++ crates/net/downloaders/src/lib.rs | 6 ++++++ crates/net/ecies/src/lib.rs | 6 ++++++ crates/net/eth-wire/src/lib.rs | 6 ++++++ crates/net/nat/src/lib.rs | 6 ++++++ crates/net/network-api/src/lib.rs | 6 ++++++ crates/net/network/src/lib.rs | 6 ++++++ crates/payload/basic/src/lib.rs | 6 ++++++ crates/payload/builder/src/lib.rs | 6 ++++++ crates/primitives/src/lib.rs | 6 ++++++ crates/revm/revm-inspectors/src/lib.rs | 6 ++++++ crates/revm/revm-primitives/src/lib.rs | 6 ++++++ crates/revm/src/lib.rs | 6 ++++++ crates/rlp/rlp-derive/src/lib.rs | 6 ++++++ crates/rlp/src/lib.rs | 6 ++++++ crates/rpc/ipc/src/lib.rs | 6 ++++++ crates/rpc/rpc-api/src/lib.rs | 6 ++++++ crates/rpc/rpc-builder/src/lib.rs | 6 ++++++ crates/rpc/rpc-engine-api/src/lib.rs | 6 ++++++ crates/rpc/rpc-testing-util/src/lib.rs | 6 ++++++ crates/rpc/rpc-types/src/lib.rs | 6 ++++++ crates/rpc/rpc/src/lib.rs | 6 ++++++ crates/staged-sync/src/lib.rs | 6 ++++++ crates/stages/src/lib.rs | 6 ++++++ crates/storage/codecs/derive/src/lib.rs | 6 ++++++ crates/storage/codecs/src/lib.rs | 6 ++++++ crates/storage/db/src/lib.rs | 6 ++++++ crates/storage/libmdbx-rs/mdbx-sys/src/lib.rs | 6 ++++++ crates/storage/libmdbx-rs/src/lib.rs | 6 ++++++ crates/storage/provider/src/lib.rs | 6 ++++++ crates/tasks/src/lib.rs | 6 ++++++ crates/tracing/src/lib.rs | 6 ++++++ crates/transaction-pool/src/lib.rs | 6 ++++++ crates/trie/src/lib.rs | 6 ++++++ 45 files changed, 264 insertions(+) create mode 100644 assets/reth-docs.png diff --git a/assets/reth-docs.png b/assets/reth-docs.png new file mode 100644 index 0000000000000000000000000000000000000000..c49ce28364a11ae3446b897b22738d111bf414da GIT binary patch literal 8251 zcmW+*1ymGm7hbv>VF}5lL%Kn_K|s1CmJ$S{5m>r&rMtUCx*Mb$q+LQnLZk)%`TjjS zJ9B2wdFP!Q&wcK5qqQ`Z@USVdK_C#Gin6>8aEASNV4?%RgHrD%Kp>=02RS({6*)ON zSN9K4hxfK15NmQkvZV5$H1)8>+h>e6OS*Nrhr-3JTOtFJiRl7qM%_GE5)PG;md7 zVvBc-T`#N~k9U65REJj6VFtq5Iz-qyl%xNtVRKnYh@6df`X_dJ!)9xEr;b&Svs|** zJX9yi^@nCT9th=7=^-{yil&s$!RL1!O0arwti1i8vl-qBGA?vf`5zE|^=zM9AHM}5 zk#iCyBL)5FndniC4!TRml^2QIBI#yiKG4NU+ry~hA*=S-RBq^tw~ToV3DsGe_p)2o zsaIrnwfc6y{C|^2#6J8cUU5jEi$~*>4Z%e7)YT4+N39iOBm43-|M%snX|FtYxTzkG z`=m7rrOgV#Ge&P`vnrg#Tt@`$Pmjp-C@bN2G-JHcdnvD+Wh}x^=yiV*f{L*wKc#H_ zw7+De`+{d`>MdYP(6)gTk&nCf*^Ev3Eo&<5mj9s)na2YMx5lr#`PS_Lv{z9*iRbEy z8JEiDKXthIwAuaSU$(hTkdo})ROb`P4|oOLN=-=~I0K)1xy~TqgypJi^brK&X8i9! z@+p1o1>D5&P|;ApI7Fu;z@nu)D$WpsVM@07IAARY|j5ON+BHNeH!&T?Ie#D4m9C zF~7*c_SM70IIudKMrGHPQT8dNbE`8YH(=M=gGEKg=F}5oSE#wF;|>^-MaIY5QcaAt zgxN^ejP`hY*_{zMMCNX0uKCuHl~?Pg`1y7CKRiA>25zrdc#~vkCQ3c9UQdgkzmHWc zCP;{j)4h}+2%+Ok?7xx#Mg7KRs{G=(oz79tWTEy5-LLk|dsIKqurEXd-F@nb#g9ZZ zz6v-}>-GJ6Il2In2KD{kpP&e#GslY@6h2~#Oa3y3PIU$hq zWO{_Mn8r_KZ8#z3R-!v?)eB6OcLcS>@>L&nh*j%yWq+x~tGGP5-?81AwFmAm z*Fv-n(-hcKW%)A0HeJ&HdWnfM(j+nxNB13h4D7nX=rDP3QSuIXd3ou@MRYdm$vCT+ zs)z?G=|2M5q=s3 z?ApSDR<#)tCL=Ga=ui$iUCfEza0RL|AQGDbz5F}kF zv=aQ$`kkYa9DFEsD55FQgcSl(NwEZ@1KYAS8bY|0EI7<{k}5>&Nw=_%$;2}7LLzY% zvv|Y5{q!B3Y?*A?ti3qcS4B!dpl+lgT{arxHX5pHrzSfN>oKqW@QlIly43Gm5`OW85dPSI|AcP{HlO8 zXi7&?F+=%PADpp`@(rB~Vb)LiDvBChSJp+}>L}5-P0Ob}Y?a8(#A?;+ZG>jZ-D>fE z=yuG}wJeorp8BXg8NmwJ_jo>e>H0{1rp@}%{v5-dTVS*%`Q8_F4O&4T|COhj<|$%` ztsH#C{>)bUIz#fRrp35MtEs*2{`8VzYVWsfOZk>xtraE5gH^NG?2jPrtN4^am@E#v zqaJK)tZ<^Ag@>fWB9Wc0nY3kIh+9ETmSco1;zL~4=C(v+?XK9bCt}1w> z=8_>5w5>igr^Xbqr>*!VysoxJ*~%A&2Ku}%A~HVlv3X8CLRaUiB#kxSO3WIZGD`eU zm++h9#7N`GgSqai=z9xK{M#C_L?bW!&N}f}ag#FHrL;T4)$s`v$Df%=;YD>7mOOV0 zr+r#XPbo%D%hm9&T_4|?Q4^Ry5<%y zEd5Zr59ZrKe8i zp!KG_6uo8={jbuAuW4L|mZkzIEs4&DtoiIP znyAAr=X*RDs)F5|?fi9KrE>dgDs|;S@p)xFs>Vk;L*ybBzM)iG)MyDl1xx>5;3Cd& zyT~U6!Opk}^~%gDauS<*RMqQfp|>nY_doZ8c^5k|&k zLOShf17R4Iu?{WqA+B?JZ5I8j+1XdIZ(Yi!2^fX69B4dZPz+(pNT?i7axR-V50&t{ z!?PD)JdJ&NG(zv5_(cTE#ey7n34DPa@@OYElXlS8)JL=o9ZRkH(@W&Wwcial>6-K3 zNHDb+w{p$a&Dz2Iqj`rmz?coIocVWPM7MZh>k}X)VD|5g&n+@igESwKJXM;NzTS)H zbW>Q58$&+Qmq1M=l&IjZasADwF*=>?g5D^VWCI61=})F^P8m5QW2e`_k0{oL`)8?; zjy~K4-*az|g5F-*pu0g@>9Tva=ze6{pg%-X&v(`y-rkH9=`2zyeS$3ucd3uaP&M)%_Q-hK7c3XK1N8or;c#uWao@Ft%-Dxt*2*}-Co_sJ0H6rsr!&etvv4vUWXLK4}bfO zS!K3ys2nJEtq^ROGFq(WgUTEvF^ye-b7Dr4 zZxh_~$wkQX^|4|b_-8=r3(606I7CcTju-3qM3K*F1)O+?7#2vo
*s5-9$U4s3KD6pFFUX z%YxzK#((WP@FiH~bEF7CH^cRPSkut&tv}5)4H%bGr3<&-YPnC0abq0z?8%7q6q4R^d0HsEL@iC`t>4 z(gZ(}FLWTj*3)2qebCaRR?`!bJ9|`12+305;`tAn3wf01;gv-3(UaXh(JnaDRbnHI ztA%iRfklQ6oq&9}NB2d7GI+>cW7w^!Bh7 zYdn#Z7Ng_dl|isyjx@9uWseuOx08`2IAk0hYaM3$mIzNEH@C&aXoqL)>7hL=T==4t2~p55qK zAu|RBHH!ya|4CQGD!FOHiPsf-^{@0RL8v^=_px`YzGS%-BYhs};&<*tm{dNx2uul) zZ{j5w1bwlW*b4y{synJ|66?oPe8%g9_edqvwua{Shz-O zVm;D!D`0`q!?vlYGPKepakg7e`cRp;^nwlc!1gRNrg-Y6x46F?y3a^~cZa zJ$Oo!qvedR-$9wWIlLU74)jIb<9Hgr$MSytR6Q>Y_e0;F%MKmV~t zQw(?*IoPogO zeU?8uGLBEu!Pa`1cwZm?uln~Rs94tC4cRO-@IC$OzhxTPuItXNY{pa)GmK=hy941k zK99dz0V1m1fX9#jW$Z;}>vo{{a*Yv+Y}9Tm zpZ(=!J(}=xwZqe?pU{b`{DTqGk?N)lqyv5R82vN)-k+ldpS@%yNPBB@^NU1@-hvPWlmuoiV9Oc@|Wxk#dbDm^{`8#D7Sbi>8Jbo z+`aubX3%!P?YqGUybB+RBD>dj{RFdXYtbzEpNJV589^`%+D}?^Ud(8rG7?XJ7C>cQ zi(9UPcw>@0j3}z!r>nvDSBJ5`dmlH?i@LjCzUuw!-oK`g3pb8`#q8h8;~e(37MgZ^ z>^S$YQdgDrFr> zM(1@Qb#?Wfc;T%v6c@$J{;I|$Pj6RV_~35ea;?dgv^De7-^17BL_w~ugy-G2G+P0; zW~ObfGGqDtcK9@YKj}ezV`G@;=;(y;is6Tx6IPa%AQTi7z$~BT*GUzyhG=PNM`ve~ zR+95_y1!n|qSb=< zsbw9dPQN?V4>zxvn>64jATEByzQVS2nn>dsXnKAQl(^_a@r4~Vj?xS+8Cc-;ZWF{5 zH8?CVe*zZn$n-ql>PEgq+UW~MS?vva?b3CrSU6=nk+!?h{xK_8M(3ZCB!)8n-ir?6 zD8=YO>$=jSH;SXC9yXOG9o~|~^x%XK&g8x2O@`sRf8Wv<50WB%ctF9!2 zv&G7{$S5|t{pwL}jM3bR2>0vK1qYe;a0naUOwkb~glo>uw(fSlWKa`Th(|iQ@yDv-7sR24g`F`yS8Ule};GA7r zVqyp!dCxycJum(^DN?5Avafl5M$;@)s)LZ35g><(iHnFtZU1||UF`|zgms;FoN%9R z_tFBrsL$b#;n*Uv@9XPsfA20shNcytOApfY29Th`RAakH9iP#E+#lwTK3tzPdK~G1 zq%}0~0{+}|Ss*}0vCnO22^C2 z0LExc-{BVqmTO;Z8GgLc)cjFdo+ld=)SGnsa8e(8+_L@)6BQYdZDu-OHXHhxjI%fV z;EYiB-%}T%UGzAsZQUB!e!dZ=pE>gQq4fG8d2IaS=u7Vc09fSYYG`n7qc5mSOG`T<_)kwy zcYE+ReOD6ig{V%wIxn6m`1w}8xr$KBnoH1-tE6RmpvK3??;AndtrZspqB!c>WIeP> zs2MZ*L4G-ce%$X^|0Gbt-*7elG0!Qz?z1mdV=6tSp`}g##F9;6pC$B~eU*J-u7>bc z@n{RXsNrNs*taOvS=HAaq)!XR7++??HS|XwRa7Q)x-(5x{gR9=*wIEKQpeWculJHd zs^%R^m?(&Bh8_3R&#vI&*G(BT((N~@LNo=0KedtNj1%02zvU<{spTV@W@a(Iz7EIFA72vE$Hrip!7IzmF(I*{f5x73R2YB~1Qfk@iG zsGc6FU~sTB$lBVvD06*w7RIEOhUwaGqZuS z!pVXL&e%{40_^Gf^G}E36?JtZIieqvR7?LWewI8@)pZ@%82Hp6jRkvW=kUQ@X%T8g z1A}zJnFcq6;-|aa$PnGPRH#{9Sr;M5x1&7$ATx^-@48+lvb43av#$!VW zl)fT`*jZpxa*_D&?Cq7K&jXxn1k2%= z7XwYWD7yOk*!)eEl~^pAIntbD|G_)geJ${wsi~;|WkF+&kp=d|1D01%2&Wr&bab?A zU3Z%jT!O9#UEM9Y!CH_*#NPn;Zq-UK==qcOhK0Ac z@H0?F8Ns*%hgbba9>^Kr>$m6Tf`fyRsHHv`8qPbrxF`b8@%0rA>8G=51e0-_vaa7; zB1Y#H7DA_|H4D#71wALYT6YezKa2oK``_)eGnchB1#MD5S+xQayc7#-vYSE9F!q@5 zV5|h=7o1ACGZX$BhqGvFYX^69h-r~B4b9tOcKBXF0f`rvfFO36ftiIR+&vSG6W|FR zVt6SKnG^`OX&Vgiwb5yk(QOin9iTMMy#6OKohy!!fB?M4W+e3XJQxLoM?fI#;lc0y zMjH_jR>S0MY>H?m!{BinVj$UpKycpD%o-U{%#jM9Jny=~9~m7DmjWEYY2Ia+QJU$D z7#th~#Oz1{+L7JCNEyN~<~^>`boe+#OiZlLjk>P=fHL@-l!Mw@Zs z91%BU|C>{pprF?w{RLWfQ2y%q=zQb8!i{bjogi}KmANkQ)%Q>l&tp;{myIaEK-$j$ zg==`f#;2>N*AFPi5S1F!c48nn|BH`(yA0^)VdTP%qVq8ny!PKXT+@o&)6$GH(?d%j> zIO7f$6czo)bCn^)3go{4Njv2h9r{mW!Q**2+1*q@`?I~0CL0U?tLAx*`xsxf7;2QU z_R%H^*pO|;WLMkxSgypd;osyz6i1TUp?-@+l%)L}PE|2IE)sCJzjFK{kGnwJvSf4> zLx`$ipUgy`ljxk<$&m##7uo9OCARx^>gLLX4jP!~-gttY{#<8NE`>-&$8z@^kM<|; z%;)zX`Mr=$kRq@`SZg!-bOte?Bvo4PLo>@`DA0q`alpFbK8ncN@iYQPxKMb?X$Ur} z$Ai~Mw_iJd2pNCXXU5>g4X-i|RsnyEx$RKDS*Kg_6A=+6u}Wt^%(`y>H;TBNFviXh z9V{gasPVwJ{Sc7;QBtjv%{caXhBE~)=3mKRC5}&w;Th77kB0OSyCV1iyAiCT$CUj} z1k8{KF;V+?aAgY#M@B}%-ips32>{pNh~OzGNv=3Yc!Qm>6{~2g`(V`{m<(6I><^y# z4%x9UoGIfaS_$vKKHcVRrrZ!>tT0*nxp9un+usmrAm=Hms1%&m0FQ`#Jc!ehIWSH) z7_KCE`*9GejByEj>Lw^TA94J^zN_94Udv2)xo-E#q8a#{{pp4}pT775&)Z z-)E-lD=G-1@-x4>v{C^j`P(fTFkJyc5a@LbIV0p5fD63Vb-el&4$odj`(qE7ASq}g&rT?Zs#A!D zaQLn+nkHB-X4~C(om(MDn-)=Ku8Lsfmh1vth>3Ch^kN6fl2HW3H(lb`;V^rX5IVrt zz?-nA5_6leE>g5(>bBb{qH}d>a`AV~CT`s>29|&S;`jhb6_%3XmPfJG?J)r;8z2%wl>zd_ioorpwpGa6+xwq|1dTWJL?e9yEv6_*omt`Mc9e?E zH4~O%f)g8haBMin7pAr;sGL~t^v55`5DN2l_kBlXP|xqw`i&=mvYzQ9&V?W8_FoNj z_*Nn-NglFjkkG27b9bKuwdaWm4P*y2;a$+Ftp4%w@t_@%X=sUdZINlcORA&NS-W;_ z?eJPjWR%~i`bm?6bcSJv$58-)iT5u+QL2GYCl>WoFJE@&(?wA!DJmY==FitI^%q}@ z!C$GeA24aD$}bZy?0AT28yWc~Alg={s;ct*0NNb3djAiD9ce?dm-weEFhmBaC}_&p I$y$c|52fyc`2YX_ literal 0 HcmV?d00001 diff --git a/crates/blockchain-tree/src/lib.rs b/crates/blockchain-tree/src/lib.rs index 87bdcaeb2279..15aa1da16a45 100644 --- a/crates/blockchain-tree/src/lib.rs +++ b/crates/blockchain-tree/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index b08290513ee1..a133180eb03c 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/consensus/auto-seal/src/lib.rs b/crates/consensus/auto-seal/src/lib.rs index 54b583457939..2cff15528c1f 100644 --- a/crates/consensus/auto-seal/src/lib.rs +++ b/crates/consensus/auto-seal/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/consensus/beacon/src/lib.rs b/crates/consensus/beacon/src/lib.rs index f76d1d6c1eb5..711a30e14460 100644 --- a/crates/consensus/beacon/src/lib.rs +++ b/crates/consensus/beacon/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/consensus/common/src/lib.rs b/crates/consensus/common/src/lib.rs index f8bc59b58d20..390c575707fa 100644 --- a/crates/consensus/common/src/lib.rs +++ b/crates/consensus/common/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/interfaces/src/lib.rs b/crates/interfaces/src/lib.rs index 7c92bc21b8bd..0c2e5e2c664d 100644 --- a/crates/interfaces/src/lib.rs +++ b/crates/interfaces/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/metrics/metrics-derive/src/lib.rs b/crates/metrics/metrics-derive/src/lib.rs index a451e54eaea7..a0b75eddcb2a 100644 --- a/crates/metrics/metrics-derive/src/lib.rs +++ b/crates/metrics/metrics-derive/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/metrics/src/lib.rs b/crates/metrics/src/lib.rs index ff0aaa4b0e01..e80719cb08a6 100644 --- a/crates/metrics/src/lib.rs +++ b/crates/metrics/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/common/src/lib.rs b/crates/net/common/src/lib.rs index b9997b782519..fb4f6156f358 100644 --- a/crates/net/common/src/lib.rs +++ b/crates/net/common/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 712b77a00553..f5bff545f754 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/dns/src/lib.rs b/crates/net/dns/src/lib.rs index 0edcf3df0751..b101c8b3ed4d 100644 --- a/crates/net/dns/src/lib.rs +++ b/crates/net/dns/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/downloaders/src/lib.rs b/crates/net/downloaders/src/lib.rs index 5a59412a2ce4..19d9dccfeea9 100644 --- a/crates/net/downloaders/src/lib.rs +++ b/crates/net/downloaders/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/ecies/src/lib.rs b/crates/net/ecies/src/lib.rs index 87c9773fff0c..7ab7878bcb9a 100644 --- a/crates/net/ecies/src/lib.rs +++ b/crates/net/ecies/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/eth-wire/src/lib.rs b/crates/net/eth-wire/src/lib.rs index 24197e22c81e..ef833030b4cb 100644 --- a/crates/net/eth-wire/src/lib.rs +++ b/crates/net/eth-wire/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/nat/src/lib.rs b/crates/net/nat/src/lib.rs index fadd2d01f614..1822dc7c7322 100644 --- a/crates/net/nat/src/lib.rs +++ b/crates/net/nat/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/network-api/src/lib.rs b/crates/net/network-api/src/lib.rs index 62af27bc22e6..fb1a92761638 100644 --- a/crates/net/network-api/src/lib.rs +++ b/crates/net/network-api/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index 35d6dac79436..766ac6684995 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs)] #![deny(unused_must_use, rust_2018_idioms, rustdoc::broken_intra_doc_links)] #![allow(rustdoc::private_intra_doc_links)] diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index f136d92cb26c..c598ab53509e 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/payload/builder/src/lib.rs b/crates/payload/builder/src/lib.rs index 49a12ef2ccf5..8bc35c469c5e 100644 --- a/crates/payload/builder/src/lib.rs +++ b/crates/payload/builder/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs)] #![deny( unused_must_use, diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index b2ee21b88f17..73845c2fd226 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_debug_implementations, missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/revm/revm-inspectors/src/lib.rs b/crates/revm/revm-inspectors/src/lib.rs index ff8ba39ffa2f..c02dfeb32c9b 100644 --- a/crates/revm/revm-inspectors/src/lib.rs +++ b/crates/revm/revm-inspectors/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/revm/revm-primitives/src/lib.rs b/crates/revm/revm-primitives/src/lib.rs index b329c9b7c077..3b5d59d34672 100644 --- a/crates/revm/revm-primitives/src/lib.rs +++ b/crates/revm/revm-primitives/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/revm/src/lib.rs b/crates/revm/src/lib.rs index 1d6060097a4d..6a99b020381d 100644 --- a/crates/revm/src/lib.rs +++ b/crates/revm/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/rlp/rlp-derive/src/lib.rs b/crates/rlp/rlp-derive/src/lib.rs index a23fe79c1d9e..02d9bc4449cb 100644 --- a/crates/rlp/rlp-derive/src/lib.rs +++ b/crates/rlp/rlp-derive/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, unused_crate_dependencies)] #![doc(test( diff --git a/crates/rlp/src/lib.rs b/crates/rlp/src/lib.rs index 1e60ea5dd4b2..c9bf35e5c5b8 100644 --- a/crates/rlp/src/lib.rs +++ b/crates/rlp/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(unreachable_pub)] #![deny(unused_must_use)] #![doc(test( diff --git a/crates/rpc/ipc/src/lib.rs b/crates/rpc/ipc/src/lib.rs index 13517148a5ea..0bc5e1207c34 100644 --- a/crates/rpc/ipc/src/lib.rs +++ b/crates/rpc/ipc/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_debug_implementations, missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/rpc/rpc-api/src/lib.rs b/crates/rpc/rpc-api/src/lib.rs index 54b8ddb4c12a..acca756711c1 100644 --- a/crates/rpc/rpc-api/src/lib.rs +++ b/crates/rpc/rpc-api/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_debug_implementations, missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 1c48b6d6ca09..38fbb76f4f51 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/rpc/rpc-engine-api/src/lib.rs b/crates/rpc/rpc-engine-api/src/lib.rs index 2a68d3d12e76..fa440801b118 100644 --- a/crates/rpc/rpc-engine-api/src/lib.rs +++ b/crates/rpc/rpc-engine-api/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms, unused_crate_dependencies)] #![doc(test( diff --git a/crates/rpc/rpc-testing-util/src/lib.rs b/crates/rpc/rpc-testing-util/src/lib.rs index 67f20bafff0d..144b7408f4bd 100644 --- a/crates/rpc/rpc-testing-util/src/lib.rs +++ b/crates/rpc/rpc-testing-util/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_debug_implementations, missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/rpc/rpc-types/src/lib.rs b/crates/rpc/rpc-types/src/lib.rs index d57ae9fa3788..64d6537291e3 100644 --- a/crates/rpc/rpc-types/src/lib.rs +++ b/crates/rpc/rpc-types/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_debug_implementations, missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index 20d32a6b0a79..8ec0893a0282 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_debug_implementations, missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/staged-sync/src/lib.rs b/crates/staged-sync/src/lib.rs index e864aa337c31..7654d0bd393a 100644 --- a/crates/staged-sync/src/lib.rs +++ b/crates/staged-sync/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/stages/src/lib.rs b/crates/stages/src/lib.rs index 0a8ba2f10f0a..ef0a673ac5a8 100644 --- a/crates/stages/src/lib.rs +++ b/crates/stages/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_debug_implementations, missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/storage/codecs/derive/src/lib.rs b/crates/storage/codecs/derive/src/lib.rs index db6a112f2e98..a46081c5748e 100644 --- a/crates/storage/codecs/derive/src/lib.rs +++ b/crates/storage/codecs/derive/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] use proc_macro::{self, TokenStream, TokenTree}; use quote::{format_ident, quote}; use syn::{parse_macro_input, DeriveInput}; diff --git a/crates/storage/codecs/src/lib.rs b/crates/storage/codecs/src/lib.rs index 755f8c2005f9..23df27b47f59 100644 --- a/crates/storage/codecs/src/lib.rs +++ b/crates/storage/codecs/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] use bytes::{Buf, Bytes}; pub use codecs_derive::*; use revm_primitives::{B160 as H160, B256 as H256, U256}; diff --git a/crates/storage/db/src/lib.rs b/crates/storage/db/src/lib.rs index 31cd55cda939..49bdad2061be 100644 --- a/crates/storage/db/src/lib.rs +++ b/crates/storage/db/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] //! reth's database abstraction layer with concrete implementations. //! //! The database abstraction assumes that the underlying store is a KV store subdivided into tables. diff --git a/crates/storage/libmdbx-rs/mdbx-sys/src/lib.rs b/crates/storage/libmdbx-rs/mdbx-sys/src/lib.rs index afe027d91ef4..78345d9185a8 100644 --- a/crates/storage/libmdbx-rs/mdbx-sys/src/lib.rs +++ b/crates/storage/libmdbx-rs/mdbx-sys/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![deny(warnings)] #![allow(non_upper_case_globals)] #![allow(non_camel_case_types)] diff --git a/crates/storage/libmdbx-rs/src/lib.rs b/crates/storage/libmdbx-rs/src/lib.rs index 8e960079a9cd..2e8c1002b740 100644 --- a/crates/storage/libmdbx-rs/src/lib.rs +++ b/crates/storage/libmdbx-rs/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![allow(clippy::type_complexity)] #![doc = include_str!("../README.md")] diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index 47929cc4f0ea..b07c2a5fbf5e 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub, unused_crate_dependencies)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/tasks/src/lib.rs b/crates/tasks/src/lib.rs index 31e785979ff4..2d045906fa58 100644 --- a/crates/tasks/src/lib.rs +++ b/crates/tasks/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/tracing/src/lib.rs b/crates/tracing/src/lib.rs index 4694a8fa0ff1..1695c336e2e4 100644 --- a/crates/tracing/src/lib.rs +++ b/crates/tracing/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 598cb6714d94..5c29775507b2 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs)] #![deny( unused_must_use, diff --git a/crates/trie/src/lib.rs b/crates/trie/src/lib.rs index d52d4acc06c3..507fb950c6d9 100644 --- a/crates/trie/src/lib.rs +++ b/crates/trie/src/lib.rs @@ -1,3 +1,9 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxzy/reth/issues/" +)] #![warn(missing_docs, unreachable_pub)] #![deny(unused_must_use, rust_2018_idioms)] #![doc(test( From e9caf3de7587dcf448a7a3aef17eab05a09bac87 Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 26 Jun 2023 17:46:11 +0200 Subject: [PATCH 186/216] refactor: re-implement `eth_feeHistory` (#3288) Co-authored-by: Matthias Seitz --- Cargo.lock | 11 - crates/primitives/src/transaction/mod.rs | 1 + crates/rpc/rpc-api/src/eth.rs | 8 +- crates/rpc/rpc-builder/tests/it/http.rs | 2 +- crates/rpc/rpc-types/Cargo.toml | 4 - crates/rpc/rpc-types/src/eth/fee.rs | 51 ++--- crates/rpc/rpc-types/src/eth/mod.rs | 2 +- crates/rpc/rpc/Cargo.toml | 3 +- crates/rpc/rpc/src/eth/api/fees.rs | 273 +++++++++++------------ crates/rpc/rpc/src/eth/api/mod.rs | 12 +- crates/rpc/rpc/src/eth/api/server.rs | 184 +++++++++++---- crates/rpc/rpc/src/eth/error.rs | 6 +- crates/rpc/rpc/src/eth/gas_oracle.rs | 5 + 13 files changed, 306 insertions(+), 256 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d8e731b52a4..ab30c41beef0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3510,15 +3510,6 @@ dependencies = [ "hashbrown 0.12.3", ] -[[package]] -name = "lru" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e7d46de488603ffdd5f30afbc64fbba2378214a2c3a2fb83abf3d33126df17" -dependencies = [ - "hashbrown 0.13.2", -] - [[package]] name = "lru" version = "0.10.0" @@ -5663,7 +5654,6 @@ version = "0.1.0-alpha.1" dependencies = [ "assert_matches", "jsonrpsee-types", - "lru 0.9.0", "rand 0.8.5", "reth-interfaces", "reth-primitives", @@ -5672,7 +5662,6 @@ dependencies = [ "serde_json", "similar-asserts", "thiserror", - "tokio", ] [[package]] diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 4a682f8c717c..cd53334a32bd 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -437,6 +437,7 @@ impl Transaction { pub fn effective_gas_tip(&self, base_fee: Option) -> Option { if let Some(base_fee) = base_fee { let max_fee_per_gas = self.max_fee_per_gas(); + if max_fee_per_gas < base_fee as u128 { None } else { diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs index 3dad16355373..9a66557c83ce 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-api/src/eth.rs @@ -1,7 +1,7 @@ use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use reth_primitives::{ - serde_helper::JsonStorageKey, AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, - H256, H64, U256, U64, + serde_helper::{num::U64HexOrNumber, JsonStorageKey}, + AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, H256, H64, U256, U64, }; use reth_rpc_types::{ state::StateOverride, BlockOverrides, CallRequest, EIP1186AccountProofResponse, FeeHistory, @@ -194,8 +194,8 @@ pub trait EthApi { #[method(name = "feeHistory")] async fn fee_history( &self, - block_count: U64, - newest_block: BlockId, + block_count: U64HexOrNumber, + newest_block: BlockNumberOrTag, reward_percentiles: Option>, ) -> RpcResult; diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index baab8a3e577f..221b2913ddab 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -65,7 +65,7 @@ where EthApiClient::block_number(client).await.unwrap(); EthApiClient::get_code(client, address, None).await.unwrap(); EthApiClient::send_raw_transaction(client, tx).await.unwrap(); - EthApiClient::fee_history(client, 0.into(), block_number.into(), None).await.unwrap(); + EthApiClient::fee_history(client, 0.into(), block_number, None).await.unwrap(); EthApiClient::balance(client, address, None).await.unwrap(); EthApiClient::transaction_count(client, address, None).await.unwrap(); EthApiClient::storage_at(client, address, U256::default().into(), None).await.unwrap(); diff --git a/crates/rpc/rpc-types/Cargo.toml b/crates/rpc/rpc-types/Cargo.toml index 037cf873bb46..5db05498bac2 100644 --- a/crates/rpc/rpc-types/Cargo.toml +++ b/crates/rpc/rpc-types/Cargo.toml @@ -18,14 +18,10 @@ reth-rlp = { workspace = true } # errors thiserror = { workspace = true } -# async -tokio = { workspace = true, features = ["sync"] } - # misc serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } jsonrpsee-types = { version = "0.18" } -lru = "0.9" [dev-dependencies] # reth diff --git a/crates/rpc/rpc-types/src/eth/fee.rs b/crates/rpc/rpc-types/src/eth/fee.rs index 4ace0e0c5cbc..6463e7b433c1 100644 --- a/crates/rpc/rpc-types/src/eth/fee.rs +++ b/crates/rpc/rpc-types/src/eth/fee.rs @@ -1,15 +1,12 @@ -use lru::LruCache; -use reth_primitives::{BlockNumber, H256, U256}; +use reth_primitives::U256; use serde::{Deserialize, Serialize}; -use std::{num::NonZeroUsize, sync::Arc}; -use tokio::sync::Mutex; /// Internal struct to calculate reward percentiles #[derive(Clone, Debug, PartialEq, Eq)] pub struct TxGasAndReward { - /// gas used by a block - pub gas_used: u128, - /// minimum between max_priority_fee_per_gas or max_fee_per_gas - base_fee_for_block + /// Gas used by the transaction + pub gas_used: u64, + /// The effective gas tip by the transaction pub reward: u128, } @@ -32,16 +29,28 @@ impl Ord for TxGasAndReward { } /// Response type for `eth_feeHistory` -#[derive(Debug, Clone, Default, Deserialize, Serialize)] +#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct FeeHistory { /// An array of block base fees per gas. /// This includes the next block after the newest of the returned range, /// because this value can be derived from the newest block. Zeroes are /// returned for pre-EIP-1559 blocks. + /// + /// # Note + /// + /// The `Option` is only for compatability with Erigon and Geth. + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] pub base_fee_per_gas: Vec, /// An array of block gas used ratios. These are calculated as the ratio /// of `gasUsed` and `gasLimit`. + /// + /// # Note + /// + /// The `Option` is only for compatability with Erigon and Geth. + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] pub gas_used_ratio: Vec, /// Lowest number block of the returned range. pub oldest_block: U256, @@ -50,29 +59,3 @@ pub struct FeeHistory { #[serde(default)] pub reward: Option>>, } - -/// LRU cache for `eth_feeHistory` RPC method. Block Number => Fee History. -#[derive(Clone, Debug)] -pub struct FeeHistoryCache(pub Arc>>); - -impl FeeHistoryCache { - /// Creates a new LRU Cache that holds at most cap items. - pub fn new(cap: NonZeroUsize) -> Self { - Self(Arc::new(Mutex::new(LruCache::new(cap)))) - } -} - -/// [FeeHistoryCache] item. -#[derive(Clone, Debug)] -pub struct FeeHistoryCacheItem { - /// Block hash (`None` if it wasn't the oldest block in `eth_feeHistory` response where - /// cache is populated) - pub hash: Option, - /// Block base fee per gas. Zero for pre-EIP-1559 blocks. - pub base_fee_per_gas: U256, - /// Block gas used ratio. Calculated as the ratio of `gasUsed` and `gasLimit`. - pub gas_used_ratio: f64, - /// An (optional) array of effective priority fee per gas data points for a - /// block. All zeroes are returned if the block is empty. - pub reward: Option>, -} diff --git a/crates/rpc/rpc-types/src/eth/mod.rs b/crates/rpc/rpc-types/src/eth/mod.rs index 25929985346a..04b2da62143a 100644 --- a/crates/rpc/rpc-types/src/eth/mod.rs +++ b/crates/rpc/rpc-types/src/eth/mod.rs @@ -20,7 +20,7 @@ mod work; pub use account::*; pub use block::*; pub use call::CallRequest; -pub use fee::{FeeHistory, FeeHistoryCache, FeeHistoryCacheItem, TxGasAndReward}; +pub use fee::{FeeHistory, TxGasAndReward}; pub use filter::*; pub use index::Index; pub use log::Log; diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 9fdf2c913fa6..796c5ded0ef8 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -62,4 +62,5 @@ futures = { workspace = true } [dev-dependencies] jsonrpsee = { version = "0.18", features = ["client"] } assert_matches = "1.5.0" -tempfile = "3.5.0" \ No newline at end of file +tempfile = "3.5.0" +reth-interfaces = { workspace = true, features = ["test-utils"] } \ No newline at end of file diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs index 6077475fce37..630c0c2305d3 100644 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ b/crates/rpc/rpc/src/eth/api/fees.rs @@ -1,15 +1,17 @@ //! Contains RPC handler implementations for fee history. use crate::{ - eth::error::{EthApiError, EthResult, RpcInvalidTransactionError}, + eth::error::{EthApiError, EthResult}, EthApi, }; use reth_network_api::NetworkInfo; -use reth_primitives::{BlockId, BlockNumberOrTag, U256}; +use reth_primitives::{ + basefee::calculate_next_block_base_fee, BlockNumberOrTag, SealedHeader, U256, +}; use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderFactory}; -use reth_rpc_types::{FeeHistory, FeeHistoryCacheItem, TxGasAndReward}; +use reth_rpc_types::{FeeHistory, TxGasAndReward}; use reth_transaction_pool::TransactionPool; -use std::collections::BTreeMap; +use tracing::debug; impl EthApi where @@ -37,168 +39,155 @@ where /// provided. pub(crate) async fn fee_history( &self, - block_count: u64, - newest_block: BlockId, + mut block_count: u64, + newest_block: BlockNumberOrTag, reward_percentiles: Option>, ) -> EthResult { if block_count == 0 { return Ok(FeeHistory::default()) } - let Some(previous_to_end_block) = self.inner.provider.block_number_for_id(newest_block)? else { return Err(EthApiError::UnknownBlockNumber)}; - let end_block = previous_to_end_block + 1; - - if end_block < block_count { - return Err(EthApiError::InvalidBlockRange) + // See https://github.com/ethereum/go-ethereum/blob/2754b197c935ee63101cbbca2752338246384fec/eth/gasprice/feehistory.go#L218C8-L225 + let max_fee_history = if reward_percentiles.is_none() { + self.gas_oracle().config().max_header_history + } else { + self.gas_oracle().config().max_block_history + }; + + if block_count > max_fee_history { + debug!( + requested = block_count, + truncated = max_fee_history, + "Sanitizing fee history block count" + ); + block_count = max_fee_history } - let mut start_block = end_block - block_count; + let Some(end_block) = self.provider().block_number_for_id(newest_block.into())? else { + return Err(EthApiError::UnknownBlockNumber) }; - if block_count == 1 { - start_block = previous_to_end_block; + // Check that we would not be querying outside of genesis + if end_block < block_count { + return Err(EthApiError::InvalidBlockRange) } - // if not provided the percentiles are [] - let reward_percentiles = reward_percentiles.unwrap_or_default(); - - // checks for rewardPercentile's sorted-ness - // check if any of rewardPercentile is greater than 100 - // pre 1559 blocks, return 0 for baseFeePerGas - for window in reward_percentiles.windows(2) { - if window[0] >= window[1] { - return Err(EthApiError::InvalidRewardPercentile(window[1])) - } - - if window[0] < 0.0 || window[0] > 100.0 { - return Err(EthApiError::InvalidRewardPercentile(window[0])) + // If reward percentiles were specified, we need to validate that they are monotonically + // increasing and 0 <= p <= 100 + // + // Note: The types used ensure that the percentiles are never < 0 + if let Some(percentiles) = &reward_percentiles { + if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) { + return Err(EthApiError::InvalidRewardPercentiles) } } - let mut fee_history_cache = self.inner.fee_history_cache.0.lock().await; - - // Sorted map that's populated in two rounds: - // 1. Cache entries until first non-cached block - // 2. Database query from the first non-cached block - let mut fee_history_cache_items = BTreeMap::new(); - - let mut first_non_cached_block = None; - let mut last_non_cached_block = None; - for block in start_block..=end_block { - // Check if block exists in cache, and move it to the head of the list if so - if let Some(fee_history_cache_item) = fee_history_cache.get(&block) { - fee_history_cache_items.insert(block, fee_history_cache_item.clone()); - } else { - // If block doesn't exist in cache, set it as a first non-cached block to query it - // from the database - first_non_cached_block.get_or_insert(block); - // And last non-cached block, so we could query the database until we reach it - last_non_cached_block = Some(block); - } + // Fetch the headers and ensure we got all of them + // + // Treat a request for 1 block as a request for `newest_block..=newest_block`, + // otherwise `newest_block - 2 + let start_block = end_block - block_count + 1; + let headers = self.provider().sealed_headers_range(start_block..=end_block)?; + if headers.len() != block_count as usize { + return Err(EthApiError::InvalidBlockRange) } - // If we had any cache misses, query the database starting with the first non-cached block - // and ending with the last - if let (Some(start_block), Some(end_block)) = - (first_non_cached_block, last_non_cached_block) - { - let header_range = start_block..=end_block; - - let headers = self.inner.provider.headers_range(header_range.clone())?; - let transactions_by_block = - self.inner.provider.transactions_by_block_range(header_range)?; - - let header_tx = headers.iter().zip(&transactions_by_block); - - // We should receive exactly the amount of blocks missing from the cache - if headers.len() != (end_block - start_block + 1) as usize { - return Err(EthApiError::InvalidBlockRange) - } - - // We should receive exactly the amount of blocks missing from the cache - if transactions_by_block.len() != (end_block - start_block + 1) as usize { - return Err(EthApiError::InvalidBlockRange) - } - - for (header, transactions) in header_tx { - let base_fee_per_gas: U256 = header.base_fee_per_gas. - unwrap_or_default(). // Zero for pre-EIP-1559 blocks - try_into().unwrap(); // u64 -> U256 won't fail - let gas_used_ratio = header.gas_used as f64 / header.gas_limit as f64; - - let mut sorter = Vec::with_capacity(transactions.len()); - for transaction in transactions.iter() { - let reward = transaction - .effective_gas_tip(header.base_fee_per_gas) - .ok_or(RpcInvalidTransactionError::FeeCapTooLow)?; - - sorter.push(TxGasAndReward { gas_used: header.gas_used as u128, reward }) - } - - sorter.sort(); - - let mut rewards = Vec::with_capacity(reward_percentiles.len()); - let mut sum_gas_used = sorter.first().map(|tx| tx.gas_used).unwrap_or_default(); - let mut tx_index = 0; - - for percentile in reward_percentiles.iter() { - let threshold_gas_used = (header.gas_used as f64) * percentile / 100_f64; - while sum_gas_used < threshold_gas_used as u128 && tx_index < transactions.len() - { - tx_index += 1; - sum_gas_used += sorter[tx_index].gas_used; - } - - rewards.push(U256::from(sorter[tx_index].reward)); - } - - let fee_history_cache_item = FeeHistoryCacheItem { - hash: None, - base_fee_per_gas, - gas_used_ratio, - reward: Some(rewards), - }; - - // Insert missing cache entries in the map for further response composition from - // it - fee_history_cache_items.insert(header.number, fee_history_cache_item.clone()); - // And populate the cache with new entries - fee_history_cache.push(header.number, fee_history_cache_item); + // Collect base fees, gas usage ratios and (optionally) reward percentile data + let mut base_fee_per_gas: Vec = Vec::new(); + let mut gas_used_ratio: Vec = Vec::new(); + let mut rewards: Vec> = Vec::new(); + for header in &headers { + base_fee_per_gas + .push(U256::try_from(header.base_fee_per_gas.unwrap_or_default()).unwrap()); + gas_used_ratio.push(header.gas_used as f64 / header.gas_limit as f64); + + // Percentiles were specified, so we need to collect reward percentile ino + if let Some(percentiles) = &reward_percentiles { + rewards.push(self.calculate_reward_percentiles(percentiles, header).await?); } } - // get the first block in the range from the db - let oldest_block_hash = - self.inner.provider.block_hash(start_block)?.ok_or(EthApiError::UnknownBlockNumber)?; - - // Set the hash in cache items if the block is present in the cache - if let Some(cache_item) = fee_history_cache_items.get_mut(&start_block) { - cache_item.hash = Some(oldest_block_hash); - } - - if let Some(cache_item) = fee_history_cache.get_mut(&start_block) { - cache_item.hash = Some(oldest_block_hash); - } - - // `fee_history_cache_items` now contains full requested block range (populated from both - // cache and database), so we can iterate over it in order and populate the response fields - let base_fee_per_gas = - fee_history_cache_items.values().map(|item| item.base_fee_per_gas).collect(); - - let mut gas_used_ratio: Vec = - fee_history_cache_items.values().map(|item| item.gas_used_ratio).collect(); - - let mut rewards: Vec> = - fee_history_cache_items.values().filter_map(|item| item.reward.clone()).collect(); - - // gasUsedRatio doesn't have data for next block in this case the last block - gas_used_ratio.pop(); - rewards.pop(); + // The spec states that `base_fee_per_gas` "[..] includes the next block after the newest of + // the returned range, because this value can be derived from the newest block" + // + // The unwrap is safe since we checked earlier that we got at least 1 header. + let last_header = headers.last().unwrap(); + base_fee_per_gas.push(U256::from(calculate_next_block_base_fee( + last_header.gas_used, + last_header.gas_limit, + last_header.base_fee_per_gas.unwrap_or_default(), + ))); Ok(FeeHistory { base_fee_per_gas, gas_used_ratio, oldest_block: U256::from(start_block), - reward: Some(rewards), + reward: reward_percentiles.map(|_| rewards), }) } + + // todo: docs + async fn calculate_reward_percentiles( + &self, + percentiles: &[f64], + header: &SealedHeader, + ) -> Result, EthApiError> { + let Some(receipts) = + self.cache().get_receipts(header.hash).await? else { + // If there are no receipts, then we do not have all info on the block + return Err(EthApiError::InvalidBlockRange) + }; + let Some(mut transactions): Option> = self + .cache() + .get_block_transactions(header.hash).await? + .map(|txs|txs + .into_iter() + .zip(receipts.into_iter()) + .scan(0, |previous_gas, (tx, receipt)| { + // Convert the cumulative gas used in the receipts + // to the gas usage by the transaction + // + // While we will sum up the gas again later, it is worth + // noting that the order of the transactions will be different, + // so the sum will also be different for each receipt. + let gas_used = receipt.cumulative_gas_used - *previous_gas; + *previous_gas = receipt.cumulative_gas_used; + + Some(TxGasAndReward { + gas_used, + reward: tx.effective_gas_tip(header.base_fee_per_gas).unwrap_or_default(), + }) + }) + .collect()) else { + // If there are no transactions, then we do not have all info on the block + return Err(EthApiError::InvalidBlockRange) + }; + + // Sort the transactions by their rewards in ascending order + transactions.sort_by_key(|tx| tx.reward); + + // Find the transaction that corresponds to the given percentile + // + // We use a `tx_index` here that is shared across all percentiles, since we know + // the percentiles are monotonically increasing. + let mut tx_index = 0; + let mut cumulative_gas_used = + transactions.first().map(|tx| tx.gas_used).unwrap_or_default(); + let mut rewards_in_block = Vec::new(); + for percentile in percentiles { + // Empty blocks should return in a zero row + if transactions.is_empty() { + rewards_in_block.push(U256::ZERO); + continue + } + + let threshold = (header.gas_used as f64 * percentile / 100.) as u64; + while cumulative_gas_used < threshold && tx_index < transactions.len() - 1 { + tx_index += 1; + cumulative_gas_used += transactions[tx_index].gas_used; + } + rewards_in_block.push(U256::from(transactions[tx_index].reward)); + } + + Ok(rewards_in_block) + } } diff --git a/crates/rpc/rpc/src/eth/api/mod.rs b/crates/rpc/rpc/src/eth/api/mod.rs index f611034fd58e..227e14cefb6e 100644 --- a/crates/rpc/rpc/src/eth/api/mod.rs +++ b/crates/rpc/rpc/src/eth/api/mod.rs @@ -14,10 +14,10 @@ use reth_interfaces::Result; use reth_network_api::NetworkInfo; use reth_primitives::{Address, BlockId, BlockNumberOrTag, ChainInfo, H256, U256, U64}; use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderBox, StateProviderFactory}; -use reth_rpc_types::{FeeHistoryCache, SyncInfo, SyncStatus}; +use reth_rpc_types::{SyncInfo, SyncStatus}; use reth_tasks::{TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::TransactionPool; -use std::{future::Future, num::NonZeroUsize, sync::Arc}; +use std::{future::Future, sync::Arc}; use tokio::sync::oneshot; mod block; @@ -30,9 +30,6 @@ mod transactions; pub use transactions::{EthTransactions, TransactionSource}; -/// Cache limit of block-level fee history for `eth_feeHistory` RPC method. -const FEE_HISTORY_CACHE_LIMIT: usize = 2048; - /// `Eth` API trait. /// /// Defines core functionality of the `eth` API implementation. @@ -118,9 +115,6 @@ where gas_oracle, starting_block: U256::from(latest_block), task_spawner, - fee_history_cache: FeeHistoryCache::new( - NonZeroUsize::new(FEE_HISTORY_CACHE_LIMIT).unwrap(), - ), }; Self { inner: Arc::new(inner) } } @@ -290,6 +284,4 @@ struct EthApiInner { starting_block: U256, /// The type that can spawn tasks which would otherwise block. task_spawner: Box, - /// The cache for fee history entries, - fee_history_cache: FeeHistoryCache, } diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index f8244dcf53d2..90965b8a865a 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -12,8 +12,8 @@ use crate::{ use jsonrpsee::core::RpcResult as Result; use reth_network_api::NetworkInfo; use reth_primitives::{ - serde_helper::JsonStorageKey, AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, - H256, H64, U256, U64, + serde_helper::{num::U64HexOrNumber, JsonStorageKey}, + AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, H256, H64, U256, U64, }; use reth_provider::{ BlockIdReader, BlockReader, BlockReaderIdExt, EvmEnvProvider, HeaderProvider, @@ -295,8 +295,8 @@ where /// Handler for: `eth_feeHistory` async fn fee_history( &self, - block_count: U64, - newest_block: BlockId, + block_count: U64HexOrNumber, + newest_block: BlockNumberOrTag, reward_percentiles: Option>, ) -> Result { trace!(target: "rpc::eth", ?block_count, ?newest_block, ?reward_percentiles, "Serving eth_feeHistory"); @@ -386,50 +386,78 @@ mod tests { EthApi, }; use jsonrpsee::types::error::INVALID_PARAMS_CODE; - use rand::random; + use reth_interfaces::test_utils::{generators, generators::Rng}; use reth_network_api::test_utils::NoopNetwork; - use reth_primitives::{Block, BlockNumberOrTag, Header, TransactionSigned, H256, U256}; - use reth_provider::test_utils::{MockEthProvider, NoopProvider}; + use reth_primitives::{ + basefee::calculate_next_block_base_fee, Block, BlockNumberOrTag, Header, TransactionSigned, + H256, U256, + }; + use reth_provider::{ + test_utils::{MockEthProvider, NoopProvider}, + BlockReader, BlockReaderIdExt, EvmEnvProvider, StateProviderFactory, + }; use reth_rpc_api::EthApiServer; - use reth_transaction_pool::test_utils::testing_pool; - - #[tokio::test] - /// Handler for: `eth_test_fee_history` - async fn test_fee_history() { - let cache = EthStateCache::spawn(NoopProvider::default(), Default::default()); - let eth_api = EthApi::new( - NoopProvider::default(), + use reth_rpc_types::FeeHistory; + use reth_transaction_pool::test_utils::{testing_pool, TestPool}; + + fn build_test_eth_api< + P: BlockReaderIdExt + + BlockReader + + EvmEnvProvider + + StateProviderFactory + + Unpin + + Clone + + 'static, + >( + provider: P, + ) -> EthApi { + let cache = EthStateCache::spawn(provider.clone(), Default::default()); + EthApi::new( + provider.clone(), testing_pool(), NoopNetwork, cache.clone(), - GasPriceOracle::new(NoopProvider::default(), Default::default(), cache), - ); + GasPriceOracle::new(provider, Default::default(), cache), + ) + } + /// Invalid block range + #[tokio::test] + async fn test_fee_history_empty() { let response = as EthApiServer>::fee_history( - ð_api, + &build_test_eth_api(NoopProvider::default()), 1.into(), - BlockNumberOrTag::Latest.into(), + BlockNumberOrTag::Latest, None, ) .await; assert!(response.is_err()); let error_object = response.unwrap_err(); assert_eq!(error_object.code(), INVALID_PARAMS_CODE); + } + + /// Handler for: `eth_test_fee_history` + // TODO: Split this into multiple tests, and add tests for percentiles. + #[tokio::test] + async fn test_fee_history() { + let mut rng = generators::rng(); let block_count = 10; let newest_block = 1337; + // Build mock data let mut oldest_block = None; let mut gas_used_ratios = Vec::new(); let mut base_fees_per_gas = Vec::new(); - + let mut last_header = None; let mock_provider = MockEthProvider::default(); - for i in (0..=block_count).rev() { + for i in (0..block_count).rev() { let hash = H256::random(); - let gas_limit: u64 = random(); - let gas_used: u64 = random(); - let base_fee_per_gas: Option = random::().then(random); + let gas_limit: u64 = rng.gen(); + let gas_used: u64 = rng.gen(); + // Note: Generates a u32 to avoid overflows later + let base_fee_per_gas: Option = rng.gen::().then(|| rng.gen::() as u64); let header = Header { number: newest_block - i, @@ -438,10 +466,11 @@ mod tests { base_fee_per_gas, ..Default::default() }; + last_header = Some(header.clone()); let mut transactions = vec![]; for _ in 0..100 { - let random_fee: u128 = random(); + let random_fee: u128 = rng.gen(); if let Some(base_fee_per_gas) = header.base_fee_per_gas { let transaction = TransactionSigned { @@ -480,17 +509,17 @@ mod tests { .push(base_fee_per_gas.map(|fee| U256::try_from(fee).unwrap()).unwrap_or_default()); } - gas_used_ratios.pop(); + // Add final base fee (for the next block outside of the request) + let last_header = last_header.unwrap(); + base_fees_per_gas.push(U256::from(calculate_next_block_base_fee( + last_header.gas_used, + last_header.gas_limit, + last_header.base_fee_per_gas.unwrap_or_default(), + ))); - let cache = EthStateCache::spawn(mock_provider.clone(), Default::default()); - let eth_api = EthApi::new( - mock_provider.clone(), - testing_pool(), - NoopNetwork, - cache.clone(), - GasPriceOracle::new(mock_provider, Default::default(), cache.clone()), - ); + let eth_api = build_test_eth_api(mock_provider); + // Invalid block range (request is before genesis) let response = as EthApiServer>::fee_history( ð_api, (newest_block + 1).into(), @@ -502,20 +531,85 @@ mod tests { let error_object = response.unwrap_err(); assert_eq!(error_object.code(), INVALID_PARAMS_CODE); - // newest_block is finalized - let fee_history = - eth_api.fee_history(block_count, (newest_block - 1).into(), None).await.unwrap(); + // Invalid block range (request is in in the future) + let response = as EthApiServer>::fee_history( + ð_api, + (1).into(), + (newest_block + 1000).into(), + Some(vec![10.0]), + ) + .await; + assert!(response.is_err()); + let error_object = response.unwrap_err(); + assert_eq!(error_object.code(), INVALID_PARAMS_CODE); - assert_eq!(fee_history.base_fee_per_gas, base_fees_per_gas); - assert_eq!(fee_history.gas_used_ratio, gas_used_ratios); - assert_eq!(fee_history.oldest_block, U256::from(newest_block - block_count)); + // Requesting no block should result in a default response + let response = as EthApiServer>::fee_history( + ð_api, + (0).into(), + (newest_block).into(), + None, + ) + .await + .unwrap(); + assert_eq!( + response, + FeeHistory::default(), + "none: requesting no block should yield a default response" + ); - // newest_block is pending + // Requesting a single block should return 1 block (+ base fee for the next block over) + let fee_history = eth_api.fee_history(1, (newest_block).into(), None).await.unwrap(); + assert_eq!( + &fee_history.base_fee_per_gas, + &base_fees_per_gas[base_fees_per_gas.len() - 2..], + "one: base fee per gas is incorrect" + ); + assert_eq!( + fee_history.base_fee_per_gas.len(), + 2, + "one: should return base fee of the next block as well" + ); + assert_eq!( + &fee_history.gas_used_ratio, + &gas_used_ratios[gas_used_ratios.len() - 1..], + "one: gas used ratio is incorrect" + ); + assert_eq!( + fee_history.oldest_block, + U256::from(newest_block), + "one: oldest block is incorrect" + ); + assert!( + fee_history.reward.is_none(), + "one: no percentiles were requested, so there should be no rewards result" + ); + + // Requesting all blocks should be ok let fee_history = - eth_api.fee_history(block_count, (newest_block - 1).into(), None).await.unwrap(); + eth_api.fee_history(block_count, (newest_block).into(), None).await.unwrap(); - assert_eq!(fee_history.base_fee_per_gas, base_fees_per_gas); - assert_eq!(fee_history.gas_used_ratio, gas_used_ratios); - assert_eq!(fee_history.oldest_block, U256::from(newest_block - block_count)); + assert_eq!( + &fee_history.base_fee_per_gas, &base_fees_per_gas, + "all: base fee per gas is incorrect" + ); + assert_eq!( + fee_history.base_fee_per_gas.len() as u64, + block_count + 1, + "all: should return base fee of the next block as well" + ); + assert_eq!( + &fee_history.gas_used_ratio, &gas_used_ratios, + "all: gas used ratio is incorrect" + ); + assert_eq!( + fee_history.oldest_block, + U256::from(newest_block - block_count + 1), + "all: oldest block is incorrect" + ); + assert!( + fee_history.reward.is_none(), + "all: no percentiles were requested, so there should be no rewards result" + ); } } diff --git a/crates/rpc/rpc/src/eth/error.rs b/crates/rpc/rpc/src/eth/error.rs index 625c0e2bd552..f7bf25700753 100644 --- a/crates/rpc/rpc/src/eth/error.rs +++ b/crates/rpc/rpc/src/eth/error.rs @@ -65,8 +65,8 @@ pub enum EthApiError { #[error("invalid tracer config")] InvalidTracerConfig, /// Percentile array is invalid - #[error("invalid reward percentile")] - InvalidRewardPercentile(f64), + #[error("invalid reward percentiles")] + InvalidRewardPercentiles, /// Error thrown when a spawned tracing task failed to deliver an anticipated response. #[error("internal error while tracing")] InternalTracingError, @@ -101,7 +101,7 @@ impl From for ErrorObject<'static> { EthApiError::Unsupported(msg) => internal_rpc_err(msg), EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg), EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg), - EthApiError::InvalidRewardPercentile(msg) => internal_rpc_err(msg.to_string()), + EthApiError::InvalidRewardPercentiles => internal_rpc_err(error.to_string()), err @ EthApiError::InternalTracingError => internal_rpc_err(err.to_string()), err @ EthApiError::InternalEthError => internal_rpc_err(err.to_string()), } diff --git a/crates/rpc/rpc/src/eth/gas_oracle.rs b/crates/rpc/rpc/src/eth/gas_oracle.rs index 8a364088d13a..2448ad9a2ecd 100644 --- a/crates/rpc/rpc/src/eth/gas_oracle.rs +++ b/crates/rpc/rpc/src/eth/gas_oracle.rs @@ -111,6 +111,11 @@ where Self { provider, oracle_config, last_price: Default::default(), cache } } + /// Returns the configuration of the gas price oracle. + pub fn config(&self) -> &GasPriceOracleConfig { + &self.oracle_config + } + /// Suggests a gas price estimate based on recent blocks, using the configured percentile. pub async fn suggest_tip_cap(&self) -> EthResult { let header = self From 3c126a04d8cc46045fc6e946e2026b456bcc91b0 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Mon, 26 Jun 2023 18:54:47 +0300 Subject: [PATCH 187/216] fix(txpool): gas cost ordering (#3389) --- crates/primitives/src/transaction/mod.rs | 12 ++-- crates/transaction-pool/src/lib.rs | 8 +-- crates/transaction-pool/src/ordering.rs | 10 ++-- crates/transaction-pool/src/pool/mod.rs | 1 - crates/transaction-pool/src/pool/parked.rs | 2 +- crates/transaction-pool/src/pool/txpool.rs | 4 +- .../transaction-pool/src/test_utils/mock.rs | 18 ++++-- crates/transaction-pool/src/traits.rs | 60 +++++++++---------- crates/transaction-pool/src/validate/eth.rs | 4 +- crates/transaction-pool/src/validate/mod.rs | 28 +++++---- 10 files changed, 76 insertions(+), 71 deletions(-) diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index cd53334a32bd..055402c302f1 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -419,13 +419,11 @@ impl Transaction { /// /// If the transaction is a legacy or EIP2930 transaction, the gas price is returned. pub fn effective_gas_price(&self, base_fee: Option) -> u128 { - let dynamic_tx = match self { - Transaction::Legacy(tx) => return tx.gas_price, - Transaction::Eip2930(tx) => return tx.gas_price, - Transaction::Eip1559(dynamic_tx) => dynamic_tx, - }; - - dynamic_tx.effective_gas_price(base_fee) + match self { + Transaction::Legacy(tx) => tx.gas_price, + Transaction::Eip2930(tx) => tx.gas_price, + Transaction::Eip1559(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee), + } } // TODO: dedup with effective_tip_per_gas diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 5c29775507b2..22767adc03ad 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -101,7 +101,7 @@ use tracing::{instrument, trace}; pub use crate::{ config::PoolConfig, error::PoolResult, - ordering::{CostOrdering, TransactionOrdering}, + ordering::{GasCostOrdering, TransactionOrdering}, pool::TransactionEvents, traits::{ AllPoolTransactions, BestTransactions, BlockInfo, CanonicalStateUpdate, ChangedAccount, @@ -226,17 +226,17 @@ where } impl - Pool, CostOrdering> + Pool, GasCostOrdering> where Client: StateProviderFactory + Clone + 'static, { /// Returns a new [Pool] that uses the default [EthTransactionValidator] when validating - /// [PooledTransaction]s and ords via [CostOrdering] + /// [PooledTransaction]s and ords via [GasCostOrdering] pub fn eth_pool( validator: EthTransactionValidator, config: PoolConfig, ) -> Self { - Self::new(validator, CostOrdering::default(), config) + Self::new(validator, GasCostOrdering::default(), config) } } diff --git a/crates/transaction-pool/src/ordering.rs b/crates/transaction-pool/src/ordering.rs index 21334771a9b9..34044588de2c 100644 --- a/crates/transaction-pool/src/ordering.rs +++ b/crates/transaction-pool/src/ordering.rs @@ -22,13 +22,13 @@ pub trait TransactionOrdering: Send + Sync + 'static { /// Default ordering for the pool. /// -/// The transactions are ordered by their cost. The higher the cost, +/// The transactions are ordered by their gas cost. The higher the gas cost, /// the higher the priority of this transaction is. #[derive(Debug)] #[non_exhaustive] -pub struct CostOrdering(PhantomData); +pub struct GasCostOrdering(PhantomData); -impl TransactionOrdering for CostOrdering +impl TransactionOrdering for GasCostOrdering where T: PoolTransaction + 'static, { @@ -36,11 +36,11 @@ where type Transaction = T; fn priority(&self, transaction: &Self::Transaction) -> Self::Priority { - transaction.cost() + transaction.gas_cost() } } -impl Default for CostOrdering { +impl Default for GasCostOrdering { fn default() -> Self { Self(Default::default()) } diff --git a/crates/transaction-pool/src/pool/mod.rs b/crates/transaction-pool/src/pool/mod.rs index 293e7af22a36..8832ed644f60 100644 --- a/crates/transaction-pool/src/pool/mod.rs +++ b/crates/transaction-pool/src/pool/mod.rs @@ -255,7 +255,6 @@ where let encoded_length = transaction.encoded_length(); let tx = ValidPoolTransaction { - cost: transaction.cost(), transaction, transaction_id, propagate: false, diff --git a/crates/transaction-pool/src/pool/parked.rs b/crates/transaction-pool/src/pool/parked.rs index 44761a3ca73d..d6a62685cfbf 100644 --- a/crates/transaction-pool/src/pool/parked.rs +++ b/crates/transaction-pool/src/pool/parked.rs @@ -289,7 +289,7 @@ impl_ord_wrapper!(QueuedOrd); impl Ord for QueuedOrd { fn cmp(&self, other: &Self) -> Ordering { // Higher cost is better - self.cost.cmp(&other.cost).then_with(|| + self.gas_cost().cmp(&other.gas_cost()).then_with(|| // Lower timestamp is better other.timestamp.cmp(&self.timestamp)) } diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index d32fe02e27d8..3b4b03e70475 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -748,7 +748,7 @@ impl AllTransactions { tx.state.insert(TxState::NO_NONCE_GAPS); tx.state.insert(TxState::NO_PARKED_ANCESTORS); tx.cumulative_cost = U256::ZERO; - if tx.transaction.cost > info.balance { + if tx.transaction.cost() > info.balance { // sender lacks sufficient funds to pay for this transaction tx.state.remove(TxState::ENOUGH_BALANCE); } else { @@ -1268,7 +1268,7 @@ pub(crate) struct PoolInternalTransaction { impl PoolInternalTransaction { fn next_cumulative_cost(&self) -> U256 { - self.cumulative_cost + self.transaction.cost + self.cumulative_cost + self.transaction.cost() } } diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 735bb8d57beb..13ec9b5af010 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -329,8 +329,15 @@ impl PoolTransaction for MockTransaction { } } - fn effective_gas_price(&self) -> u128 { - self.get_gas_price() + fn gas_cost(&self) -> U256 { + match self { + MockTransaction::Legacy { gas_price, gas_limit, .. } => { + U256::from(*gas_limit) * U256::from(*gas_price) + } + MockTransaction::Eip1559 { max_fee_per_gas, gas_limit, .. } => { + U256::from(*gas_limit) * U256::from(*max_fee_per_gas) + } + } } fn gas_limit(&self) -> u64 { @@ -485,7 +492,6 @@ impl MockTransactionFactory { MockValidTx { propagate: false, transaction_id, - cost: transaction.cost(), transaction, timestamp: Instant::now(), origin, @@ -511,7 +517,7 @@ impl TransactionOrdering for MockOrdering { type Transaction = MockTransaction; fn priority(&self, transaction: &Self::Transaction) -> Self::Priority { - transaction.cost() + transaction.gas_cost() } } @@ -551,7 +557,7 @@ impl MockTransactionDistribution { #[test] fn test_mock_priority() { let o = MockOrdering; - let lo = MockTransaction::eip1559(); - let hi = lo.next().inc_value(); + let lo = MockTransaction::eip1559().with_gas_limit(100_000); + let hi = lo.next().inc_price(); assert!(o.priority(&hi) > o.priority(&lo)); } diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index aa5891d52b28..176b20024f81 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -383,20 +383,22 @@ pub trait PoolTransaction: /// Returns the nonce for this transaction. fn nonce(&self) -> u64; - /// Calculates the cost that this transaction is allowed to consume: + /// Returns the cost that this transaction is allowed to consume: /// - /// For EIP-1559 transactions that is `feeCap x gasLimit + transferred_value` + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`. + /// For legacy transactions: `gas_price * gas_limit + tx_value`. fn cost(&self) -> U256; - /// Returns the effective gas price for this transaction. + /// Returns the gas cost for this transaction. /// - /// This is `priority + basefee`for EIP-1559 and `gasPrice` for legacy transactions. - fn effective_gas_price(&self) -> u128; + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit`. + /// For legacy transactions: `gas_price * gas_limit`. + fn gas_cost(&self) -> U256; /// Amount of gas that should be used in executing this transaction. This is paid up-front. fn gas_limit(&self) -> u64; - /// Returns the EIP-1559 Max base fee the caller is willing to pay. + /// Returns the EIP-1559 the maximum fee per gas the caller is willing to pay. /// /// For legacy transactions this is gas_price. /// @@ -439,11 +441,13 @@ pub struct PooledTransaction { /// EcRecovered transaction info pub(crate) transaction: TransactionSignedEcRecovered, - /// For EIP-1559 transactions that is `feeCap x gasLimit + transferred_value + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`. + /// For legacy transactions: `gas_price * gas_limit + tx_value`. pub(crate) cost: U256, - /// This is `priority + basefee`for EIP-1559 and `gasPrice` for legacy transactions. - pub(crate) effective_gas_price: u128, + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit`. + /// For legacy transactions: `gas_price * gas_limit`. + pub(crate) gas_cost: U256, } impl PooledTransaction { @@ -469,18 +473,20 @@ impl PoolTransaction for PooledTransaction { self.transaction.nonce() } - /// Calculates the cost that this transaction is allowed to consume: + /// Returns the cost that this transaction is allowed to consume: /// - /// For EIP-1559 transactions that is `feeCap x gasLimit + transferred_value` + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`. + /// For legacy transactions: `gas_price * gas_limit + tx_value`. fn cost(&self) -> U256 { self.cost } - /// Returns the effective gas price for this transaction. + /// Returns the gas cost for this transaction. /// - /// This is `priority + basefee`for EIP-1559 and `gasPrice` for legacy transactions. - fn effective_gas_price(&self) -> u128 { - self.effective_gas_price + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`. + /// For legacy transactions: `gas_price * gas_limit + tx_value`. + fn gas_cost(&self) -> U256 { + self.gas_cost } /// Amount of gas that should be used in executing this transaction. This is paid up-front. @@ -541,26 +547,14 @@ impl PoolTransaction for PooledTransaction { impl FromRecoveredTransaction for PooledTransaction { fn from_recovered_transaction(tx: TransactionSignedEcRecovered) -> Self { - let (cost, effective_gas_price) = match &tx.transaction { - Transaction::Legacy(t) => { - let cost = U256::from(t.gas_price) * U256::from(t.gas_limit) + U256::from(t.value); - let effective_gas_price = t.gas_price; - (cost, effective_gas_price) - } - Transaction::Eip2930(t) => { - let cost = U256::from(t.gas_price) * U256::from(t.gas_limit) + U256::from(t.value); - let effective_gas_price = t.gas_price; - (cost, effective_gas_price) - } - Transaction::Eip1559(t) => { - let cost = - U256::from(t.max_fee_per_gas) * U256::from(t.gas_limit) + U256::from(t.value); - let effective_gas_price = t.max_priority_fee_per_gas; - (cost, effective_gas_price) - } + let gas_cost = match &tx.transaction { + Transaction::Legacy(t) => U256::from(t.gas_price) * U256::from(t.gas_limit), + Transaction::Eip2930(t) => U256::from(t.gas_price) * U256::from(t.gas_limit), + Transaction::Eip1559(t) => U256::from(t.max_fee_per_gas) * U256::from(t.gas_limit), }; + let cost = gas_cost + U256::from(tx.value()); - PooledTransaction { transaction: tx, cost, effective_gas_price } + PooledTransaction { transaction: tx, cost, gas_cost } } } diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index 6abe0cb5237b..ee24c6266914 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -284,8 +284,8 @@ where } // Checks for max cost - if transaction.cost() > account.balance { - let cost = transaction.cost(); + let cost = transaction.cost(); + if cost > account.balance { return TransactionValidationOutcome::Invalid( transaction, InvalidTransactionError::InsufficientFunds { diff --git a/crates/transaction-pool/src/validate/mod.rs b/crates/transaction-pool/src/validate/mod.rs index db414530f647..1f7b7c5377e0 100644 --- a/crates/transaction-pool/src/validate/mod.rs +++ b/crates/transaction-pool/src/validate/mod.rs @@ -113,8 +113,6 @@ pub struct ValidPoolTransaction { pub transaction_id: TransactionId, /// Whether to propagate the transaction. pub propagate: bool, - /// Total cost of the transaction: `feeCap x gasLimit + transferredValue`. - pub cost: U256, /// Timestamp when this was added to the pool. pub timestamp: Instant, /// Where this transaction originated from. @@ -156,19 +154,30 @@ impl ValidPoolTransaction { self.transaction.nonce() } - /// Returns the EIP-1559 Max base fee the caller is willing to pay. + /// Returns the cost that this transaction is allowed to consume: /// - /// For legacy transactions this is gas_price. - pub fn max_fee_per_gas(&self) -> u128 { - self.transaction.max_fee_per_gas() + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit + tx_value`. + /// For legacy transactions: `gas_price * gas_limit + tx_value`. + pub fn cost(&self) -> U256 { + self.transaction.cost() + } + + /// Returns the gas cost for this transaction. + /// + /// For EIP-1559 transactions: `max_fee_per_gas * gas_limit`. + /// For legacy transactions: `gas_price * gas_limit`. + pub fn gas_cost(&self) -> U256 { + self.transaction.gas_cost() } /// Returns the EIP-1559 Max base fee the caller is willing to pay. - pub fn effective_gas_price(&self) -> u128 { - self.transaction.effective_gas_price() + /// + /// For legacy transactions this is `gas_price`. + pub fn max_fee_per_gas(&self) -> u128 { + self.transaction.max_fee_per_gas() } - /// Amount of gas that should be used in executing this transaction. This is paid up-front. + /// Maximum amount of gas that the transaction is allowed to consume. pub fn gas_limit(&self) -> u64 { self.transaction.gas_limit() } @@ -197,7 +206,6 @@ impl Clone for ValidPoolTransaction { transaction: self.transaction.clone(), transaction_id: self.transaction_id, propagate: self.propagate, - cost: self.cost, timestamp: self.timestamp, origin: self.origin, encoded_length: self.encoded_length, From 6d4fe271fc71609107c83d5e1bee702a140e6563 Mon Sep 17 00:00:00 2001 From: Paolo Facchinetti <51409747+paolofacchinetti@users.noreply.github.com> Date: Mon, 26 Jun 2023 18:36:44 +0200 Subject: [PATCH 188/216] fix: exclude non source files in .dockerignore (#3387) --- .dockerignore | 25 +++++++++++++++++++------ Dockerfile | 3 +++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.dockerignore b/.dockerignore index f350faa77756..0fbda481bfe0 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,19 @@ -/target -/book -/assets -/.github -/data -*.tar.gz \ No newline at end of file +# exclude everything +* + +# include source files +!/bin +!/crates +!/testing +!book.toml +!Cargo.lock +!Cargo.toml +!Cross.toml +!deny.toml +!Makefile + +# include dist directory, where the reth binary is located after compilation +!/dist + +# include licenses +!LICENSE-* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9289d0056e2b..47dd30adbd57 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,5 +33,8 @@ WORKDIR app # Copy reth over from the build stage COPY --from=builder /app/target/release/reth /usr/local/bin +# Copy licenses +COPY LICENSE-* . + EXPOSE 30303 30303/udp 9001 8545 8546 ENTRYPOINT ["/usr/local/bin/reth"] From 28897d7c1cfb024f1b711c555279d3567ce8be88 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Jun 2023 19:20:28 +0200 Subject: [PATCH 189/216] perf: fetch receipts and transactions concurrently (#3406) --- crates/rpc/rpc/src/eth/api/fees.rs | 51 ++++++++++++++---------------- crates/rpc/rpc/src/eth/cache.rs | 13 ++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/crates/rpc/rpc/src/eth/api/fees.rs b/crates/rpc/rpc/src/eth/api/fees.rs index 630c0c2305d3..5122fb0627fb 100644 --- a/crates/rpc/rpc/src/eth/api/fees.rs +++ b/crates/rpc/rpc/src/eth/api/fees.rs @@ -131,36 +131,31 @@ where percentiles: &[f64], header: &SealedHeader, ) -> Result, EthApiError> { - let Some(receipts) = - self.cache().get_receipts(header.hash).await? else { - // If there are no receipts, then we do not have all info on the block - return Err(EthApiError::InvalidBlockRange) - }; - let Some(mut transactions): Option> = self + let (transactions, receipts) = self .cache() - .get_block_transactions(header.hash).await? - .map(|txs|txs - .into_iter() - .zip(receipts.into_iter()) - .scan(0, |previous_gas, (tx, receipt)| { - // Convert the cumulative gas used in the receipts - // to the gas usage by the transaction - // - // While we will sum up the gas again later, it is worth - // noting that the order of the transactions will be different, - // so the sum will also be different for each receipt. - let gas_used = receipt.cumulative_gas_used - *previous_gas; - *previous_gas = receipt.cumulative_gas_used; - - Some(TxGasAndReward { - gas_used, - reward: tx.effective_gas_tip(header.base_fee_per_gas).unwrap_or_default(), - }) + .get_transactions_and_receipts(header.hash) + .await? + .ok_or(EthApiError::InvalidBlockRange)?; + + let mut transactions = transactions + .into_iter() + .zip(receipts.into_iter()) + .scan(0, |previous_gas, (tx, receipt)| { + // Convert the cumulative gas used in the receipts + // to the gas usage by the transaction + // + // While we will sum up the gas again later, it is worth + // noting that the order of the transactions will be different, + // so the sum will also be different for each receipt. + let gas_used = receipt.cumulative_gas_used - *previous_gas; + *previous_gas = receipt.cumulative_gas_used; + + Some(TxGasAndReward { + gas_used, + reward: tx.effective_gas_tip(header.base_fee_per_gas).unwrap_or_default(), }) - .collect()) else { - // If there are no transactions, then we do not have all info on the block - return Err(EthApiError::InvalidBlockRange) - }; + }) + .collect::>(); // Sort the transactions by their rewards in ascending order transactions.sort_by_key(|tx| tx.reward); diff --git a/crates/rpc/rpc/src/eth/cache.rs b/crates/rpc/rpc/src/eth/cache.rs index 8a580f36e290..3adf1b78924a 100644 --- a/crates/rpc/rpc/src/eth/cache.rs +++ b/crates/rpc/rpc/src/eth/cache.rs @@ -179,6 +179,19 @@ impl EthStateCache { rx.await.map_err(|_| ProviderError::CacheServiceUnavailable)? } + /// Fetches both transactions and receipts for the given block hash. + pub(crate) async fn get_transactions_and_receipts( + &self, + block_hash: H256, + ) -> Result, Vec)>> { + let transactions = self.get_block_transactions(block_hash); + let receipts = self.get_receipts(block_hash); + + let (transactions, receipts) = futures::try_join!(transactions, receipts)?; + + Ok(transactions.zip(receipts)) + } + /// Requests the [Receipt] for the block hash /// /// Returns `None` if the block was not found. From 112313e55a7c82413b359553af4f0a7bc423634c Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Jun 2023 19:25:46 +0200 Subject: [PATCH 190/216] feat: add pending block with receipts (#3404) Co-authored-by: Bjerg --- crates/blockchain-tree/src/shareable.rs | 7 +++++++ crates/interfaces/src/blockchain_tree/mod.rs | 6 ++++++ .../storage/provider/src/providers/database/mod.rs | 4 ++++ .../provider/src/providers/database/provider.rs | 4 ++++ crates/storage/provider/src/providers/mod.rs | 8 ++++++++ crates/storage/provider/src/test_utils/mock.rs | 12 ++++++++---- crates/storage/provider/src/test_utils/noop.rs | 4 ++++ crates/storage/provider/src/traits/block.rs | 5 ++++- 8 files changed, 45 insertions(+), 5 deletions(-) diff --git a/crates/blockchain-tree/src/shareable.rs b/crates/blockchain-tree/src/shareable.rs index 759ca402a7bf..d23dff99544e 100644 --- a/crates/blockchain-tree/src/shareable.rs +++ b/crates/blockchain-tree/src/shareable.rs @@ -154,6 +154,13 @@ impl BlockchainTreeViewer self.tree.read().pending_block().cloned() } + fn pending_block_and_receipts(&self) -> Option<(SealedBlock, Vec)> { + let tree = self.tree.read(); + let pending_block = tree.pending_block()?.clone(); + let receipts = tree.receipts_by_block_hash(pending_block.hash)?.to_vec(); + Some((pending_block, receipts)) + } + fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option> { let tree = self.tree.read(); Some(tree.receipts_by_block_hash(block_hash)?.to_vec()) diff --git a/crates/interfaces/src/blockchain_tree/mod.rs b/crates/interfaces/src/blockchain_tree/mod.rs index c1b182b24cfa..8b1eda0a3a61 100644 --- a/crates/interfaces/src/blockchain_tree/mod.rs +++ b/crates/interfaces/src/blockchain_tree/mod.rs @@ -216,6 +216,12 @@ pub trait BlockchainTreeViewer: Send + Sync { self.block_by_hash(self.pending_block_num_hash()?.hash) } + /// Returns the pending block and its receipts in one call. + /// + /// This exists to prevent a potential data race if the pending block changes in between + /// [Self::pending_block] and [Self::pending_receipts] calls. + fn pending_block_and_receipts(&self) -> Option<(SealedBlock, Vec)>; + /// Returns the pending receipts if there is one. fn pending_receipts(&self) -> Option> { self.receipts_by_block_hash(self.pending_block_num_hash()?.hash) diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index 6109a6e6384b..ebfefd57c1e4 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -184,6 +184,10 @@ impl BlockReader for ProviderFactory { self.provider()?.pending_block() } + fn pending_block_and_receipts(&self) -> Result)>> { + self.provider()?.pending_block_and_receipts() + } + fn ommers(&self, id: BlockHashOrNumber) -> Result>> { self.provider()?.ommers(id) } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 7d6b389c4250..c068ef23ab68 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1005,6 +1005,10 @@ impl<'this, TX: DbTx<'this>> BlockReader for DatabaseProvider<'this, TX> { Ok(None) } + fn pending_block_and_receipts(&self) -> Result)>> { + Ok(None) + } + fn ommers(&self, id: BlockHashOrNumber) -> Result>> { if let Some(number) = self.convert_hash_or_number(id)? { // If the Paris (Merge) hardfork block is known and block is after it, return empty diff --git a/crates/storage/provider/src/providers/mod.rs b/crates/storage/provider/src/providers/mod.rs index bdb9bc4fc4f4..295d9fcc7f3d 100644 --- a/crates/storage/provider/src/providers/mod.rs +++ b/crates/storage/provider/src/providers/mod.rs @@ -233,6 +233,10 @@ where Ok(self.tree.pending_block()) } + fn pending_block_and_receipts(&self) -> Result)>> { + Ok(self.tree.pending_block_and_receipts()) + } + fn ommers(&self, id: BlockHashOrNumber) -> Result>> { self.database.provider()?.ommers(id) } @@ -628,6 +632,10 @@ where self.tree.pending_block_num_hash() } + fn pending_block_and_receipts(&self) -> Option<(SealedBlock, Vec)> { + self.tree.pending_block_and_receipts() + } + fn receipts_by_block_hash(&self, block_hash: BlockHash) -> Option> { self.tree.receipts_by_block_hash(block_hash) } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 825bee816c7e..98a7ee7f1731 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -209,10 +209,6 @@ impl TransactionsProvider for MockEthProvider { Ok(map.into_values().collect()) } - fn senders_by_tx_range(&self, _range: impl RangeBounds) -> Result> { - unimplemented!() - } - fn transactions_by_tx_range( &self, _range: impl RangeBounds, @@ -220,6 +216,10 @@ impl TransactionsProvider for MockEthProvider { unimplemented!() } + fn senders_by_tx_range(&self, _range: impl RangeBounds) -> Result> { + unimplemented!() + } + fn transaction_sender(&self, _id: TxNumber) -> Result> { unimplemented!() } @@ -324,6 +324,10 @@ impl BlockReader for MockEthProvider { Ok(None) } + fn pending_block_and_receipts(&self) -> Result)>> { + Ok(None) + } + fn ommers(&self, _id: BlockHashOrNumber) -> Result>> { Ok(None) } diff --git a/crates/storage/provider/src/test_utils/noop.rs b/crates/storage/provider/src/test_utils/noop.rs index 9e7f629fb414..31ece7b38b42 100644 --- a/crates/storage/provider/src/test_utils/noop.rs +++ b/crates/storage/provider/src/test_utils/noop.rs @@ -63,6 +63,10 @@ impl BlockReader for NoopProvider { Ok(None) } + fn pending_block_and_receipts(&self) -> Result)>> { + Ok(None) + } + fn ommers(&self, _id: BlockHashOrNumber) -> Result>> { Ok(None) } diff --git a/crates/storage/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index 079e12a086be..8632822d2adb 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -6,7 +6,7 @@ use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::Result; use reth_primitives::{ Block, BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, BlockWithSenders, Header, - SealedBlock, SealedHeader, H256, + Receipt, SealedBlock, SealedHeader, H256, }; /// A helper enum that represents the origin of the requested block. @@ -72,6 +72,9 @@ pub trait BlockReader: /// and the caller does not know the hash. fn pending_block(&self) -> Result>; + /// Returns the pending block and receipts if available. + fn pending_block_and_receipts(&self) -> Result)>>; + /// Returns the ommers/uncle headers of the given block from the database. /// /// Returns `None` if block is not found. From 682dfc11a28168f8d63b660f0dea005a9c33de35 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Jun 2023 19:40:12 +0200 Subject: [PATCH 191/216] feat: track invalid headers hit counter (#3408) --- .../beacon/src/engine/invalid_headers.rs | 91 +++++++++++++++---- crates/consensus/beacon/src/engine/mod.rs | 2 +- 2 files changed, 72 insertions(+), 21 deletions(-) diff --git a/crates/consensus/beacon/src/engine/invalid_headers.rs b/crates/consensus/beacon/src/engine/invalid_headers.rs index 129cfefa6f7a..a07908cddeb6 100644 --- a/crates/consensus/beacon/src/engine/invalid_headers.rs +++ b/crates/consensus/beacon/src/engine/invalid_headers.rs @@ -1,29 +1,22 @@ -use std::sync::Arc; - use reth_metrics::{ metrics::{self, Counter, Gauge}, Metrics, }; use reth_primitives::{Header, SealedHeader, H256}; use schnellru::{ByLength, LruMap}; +use std::sync::Arc; use tracing::warn; -/// Metrics for the invalid headers cache. -#[derive(Metrics)] -#[metrics(scope = "invalid_header_cache")] -struct InvalidHeaderCacheMetrics { - /// The total number of invalid headers in the cache. - invalid_headers: Gauge, - /// The number of inserts with a known ancestor. - known_ancestor_inserts: Counter, - /// The number of unique invalid header inserts (i.e. without a known ancestor). - unique_inserts: Counter, -} +/// The max hit counter for invalid headers in the cache before it is forcefully evicted. +/// +/// In other words, if a header is referenced more than this number of times, it will be evicted to +/// allow for reprocessing. +const INVALID_HEADER_HIT_EVICTION_THRESHOLD: u8 = 128; /// Keeps track of invalid headers. pub(crate) struct InvalidHeaderCache { /// This maps a header hash to a reference to its invalid ancestor. - headers: LruMap>, + headers: LruMap, /// Metrics for the cache. metrics: InvalidHeaderCacheMetrics, } @@ -33,9 +26,26 @@ impl InvalidHeaderCache { Self { headers: LruMap::new(ByLength::new(max_length)), metrics: Default::default() } } + fn insert_entry(&mut self, hash: H256, header: Arc
) { + self.headers.insert(hash, HeaderEntry { header, hit_count: 0 }); + } + /// Returns the invalid ancestor's header if it exists in the cache. - pub(crate) fn get(&mut self, hash: &H256) -> Option<&mut Arc
> { - self.headers.get(hash) + /// + /// If this is called, the hit count for the entry is incremented. + /// If the hit count exceeds the threshold, the entry is evicted and `None` is returned. + pub(crate) fn get(&mut self, hash: &H256) -> Option> { + { + let entry = self.headers.get(hash)?; + entry.hit_count += 1; + if entry.hit_count < INVALID_HEADER_HIT_EVICTION_THRESHOLD { + return Some(entry.header.clone()) + } + } + // if we get here, the entry has been hit too many times, so we evict it + self.headers.remove(hash); + self.metrics.hit_evictions.increment(1); + None } /// Inserts an invalid block into the cache, with a given invalid ancestor. @@ -44,9 +54,9 @@ impl InvalidHeaderCache { header_hash: H256, invalid_ancestor: Arc
, ) { - if self.headers.get(&header_hash).is_none() { + if self.get(&header_hash).is_none() { warn!(target: "consensus::engine", hash=?header_hash, ?invalid_ancestor, "Bad block with existing invalid ancestor"); - self.headers.insert(header_hash, invalid_ancestor); + self.insert_entry(header_hash, invalid_ancestor); // update metrics self.metrics.known_ancestor_inserts.increment(1); @@ -56,11 +66,11 @@ impl InvalidHeaderCache { /// Inserts an invalid ancestor into the map. pub(crate) fn insert(&mut self, invalid_ancestor: SealedHeader) { - if self.headers.get(&invalid_ancestor.hash).is_none() { + if self.get(&invalid_ancestor.hash).is_none() { let hash = invalid_ancestor.hash; let header = invalid_ancestor.unseal(); warn!(target: "consensus::engine", ?hash, ?header, "Bad block with hash"); - self.headers.insert(hash, Arc::new(header)); + self.insert_entry(hash, Arc::new(header)); // update metrics self.metrics.unique_inserts.increment(1); @@ -68,3 +78,44 @@ impl InvalidHeaderCache { } } } + +struct HeaderEntry { + /// Keeps track how many times this header has been hit. + hit_count: u8, + /// The actually header entry + header: Arc
, +} + +/// Metrics for the invalid headers cache. +#[derive(Metrics)] +#[metrics(scope = "invalid_header_cache")] +struct InvalidHeaderCacheMetrics { + /// The total number of invalid headers in the cache. + invalid_headers: Gauge, + /// The number of inserts with a known ancestor. + known_ancestor_inserts: Counter, + /// The number of unique invalid header inserts (i.e. without a known ancestor). + unique_inserts: Counter, + /// The number of times a header was evicted from the cache because it was hit too many times. + hit_evictions: Counter, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hit_eviction() { + let mut cache = InvalidHeaderCache::new(10); + let header = Header::default().seal_slow(); + cache.insert(header.clone()); + assert_eq!(cache.headers.get(&header.hash).unwrap().hit_count, 0); + + for hit in 1..INVALID_HEADER_HIT_EVICTION_THRESHOLD { + assert!(cache.get(&header.hash).is_some()); + assert_eq!(cache.headers.get(&header.hash).unwrap().hit_count, hit); + } + + assert!(cache.get(&header.hash).is_none()); + } +} diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index fd7a90047f79..9a23f31b4a56 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -466,7 +466,7 @@ where head: H256, ) -> Option { // check if the check hash was previously marked as invalid - let header = { self.invalid_headers.get(&check)?.clone() }; + let header = self.invalid_headers.get(&check)?; // populate the latest valid hash field let status = self.prepare_invalid_response(header.parent_hash); From 007c04e29e06692709d54490f16c84bb716dd2ed Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 26 Jun 2023 19:41:52 +0200 Subject: [PATCH 192/216] ci: split checks across pr/merge group/main (#3405) --- .github/workflows/bench.yml | 2 ++ .github/workflows/ci.yml | 3 --- .github/workflows/fuzz.yml | 3 --- .github/workflows/integration.yml | 7 +++---- .github/workflows/unit.yml | 3 --- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 2a1ad02ea9ee..c7b35a38e87d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -14,6 +14,8 @@ name: bench jobs: iai: runs-on: ubuntu-latest + # Only run benchmarks in merge groups + if: github.event_name != 'pull_request' steps: - name: Checkout main sources uses: actions/checkout@v3 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ce139eb29da0..2cfb7e690395 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,4 @@ on: - push: - branches: - - main pull_request: merge_group: diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml index 9dbc7568ca64..e2005ee504cf 100644 --- a/.github/workflows/fuzz.yml +++ b/.github/workflows/fuzz.yml @@ -1,7 +1,4 @@ on: - push: - branches: - - main pull_request: merge_group: diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index fcff216a8462..692d167e98d7 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -1,7 +1,4 @@ on: - push: - branches: - - main pull_request: merge_group: @@ -66,6 +63,8 @@ jobs: sync: name: sync / 100k blocks + # Only run sync tests in merge groups + if: github.event_name == 'merge_group' runs-on: ubuntu-latest env: RUST_LOG: info,sync=error @@ -95,7 +94,7 @@ jobs: if: always() name: integration success runs-on: ubuntu-latest - needs: [test, sync] + needs: [test] steps: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 1204ffa5b742..7873ee19707f 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -1,7 +1,4 @@ on: - push: - branches: - - main pull_request: merge_group: From f31a2e325fa5347bc505315307b9e513ad671fdf Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Jun 2023 19:52:33 +0200 Subject: [PATCH 193/216] fix: print auth server addr (#3410) --- bin/reth/src/args/rpc_server_args.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/bin/reth/src/args/rpc_server_args.rs b/bin/reth/src/args/rpc_server_args.rs index 8a6d9162fc6d..c08aebe0e54d 100644 --- a/bin/reth/src/args/rpc_server_args.rs +++ b/bin/reth/src/args/rpc_server_args.rs @@ -5,7 +5,7 @@ use clap::{ builder::{PossibleValue, TypedValueParser}, Arg, Args, Command, }; -use futures::{FutureExt, TryFutureExt}; +use futures::TryFutureExt; use reth_network_api::{NetworkInfo, Peers}; use reth_provider::{ BlockReaderIdExt, CanonStateSubscriptions, EvmEnvProvider, HeaderProvider, StateProviderFactory, @@ -287,8 +287,10 @@ impl RpcServerArgs { handle }); - let launch_auth = auth_module.start_server(auth_config).inspect(|_| { - info!(target: "reth::cli", "RPC auth server started"); + let launch_auth = auth_module.start_server(auth_config).map_ok(|handle| { + let addr = handle.local_addr(); + info!(target: "reth::cli", url=%addr, "RPC auth server started"); + handle }); // launch servers concurrently From 5a1d58aa22418e5e82098fd7cd57dd3d52dc7b05 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Mon, 26 Jun 2023 20:21:38 +0200 Subject: [PATCH 194/216] feat(book): add consensus layer explanation in book (#3413) --- book/run/mainnet.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/book/run/mainnet.md b/book/run/mainnet.md index bc1826d30971..7aca9aa4eaee 100644 --- a/book/run/mainnet.md +++ b/book/run/mainnet.md @@ -1,8 +1,17 @@ # Running Reth on Ethereum Mainnet or testnets -Reth is an [_execution client_](https://ethereum.org/en/developers/docs/nodes-and-clients/#execution-clients). After Ethereum's transition to Proof of Stake (aka the Merge) it became required to run a [_consensus client_](https://ethereum.org/en/developers/docs/nodes-and-clients/#consensus-clients) along your execution client in order to sync into any "post-Merge" network. This is because the Ethereum execution layer now outsources consensus to a separate component, known as the consensus client. +Reth is an [_execution client_](https://ethereum.org/en/developers/docs/nodes-and-clients/#execution-clients). After Ethereum's transition to Proof of Stake (aka the Merge) it became required to run a [_consensus client_](https://ethereum.org/en/developers/docs/nodes-and-clients/#consensus-clients) along your execution client in order to sync into any "post-Merge" network. This is because the Ethereum execution layer now outsources consensus to a separate component, known as the consensus client. -There are multiple choices of consensus clients, but in this guide we will assume usage of Lighthouse 🦀. +Consensus clients decide what blocks are part of the chain, while execution clients only validate that transactions and blocks are valid in themselves and with respect to the world state. In other words, execution clients execute blocks and transactions and check their validity, while consensus clients determine which valid blocks should be part of the chain. Therefore, running a consensus client in parallel with the execution client is necessary to ensure synchronization and participation in the network. + +By running both an execution client like Reth and a consensus client, such as Lighthouse 🦀 (which we will assume for this guide), you can effectively contribute to the Ethereum network and participate in the consensus process, even if you don't intend to run validators. + +| Client | Role | +|-------------|--------------------------------------------------| +| Execution | Validates transactions and blocks | +| | (checks their validity and global state) | +| Consensus | Determines which blocks are part of the chain | +| | (makes consensus decisions) | ## Running the Reth Node @@ -16,7 +25,7 @@ RUST_LOG=info reth node > Note that this command will not open any HTTP/WS ports by default. You can change this by adding the `--http`, `--ws` flags, respectively and using the `--http.api` and `--ws.api` flags to enable various [JSON-RPC APIs](../jsonrpc/intro.md). For more commands, see the [`reth node` CLI reference](../cli/node.md). -The EL <> CL communication happens over the [Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md), which is by default exposed at `http://localhost:8551`. The connection is authenticated over JWT using a JWT secret which is auto-generated by Reth and placed in a file called `jwt.hex` in the data directory, which on Linux by default is `$HOME/.local/share/reth/` (`/Users//Library/Application Support/reth/mainnet/jwt.hex` in Mac). +The EL <> CL communication happens over the [Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md), which is by default exposed at `http://localhost:8551`. The connection is authenticated over JWT using a JWT secret which is auto-generated by Reth and placed in a file called `jwt.hex` in the data directory, which on Linux by default is `$HOME/.local/share/reth/` (`/Users//Library/Application Support/reth/mainnet/jwt.hex` in Mac). You can override this path using the `--authrpc.jwtsecret` option. You MUST use the same JWT secret in BOTH Reth and the chosen Consensus Layer. If you want to override the address or port, you can use the `--authrpc.addr` and `--authrpc.port` options, respectively. @@ -29,7 +38,7 @@ RUST_LOG=info reth node \ --authrpc.port 9999 ``` -At this point, our Reth node has started discovery, and even discovered some new peers. But it will not start syncing until you spin up the consensus layer! +At this point, our Reth node has started discovery, and even discovered some new peers. But it will not start syncing until you spin up the consensus layer! ## Running the Consensus Layer From 90a99476cdd161a46c330eea5f72eec2c993483e Mon Sep 17 00:00:00 2001 From: Bjerg Date: Mon, 26 Jun 2023 20:55:16 +0200 Subject: [PATCH 195/216] ci: report coverage by component (#3273) --- codecov.yml | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/codecov.yml b/codecov.yml index 2c6836c4640f..267b6ebec493 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,4 +8,59 @@ coverage: github_checks: annotations: false comment: + layout: "reach, files, flags, components" require_changes: true +component_management: + individual_components: + - component_id: reth_binary + name: reth binary + paths: + - bin/** + - crates/config/** + - crates/metrics/** + - crates/tracing/** + - component_id: blockchain_tree + name: blockchain tree + paths: + - crates/blockchain-tree/** + - component_id: staged_sync + name: pipeline + paths: + - crates/stages/** + - crates/staged-sync/** + - component_id: storage + name: storage (db) + paths: + - crates/storage/** + - component_id: trie + name: trie + paths: + - crates/trie/** + - component_id: txpool + name: txpool + paths: + - crates/transaction-pool/** + - component_id: networking + name: networking + paths: + - crates/net/** + - component_id: rpc + name: rpc + paths: + - crates/rpc/** + - component_id: core + name: consensus/evm + paths: + - crates/revm/** + - crates/consensus/** + - component_id: builder + name: payload builder + paths: + - crates/payload/** + - component_id: primitives + name: primitives + paths: + - crates/primitives/** + - crates/tasks/** + - crates/rlp/** + - crates/interfaces/** \ No newline at end of file From 9721ecfc6d403efd6004f7745f13bbd4c5ef49e5 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Mon, 26 Jun 2023 21:55:17 +0200 Subject: [PATCH 196/216] feat: add getBlockReceipts (#3321) --- crates/rpc/rpc-api/src/eth.rs | 7 + crates/rpc/rpc/src/eth/api/block.rs | 50 +++++- crates/rpc/rpc/src/eth/api/server.rs | 9 ++ crates/rpc/rpc/src/eth/api/transactions.rs | 178 +++++++++++---------- crates/rpc/rpc/src/eth/cache.rs | 13 ++ crates/rpc/rpc/src/eth/filter.rs | 20 +-- 6 files changed, 176 insertions(+), 101 deletions(-) diff --git a/crates/rpc/rpc-api/src/eth.rs b/crates/rpc/rpc-api/src/eth.rs index 9a66557c83ce..6ca403dd7d11 100644 --- a/crates/rpc/rpc-api/src/eth.rs +++ b/crates/rpc/rpc-api/src/eth.rs @@ -71,6 +71,13 @@ pub trait EthApi { number: BlockNumberOrTag, ) -> RpcResult>; + /// Returns all transaction receipts for a given block. + #[method(name = "getBlockReceipts")] + async fn block_receipts( + &self, + number: BlockNumberOrTag, + ) -> RpcResult>>; + /// Returns an uncle block of the given block and index. #[method(name = "getUncleByBlockHashAndIndex")] async fn uncle_by_block_hash_and_index( diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 472ac9bd2a89..21b557b032dd 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -1,12 +1,15 @@ //! Contains RPC handler implementations specific to blocks. use crate::{ - eth::error::{EthApiError, EthResult}, + eth::{ + api::transactions::build_transaction_receipt_with_block_receipts, + error::{EthApiError, EthResult}, + }, EthApi, }; -use reth_primitives::BlockId; +use reth_primitives::{BlockId, BlockNumberOrTag, TransactionMeta}; use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProviderFactory}; -use reth_rpc_types::{Block, Index, RichBlock}; +use reth_rpc_types::{Block, Index, RichBlock, TransactionReceipt}; impl EthApi where @@ -46,6 +49,47 @@ where Ok(uncle) } + /// Returns all transaction receipts in the block. + /// + /// Returns `None` if the block wasn't found. + pub(crate) async fn block_receipts( + &self, + number: BlockNumberOrTag, + ) -> EthResult>> { + let mut block_and_receipts = None; + + if number.is_pending() { + block_and_receipts = self.provider().pending_block_and_receipts()?; + } else if let Some(block_hash) = self.provider().block_hash_for_id(number.into())? { + block_and_receipts = self.cache().get_block_and_receipts(block_hash).await?; + } + + if let Some((block, receipts)) = block_and_receipts { + let block_number = block.number; + let base_fee = block.base_fee_per_gas; + let block_hash = block.hash; + let receipts = block + .body + .into_iter() + .zip(receipts.clone()) + .enumerate() + .map(|(idx, (tx, receipt))| { + let meta = TransactionMeta { + tx_hash: tx.hash, + index: idx as u64, + block_hash, + block_number, + base_fee, + }; + build_transaction_receipt_with_block_receipts(tx, meta, receipt, &receipts) + }) + .collect::>>(); + return receipts.map(Some) + } + + Ok(None) + } + /// Returns the number transactions in the given block. /// /// Returns `None` if the block does not exist diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index 90965b8a865a..acca88323cdc 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -122,6 +122,15 @@ where Ok(EthApi::ommers(self, number)?.map(|ommers| U256::from(ommers.len()))) } + /// Handler for: `eth_getBlockReceipts` + async fn block_receipts( + &self, + number: BlockNumberOrTag, + ) -> Result>> { + trace!(target: "rpc::eth", ?number, "Serving eth_getBlockReceipts"); + Ok(EthApi::block_receipts(self, number).await?) + } + /// Handler for: `eth_getUncleByBlockHashAndIndex` async fn uncle_by_block_hash_and_index( &self, diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index ece5457e565d..fe4eca4be8f8 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -649,6 +649,28 @@ where // === impl EthApi === +impl EthApi +where + Provider: BlockReaderIdExt + StateProviderFactory + EvmEnvProvider + 'static, +{ + /// Helper function for `eth_getTransactionReceipt` + /// + /// Returns the receipt + pub(crate) async fn build_transaction_receipt( + &self, + tx: TransactionSigned, + meta: TransactionMeta, + receipt: Receipt, + ) -> EthResult { + // get all receipts for the block + let all_receipts = match self.cache().get_receipts(meta.block_hash).await? { + Some(recpts) => recpts, + None => return Err(EthApiError::UnknownBlockNumber), + }; + build_transaction_receipt_with_block_receipts(tx, meta, receipt, &all_receipts) + } +} + impl EthApi where Pool: TransactionPool + 'static, @@ -699,88 +721,6 @@ where Ok(None) } - - /// Helper function for `eth_getTransactionReceipt` - /// - /// Returns the receipt - pub(crate) async fn build_transaction_receipt( - &self, - tx: TransactionSigned, - meta: TransactionMeta, - receipt: Receipt, - ) -> EthResult { - let transaction = - tx.clone().into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?; - - // get all receipts for the block - let all_receipts = match self.cache().get_receipts(meta.block_hash).await? { - Some(recpts) => recpts, - None => return Err(EthApiError::UnknownBlockNumber), - }; - - // get the previous transaction cumulative gas used - let gas_used = if meta.index == 0 { - receipt.cumulative_gas_used - } else { - let prev_tx_idx = (meta.index - 1) as usize; - all_receipts - .get(prev_tx_idx) - .map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used) - .unwrap_or_default() - }; - - let mut res_receipt = TransactionReceipt { - transaction_hash: Some(meta.tx_hash), - transaction_index: Some(U256::from(meta.index)), - block_hash: Some(meta.block_hash), - block_number: Some(U256::from(meta.block_number)), - from: transaction.signer(), - to: None, - cumulative_gas_used: U256::from(receipt.cumulative_gas_used), - gas_used: Some(U256::from(gas_used)), - contract_address: None, - logs: Vec::with_capacity(receipt.logs.len()), - effective_gas_price: U128::from(transaction.effective_gas_price(meta.base_fee)), - transaction_type: tx.transaction.tx_type().into(), - // TODO pre-byzantium receipts have a post-transaction state root - state_root: None, - logs_bloom: receipt.bloom_slow(), - status_code: if receipt.success { Some(U64::from(1)) } else { Some(U64::from(0)) }, - }; - - match tx.transaction.kind() { - Create => { - res_receipt.contract_address = - Some(create_address(transaction.signer(), tx.transaction.nonce())); - } - Call(addr) => { - res_receipt.to = Some(*addr); - } - } - - // get number of logs in the block - let mut num_logs = 0; - for prev_receipt in all_receipts.iter().take(meta.index as usize) { - num_logs += prev_receipt.logs.len(); - } - - for (tx_log_idx, log) in receipt.logs.into_iter().enumerate() { - let rpclog = Log { - address: log.address, - topics: log.topics, - data: log.data, - block_hash: Some(meta.block_hash), - block_number: Some(U256::from(meta.block_number)), - transaction_hash: Some(meta.tx_hash), - transaction_index: Some(U256::from(meta.index)), - log_index: Some(U256::from(num_logs + tx_log_idx)), - removed: false, - }; - res_receipt.logs.push(rpclog); - } - - Ok(res_receipt) - } } /// Represents from where a transaction was fetched. #[derive(Debug, Clone, Eq, PartialEq)] @@ -871,6 +811,80 @@ impl From for Transaction { } } +/// Helper function to construct a transaction receipt +pub(crate) fn build_transaction_receipt_with_block_receipts( + tx: TransactionSigned, + meta: TransactionMeta, + receipt: Receipt, + all_receipts: &[Receipt], +) -> EthResult { + let transaction = + tx.clone().into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?; + + // get the previous transaction cumulative gas used + let gas_used = if meta.index == 0 { + receipt.cumulative_gas_used + } else { + let prev_tx_idx = (meta.index - 1) as usize; + all_receipts + .get(prev_tx_idx) + .map(|prev_receipt| receipt.cumulative_gas_used - prev_receipt.cumulative_gas_used) + .unwrap_or_default() + }; + + let mut res_receipt = TransactionReceipt { + transaction_hash: Some(meta.tx_hash), + transaction_index: Some(U256::from(meta.index)), + block_hash: Some(meta.block_hash), + block_number: Some(U256::from(meta.block_number)), + from: transaction.signer(), + to: None, + cumulative_gas_used: U256::from(receipt.cumulative_gas_used), + gas_used: Some(U256::from(gas_used)), + contract_address: None, + logs: Vec::with_capacity(receipt.logs.len()), + effective_gas_price: U128::from(transaction.effective_gas_price(meta.base_fee)), + transaction_type: tx.transaction.tx_type().into(), + // TODO pre-byzantium receipts have a post-transaction state root + state_root: None, + logs_bloom: receipt.bloom_slow(), + status_code: if receipt.success { Some(U64::from(1)) } else { Some(U64::from(0)) }, + }; + + match tx.transaction.kind() { + Create => { + res_receipt.contract_address = + Some(create_address(transaction.signer(), tx.transaction.nonce())); + } + Call(addr) => { + res_receipt.to = Some(*addr); + } + } + + // get number of logs in the block + let mut num_logs = 0; + for prev_receipt in all_receipts.iter().take(meta.index as usize) { + num_logs += prev_receipt.logs.len(); + } + + for (tx_log_idx, log) in receipt.logs.into_iter().enumerate() { + let rpclog = Log { + address: log.address, + topics: log.topics, + data: log.data, + block_hash: Some(meta.block_hash), + block_number: Some(U256::from(meta.block_number)), + transaction_hash: Some(meta.tx_hash), + transaction_index: Some(U256::from(meta.index)), + log_index: Some(U256::from(num_logs + tx_log_idx)), + removed: false, + }; + res_receipt.logs.push(rpclog); + } + + Ok(res_receipt) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc/src/eth/cache.rs b/crates/rpc/rpc/src/eth/cache.rs index 3adf1b78924a..5e84fcd6934e 100644 --- a/crates/rpc/rpc/src/eth/cache.rs +++ b/crates/rpc/rpc/src/eth/cache.rs @@ -201,6 +201,19 @@ impl EthStateCache { rx.await.map_err(|_| ProviderError::CacheServiceUnavailable)? } + /// Fetches both receipts and block for the given block hash. + pub(crate) async fn get_block_and_receipts( + &self, + block_hash: H256, + ) -> Result)>> { + let block = self.get_sealed_block(block_hash); + let receipts = self.get_receipts(block_hash); + + let (block, receipts) = futures::try_join!(block, receipts)?; + + Ok(block.zip(receipts)) + } + /// Requests the evm env config for the block hash. /// /// Returns an error if the corresponding header (required for populating the envs) was not diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index 891f24f4758e..47bddaa98eeb 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -9,7 +9,7 @@ use crate::{ }; use async_trait::async_trait; use jsonrpsee::{core::RpcResult, server::IdProvider}; -use reth_primitives::{BlockHashOrNumber, Receipt, SealedBlock, H256}; +use reth_primitives::{BlockHashOrNumber, Receipt, SealedBlock}; use reth_provider::{BlockIdReader, BlockReader, EvmEnvProvider}; use reth_rpc_api::EthFilterApiServer; use reth_rpc_types::{Filter, FilterBlockOption, FilterChanges, FilterId, FilteredParams, Log}; @@ -283,7 +283,8 @@ where FilterBlockOption::AtBlockHash(block_hash) => { let mut all_logs = Vec::new(); // all matching logs in the block, if it exists - if let Some((block, receipts)) = self.block_and_receipts_by_hash(block_hash).await? + if let Some((block, receipts)) = + self.eth_cache.get_block_and_receipts(block_hash).await? { let filter = FilteredParams::new(Some(filter)); logs_utils::append_matching_block_logs( @@ -343,20 +344,7 @@ where None => return Ok(None), }; - self.block_and_receipts_by_hash(block_hash).await - } - - /// Fetches both receipts and block for the given block hash. - async fn block_and_receipts_by_hash( - &self, - block_hash: H256, - ) -> EthResult)>> { - let block = self.eth_cache.get_sealed_block(block_hash); - let receipts = self.eth_cache.get_receipts(block_hash); - - let (block, receipts) = futures::try_join!(block, receipts)?; - - Ok(block.zip(receipts)) + Ok(self.eth_cache.get_block_and_receipts(block_hash).await?) } /// Returns all logs in the given _inclusive_ range that match the filter From d3d44fd46f03481ae0ab42adc5e6334ee64cd3b5 Mon Sep 17 00:00:00 2001 From: Thomas Coratger <60488569+tcoratger@users.noreply.github.com> Date: Tue, 27 Jun 2023 01:21:34 +0200 Subject: [PATCH 197/216] feat(cli): clean up log level features (#3414) --- .github/workflows/integration.yml | 4 ++-- bin/reth/Cargo.toml | 6 +++++- bin/reth/src/lib.rs | 8 ++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 692d167e98d7..d4375665de0b 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -84,7 +84,7 @@ jobs: - name: Run sync (${{ matrix.profile }}) run: | - cargo run --profile release --features jemalloc,only-info-logs \ + cargo run --profile release --features jemalloc,min-error-logs \ --bin reth -- node \ --debug.tip 0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4 \ --debug.max-block 100000 \ @@ -99,4 +99,4 @@ jobs: - name: Decide whether the needed jobs succeeded or failed uses: re-actors/alls-green@release/v1 with: - jobs: ${{ toJSON(needs) }} \ No newline at end of file + jobs: ${{ toJSON(needs) }} diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index c6b3560530bb..29470c9d1a86 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -88,7 +88,11 @@ humantime = "2.1.0" [features] jemalloc = ["dep:jemallocator"] jemalloc-prof = ["jemalloc", "jemallocator?/profiling"] -only-info-logs = ["tracing/release_max_level_info"] +min-error-logs = ["tracing/release_max_level_error"] +min-warn-logs = ["tracing/release_max_level_warn"] +min-info-logs = ["tracing/release_max_level_info"] +min-debug-logs = ["tracing/release_max_level_debug"] +min-trace-logs = ["tracing/release_max_level_trace"] [build-dependencies] vergen = { version = "8.0.0", features = ["build", "cargo", "git", "gitcl"] } diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 94a940828312..d42274e28657 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -16,8 +16,12 @@ //! and leak detection functionality. See [jemalloc's opt.prof](https://jemalloc.net/jemalloc.3.html#opt.prof) //! documentation for usage details. This is **not recommended on Windows**. See [here](https://rust-lang.github.io/rfcs/1974-global-allocators.html#jemalloc) //! for more info. -//! - `only-info-logs`: Disables all logs below `info` level. This can speed up the node, since -//! fewer calls to the logging component is made. +//! - `min-error-logs`: Disables all logs below `error` level. +//! - `min-warn-logs`: Disables all logs below `warn` level. +//! - `min-info-logs`: Disables all logs below `info` level. This can speed up the node, since fewer +//! calls to the logging component is made. +//! - `min-debug-logs`: Disables all logs below `debug` level. +//! - `min-trace-logs`: Disables all logs below `trace` level. pub mod args; pub mod chain; From 085a703d7c8f7d7bbb310f8cbcda3632b7f85ad9 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Tue, 27 Jun 2023 10:02:17 +0100 Subject: [PATCH 198/216] refactor: add `BlockWriter` and `BlockExecutionWriter` (#3384) --- bin/reth/src/debug_cmd/execution.rs | 2 +- bin/reth/src/stage/unwind.rs | 2 +- crates/blockchain-tree/src/blockchain_tree.rs | 28 +- crates/consensus/beacon/src/engine/mod.rs | 9 +- crates/stages/src/stages/execution.rs | 46 +-- crates/stages/src/stages/hashing_account.rs | 4 +- crates/stages/src/test_utils/test_db.rs | 6 +- crates/storage/provider/src/lib.rs | 20 +- .../src/providers/database/provider.rs | 332 ++++++++++-------- crates/storage/provider/src/traits/block.rs | 62 +++- crates/storage/provider/src/traits/mod.rs | 2 +- crates/storage/provider/src/transaction.rs | 8 +- crates/storage/provider/src/utils.rs | 103 ------ crates/trie/src/trie.rs | 87 +++-- testing/ef-tests/src/cases/blockchain_test.rs | 10 +- testing/ef-tests/src/models.rs | 20 +- 16 files changed, 369 insertions(+), 372 deletions(-) delete mode 100644 crates/storage/provider/src/utils.rs diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index 0533dd3daf55..76fef4a28463 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -26,7 +26,7 @@ use reth_interfaces::{ use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256}; -use reth_provider::{ProviderFactory, StageCheckpointReader}; +use reth_provider::{BlockExecutionWriter, ProviderFactory, StageCheckpointReader}; use reth_staged_sync::utils::init::{init_db, init_genesis}; use reth_stages::{ sets::DefaultStages, diff --git a/bin/reth/src/stage/unwind.rs b/bin/reth/src/stage/unwind.rs index cd78c0a2c9ce..143fac13a910 100644 --- a/bin/reth/src/stage/unwind.rs +++ b/bin/reth/src/stage/unwind.rs @@ -13,7 +13,7 @@ use reth_db::{ transaction::DbTx, }; use reth_primitives::{BlockHashOrNumber, ChainSpec}; -use reth_provider::ProviderFactory; +use reth_provider::{BlockExecutionWriter, ProviderFactory}; use std::{ops::RangeInclusive, sync::Arc}; /// `reth stage unwind` command diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 920e5ddb016b..6b52ec098609 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -22,8 +22,9 @@ use reth_primitives::{ use reth_provider::{ chain::{ChainSplit, SplitAt}, post_state::PostState, - BlockNumReader, CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications, - Chain, DatabaseProvider, DisplayBlocksChain, ExecutorFactory, HeaderProvider, + BlockExecutionWriter, BlockNumReader, BlockWriter, CanonStateNotification, + CanonStateNotificationSender, CanonStateNotifications, Chain, DatabaseProvider, + DisplayBlocksChain, ExecutorFactory, HeaderProvider, }; use std::{ collections::{BTreeMap, HashMap}, @@ -1007,8 +1008,8 @@ impl BlockchainTree } /// Canonicalize the given chain and commit it to the database. - fn commit_canonical(&mut self, chain: Chain) -> Result<(), Error> { - let mut provider = DatabaseProvider::new_rw( + fn commit_canonical(&self, chain: Chain) -> Result<(), Error> { + let provider = DatabaseProvider::new_rw( self.externals.db.tx_mut()?, self.externals.chain_spec.clone(), ); @@ -1093,9 +1094,9 @@ mod tests { proofs::EMPTY_ROOT, stage::StageCheckpoint, ChainSpecBuilder, H256, MAINNET, }; use reth_provider::{ - insert_block, post_state::PostState, test_utils::{blocks::BlockChainTestData, TestExecutorFactory}, + BlockWriter, ProviderFactory, }; use std::{collections::HashSet, sync::Arc}; @@ -1122,16 +1123,23 @@ mod tests { genesis.header.header.number = 10; genesis.header.header.state_root = EMPTY_ROOT; - let tx_mut = db.tx_mut().unwrap(); + let factory = ProviderFactory::new(&db, MAINNET.clone()); + let provider = factory.provider_rw().unwrap(); - insert_block(&tx_mut, genesis, None).unwrap(); + provider.insert_block(genesis, None).unwrap(); // insert first 10 blocks for i in 0..10 { - tx_mut.put::(i, H256([100 + i as u8; 32])).unwrap(); + provider + .tx_ref() + .put::(i, H256([100 + i as u8; 32])) + .unwrap(); } - tx_mut.put::("Finish".to_string(), StageCheckpoint::new(10)).unwrap(); - tx_mut.commit().unwrap(); + provider + .tx_ref() + .put::("Finish".to_string(), StageCheckpoint::new(10)) + .unwrap(); + provider.commit().unwrap(); } /// Test data structure that will check tree internals diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 9a23f31b4a56..ff1b807785d9 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1368,7 +1368,8 @@ mod tests { use reth_payload_builder::test_utils::spawn_test_payload_service; use reth_primitives::{stage::StageCheckpoint, ChainSpec, ChainSpecBuilder, H256, MAINNET}; use reth_provider::{ - providers::BlockchainProvider, test_utils::TestExecutorFactory, ProviderFactory, + providers::BlockchainProvider, test_utils::TestExecutorFactory, BlockWriter, + ProviderFactory, }; use reth_stages::{test_utils::TestStages, ExecOutput, PipelineError, StageError}; use reth_tasks::TokioTaskExecutor; @@ -1711,8 +1712,10 @@ mod tests { mut blocks: impl Iterator, ) { let factory = ProviderFactory::new(db, chain); - let mut provider = factory.provider_rw().unwrap(); - blocks.try_for_each(|b| provider.insert_block(b.clone(), None)).expect("failed to insert"); + let provider = factory.provider_rw().unwrap(); + blocks + .try_for_each(|b| provider.insert_block(b.clone(), None).map(|_| ())) + .expect("failed to insert"); provider.commit().unwrap(); } diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 259c91b445eb..8ba83a63a332 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -422,7 +422,7 @@ mod tests { hex_literal::hex, keccak256, stage::StageUnitCheckpoint, Account, Bytecode, ChainSpecBuilder, SealedBlock, StorageEntry, H160, H256, MAINNET, U256, }; - use reth_provider::{insert_canonical_block, AccountReader, ProviderFactory, ReceiptProvider}; + use reth_provider::{AccountReader, BlockWriter, ProviderFactory, ReceiptProvider}; use reth_revm::Factory; use reth_rlp::Decodable; use std::sync::Arc; @@ -465,14 +465,14 @@ mod tests { fn execution_checkpoint_precedes() { let state_db = create_test_db::(EnvKind::RW); let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); - insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.insert_block(genesis, None).unwrap(); + provider.insert_block(block.clone(), None).unwrap(); provider.commit().unwrap(); let previous_stage_checkpoint = ExecutionCheckpoint { @@ -501,14 +501,14 @@ mod tests { fn execution_checkpoint_recalculate_full_previous_some() { let state_db = create_test_db::(EnvKind::RW); let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); - insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.insert_block(genesis, None).unwrap(); + provider.insert_block(block.clone(), None).unwrap(); provider.commit().unwrap(); let previous_stage_checkpoint = ExecutionCheckpoint { @@ -537,14 +537,14 @@ mod tests { fn execution_checkpoint_recalculate_full_previous_none() { let state_db = create_test_db::(EnvKind::RW); let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let mut genesis_rlp = hex!("f901faf901f5a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa045571b40ae66ca7480791bbb2887286e4e4c4b1b298b191c889d6959023a32eda056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808502540be400808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0").as_slice(); let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); - insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.insert_block(genesis, None).unwrap(); + provider.insert_block(block.clone(), None).unwrap(); provider.commit().unwrap(); let previous_checkpoint = StageCheckpoint { block_number: 1, stage_checkpoint: None }; @@ -567,7 +567,7 @@ mod tests { // is merged as it has similar framework let state_db = create_test_db::(EnvKind::RW); let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -577,13 +577,13 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); - insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.insert_block(genesis, None).unwrap(); + provider.insert_block(block.clone(), None).unwrap(); provider.commit().unwrap(); // insert pre state - let mut provider = factory.provider_rw().unwrap(); - let db_tx = provider.tx_mut(); + let provider = factory.provider_rw().unwrap(); + let db_tx = provider.tx_ref(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); let code = hex!("5a465a905090036002900360015500"); @@ -676,7 +676,7 @@ mod tests { let state_db = create_test_db::(EnvKind::RW); let factory = ProviderFactory::new(state_db.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -686,8 +686,8 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f90262f901f9a075c371ba45999d87f4542326910a11af515897aebce5265d3f6acd1f1161f82fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa098f2dcd87c8ae4083e7017a05456c14eea4b1db2032126e27b3b1563d57d7cc0a08151d548273f6683169524b66ca9fe338b9ce42bc3540046c828fd939ae23bcba03f4e5c2ec5b2170b711d97ee755c160457bb58d8daa338e835ec02ae6860bbabb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018502540be40082a8798203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f863f861800a8405f5e10094100000000000000000000000000000000000000080801ba07e09e26678ed4fac08a249ebe8ed680bf9051a5e14ad223e4b2b9d26e0208f37a05f6e3f188e3e6eab7d7d3b6568f5eac7d687b08d307d3154ccd8c87b4630509bc0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); - insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.insert_block(genesis, None).unwrap(); + provider.insert_block(block.clone(), None).unwrap(); provider.commit().unwrap(); // variables @@ -695,8 +695,8 @@ mod tests { let balance = U256::from(0x3635c9adc5dea00000u128); let code_hash = keccak256(code); // pre state - let mut provider = factory.provider_rw().unwrap(); - let db_tx = provider.tx_mut(); + let provider = factory.provider_rw().unwrap(); + let db_tx = provider.tx_ref(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc1_info = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }; let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); @@ -753,7 +753,7 @@ mod tests { async fn test_selfdestruct() { let test_tx = TestTransaction::default(); let factory = ProviderFactory::new(test_tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let input = ExecInput { target: Some(1), /// The progress of this stage the last time it was executed. @@ -763,8 +763,8 @@ mod tests { let genesis = SealedBlock::decode(&mut genesis_rlp).unwrap(); let mut block_rlp = hex!("f9025ff901f7a0c86e8cc0310ae7c531c758678ddbfd16fc51c8cef8cec650b032de9869e8b94fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa050554882fbbda2c2fd93fdc466db9946ea262a67f7a76cc169e714f105ab583da00967f09ef1dfed20c0eacfaa94d5cd4002eda3242ac47eae68972d07b106d192a0e3c8b47fbfc94667ef4cceb17e5cc21e3b1eebd442cebb27f07562b33836290db90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001830f42408238108203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d8780801ba072ed817487b84ba367d15d2f039b5fc5f087d0a8882fbdf73e8cb49357e1ce30a0403d800545b8fc544f92ce8124e2255f8c3c6af93f28243a120585d4c4c6a2a3c0").as_slice(); let block = SealedBlock::decode(&mut block_rlp).unwrap(); - insert_canonical_block(provider.tx_mut(), genesis, None).unwrap(); - insert_canonical_block(provider.tx_mut(), block.clone(), None).unwrap(); + provider.insert_block(genesis, None).unwrap(); + provider.insert_block(block.clone(), None).unwrap(); provider.commit().unwrap(); // variables diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 79b93cb2da4b..1d42108a75b9 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -88,14 +88,14 @@ impl AccountHashingStage { generators::{random_block_range, random_eoa_account_range}, }; use reth_primitives::{Account, H256, U256}; - use reth_provider::insert_canonical_block; + use reth_provider::BlockWriter; let mut rng = generators::rng(); let blocks = random_block_range(&mut rng, opts.blocks.clone(), H256::zero(), opts.txs); for block in blocks { - insert_canonical_block(provider.tx_ref(), block, None).unwrap(); + provider.insert_block(block, None).unwrap(); } let mut accounts = random_eoa_account_range(&mut rng, opts.accounts); { diff --git a/crates/stages/src/test_utils/test_db.rs b/crates/stages/src/test_utils/test_db.rs index ea0b6974cae2..6d4a62595113 100644 --- a/crates/stages/src/test_utils/test_db.rs +++ b/crates/stages/src/test_utils/test_db.rs @@ -76,10 +76,10 @@ impl TestTransaction { /// Invoke a callback with transaction committing it afterwards pub fn commit(&self, f: F) -> Result<(), DbError> where - F: FnOnce(&mut Tx<'_, RW, WriteMap>) -> Result<(), DbError>, + F: FnOnce(&Tx<'_, RW, WriteMap>) -> Result<(), DbError>, { let mut tx = self.inner_rw(); - f(tx.tx_mut())?; + f(tx.tx_ref())?; tx.commit().expect("failed to commit"); Ok(()) } @@ -200,7 +200,7 @@ impl TestTransaction { } /// Inserts a single [SealedHeader] into the corresponding tables of the headers stage. - fn insert_header(tx: &mut Tx<'_, RW, WriteMap>, header: &SealedHeader) -> Result<(), DbError> { + fn insert_header(tx: &Tx<'_, RW, WriteMap>, header: &SealedHeader) -> Result<(), DbError> { tx.put::(header.number, header.hash())?; tx.put::(header.hash(), header.number)?; tx.put::(header.number, header.clone().unseal()) diff --git a/crates/storage/provider/src/lib.rs b/crates/storage/provider/src/lib.rs index b07c2a5fbf5e..3d00040c0dec 100644 --- a/crates/storage/provider/src/lib.rs +++ b/crates/storage/provider/src/lib.rs @@ -21,14 +21,14 @@ /// Various provider traits. mod traits; pub use traits::{ - AccountExtReader, AccountReader, BlockExecutor, BlockHashReader, BlockIdReader, BlockNumReader, - BlockReader, BlockReaderIdExt, BlockSource, BlockchainTreePendingStateProvider, - CanonChainTracker, CanonStateNotification, CanonStateNotificationSender, - CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, ExecutorFactory, - HashingWriter, HeaderProvider, HistoryWriter, PostStateDataProvider, ReceiptProvider, - ReceiptProviderIdExt, StageCheckpointReader, StageCheckpointWriter, StateProvider, - StateProviderBox, StateProviderFactory, StateRootProvider, StorageReader, TransactionsProvider, - WithdrawalsProvider, + AccountExtReader, AccountReader, BlockExecutionWriter, BlockExecutor, BlockHashReader, + BlockIdReader, BlockNumReader, BlockReader, BlockReaderIdExt, BlockSource, BlockWriter, + BlockchainTreePendingStateProvider, CanonChainTracker, CanonStateNotification, + CanonStateNotificationSender, CanonStateNotifications, CanonStateSubscriptions, EvmEnvProvider, + ExecutorFactory, HashingWriter, HeaderProvider, HistoryWriter, PostStateDataProvider, + ReceiptProvider, ReceiptProviderIdExt, StageCheckpointReader, StageCheckpointWriter, + StateProvider, StateProviderBox, StateProviderFactory, StateRootProvider, StorageReader, + TransactionsProvider, WithdrawalsProvider, }; /// Provider trait implementations. @@ -42,10 +42,6 @@ pub use providers::{ pub mod post_state; pub use post_state::PostState; -/// Common database utilities. -mod utils; -pub use utils::{insert_block, insert_canonical_block}; - #[cfg(any(test, feature = "test-utils"))] /// Common test helpers for mocking the Provider. pub mod test_utils; diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index c068ef23ab68..d0842f6c5b12 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -1,10 +1,9 @@ use crate::{ - insert_canonical_block, post_state::StorageChangeset, traits::{AccountExtReader, BlockSource, ReceiptProvider, StageCheckpointWriter}, - AccountReader, BlockHashReader, BlockNumReader, BlockReader, EvmEnvProvider, HashingWriter, - HeaderProvider, HistoryWriter, PostState, ProviderError, StageCheckpointReader, StorageReader, - TransactionsProvider, WithdrawalsProvider, + AccountReader, BlockExecutionWriter, BlockHashReader, BlockNumReader, BlockReader, BlockWriter, + EvmEnvProvider, HashingWriter, HeaderProvider, HistoryWriter, PostState, ProviderError, + StageCheckpointReader, StorageReader, TransactionsProvider, WithdrawalsProvider, }; use itertools::{izip, Itertools}; use reth_db::{ @@ -13,7 +12,7 @@ use reth_db::{ database::{Database, DatabaseGAT}, models::{ sharded_key, storage_sharded_key::StorageShardedKey, AccountBeforeTx, BlockNumberAddress, - ShardedKey, StoredBlockBodyIndices, + ShardedKey, StoredBlockBodyIndices, StoredBlockOmmers, StoredBlockWithdrawals, }, table::Table, tables, @@ -190,24 +189,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { // TODO(joshie) TEMPORARY should be moved to trait providers - /// Get range of blocks and its execution result - pub fn get_block_and_execution_range( - &self, - chain_spec: &ChainSpec, - range: RangeInclusive, - ) -> Result> { - self.get_take_block_and_execution_range::(chain_spec, range) - } - - /// Take range of blocks and its execution result - pub fn take_block_and_execution_range( - &self, - chain_spec: &ChainSpec, - range: RangeInclusive, - ) -> Result> { - self.get_take_block_and_execution_range::(chain_spec, range) - } - /// Traverse over changesets and plain state and recreate the [`PostState`]s for the given range /// of blocks. /// @@ -389,72 +370,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(block_states.into_values().collect()) } - /// Return range of blocks and its execution result - pub fn get_take_block_and_execution_range( - &self, - chain_spec: &ChainSpec, - range: RangeInclusive, - ) -> Result> { - if TAKE { - let storage_range = BlockNumberAddress::range(range.clone()); - - self.unwind_account_hashing(range.clone())?; - self.unwind_account_history_indices(range.clone())?; - self.unwind_storage_hashing(storage_range.clone())?; - self.unwind_storage_history_indices(storage_range)?; - - // merkle tree - let (new_state_root, trie_updates) = - StateRoot::incremental_root_with_updates(&self.tx, range.clone()) - .map_err(Into::::into)?; - - let parent_number = range.start().saturating_sub(1); - let parent_state_root = self - .header_by_number(parent_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))? - .state_root; - - // state root should be always correct as we are reverting state. - // but for sake of double verification we will check it again. - if new_state_root != parent_state_root { - let parent_hash = self - .block_hash(parent_number)? - .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))?; - return Err(ProviderError::UnwindStateRootMismatch { - got: new_state_root, - expected: parent_state_root, - block_number: parent_number, - block_hash: parent_hash, - } - .into()) - } - trie_updates.flush(&self.tx)?; - } - // get blocks - let blocks = self.get_take_block_range::(chain_spec, range.clone())?; - let unwind_to = blocks.first().map(|b| b.number.saturating_sub(1)); - // get execution res - let execution_res = self.get_take_block_execution_result_range::(range.clone())?; - // combine them - let blocks_with_exec_result: Vec<_> = - blocks.into_iter().zip(execution_res.into_iter()).collect(); - - // remove block bodies it is needed for both get block range and get block execution results - // that is why it is deleted afterwards. - if TAKE { - // rm block bodies - self.get_or_take::(range)?; - - // Update pipeline progress - if let Some(fork_number) = unwind_to { - self.update_pipeline_stages(fork_number, true)?; - } - } - - // return them - Ok(blocks_with_exec_result) - } - /// Return list of entries from table /// /// If TAKE is true, opened cursor would be write and it would delete all values from db. @@ -770,56 +685,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { } Ok(()) } - - /// Append blocks and insert its post state. - /// This will insert block data to all related tables and will update pipeline progress. - pub fn append_blocks_with_post_state( - &mut self, - blocks: Vec, - state: PostState, - ) -> Result<()> { - if blocks.is_empty() { - return Ok(()) - } - let new_tip = blocks.last().unwrap(); - let new_tip_number = new_tip.number; - - let first_number = blocks.first().unwrap().number; - - let last = blocks.last().unwrap(); - let last_block_number = last.number; - let last_block_hash = last.hash(); - let expected_state_root = last.state_root; - - // Insert the blocks - for block in blocks { - let (block, senders) = block.into_components(); - insert_canonical_block(self.tx_mut(), block, Some(senders))?; - } - - // Write state and changesets to the database. - // Must be written after blocks because of the receipt lookup. - state.write_to_db(self.tx_mut())?; - - self.insert_hashes(first_number..=last_block_number, last_block_hash, expected_state_root)?; - - self.calculate_history_indices(first_number..=last_block_number)?; - - // Update pipeline progress - self.update_pipeline_stages(new_tip_number, false)?; - - Ok(()) - } - - /// Insert full block and make it canonical. - pub fn insert_block( - &mut self, - block: SealedBlock, - senders: Option>, - ) -> Result<()> { - insert_canonical_block(self.tx_mut(), block, senders)?; - Ok(()) - } } impl<'this, TX: DbTx<'this>> AccountReader for DatabaseProvider<'this, TX> { @@ -1745,3 +1610,192 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> HistoryWriter for DatabaseProvider Ok(changesets) } } + +impl<'this, TX: DbTxMut<'this> + DbTx<'this>> BlockExecutionWriter for DatabaseProvider<'this, TX> { + fn get_or_take_block_and_execution_range( + &self, + chain_spec: &ChainSpec, + range: RangeInclusive, + ) -> Result> { + if TAKE { + let storage_range = BlockNumberAddress::range(range.clone()); + + self.unwind_account_hashing(range.clone())?; + self.unwind_account_history_indices(range.clone())?; + self.unwind_storage_hashing(storage_range.clone())?; + self.unwind_storage_history_indices(storage_range)?; + + // merkle tree + let (new_state_root, trie_updates) = + StateRoot::incremental_root_with_updates(&self.tx, range.clone()) + .map_err(Into::::into)?; + + let parent_number = range.start().saturating_sub(1); + let parent_state_root = self + .header_by_number(parent_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))? + .state_root; + + // state root should be always correct as we are reverting state. + // but for sake of double verification we will check it again. + if new_state_root != parent_state_root { + let parent_hash = self + .block_hash(parent_number)? + .ok_or_else(|| ProviderError::HeaderNotFound(parent_number.into()))?; + return Err(ProviderError::UnwindStateRootMismatch { + got: new_state_root, + expected: parent_state_root, + block_number: parent_number, + block_hash: parent_hash, + } + .into()) + } + trie_updates.flush(&self.tx)?; + } + // get blocks + let blocks = self.get_take_block_range::(chain_spec, range.clone())?; + let unwind_to = blocks.first().map(|b| b.number.saturating_sub(1)); + // get execution res + let execution_res = self.get_take_block_execution_result_range::(range.clone())?; + // combine them + let blocks_with_exec_result: Vec<_> = + blocks.into_iter().zip(execution_res.into_iter()).collect(); + + // remove block bodies it is needed for both get block range and get block execution results + // that is why it is deleted afterwards. + if TAKE { + // rm block bodies + self.get_or_take::(range)?; + + // Update pipeline progress + if let Some(fork_number) = unwind_to { + self.update_pipeline_stages(fork_number, true)?; + } + } + + // return them + Ok(blocks_with_exec_result) + } +} + +impl<'this, TX: DbTxMut<'this> + DbTx<'this>> BlockWriter for DatabaseProvider<'this, TX> { + fn insert_block( + &self, + block: SealedBlock, + senders: Option>, + ) -> Result { + let block_number = block.number; + self.tx.put::(block.number, block.hash())?; + // Put header with canonical hashes. + self.tx.put::(block.number, block.header.as_ref().clone())?; + self.tx.put::(block.hash(), block.number)?; + + // total difficulty + let ttd = if block.number == 0 { + block.difficulty + } else { + let parent_block_number = block.number - 1; + let parent_ttd = + self.tx.get::(parent_block_number)?.unwrap_or_default(); + parent_ttd.0 + block.difficulty + }; + + self.tx.put::(block.number, ttd.into())?; + + // insert body ommers data + if !block.ommers.is_empty() { + self.tx.put::( + block.number, + StoredBlockOmmers { ommers: block.ommers }, + )?; + } + + let mut next_tx_num = self + .tx + .cursor_read::()? + .last()? + .map(|(n, _)| n + 1) + .unwrap_or_default(); + let first_tx_num = next_tx_num; + + let tx_count = block.body.len() as u64; + + let senders_len = senders.as_ref().map(|s| s.len()); + let tx_iter = if Some(block.body.len()) == senders_len { + block.body.into_iter().zip(senders.unwrap().into_iter()).collect::>() + } else { + block + .body + .into_iter() + .map(|tx| { + let signer = tx.recover_signer(); + (tx, signer.unwrap_or_default()) + }) + .collect::>() + }; + + for (transaction, sender) in tx_iter { + let hash = transaction.hash(); + self.tx.put::(next_tx_num, sender)?; + self.tx.put::(next_tx_num, transaction.into())?; + self.tx.put::(hash, next_tx_num)?; + next_tx_num += 1; + } + + if let Some(withdrawals) = block.withdrawals { + if !withdrawals.is_empty() { + self.tx.put::( + block_number, + StoredBlockWithdrawals { withdrawals }, + )?; + } + } + + let block_indices = StoredBlockBodyIndices { first_tx_num, tx_count }; + self.tx.put::(block_number, block_indices.clone())?; + + if !block_indices.is_empty() { + self.tx.put::(block_indices.last_tx_num(), block_number)?; + } + + Ok(block_indices) + } + + fn append_blocks_with_post_state( + &self, + blocks: Vec, + state: PostState, + ) -> Result<()> { + if blocks.is_empty() { + return Ok(()) + } + let new_tip = blocks.last().unwrap(); + let new_tip_number = new_tip.number; + + let first_number = blocks.first().unwrap().number; + + let last = blocks.last().unwrap(); + let last_block_number = last.number; + let last_block_hash = last.hash(); + let expected_state_root = last.state_root; + + // Insert the blocks + for block in blocks { + let (block, senders) = block.into_components(); + self.insert_block(block, Some(senders))?; + } + + // Write state and changesets to the database. + // Must be written after blocks because of the receipt lookup. + state.write_to_db(self.tx_ref())?; + + self.insert_hashes(first_number..=last_block_number, last_block_hash, expected_state_root)?; + + self.calculate_history_indices(first_number..=last_block_number)?; + + // Update pipeline progress + self.update_pipeline_stages(new_tip_number, false)?; + + Ok(()) + } +} diff --git a/crates/storage/provider/src/traits/block.rs b/crates/storage/provider/src/traits/block.rs index 8632822d2adb..1c45db5da720 100644 --- a/crates/storage/provider/src/traits/block.rs +++ b/crates/storage/provider/src/traits/block.rs @@ -1,13 +1,15 @@ use crate::{ - BlockIdReader, BlockNumReader, HeaderProvider, ReceiptProvider, ReceiptProviderIdExt, - TransactionsProvider, WithdrawalsProvider, + BlockIdReader, BlockNumReader, HeaderProvider, PostState, ReceiptProvider, + ReceiptProviderIdExt, TransactionsProvider, WithdrawalsProvider, }; +use auto_impl::auto_impl; use reth_db::models::StoredBlockBodyIndices; use reth_interfaces::Result; use reth_primitives::{ - Block, BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, BlockWithSenders, Header, - Receipt, SealedBlock, SealedHeader, H256, + Address, Block, BlockHashOrNumber, BlockId, BlockNumber, BlockNumberOrTag, BlockWithSenders, + ChainSpec, Header, Receipt, SealedBlock, SealedBlockWithSenders, SealedHeader, H256, }; +use std::ops::RangeInclusive; /// A helper enum that represents the origin of the requested block. /// @@ -198,3 +200,55 @@ pub trait BlockReaderIdExt: BlockReader + BlockIdReader + ReceiptProviderIdExt { /// Returns `None` if block is not found. fn ommers_by_id(&self, id: BlockId) -> Result>>; } + +/// BlockExecution Writer +#[auto_impl(&, Arc, Box)] +pub trait BlockExecutionWriter: BlockWriter + BlockReader + Send + Sync { + /// Get range of blocks and its execution result + fn get_block_and_execution_range( + &self, + chain_spec: &ChainSpec, + range: RangeInclusive, + ) -> Result> { + self.get_or_take_block_and_execution_range::(chain_spec, range) + } + + /// Take range of blocks and its execution result + fn take_block_and_execution_range( + &self, + chain_spec: &ChainSpec, + range: RangeInclusive, + ) -> Result> { + self.get_or_take_block_and_execution_range::(chain_spec, range) + } + + /// Return range of blocks and its execution result + fn get_or_take_block_and_execution_range( + &self, + chain_spec: &ChainSpec, + range: RangeInclusive, + ) -> Result>; +} + +/// Block Writer +#[auto_impl(&, Arc, Box)] +pub trait BlockWriter: Send + Sync { + /// Insert full block and make it canonical. Parent tx num and transition id is taken from + /// parent block in database. + /// + /// Return [StoredBlockBodyIndices] that contains indices of the first and last transactions and + /// transition in the block. + fn insert_block( + &self, + block: SealedBlock, + senders: Option>, + ) -> Result; + + /// Append blocks and insert its post state. + /// This will insert block data to all related tables and will update pipeline progress. + fn append_blocks_with_post_state( + &self, + blocks: Vec, + state: PostState, + ) -> Result<()>; +} diff --git a/crates/storage/provider/src/traits/mod.rs b/crates/storage/provider/src/traits/mod.rs index 88b7358f4ba1..503162758757 100644 --- a/crates/storage/provider/src/traits/mod.rs +++ b/crates/storage/provider/src/traits/mod.rs @@ -7,7 +7,7 @@ mod storage; pub use storage::StorageReader; mod block; -pub use block::{BlockReader, BlockReaderIdExt, BlockSource}; +pub use block::{BlockExecutionWriter, BlockReader, BlockReaderIdExt, BlockSource, BlockWriter}; mod block_hash; pub use block_hash::BlockHashReader; diff --git a/crates/storage/provider/src/transaction.rs b/crates/storage/provider/src/transaction.rs index c64ae629f3a2..cdd38b6e8054 100644 --- a/crates/storage/provider/src/transaction.rs +++ b/crates/storage/provider/src/transaction.rs @@ -5,9 +5,7 @@ use std::fmt::Debug; #[cfg(test)] mod test { - use crate::{ - insert_canonical_block, test_utils::blocks::*, ProviderFactory, TransactionsProvider, - }; + use crate::{test_utils::blocks::*, ProviderFactory, TransactionsProvider}; use reth_db::{ mdbx::test_utils::create_test_rw_db, models::{storage_sharded_key::StorageShardedKey, ShardedKey}, @@ -40,7 +38,7 @@ mod test { let storage1_shard_key = StorageShardedKey::new(H160([0x60; 20]), U256::from(5).into(), u64::MAX); - insert_canonical_block(provider.tx_ref(), data.genesis.clone(), None).unwrap(); + provider.insert_block(data.genesis.clone(), None).unwrap(); assert_genesis_block(&provider, data.genesis); @@ -153,7 +151,7 @@ mod test { let (block1, exec_res1) = data.blocks[0].clone(); let (block2, exec_res2) = data.blocks[1].clone(); - insert_canonical_block(provider.tx_mut(), data.genesis.clone(), None).unwrap(); + provider.insert_block(data.genesis.clone(), None).unwrap(); assert_genesis_block(&provider, data.genesis); diff --git a/crates/storage/provider/src/utils.rs b/crates/storage/provider/src/utils.rs deleted file mode 100644 index 83aa97ed3748..000000000000 --- a/crates/storage/provider/src/utils.rs +++ /dev/null @@ -1,103 +0,0 @@ -use reth_db::{ - cursor::DbCursorRO, - models::{StoredBlockBodyIndices, StoredBlockOmmers, StoredBlockWithdrawals}, - tables, - transaction::{DbTx, DbTxMut}, - DatabaseError, -}; -use reth_primitives::{Address, SealedBlock}; - -/// Insert block data into corresponding tables. Used mainly for testing & internal tooling. -/// -/// -/// Check parent dependency in [tables::HeaderNumbers] and in [tables::BlockBodyIndices] tables. -/// Inserts header data to [tables::CanonicalHeaders], [tables::Headers], [tables::HeaderNumbers]. -/// and transactions data to [tables::TxSenders], [tables::Transactions], [tables::TxHashNumber]. -/// and transition/transaction meta data to [tables::BlockBodyIndices] -/// and block data to [tables::BlockOmmers] and [tables::BlockWithdrawals]. -/// -/// Return [StoredBlockBodyIndices] that contains indices of the first and last transactions and -/// transition in the block. -pub fn insert_block<'a, TX: DbTxMut<'a> + DbTx<'a>>( - tx: &TX, - block: SealedBlock, - senders: Option>, -) -> Result { - let block_number = block.number; - tx.put::(block.number, block.hash())?; - // Put header with canonical hashes. - tx.put::(block.number, block.header.as_ref().clone())?; - tx.put::(block.hash(), block.number)?; - - // total difficulty - let ttd = if block.number == 0 { - block.difficulty - } else { - let parent_block_number = block.number - 1; - let parent_ttd = tx.get::(parent_block_number)?.unwrap_or_default(); - parent_ttd.0 + block.difficulty - }; - - tx.put::(block.number, ttd.into())?; - - // insert body ommers data - if !block.ommers.is_empty() { - tx.put::(block.number, StoredBlockOmmers { ommers: block.ommers })?; - } - - let mut next_tx_num = - tx.cursor_read::()?.last()?.map(|(n, _)| n + 1).unwrap_or_default(); - let first_tx_num = next_tx_num; - - let tx_count = block.body.len() as u64; - - let senders_len = senders.as_ref().map(|s| s.len()); - let tx_iter = if Some(block.body.len()) == senders_len { - block.body.into_iter().zip(senders.unwrap().into_iter()).collect::>() - } else { - block - .body - .into_iter() - .map(|tx| { - let signer = tx.recover_signer(); - (tx, signer.unwrap_or_default()) - }) - .collect::>() - }; - - for (transaction, sender) in tx_iter { - let hash = transaction.hash(); - tx.put::(next_tx_num, sender)?; - tx.put::(next_tx_num, transaction.into())?; - tx.put::(hash, next_tx_num)?; - next_tx_num += 1; - } - - if let Some(withdrawals) = block.withdrawals { - if !withdrawals.is_empty() { - tx.put::( - block_number, - StoredBlockWithdrawals { withdrawals }, - )?; - } - } - - let block_indices = StoredBlockBodyIndices { first_tx_num, tx_count }; - tx.put::(block_number, block_indices.clone())?; - - if !block_indices.is_empty() { - tx.put::(block_indices.last_tx_num(), block_number)?; - } - - Ok(block_indices) -} - -/// Inserts canonical block in blockchain. Parent tx num and transition id is taken from -/// parent block in database. -pub fn insert_canonical_block<'a, TX: DbTxMut<'a> + DbTx<'a>>( - tx: &TX, - block: SealedBlock, - senders: Option>, -) -> Result { - insert_block(tx, block, senders) -} diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index 2dc257ca2092..ccf9b6ebe138 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -529,7 +529,7 @@ mod tests { use std::{collections::BTreeMap, ops::Mul, str::FromStr}; fn insert_account<'a, TX: DbTxMut<'a>>( - tx: &mut TX, + tx: &TX, address: Address, account: Account, storage: &BTreeMap, @@ -540,7 +540,7 @@ mod tests { } fn insert_storage<'a, TX: DbTxMut<'a>>( - tx: &mut TX, + tx: &TX, hashed_address: H256, storage: &BTreeMap, ) { @@ -556,7 +556,7 @@ mod tests { fn incremental_vs_full_root(inputs: &[&str], modified: &str) { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); let hashed_address = H256::from_low_u64_be(1); let mut hashed_storage_cursor = @@ -592,7 +592,7 @@ mod tests { // 3. Calculate the incremental root let mut storage_changes = PrefixSet::default(); storage_changes.insert(Nibbles::unpack(modified_key)); - let loader = StorageRoot::new_hashed(tx.tx_mut(), hashed_address) + let loader = StorageRoot::new_hashed(tx.tx_ref(), hashed_address) .with_changed_prefixes(storage_changes); let incremental_root = loader.root().unwrap(); @@ -633,8 +633,8 @@ mod tests { } tx.commit().unwrap(); - let mut tx = factory.provider_rw().unwrap(); - let got = StorageRoot::new(tx.tx_mut(), address).root().unwrap(); + let tx = factory.provider_rw().unwrap(); + let got = StorageRoot::new(tx.tx_ref(), address).root().unwrap(); let expected = storage_root(storage.into_iter()); assert_eq!(expected, got); }); @@ -681,7 +681,7 @@ mod tests { fn test_empty_storage_root() { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); let address = Address::random(); let code = "el buen fla"; @@ -690,11 +690,11 @@ mod tests { balance: U256::from(414241124u32), bytecode_hash: Some(keccak256(code)), }; - insert_account(tx.tx_mut(), address, account, &Default::default()); + insert_account(tx.tx_ref(), address, account, &Default::default()); tx.commit().unwrap(); - let mut tx = factory.provider_rw().unwrap(); - let got = StorageRoot::new(tx.tx_mut(), address).root().unwrap(); + let tx = factory.provider_rw().unwrap(); + let got = StorageRoot::new(tx.tx_ref(), address).root().unwrap(); assert_eq!(got, EMPTY_ROOT); } @@ -703,7 +703,7 @@ mod tests { fn test_storage_root() { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); let address = Address::random(); let storage = BTreeMap::from([ @@ -718,11 +718,11 @@ mod tests { bytecode_hash: Some(keccak256(code)), }; - insert_account(tx.tx_mut(), address, account, &storage); + insert_account(tx.tx_ref(), address, account, &storage); tx.commit().unwrap(); - let mut tx = factory.provider_rw().unwrap(); - let got = StorageRoot::new(tx.tx_mut(), address).root().unwrap(); + let tx = factory.provider_rw().unwrap(); + let got = StorageRoot::new(tx.tx_ref(), address).root().unwrap(); assert_eq!(storage_root(storage.into_iter()), got); } @@ -747,13 +747,13 @@ mod tests { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); for (address, (account, storage)) in &state { - insert_account(tx.tx_mut(), *address, *account, storage) + insert_account(tx.tx_ref(), *address, *account, storage) } tx.commit().unwrap(); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); let expected = state_root(state.into_iter()); @@ -763,7 +763,7 @@ mod tests { let mut intermediate_state: Option> = None; while got.is_none() { - let calculator = StateRoot::new(tx.tx_mut()) + let calculator = StateRoot::new(tx.tx_ref()) .with_threshold(threshold) .with_intermediate_state(intermediate_state.take().map(|state| *state)); match calculator.root_with_progress().unwrap() { @@ -786,16 +786,16 @@ mod tests { fn test_state_root_with_state(state: State) { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); for (address, (account, storage)) in &state { - insert_account(tx.tx_mut(), *address, *account, storage) + insert_account(tx.tx_ref(), *address, *account, storage) } tx.commit().unwrap(); let expected = state_root(state.into_iter()); - let mut tx = factory.provider_rw().unwrap(); - let got = StateRoot::new(tx.tx_mut()).root().unwrap(); + let tx = factory.provider_rw().unwrap(); + let got = StateRoot::new(tx.tx_ref()).root().unwrap(); assert_eq!(expected, got); } @@ -836,9 +836,9 @@ mod tests { hashed_storage_cursor.upsert(key3, StorageEntry { key: hashed_slot, value }).unwrap(); } tx.commit().unwrap(); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); - let account3_storage_root = StorageRoot::new(tx.tx_mut(), address3).root().unwrap(); + let account3_storage_root = StorageRoot::new(tx.tx_ref(), address3).root().unwrap(); let expected_root = storage_root_prehashed(storage.into_iter()); assert_eq!(expected_root, account3_storage_root); } @@ -858,7 +858,7 @@ mod tests { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); let mut hashed_account_cursor = tx.tx_ref().cursor_write::().unwrap(); @@ -906,7 +906,7 @@ mod tests { } hashed_storage_cursor.upsert(key3, StorageEntry { key: hashed_slot, value }).unwrap(); } - let account3_storage_root = StorageRoot::new(tx.tx_mut(), address3).root().unwrap(); + let account3_storage_root = StorageRoot::new(tx.tx_ref(), address3).root().unwrap(); hash_builder.add_leaf( Nibbles::unpack(key3), &encode_account(account3, Some(account3_storage_root)), @@ -1105,7 +1105,7 @@ mod tests { drop(tx); } - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); { let mut hashed_account_cursor = tx.tx_ref().cursor_write::().unwrap(); @@ -1129,7 +1129,7 @@ mod tests { (key6, encode_account(account6, None)), ]); - let (root, trie_updates) = StateRoot::new(tx.tx_mut()) + let (root, trie_updates) = StateRoot::new(tx.tx_ref()) .with_changed_account_prefixes(account_prefix_set) .root_with_updates() .unwrap(); @@ -1165,11 +1165,11 @@ mod tests { fn account_trie_around_extension_node() { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); - let expected = extension_node_trie(&mut tx); + let expected = extension_node_trie(&tx); - let (got, updates) = StateRoot::new(tx.tx_mut()).root_with_updates().unwrap(); + let (got, updates) = StateRoot::new(tx.tx_ref()).root_with_updates().unwrap(); assert_eq!(expected, got); // Check account trie @@ -1191,13 +1191,13 @@ mod tests { fn account_trie_around_extension_node_with_dbtrie() { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); - let expected = extension_node_trie(&mut tx); + let expected = extension_node_trie(&tx); - let (got, updates) = StateRoot::new(tx.tx_mut()).root_with_updates().unwrap(); + let (got, updates) = StateRoot::new(tx.tx_ref()).root_with_updates().unwrap(); assert_eq!(expected, got); - updates.flush(tx.tx_mut()).unwrap(); + updates.flush(tx.tx_ref()).unwrap(); // read the account updates from the db let mut accounts_trie = tx.tx_ref().cursor_read::().unwrap(); @@ -1219,7 +1219,7 @@ mod tests { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); let mut hashed_account_cursor = tx.tx_ref().cursor_write::().unwrap(); let mut state = BTreeMap::default(); @@ -1233,7 +1233,7 @@ mod tests { } } - let (state_root, trie_updates) = StateRoot::new(tx.tx_mut()) + let (state_root, trie_updates) = StateRoot::new(tx.tx_ref()) .with_changed_account_prefixes(changes) .root_with_updates() .unwrap(); @@ -1243,7 +1243,7 @@ mod tests { state.clone().into_iter().map(|(key, balance)| (key, (Account { balance, ..Default::default() }, std::iter::empty()))) ); assert_eq!(expected_root, state_root); - trie_updates.flush(tx.tx_mut()).unwrap(); + trie_updates.flush(tx.tx_ref()).unwrap(); } }); } @@ -1253,14 +1253,13 @@ mod tests { fn storage_trie_around_extension_node() { let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut tx = factory.provider_rw().unwrap(); + let tx = factory.provider_rw().unwrap(); let hashed_address = H256::random(); - let (expected_root, expected_updates) = - extension_node_storage_trie(&mut tx, hashed_address); + let (expected_root, expected_updates) = extension_node_storage_trie(&tx, hashed_address); let (got, _, updates) = - StorageRoot::new_hashed(tx.tx_mut(), hashed_address).root_with_updates().unwrap(); + StorageRoot::new_hashed(tx.tx_ref(), hashed_address).root_with_updates().unwrap(); assert_eq!(expected_root, got); // Check account trie @@ -1279,7 +1278,7 @@ mod tests { } fn extension_node_storage_trie( - tx: &mut DatabaseProviderRW<'_, &Env>, + tx: &DatabaseProviderRW<'_, &Env>, hashed_address: H256, ) -> (H256, HashMap) { let value = U256::from(1); @@ -1305,7 +1304,7 @@ mod tests { (root, updates) } - fn extension_node_trie(tx: &mut DatabaseProviderRW<'_, &Env>) -> H256 { + fn extension_node_trie(tx: &DatabaseProviderRW<'_, &Env>) -> H256 { let a = Account { nonce: 0, balance: U256::from(1u64), bytecode_hash: Some(H256::random()) }; let val = encode_account(a, None); diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 88ecb8ee0a32..2893ab8f36df 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -6,7 +6,8 @@ use crate::{ }; use reth_db::mdbx::test_utils::create_test_rw_db; use reth_primitives::{BlockBody, SealedBlock}; -use reth_provider::ProviderFactory; +use reth_provider::{BlockWriter, ProviderFactory}; +use reth_rlp::Decodable; use reth_stages::{stages::ExecutionStage, ExecInput, Stage}; use std::{collections::BTreeMap, ffi::OsStr, fs, path::Path, sync::Arc}; @@ -79,8 +80,7 @@ impl Case for BlockchainTestCase { let mut provider = factory.provider_rw().unwrap(); // Insert test state - reth_provider::insert_canonical_block( - provider.tx_ref(), + provider.insert_block( SealedBlock::new(case.genesis_block_header.clone().into(), BlockBody::default()), None, )?; @@ -88,7 +88,9 @@ impl Case for BlockchainTestCase { let mut last_block = None; for block in case.blocks.iter() { - last_block = Some(block.write_to_db(provider.tx_ref())?); + let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?; + last_block = Some(decoded.number); + provider.insert_block(decoded, None)?; } // Call execution stage diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index abc9bf425b91..52da1fc02599 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -7,11 +7,10 @@ use reth_db::{ transaction::{DbTx, DbTxMut}, }; use reth_primitives::{ - keccak256, Account as RethAccount, Address, BigEndianHash, BlockNumber, Bloom, Bytecode, Bytes, - ChainSpec, ChainSpecBuilder, Header as RethHeader, JsonU256, SealedBlock, SealedHeader, - StorageEntry, Withdrawal, H160, H256, H64, U256, + keccak256, Account as RethAccount, Address, BigEndianHash, Bloom, Bytecode, Bytes, ChainSpec, + ChainSpecBuilder, Header as RethHeader, JsonU256, SealedHeader, StorageEntry, Withdrawal, H160, + H256, H64, U256, }; -use reth_rlp::Decodable; use serde::{self, Deserialize}; use std::{collections::BTreeMap, ops::Deref}; @@ -138,19 +137,6 @@ pub struct Block { pub withdrawals: Option>, } -impl Block { - /// Write the block to the database. - pub fn write_to_db<'a, Tx>(&self, tx: &'a Tx) -> Result - where - Tx: DbTx<'a> + DbTxMut<'a>, - { - let decoded = SealedBlock::decode(&mut self.rlp.as_ref())?; - let block_number = decoded.number; - reth_provider::insert_canonical_block(tx, decoded, None)?; - Ok(block_number) - } -} - /// Transaction sequence in block #[derive(Debug, PartialEq, Eq, Deserialize)] #[serde(deny_unknown_fields)] From ee8b1ebe13fd76c2f7693d52f412bb9b54504d08 Mon Sep 17 00:00:00 2001 From: Dan Cline <6798349+Rjected@users.noreply.github.com> Date: Tue, 27 Jun 2023 08:39:54 -0400 Subject: [PATCH 199/216] fix: keep log guard around until program exits (#3415) --- bin/reth/src/cli.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/reth/src/cli.rs b/bin/reth/src/cli.rs index 6d7aac17b4e8..c32b1ae17ed2 100644 --- a/bin/reth/src/cli.rs +++ b/bin/reth/src/cli.rs @@ -19,9 +19,11 @@ pub fn run() -> eyre::Result<()> { let opt = Cli::parse(); let mut layers = vec![reth_tracing::stdout(opt.verbosity.directive())]; - if let Some((layer, _guard)) = opt.logs.layer()? { + let _guard = opt.logs.layer()?.map(|(layer, guard)| { layers.push(layer); - } + guard + }); + reth_tracing::init(layers); let runner = CliRunner::default(); From 46dd5b8482d6257e7d306b8284668ae34e0bb375 Mon Sep 17 00:00:00 2001 From: Roman Krasiuk Date: Tue, 27 Jun 2023 15:47:52 +0300 Subject: [PATCH 200/216] chore(txpool): generic maintenance task (#3379) --- crates/transaction-pool/src/lib.rs | 20 +++++++++++------ crates/transaction-pool/src/maintain.rs | 29 +++++++++---------------- crates/transaction-pool/src/traits.rs | 7 ++++++ 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index 22767adc03ad..cdc8a6e732fb 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -97,6 +97,7 @@ use reth_provider::StateProviderFactory; use std::{collections::HashMap, sync::Arc}; use tokio::sync::mpsc::Receiver; use tracing::{instrument, trace}; +use traits::TransactionPoolExt; pub use crate::{ config::PoolConfig, @@ -178,13 +179,6 @@ where self.inner().config() } - /// Sets the current block info for the pool. - #[instrument(skip(self), target = "txpool")] - pub fn set_block_info(&self, info: BlockInfo) { - trace!(target: "txpool", "updating pool block info"); - self.pool.set_block_info(info) - } - /// Returns future that validates all transaction in the given iterator. async fn validate_all( &self, @@ -369,6 +363,18 @@ where } } +impl TransactionPoolExt for Pool +where + V: TransactionValidator, + T: TransactionOrdering::Transaction>, +{ + #[instrument(skip(self), target = "txpool")] + fn set_block_info(&self, info: BlockInfo) { + trace!(target: "txpool", "updating pool block info"); + self.pool.set_block_info(info) + } +} + impl Clone for Pool { fn clone(&self) -> Self { Self { pool: Arc::clone(&self.pool) } diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 8fd87e0669bb..0c619f6d2bc6 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -1,8 +1,8 @@ //! Support for maintaining the state of the transaction pool use crate::{ - traits::{CanonicalStateUpdate, ChangedAccount}, - BlockInfo, Pool, TransactionOrdering, TransactionPool, TransactionValidator, + traits::{CanonicalStateUpdate, ChangedAccount, TransactionPoolExt}, + BlockInfo, TransactionPool, }; use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt}; use reth_primitives::{Address, BlockHash, BlockNumberOrTag, FromRecoveredTransaction}; @@ -19,15 +19,14 @@ use tracing::debug; const MAX_UPDATE_DEPTH: u64 = 64; /// Returns a spawnable future for maintaining the state of the transaction pool. -pub fn maintain_transaction_pool_future( +pub fn maintain_transaction_pool_future( client: Client, - pool: Pool, + pool: P, events: St, ) -> BoxFuture<'static, ()> where Client: StateProviderFactory + BlockReaderIdExt + Send + 'static, - V: TransactionValidator + Send + 'static, - T: TransactionOrdering::Transaction> + Send + 'static, + P: TransactionPoolExt + 'static, St: Stream + Send + Unpin + 'static, { async move { @@ -40,14 +39,10 @@ where /// /// This listens for any new blocks and reorgs and updates the transaction pool's state accordingly #[allow(unused)] -pub async fn maintain_transaction_pool( - client: Client, - pool: Pool, - mut events: St, -) where +pub async fn maintain_transaction_pool(client: Client, pool: P, mut events: St) +where Client: StateProviderFactory + BlockReaderIdExt + Send + 'static, - V: TransactionValidator + Send + 'static, - T: TransactionOrdering::Transaction> + Send + 'static, + P: TransactionPoolExt + 'static, St: Stream + Send + Unpin + 'static, { // ensure the pool points to latest state @@ -140,9 +135,7 @@ pub async fn maintain_transaction_pool( .transactions() .filter(|tx| !new_mined_transactions.contains(&tx.hash)) .filter_map(|tx| tx.clone().into_ecrecovered()) - .map(|tx| { - ::Transaction::from_recovered_transaction(tx) - }) + .map(

::Transaction::from_recovered_transaction) .collect(); // update the pool first @@ -199,9 +192,7 @@ pub async fn maintain_transaction_pool( let pruned_old_transactions = blocks .transactions() .filter_map(|tx| tx.clone().into_ecrecovered()) - .map(|tx| { - ::Transaction::from_recovered_transaction(tx) - }) + .map(

::Transaction::from_recovered_transaction) .collect(); // all transactions that were mined in the old chain need to be re-injected diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index 176b20024f81..eacbe9b519c4 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -207,6 +207,13 @@ pub trait TransactionPool: Send + Sync + Clone { ) -> Vec>>; } +/// Extension for [TransactionPool] trait that allows to set the current block info. +#[auto_impl::auto_impl(Arc)] +pub trait TransactionPoolExt: TransactionPool { + /// Sets the current block info for the pool. + fn set_block_info(&self, info: BlockInfo); +} + /// A Helper type that bundles all transactions in the pool. #[derive(Debug, Clone, Default)] pub struct AllPoolTransactions { From e10ab387a18137eeadaa0f9e138c2b9e46c13577 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:35:00 +0100 Subject: [PATCH 201/216] refactor: `Stage` `execute` and `unwind` take an immutable provider reference instead (#3390) --- bin/reth/src/debug_cmd/merkle.rs | 12 +++++----- bin/reth/src/stage/dump/execution.rs | 8 +++---- bin/reth/src/stage/dump/hashing_account.rs | 8 +++---- bin/reth/src/stage/dump/hashing_storage.rs | 8 +++---- bin/reth/src/stage/dump/merkle.rs | 18 +++++++------- bin/reth/src/stage/run.rs | 4 ++-- crates/stages/benches/criterion.rs | 4 ++-- .../stages/benches/setup/account_hashing.rs | 4 ++-- crates/stages/benches/setup/mod.rs | 16 ++++++------- crates/stages/src/pipeline/mod.rs | 4 ++-- crates/stages/src/stage.rs | 4 ++-- crates/stages/src/stages/bodies.rs | 4 ++-- crates/stages/src/stages/execution.rs | 24 ++++++++++--------- crates/stages/src/stages/finish.rs | 4 ++-- crates/stages/src/stages/hashing_account.rs | 10 ++++---- crates/stages/src/stages/hashing_storage.rs | 4 ++-- crates/stages/src/stages/headers.rs | 7 +++--- .../src/stages/index_account_history.rs | 12 +++++----- .../src/stages/index_storage_history.rs | 12 +++++----- crates/stages/src/stages/merkle.rs | 4 ++-- crates/stages/src/stages/sender_recovery.rs | 4 ++-- crates/stages/src/stages/total_difficulty.rs | 4 ++-- crates/stages/src/stages/tx_lookup.rs | 4 ++-- crates/stages/src/test_utils/runner.rs | 8 +++---- crates/stages/src/test_utils/stage.rs | 4 ++-- crates/storage/provider/src/transaction.rs | 4 ++-- testing/ef-tests/src/cases/blockchain_test.rs | 7 ++---- 27 files changed, 102 insertions(+), 104 deletions(-) diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index 4e7ee12d7bf3..d1aaf291d881 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -69,7 +69,7 @@ impl Command { let db = Arc::new(init_db(db_path)?); let factory = ProviderFactory::new(&db, self.chain.clone()); - let mut provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; + let provider_rw = factory.provider_rw().map_err(PipelineError::Interface)?; let execution_checkpoint_block = provider_rw.get_stage_checkpoint(StageId::Execution)?.unwrap_or_default().block_number; @@ -110,7 +110,7 @@ impl Command { execution_stage .execute( - &mut provider_rw, + &provider_rw, ExecInput { target: Some(block), checkpoint: block.checked_sub(1).map(StageCheckpoint::new), @@ -122,7 +122,7 @@ impl Command { while !account_hashing_done { let output = account_hashing_stage .execute( - &mut provider_rw, + &provider_rw, ExecInput { target: Some(block), checkpoint: progress.map(StageCheckpoint::new), @@ -136,7 +136,7 @@ impl Command { while !storage_hashing_done { let output = storage_hashing_stage .execute( - &mut provider_rw, + &provider_rw, ExecInput { target: Some(block), checkpoint: progress.map(StageCheckpoint::new), @@ -148,7 +148,7 @@ impl Command { let incremental_result = merkle_stage .execute( - &mut provider_rw, + &provider_rw, ExecInput { target: Some(block), checkpoint: progress.map(StageCheckpoint::new), @@ -171,7 +171,7 @@ impl Command { let clean_input = ExecInput { target: Some(block), checkpoint: None }; loop { - let clean_result = merkle_stage.execute(&mut provider_rw, clean_input).await; + let clean_result = merkle_stage.execute(&provider_rw, clean_input).await; assert!(clean_result.is_ok(), "Clean state root calculation failed"); if clean_result.unwrap().done { break diff --git a/bin/reth/src/stage/dump/execution.rs b/bin/reth/src/stage/dump/execution.rs index e5ce855bd882..de0499c04993 100644 --- a/bin/reth/src/stage/dump/execution.rs +++ b/bin/reth/src/stage/dump/execution.rs @@ -95,13 +95,13 @@ async fn unwind_and_copy( output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let mut exec_stage = ExecutionStage::new_with_factory(Factory::new(db_tool.chain.clone())); exec_stage .unwind( - &mut provider, + &provider, UnwindInput { unwind_to: from, checkpoint: StageCheckpoint::new(tip_block_number), @@ -130,12 +130,12 @@ async fn dry_run( info!(target: "reth::cli", "Executing stage. [dry-run]"); let factory = ProviderFactory::new(&output_db, chain.clone()); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let mut exec_stage = ExecutionStage::new_with_factory(Factory::new(chain.clone())); exec_stage .execute( - &mut provider, + &provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), diff --git a/bin/reth/src/stage/dump/hashing_account.rs b/bin/reth/src/stage/dump/hashing_account.rs index 29711c5bc107..83b1ae39f3ae 100644 --- a/bin/reth/src/stage/dump/hashing_account.rs +++ b/bin/reth/src/stage/dump/hashing_account.rs @@ -39,12 +39,12 @@ async fn unwind_and_copy( output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let mut exec_stage = AccountHashingStage::default(); exec_stage .unwind( - &mut provider, + &provider, UnwindInput { unwind_to: from, checkpoint: StageCheckpoint::new(tip_block_number), @@ -69,7 +69,7 @@ async fn dry_run( info!(target: "reth::cli", "Executing stage."); let factory = ProviderFactory::new(&output_db, chain); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let mut exec_stage = AccountHashingStage { clean_threshold: 1, // Forces hashing from scratch ..Default::default() @@ -79,7 +79,7 @@ async fn dry_run( while !exec_output { exec_output = exec_stage .execute( - &mut provider, + &provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), diff --git a/bin/reth/src/stage/dump/hashing_storage.rs b/bin/reth/src/stage/dump/hashing_storage.rs index c9f12a958127..c8e0252195ac 100644 --- a/bin/reth/src/stage/dump/hashing_storage.rs +++ b/bin/reth/src/stage/dump/hashing_storage.rs @@ -34,13 +34,13 @@ async fn unwind_and_copy( output_db: &reth_db::mdbx::Env, ) -> eyre::Result<()> { let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let mut exec_stage = StorageHashingStage::default(); exec_stage .unwind( - &mut provider, + &provider, UnwindInput { unwind_to: from, checkpoint: StageCheckpoint::new(tip_block_number), @@ -68,7 +68,7 @@ async fn dry_run( info!(target: "reth::cli", "Executing stage."); let factory = ProviderFactory::new(&output_db, chain); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let mut exec_stage = StorageHashingStage { clean_threshold: 1, // Forces hashing from scratch ..Default::default() @@ -78,7 +78,7 @@ async fn dry_run( while !exec_output { exec_output = exec_stage .execute( - &mut provider, + &provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), diff --git a/bin/reth/src/stage/dump/merkle.rs b/bin/reth/src/stage/dump/merkle.rs index 1c7b32fb0129..eb73259ac23a 100644 --- a/bin/reth/src/stage/dump/merkle.rs +++ b/bin/reth/src/stage/dump/merkle.rs @@ -49,7 +49,7 @@ async fn unwind_and_copy( ) -> eyre::Result<()> { let (from, to) = range; let factory = ProviderFactory::new(db_tool.db, db_tool.chain.clone()); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let unwind = UnwindInput { unwind_to: from, @@ -61,10 +61,10 @@ async fn unwind_and_copy( // Unwind hashes all the way to FROM - StorageHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); - AccountHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); + StorageHashingStage::default().unwind(&provider, unwind).await.unwrap(); + AccountHashingStage::default().unwind(&provider, unwind).await.unwrap(); - MerkleStage::default_unwind().unwind(&mut provider, unwind).await?; + MerkleStage::default_unwind().unwind(&provider, unwind).await?; // Bring Plainstate to TO (hashing stage execution requires it) let mut exec_stage = ExecutionStage::new( @@ -74,7 +74,7 @@ async fn unwind_and_copy( exec_stage .unwind( - &mut provider, + &provider, UnwindInput { unwind_to: to, checkpoint: StageCheckpoint::new(tip_block_number), @@ -86,11 +86,11 @@ async fn unwind_and_copy( // Bring hashes to TO AccountHashingStage { clean_threshold: u64::MAX, commit_threshold: u64::MAX } - .execute(&mut provider, execute_input) + .execute(&provider, execute_input) .await .unwrap(); StorageHashingStage { clean_threshold: u64::MAX, commit_threshold: u64::MAX } - .execute(&mut provider, execute_input) + .execute(&provider, execute_input) .await .unwrap(); @@ -116,7 +116,7 @@ async fn dry_run( ) -> eyre::Result<()> { info!(target: "reth::cli", "Executing stage."); let factory = ProviderFactory::new(&output_db, chain); - let mut provider = factory.provider_rw()?; + let provider = factory.provider_rw()?; let mut exec_output = false; while !exec_output { exec_output = MerkleStage::Execution { @@ -125,7 +125,7 @@ async fn dry_run( * scratch */ } .execute( - &mut provider, + &provider, reth_stages::ExecInput { target: Some(to), checkpoint: Some(StageCheckpoint::new(from)), diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index e6521c2800fb..1150b20f9c13 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -229,7 +229,7 @@ impl Command { if !self.skip_unwind { while unwind.checkpoint.block_number > self.from { - let unwind_output = unwind_stage.unwind(&mut provider_rw, unwind).await?; + let unwind_output = unwind_stage.unwind(&provider_rw, unwind).await?; unwind.checkpoint = unwind_output.checkpoint; if self.commit { @@ -245,7 +245,7 @@ impl Command { }; while let ExecOutput { checkpoint: stage_progress, done: false } = - exec_stage.execute(&mut provider_rw, input).await? + exec_stage.execute(&provider_rw, input).await? { input.checkpoint = Some(stage_progress); diff --git a/crates/stages/benches/criterion.rs b/crates/stages/benches/criterion.rs index 6509ef48bbf9..2e9fca48d8e5 100644 --- a/crates/stages/benches/criterion.rs +++ b/crates/stages/benches/criterion.rs @@ -137,8 +137,8 @@ fn measure_stage_with_path( |_| async { let mut stage = stage.clone(); let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); - stage.execute(&mut provider, input).await.unwrap(); + let provider = factory.provider_rw().unwrap(); + stage.execute(&provider, input).await.unwrap(); provider.commit().unwrap(); }, ) diff --git a/crates/stages/benches/setup/account_hashing.rs b/crates/stages/benches/setup/account_hashing.rs index c8210ec3ba31..341dbd42b61d 100644 --- a/crates/stages/benches/setup/account_hashing.rs +++ b/crates/stages/benches/setup/account_hashing.rs @@ -63,8 +63,8 @@ fn generate_testdata_db(num_blocks: u64) -> (PathBuf, StageRange) { std::fs::create_dir_all(&path).unwrap(); println!("Account Hashing testdata not found, generating to {:?}", path.display()); let tx = TestTransaction::new(&path); - let mut provider = tx.inner_rw(); - let _accounts = AccountHashingStage::seed(&mut provider, opts); + let provider = tx.inner_rw(); + let _accounts = AccountHashingStage::seed(&provider, opts); provider.commit().expect("failed to commit"); } (path, (ExecInput { target: Some(num_blocks), ..Default::default() }, UnwindInput::default())) diff --git a/crates/stages/benches/setup/mod.rs b/crates/stages/benches/setup/mod.rs index 008957b66db8..f3af8b075c52 100644 --- a/crates/stages/benches/setup/mod.rs +++ b/crates/stages/benches/setup/mod.rs @@ -42,11 +42,11 @@ pub(crate) fn stage_unwind>>( tokio::runtime::Runtime::new().unwrap().block_on(async { let mut stage = stage.clone(); let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); // Clear previous run stage - .unwind(&mut provider, unwind) + .unwind(&provider, unwind) .await .map_err(|e| { format!( @@ -70,16 +70,16 @@ pub(crate) fn unwind_hashes>>( tokio::runtime::Runtime::new().unwrap().block_on(async { let mut stage = stage.clone(); let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); - StorageHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); - AccountHashingStage::default().unwind(&mut provider, unwind).await.unwrap(); + StorageHashingStage::default().unwind(&provider, unwind).await.unwrap(); + AccountHashingStage::default().unwind(&provider, unwind).await.unwrap(); // Clear previous run - stage.unwind(&mut provider, unwind).await.unwrap(); + stage.unwind(&provider, unwind).await.unwrap(); - AccountHashingStage::default().execute(&mut provider, input).await.unwrap(); - StorageHashingStage::default().execute(&mut provider, input).await.unwrap(); + AccountHashingStage::default().execute(&provider, input).await.unwrap(); + StorageHashingStage::default().execute(&provider, input).await.unwrap(); provider.commit().unwrap(); }); diff --git a/crates/stages/src/pipeline/mod.rs b/crates/stages/src/pipeline/mod.rs index 96fb9b34a239..f5b089bfc018 100644 --- a/crates/stages/src/pipeline/mod.rs +++ b/crates/stages/src/pipeline/mod.rs @@ -276,7 +276,7 @@ where let input = UnwindInput { checkpoint, unwind_to: to, bad_block }; self.listeners.notify(PipelineEvent::Unwinding { stage_id, input }); - let output = stage.unwind(&mut provider_rw, input).await; + let output = stage.unwind(&provider_rw, input).await; match output { Ok(unwind_output) => { checkpoint = unwind_output.checkpoint; @@ -358,7 +358,7 @@ where }); match stage - .execute(&mut provider_rw, ExecInput { target, checkpoint: prev_checkpoint }) + .execute(&provider_rw, ExecInput { target, checkpoint: prev_checkpoint }) .await { Ok(out @ ExecOutput { checkpoint, done }) => { diff --git a/crates/stages/src/stage.rs b/crates/stages/src/stage.rs index 30b8f7fca344..09519d34b273 100644 --- a/crates/stages/src/stage.rs +++ b/crates/stages/src/stage.rs @@ -181,14 +181,14 @@ pub trait Stage: Send + Sync { /// Execute the stage. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result; /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result; } diff --git a/crates/stages/src/stages/bodies.rs b/crates/stages/src/stages/bodies.rs index 579ba2f174c9..14f6302d87e7 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -67,7 +67,7 @@ impl Stage for BodyStage { /// header, limited by the stage's batch size. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -163,7 +163,7 @@ impl Stage for BodyStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let tx = provider.tx_ref(); diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 8ba83a63a332..68038cc73003 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -87,7 +87,7 @@ impl ExecutionStage { /// Execute the stage. pub fn execute_inner( &self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -262,7 +262,7 @@ impl Stage for ExecutionStage { /// Execute the stage async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { // For Ethereum transactions that reaches the max call depth (1024) revm can use more stack @@ -289,7 +289,7 @@ impl Stage for ExecutionStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let tx = provider.tx_ref(); @@ -583,6 +583,7 @@ mod tests { // insert pre state let provider = factory.provider_rw().unwrap(); + let db_tx = provider.tx_ref(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc2 = H160(hex!("a94f5374fce5edbc8e2a8697c15331677e6ebf0b")); @@ -604,9 +605,9 @@ mod tests { db_tx.put::(code_hash, Bytecode::new_raw(code.to_vec().into())).unwrap(); provider.commit().unwrap(); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let mut execution_stage = stage(); - let output = execution_stage.execute(&mut provider, input).await.unwrap(); + let output = execution_stage.execute(&provider, input).await.unwrap(); provider.commit().unwrap(); assert_matches!(output, ExecOutput { checkpoint: StageCheckpoint { @@ -696,6 +697,7 @@ mod tests { let code_hash = keccak256(code); // pre state let provider = factory.provider_rw().unwrap(); + let db_tx = provider.tx_ref(); let acc1 = H160(hex!("1000000000000000000000000000000000000000")); let acc1_info = Account { nonce: 0, balance: U256::ZERO, bytecode_hash: Some(code_hash) }; @@ -708,16 +710,16 @@ mod tests { provider.commit().unwrap(); // execute - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let mut execution_stage = stage(); - let result = execution_stage.execute(&mut provider, input).await.unwrap(); + let result = execution_stage.execute(&provider, input).await.unwrap(); provider.commit().unwrap(); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let mut stage = stage(); let result = stage .unwind( - &mut provider, + &provider, UnwindInput { checkpoint: result.checkpoint, unwind_to: 0, bad_block: None }, ) .await @@ -811,9 +813,9 @@ mod tests { provider.commit().unwrap(); // execute - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let mut execution_stage = stage(); - let _ = execution_stage.execute(&mut provider, input).await.unwrap(); + let _ = execution_stage.execute(&provider, input).await.unwrap(); provider.commit().unwrap(); // assert unwind stage diff --git a/crates/stages/src/stages/finish.rs b/crates/stages/src/stages/finish.rs index 53d23704902b..02c63215a602 100644 --- a/crates/stages/src/stages/finish.rs +++ b/crates/stages/src/stages/finish.rs @@ -18,7 +18,7 @@ impl Stage for FinishStage { async fn execute( &mut self, - _provider: &mut DatabaseProviderRW<'_, &DB>, + _provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { Ok(ExecOutput { checkpoint: StageCheckpoint::new(input.target()), done: true }) @@ -26,7 +26,7 @@ impl Stage for FinishStage { async fn unwind( &mut self, - _provider: &mut DatabaseProviderRW<'_, &DB>, + _provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { Ok(UnwindOutput { checkpoint: StageCheckpoint::new(input.unwind_to) }) diff --git a/crates/stages/src/stages/hashing_account.rs b/crates/stages/src/stages/hashing_account.rs index 1d42108a75b9..8ef401571946 100644 --- a/crates/stages/src/stages/hashing_account.rs +++ b/crates/stages/src/stages/hashing_account.rs @@ -79,7 +79,7 @@ impl AccountHashingStage { /// Proceeds to go to the `BlockTransitionIndex` end, go back `transitions` and change the /// account state in the `AccountChangeSet` table. pub fn seed( - provider: &mut DatabaseProviderRW<'_, DB>, + provider: &DatabaseProviderRW<'_, DB>, opts: SeedOpts, ) -> Result, StageError> { use reth_db::models::AccountBeforeTx; @@ -135,7 +135,7 @@ impl Stage for AccountHashingStage { /// Execute the stage. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -267,7 +267,7 @@ impl Stage for AccountHashingStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = @@ -535,9 +535,9 @@ mod tests { type Seed = Vec<(Address, Account)>; fn seed_execution(&mut self, input: ExecInput) -> Result { - let mut provider = self.tx.inner_rw(); + let provider = self.tx.inner_rw(); let res = Ok(AccountHashingStage::seed( - &mut provider, + &provider, SeedOpts { blocks: 1..=input.target(), accounts: 0..10, txs: 0..3 }, ) .unwrap()); diff --git a/crates/stages/src/stages/hashing_storage.rs b/crates/stages/src/stages/hashing_storage.rs index db4600a02bc4..f7ee40661d28 100644 --- a/crates/stages/src/stages/hashing_storage.rs +++ b/crates/stages/src/stages/hashing_storage.rs @@ -54,7 +54,7 @@ impl Stage for StorageHashingStage { /// Execute the stage. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { let tx = provider.tx_ref(); @@ -193,7 +193,7 @@ impl Stage for StorageHashingStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = diff --git a/crates/stages/src/stages/headers.rs b/crates/stages/src/stages/headers.rs index 4dd7620cc88b..a1b7e26f05a7 100644 --- a/crates/stages/src/stages/headers.rs +++ b/crates/stages/src/stages/headers.rs @@ -20,7 +20,6 @@ use reth_primitives::{ BlockHashOrNumber, BlockNumber, SealedHeader, H256, }; use reth_provider::DatabaseProviderRW; -use std::ops::Deref; use tokio::sync::watch; use tracing::*; @@ -196,14 +195,14 @@ where /// starting from the tip of the chain async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { let tx = provider.tx_ref(); let current_checkpoint = input.checkpoint(); // Lookup the head and tip of the sync range - let gap = self.get_sync_gap(provider.deref(), current_checkpoint.block_number).await?; + let gap = self.get_sync_gap(provider, current_checkpoint.block_number).await?; let local_head = gap.local_head.number; let tip = gap.target.tip(); @@ -326,7 +325,7 @@ where /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { // TODO: handle bad block diff --git a/crates/stages/src/stages/index_account_history.rs b/crates/stages/src/stages/index_account_history.rs index 34848e71f0fb..2062b4eb02f1 100644 --- a/crates/stages/src/stages/index_account_history.rs +++ b/crates/stages/src/stages/index_account_history.rs @@ -37,7 +37,7 @@ impl Stage for IndexAccountHistoryStage { /// Execute the stage. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -56,7 +56,7 @@ impl Stage for IndexAccountHistoryStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = @@ -142,8 +142,8 @@ mod tests { let input = ExecInput { target: Some(run_to), ..Default::default() }; let mut stage = IndexAccountHistoryStage::default(); let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); - let out = stage.execute(&mut provider, input).await.unwrap(); + let provider = factory.provider_rw().unwrap(); + let out = stage.execute(&provider, input).await.unwrap(); assert_eq!(out, ExecOutput { checkpoint: StageCheckpoint::new(5), done: true }); provider.commit().unwrap(); } @@ -156,8 +156,8 @@ mod tests { }; let mut stage = IndexAccountHistoryStage::default(); let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); - let out = stage.unwind(&mut provider, input).await.unwrap(); + let provider = factory.provider_rw().unwrap(); + let out = stage.unwind(&provider, input).await.unwrap(); assert_eq!(out, UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) }); provider.commit().unwrap(); } diff --git a/crates/stages/src/stages/index_storage_history.rs b/crates/stages/src/stages/index_storage_history.rs index 31d033173ca6..945bafc5f33a 100644 --- a/crates/stages/src/stages/index_storage_history.rs +++ b/crates/stages/src/stages/index_storage_history.rs @@ -37,7 +37,7 @@ impl Stage for IndexStorageHistoryStage { /// Execute the stage. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -55,7 +55,7 @@ impl Stage for IndexStorageHistoryStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (range, unwind_progress, _) = @@ -150,8 +150,8 @@ mod tests { let input = ExecInput { target: Some(run_to), ..Default::default() }; let mut stage = IndexStorageHistoryStage::default(); let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); - let out = stage.execute(&mut provider, input).await.unwrap(); + let provider = factory.provider_rw().unwrap(); + let out = stage.execute(&provider, input).await.unwrap(); assert_eq!(out, ExecOutput { checkpoint: StageCheckpoint::new(5), done: true }); provider.commit().unwrap(); } @@ -164,8 +164,8 @@ mod tests { }; let mut stage = IndexStorageHistoryStage::default(); let factory = ProviderFactory::new(tx.tx.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); - let out = stage.unwind(&mut provider, input).await.unwrap(); + let provider = factory.provider_rw().unwrap(); + let out = stage.unwind(&provider, input).await.unwrap(); assert_eq!(out, UnwindOutput { checkpoint: StageCheckpoint::new(unwind_to) }); provider.commit().unwrap(); } diff --git a/crates/stages/src/stages/merkle.rs b/crates/stages/src/stages/merkle.rs index 3485e7a66300..1a95341d85e7 100644 --- a/crates/stages/src/stages/merkle.rs +++ b/crates/stages/src/stages/merkle.rs @@ -145,7 +145,7 @@ impl Stage for MerkleStage { /// Execute the stage. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { let threshold = match self { @@ -281,7 +281,7 @@ impl Stage for MerkleStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let tx = provider.tx_ref(); diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index 5e012e3abfdf..26def0568253 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -56,7 +56,7 @@ impl Stage for SenderRecoveryStage { /// the [`TxSenders`][reth_db::tables::TxSenders] table. async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -167,7 +167,7 @@ impl Stage for SenderRecoveryStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); diff --git a/crates/stages/src/stages/total_difficulty.rs b/crates/stages/src/stages/total_difficulty.rs index deb78f7c6a0e..2d4967520add 100644 --- a/crates/stages/src/stages/total_difficulty.rs +++ b/crates/stages/src/stages/total_difficulty.rs @@ -51,7 +51,7 @@ impl Stage for TotalDifficultyStage { /// Write total difficulty entries async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { let tx = provider.tx_ref(); @@ -98,7 +98,7 @@ impl Stage for TotalDifficultyStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index 4afec7459b8b..2d32db03d3d3 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -51,7 +51,7 @@ impl Stage for TransactionLookupStage { /// Write transaction hash -> id entries async fn execute( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: ExecInput, ) -> Result { if input.target_reached() { @@ -136,7 +136,7 @@ impl Stage for TransactionLookupStage { /// Unwind the stage. async fn unwind( &mut self, - provider: &mut DatabaseProviderRW<'_, &DB>, + provider: &DatabaseProviderRW<'_, &DB>, input: UnwindInput, ) -> Result { let tx = provider.tx_ref(); diff --git a/crates/stages/src/test_utils/runner.rs b/crates/stages/src/test_utils/runner.rs index 01ae19d49b8e..83d97a0388b9 100644 --- a/crates/stages/src/test_utils/runner.rs +++ b/crates/stages/src/test_utils/runner.rs @@ -48,9 +48,9 @@ pub(crate) trait ExecuteStageTestRunner: StageTestRunner { let (db, mut stage) = (self.tx().inner_raw(), self.stage()); tokio::spawn(async move { let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); - let result = stage.execute(&mut provider, input).await; + let result = stage.execute(&provider, input).await; provider.commit().expect("failed to commit"); tx.send(result).expect("failed to send message") }); @@ -74,9 +74,9 @@ pub(crate) trait UnwindStageTestRunner: StageTestRunner { let (db, mut stage) = (self.tx().inner_raw(), self.stage()); tokio::spawn(async move { let factory = ProviderFactory::new(db.as_ref(), MAINNET.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); - let result = stage.unwind(&mut provider, input).await; + let result = stage.unwind(&provider, input).await; provider.commit().expect("failed to commit"); tx.send(result).expect("failed to send result"); }); diff --git a/crates/stages/src/test_utils/stage.rs b/crates/stages/src/test_utils/stage.rs index 028b74218fcb..65ea51362dfb 100644 --- a/crates/stages/src/test_utils/stage.rs +++ b/crates/stages/src/test_utils/stage.rs @@ -48,7 +48,7 @@ impl Stage for TestStage { async fn execute( &mut self, - _: &mut DatabaseProviderRW<'_, &DB>, + _: &DatabaseProviderRW<'_, &DB>, _input: ExecInput, ) -> Result { self.exec_outputs @@ -58,7 +58,7 @@ impl Stage for TestStage { async fn unwind( &mut self, - _: &mut DatabaseProviderRW<'_, &DB>, + _: &DatabaseProviderRW<'_, &DB>, _input: UnwindInput, ) -> Result { self.unwind_outputs diff --git a/crates/storage/provider/src/transaction.rs b/crates/storage/provider/src/transaction.rs index cdd38b6e8054..0d3ef4453d25 100644 --- a/crates/storage/provider/src/transaction.rs +++ b/crates/storage/provider/src/transaction.rs @@ -26,7 +26,7 @@ mod test { .build(); let factory = ProviderFactory::new(db.as_ref(), Arc::new(chain_spec.clone())); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let data = BlockChainTestData::default(); let genesis = data.genesis.clone(); @@ -144,7 +144,7 @@ mod test { ); let factory = ProviderFactory::new(db.as_ref(), chain_spec.clone()); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); let data = BlockChainTestData::default(); let genesis = data.genesis.clone(); diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index 2893ab8f36df..16d609ea440e 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -77,7 +77,7 @@ impl Case for BlockchainTestCase { // Create the database let db = create_test_rw_db(); let factory = ProviderFactory::new(db.as_ref(), Arc::new(case.network.clone().into())); - let mut provider = factory.provider_rw().unwrap(); + let provider = factory.provider_rw().unwrap(); // Insert test state provider.insert_block( @@ -105,10 +105,7 @@ impl Case for BlockchainTestCase { .block_on(async { // ignore error let _ = stage - .execute( - &mut provider, - ExecInput { target: last_block, checkpoint: None }, - ) + .execute(&provider, ExecInput { target: last_block, checkpoint: None }) .await; }); } From 0a6b01853618a4fce8641702e762a57d4a93efd9 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Tue, 27 Jun 2023 14:58:47 +0100 Subject: [PATCH 202/216] feat(storage): do not require `Default` for `derive(Compact)` (#3419) --- .../codecs/derive/src/compact/enums.rs | 4 +- .../storage/codecs/derive/src/compact/mod.rs | 37 +++++++++---------- .../codecs/derive/src/compact/structs.rs | 10 ++--- 3 files changed, 25 insertions(+), 26 deletions(-) diff --git a/crates/storage/codecs/derive/src/compact/enums.rs b/crates/storage/codecs/derive/src/compact/enums.rs index 978d98d86c56..a408c59305b3 100644 --- a/crates/storage/codecs/derive/src/compact/enums.rs +++ b/crates/storage/codecs/derive/src/compact/enums.rs @@ -68,8 +68,8 @@ impl<'a> EnumHandler<'a> { // Unamed type self.enum_lines.push(quote! { #current_variant_index => { - let mut inner = #field_type::default(); - (inner, buf) = #field_type::#from_compact_ident(buf, buf.len()); + let (inner, new_buf) = #field_type::#from_compact_ident(buf, buf.len()); + buf = new_buf; #ident::#variant_name(inner) } }); diff --git a/crates/storage/codecs/derive/src/compact/mod.rs b/crates/storage/codecs/derive/src/compact/mod.rs index 3f67fb721bb4..e8daacfa2358 100644 --- a/crates/storage/codecs/derive/src/compact/mod.rs +++ b/crates/storage/codecs/derive/src/compact/mod.rs @@ -277,25 +277,24 @@ mod tests { } fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) { let (flags, mut buf) = TestStructFlags::from(buf); - let mut f_u64 = u64::default(); - (f_u64, buf) = u64::from_compact(buf, flags.f_u64_len() as usize); - let mut f_u256 = U256::default(); - (f_u256, buf) = U256::from_compact(buf, flags.f_u256_len() as usize); - let mut f_bool_t = bool::default(); - (f_bool_t, buf) = bool::from_compact(buf, flags.f_bool_t_len() as usize); - let mut f_bool_f = bool::default(); - (f_bool_f, buf) = bool::from_compact(buf, flags.f_bool_f_len() as usize); - let mut f_option_none = Option::default(); - (f_option_none, buf) = Option::from_compact(buf, flags.f_option_none_len() as usize); - let mut f_option_some = Option::default(); - (f_option_some, buf) = Option::specialized_from_compact(buf, flags.f_option_some_len() as usize); - let mut f_option_some_u64 = Option::default(); - (f_option_some_u64, buf) = - Option::from_compact(buf, flags.f_option_some_u64_len() as usize); - let mut f_vec_empty = Vec::default(); - (f_vec_empty, buf) = Vec::from_compact(buf, buf.len()); - let mut f_vec_some = Vec::default(); - (f_vec_some, buf) = Vec::specialized_from_compact(buf, buf.len()); + let (f_u64, new_buf) = u64::from_compact(buf, flags.f_u64_len() as usize); + buf = new_buf; + let (f_u256, new_buf) = U256::from_compact(buf, flags.f_u256_len() as usize); + buf = new_buf; + let (f_bool_t, new_buf) = bool::from_compact(buf, flags.f_bool_t_len() as usize); + buf = new_buf; + let (f_bool_f, new_buf) = bool::from_compact(buf, flags.f_bool_f_len() as usize); + buf = new_buf; + let (f_option_none, new_buf) = Option::from_compact(buf, flags.f_option_none_len() as usize); + buf = new_buf; + let (f_option_some, new_buf) = Option::specialized_from_compact(buf, flags.f_option_some_len() as usize); + buf = new_buf; + let (f_option_some_u64, new_buf) = Option::from_compact(buf, flags.f_option_some_u64_len() as usize); + buf = new_buf; + let (f_vec_empty, new_buf) = Vec::from_compact(buf, buf.len()); + buf = new_buf; + let (f_vec_some, new_buf) = Vec::specialized_from_compact(buf, buf.len()); + buf = new_buf; let obj = TestStruct { f_u64: f_u64, f_u256: f_u256, diff --git a/crates/storage/codecs/derive/src/compact/structs.rs b/crates/storage/codecs/derive/src/compact/structs.rs index 9bf56950ed8d..aec1d7c66e44 100644 --- a/crates/storage/codecs/derive/src/compact/structs.rs +++ b/crates/storage/codecs/derive/src/compact/structs.rs @@ -137,21 +137,21 @@ impl<'a> StructHandler<'a> { }) } else { let ident_type = format_ident!("{ftype}"); - self.lines.push(quote! { - let mut #name = #ident_type::default(); - }); if !is_flag_type(ftype) { // It's a type that handles its own length requirements. (h256, Custom, ...) self.lines.push(quote! { - (#name, buf) = #ident_type::#from_compact_ident(buf, buf.len()); + let (#name, new_buf) = #ident_type::#from_compact_ident(buf, buf.len()); }) } else if *is_compact { self.lines.push(quote! { - (#name, buf) = #ident_type::#from_compact_ident(buf, flags.#len() as usize); + let (#name, new_buf) = #ident_type::#from_compact_ident(buf, flags.#len() as usize); }); } else { todo!() } + self.lines.push(quote! { + buf = new_buf; + }); } } } From d4df89616cdd8c9149a89bdae298df981c658499 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jun 2023 16:35:08 +0200 Subject: [PATCH 203/216] chore: move on_canonical_state_change to TransactionPoolExt (#3422) --- crates/transaction-pool/src/lib.rs | 8 ++++---- crates/transaction-pool/src/traits.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index cdc8a6e732fb..ccafbe97781f 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -251,10 +251,6 @@ where self.pool.block_info() } - fn on_canonical_state_change(&self, update: CanonicalStateUpdate) { - self.pool.on_canonical_state_change(update); - } - async fn add_transaction_and_subscribe( &self, origin: TransactionOrigin, @@ -373,6 +369,10 @@ where trace!(target: "txpool", "updating pool block info"); self.pool.set_block_info(info) } + + fn on_canonical_state_change(&self, update: CanonicalStateUpdate) { + self.pool.on_canonical_state_change(update); + } } impl Clone for Pool { diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index eacbe9b519c4..05c027a804fe 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -36,13 +36,6 @@ pub trait TransactionPool: Send + Sync + Clone { /// This tracks the block that the pool has last seen. fn block_info(&self) -> BlockInfo; - /// Event listener for when the pool needs to be updated - /// - /// Implementers need to update the pool accordingly. - /// For example the base fee of the pending block is determined after a block is mined which - /// affects the dynamic fee requirement of pending transactions in the pool. - fn on_canonical_state_change(&self, update: CanonicalStateUpdate); - /// Imports an _external_ transaction. /// /// This is intended to be used by the network to insert incoming transactions received over the @@ -212,6 +205,13 @@ pub trait TransactionPool: Send + Sync + Clone { pub trait TransactionPoolExt: TransactionPool { /// Sets the current block info for the pool. fn set_block_info(&self, info: BlockInfo); + + /// Event listener for when the pool needs to be updated + /// + /// Implementers need to update the pool accordingly. + /// For example the base fee of the pending block is determined after a block is mined which + /// affects the dynamic fee requirement of pending transactions in the pool. + fn on_canonical_state_change(&self, update: CanonicalStateUpdate); } /// A Helper type that bundles all transactions in the pool. From 7984a7265767ebdd726ebae72aa05a9a734a26f8 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jun 2023 18:37:14 +0200 Subject: [PATCH 204/216] chore: use existing get sealed function (#3426) --- crates/rpc/rpc/src/eth/api/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/rpc/rpc/src/eth/api/block.rs b/crates/rpc/rpc/src/eth/api/block.rs index 21b557b032dd..d6d12438543e 100644 --- a/crates/rpc/rpc/src/eth/api/block.rs +++ b/crates/rpc/rpc/src/eth/api/block.rs @@ -129,7 +129,7 @@ where None => return Ok(None), }; - Ok(self.cache().get_block(block_hash).await?.map(|block| block.seal(block_hash))) + Ok(self.cache().get_sealed_block(block_hash).await?) } /// Returns the populated rpc block object for the given block id. From d7bc30eb76cba57cc3ccaddb917ab36da835ac04 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jun 2023 18:37:31 +0200 Subject: [PATCH 205/216] fix: add InsertBlockErrorKind::is_invalid_block block function (#3423) --- crates/consensus/beacon/src/engine/mod.rs | 37 +++++++--------- .../interfaces/src/blockchain_tree/error.rs | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 22 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index ff1b807785d9..0e37a7ffb307 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -956,26 +956,20 @@ where err: InsertBlockError, ) -> Result { let (block, error) = err.split(); - match error { - InsertBlockErrorKind::Internal(err) => { - // this is an internal error that is unrelated to the payload - Err(BeaconOnNewPayloadError::Internal(err)) - } - InsertBlockErrorKind::SenderRecovery | - InsertBlockErrorKind::Consensus(_) | - InsertBlockErrorKind::Execution(_) | - InsertBlockErrorKind::Tree(_) => { - // all of these occurred if the payload is invalid - let parent_hash = block.parent_hash; - - // keep track of the invalid header - self.invalid_headers.insert(block.header); - - let latest_valid_hash = - self.latest_valid_hash_for_invalid_payload(parent_hash, Some(&error)); - let status = PayloadStatusEnum::Invalid { validation_error: error.to_string() }; - Ok(PayloadStatus::new(status, latest_valid_hash)) - } + + if error.is_invalid_block() { + // all of these occurred if the payload is invalid + let parent_hash = block.parent_hash; + + // keep track of the invalid header + self.invalid_headers.insert(block.header); + + let latest_valid_hash = + self.latest_valid_hash_for_invalid_payload(parent_hash, Some(&error)); + let status = PayloadStatusEnum::Invalid { validation_error: error.to_string() }; + Ok(PayloadStatus::new(status, latest_valid_hash)) + } else { + Err(BeaconOnNewPayloadError::Internal(Box::new(error))) } } @@ -1089,8 +1083,7 @@ where } Err(err) => { warn!(target: "consensus::engine", ?err, "Failed to insert downloaded block"); - if !matches!(err.kind(), InsertBlockErrorKind::Internal(_)) { - // non-internal error kinds occur if the payload is invalid + if err.kind().is_invalid_block() { self.invalid_headers.insert(err.into_block().header); } } diff --git a/crates/interfaces/src/blockchain_tree/error.rs b/crates/interfaces/src/blockchain_tree/error.rs index e9bee5105e6a..c8913911cb8d 100644 --- a/crates/interfaces/src/blockchain_tree/error.rs +++ b/crates/interfaces/src/blockchain_tree/error.rs @@ -174,6 +174,48 @@ impl InsertBlockErrorKind { matches!(self, InsertBlockErrorKind::Consensus(_)) } + /// Returns true if the error is caused by an invalid block + /// + /// This is intended to be used to determine if the block should be marked as invalid. + pub fn is_invalid_block(&self) -> bool { + match self { + InsertBlockErrorKind::SenderRecovery | InsertBlockErrorKind::Consensus(_) => true, + // other execution errors that are considered internal errors + InsertBlockErrorKind::Execution(err) => { + match err { + BlockExecutionError::Validation(_) => { + // this is caused by an invalid block + true + } + // these are internal errors, not caused by an invalid block + BlockExecutionError::ProviderError | + BlockExecutionError::CanonicalRevert { .. } | + BlockExecutionError::CanonicalCommit { .. } | + BlockExecutionError::BlockHashNotFoundInChain { .. } | + BlockExecutionError::AppendChainDoesntConnect { .. } | + BlockExecutionError::UnavailableForTest => false, + } + } + InsertBlockErrorKind::Tree(err) => { + match err { + BlockchainTreeError::PendingBlockIsFinalized { .. } => { + // the block's number is lower than the finalized block's number + true + } + BlockchainTreeError::BlockSideChainIdConsistency { .. } | + BlockchainTreeError::CanonicalChain { .. } | + BlockchainTreeError::BlockNumberNotFoundInChain { .. } | + BlockchainTreeError::BlockHashNotFoundInChain { .. } | + BlockchainTreeError::BlockBufferingFailed { .. } => false, + } + } + InsertBlockErrorKind::Internal(_) => { + // any other error, such as database errors, are considered internal errors + false + } + } + } + /// Returns true if this is a block pre merge error. pub fn is_block_pre_merge(&self) -> bool { matches!( From c3dda470de6d23790bf3018ff1a3c9fe2f13cd93 Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 28 Jun 2023 01:09:53 +0800 Subject: [PATCH 206/216] fix(book/jsonrpc): copy/paste typo (#3429) Signed-off-by: jsvisa --- book/jsonrpc/debug.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/jsonrpc/debug.md b/book/jsonrpc/debug.md index 1556bd343420..7965e2e0d502 100644 --- a/book/jsonrpc/debug.md +++ b/book/jsonrpc/debug.md @@ -86,7 +86,7 @@ The `debug_traceTransaction` debugging method will attempt to run the transactio | Client | Method invocation | |--------|-------------------------------------------------------------| -| RPC | `{"method": "debug_traceBlock", "params": [tx_hash, opts]}` | +| RPC | `{"method": "debug_traceTransaction", "params": [tx_hash, opts]}` | ## `debug_traceCall` From 1b13616fd4611cbe2e814716a7d271246a8cb512 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 27 Jun 2023 20:37:23 +0200 Subject: [PATCH 207/216] fix(rpc): commit call state in trace call many (#3437) --- crates/rpc/rpc/src/trace.rs | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index ae0c1193f7a5..bfa45863c398 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -193,7 +193,9 @@ where let mut results = Vec::with_capacity(calls.len()); let mut db = SubState::new(State::new(state)); - for (call, trace_types) in calls { + let mut calls = calls.into_iter().peekable(); + + while let Some((call, trace_types)) = calls.next() { let env = prepare_call_env( cfg.clone(), block_env.clone(), @@ -204,12 +206,30 @@ where let config = tracing_config(&trace_types); let mut inspector = TracingInspector::new(config); let (res, _) = inspect(&mut db, env, &mut inspector)?; - let trace_res = inspector.into_parity_builder().into_trace_results_with_state( - res, - &trace_types, - &db, - )?; + let ResultAndState { result, state } = res; + + let mut trace_res = + inspector.into_parity_builder().into_trace_results(result, &trace_types); + + // If statediffs were requested, populate them with the account balance and + // nonce from pre-state + if let Some(ref mut state_diff) = trace_res.state_diff { + populate_account_balance_nonce_diffs( + state_diff, + &db, + state.iter().map(|(addr, acc)| (*addr, acc.info.clone())), + )?; + } + results.push(trace_res); + + // need to apply the state changes of this call before executing the + // next call + if calls.peek().is_some() { + // need to apply the state changes of this call before executing + // the next call + db.commit(state) + } } Ok(results) @@ -403,6 +423,9 @@ where move |tx_info, inspector, res, state, db| { let mut full_trace = inspector.into_parity_builder().into_trace_results(res, &trace_types); + + // If statediffs were requested, populate them with the account balance and nonce + // from pre-state if let Some(ref mut state_diff) = full_trace.state_diff { populate_account_balance_nonce_diffs( state_diff, From 6b7b89bc68a322691b7d47e936b605e0ec5c2efd Mon Sep 17 00:00:00 2001 From: Delweng Date: Wed, 28 Jun 2023 03:11:27 +0800 Subject: [PATCH 208/216] fix: docker build COPY dest should be abspath (#3425) Signed-off-by: jsvisa --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 47dd30adbd57..dd35ce2d44fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef -WORKDIR app +WORKDIR /app LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0" @@ -28,13 +28,13 @@ RUN cargo build --profile $BUILD_PROFILE --locked --bin reth # Use Ubuntu as the release image FROM ubuntu AS runtime -WORKDIR app +WORKDIR /app # Copy reth over from the build stage COPY --from=builder /app/target/release/reth /usr/local/bin # Copy licenses -COPY LICENSE-* . +COPY LICENSE-* ./ EXPOSE 30303 30303/udp 9001 8545 8546 ENTRYPOINT ["/usr/local/bin/reth"] From 0e6dd4c8811ebf783625e779cd49c7ee84f21dc1 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 28 Jun 2023 00:19:04 +0200 Subject: [PATCH 209/216] perf: update chain tracker first (#3431) --- crates/consensus/beacon/src/engine/mod.rs | 58 +++++++++++++---------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 0e37a7ffb307..70e9fc00722a 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -18,7 +18,7 @@ use reth_interfaces::{ use reth_payload_builder::{PayloadBuilderAttributes, PayloadBuilderHandle}; use reth_primitives::{ listener::EventListeners, stage::StageId, BlockNumHash, BlockNumber, Head, Header, SealedBlock, - H256, U256, + SealedHeader, H256, U256, }; use reth_provider::{ BlockReader, BlockSource, CanonChainTracker, ProviderError, StageCheckpointReader, @@ -575,7 +575,7 @@ where debug!(target: "consensus::engine", hash=?state.head_block_hash, number=outcome.header().number, "canonicalized new head"); // new VALID update that moved the canonical chain forward - let _ = self.update_canon_chain(&state); + let _ = self.update_canon_chain(outcome.header().clone(), &state); } else { debug!(target: "consensus::engine", fcu_head_num=?outcome.header().number, current_head_num=?self.blockchain.canonical_tip().number, "Ignoring beacon update to old head"); } @@ -610,11 +610,30 @@ where Ok(OnForkChoiceUpdated::valid(status)) } - /// Sets the state of the canon chain tracker based on the given forkchoice update. + /// Sets the state of the canon chain tracker based to the given head. + /// + /// This expects the given head to be the new canonical head. + /// /// Additionally, updates the head used for p2p handshakes. /// /// This should be called before issuing a VALID forkchoice update. - fn update_canon_chain(&self, update: &ForkchoiceState) -> Result<(), reth_interfaces::Error> { + fn update_canon_chain( + &self, + head: SealedHeader, + update: &ForkchoiceState, + ) -> Result<(), reth_interfaces::Error> { + let mut head_block = Head { + number: head.number, + hash: head.hash, + difficulty: head.difficulty, + timestamp: head.timestamp, + // NOTE: this will be set later + total_difficulty: Default::default(), + }; + + // we update the the tracked header first + self.blockchain.set_canonical_head(head); + if !update.finalized_block_hash.is_zero() { let finalized = self .blockchain @@ -635,26 +654,14 @@ where self.blockchain.set_safe(safe.header.seal(update.safe_block_hash)); } - // the consensus engine should ensure the head is not zero so we always update the head - let head = self - .blockchain - .find_block_by_hash(update.head_block_hash, BlockSource::Any)? - .ok_or_else(|| { - Error::Provider(ProviderError::UnknownBlockHash(update.head_block_hash)) + head_block.total_difficulty = + self.blockchain.header_td_by_number(head_block.number)?.ok_or_else(|| { + Error::Provider(ProviderError::TotalDifficultyNotFound { + number: head_block.number, + }) })?; - let head = head.header.seal(update.head_block_hash); - let head_td = self.blockchain.header_td_by_number(head.number)?.ok_or_else(|| { - Error::Provider(ProviderError::TotalDifficultyNotFound { number: head.number }) - })?; + self.sync_state_updater.update_status(head_block); - self.sync_state_updater.update_status(Head { - number: head.number, - hash: head.hash, - difficulty: head.difficulty, - timestamp: head.timestamp, - total_difficulty: head_td, - }); - self.blockchain.set_canonical_head(head); Ok(()) } @@ -1108,13 +1115,14 @@ where let new_head = outcome.into_header(); debug!(target: "consensus::engine", hash=?new_head.hash, number=new_head.number, "canonicalized new head"); + // we can update the FCU blocks + let _ = self.update_canon_chain(new_head, &target); + // we're no longer syncing self.sync_state_updater.update_sync_state(SyncState::Idle); + // clear any active block requests self.sync.clear_full_block_requests(); - - // we can update the FCU blocks - let _ = self.update_canon_chain(&target); } Err(err) => { // if we failed to make the FCU's head canonical, because we don't have that From bb57556a6ab84f1c9f09eebd1f618220430b5101 Mon Sep 17 00:00:00 2001 From: rakita Date: Wed, 28 Jun 2023 00:43:05 +0200 Subject: [PATCH 210/216] chore: split consensus and revm codecov spaces (#3438) --- codecov.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/codecov.yml b/codecov.yml index 267b6ebec493..6392d83a8d60 100644 --- a/codecov.yml +++ b/codecov.yml @@ -48,11 +48,14 @@ component_management: name: rpc paths: - crates/rpc/** - - component_id: core - name: consensus/evm + - component_id: consensus + name: consensus paths: - - crates/revm/** - crates/consensus/** + - component_id: revm + name: revm + paths: + - crates/revm/** - component_id: builder name: payload builder paths: From 6b8b478c6f6bf2d48e0c22d3437b7af47ef6290a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Wed, 28 Jun 2023 18:23:25 +0200 Subject: [PATCH 211/216] chore: get rid of some duplicated execution code (#3447) --- crates/blockchain-tree/src/chain.rs | 95 ++++++++++++----------------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/crates/blockchain-tree/src/chain.rs b/crates/blockchain-tree/src/chain.rs index 7159462b765e..8a3b1b615556 100644 --- a/crates/blockchain-tree/src/chain.rs +++ b/crates/blockchain-tree/src/chain.rs @@ -184,37 +184,6 @@ impl AppendableChain { externals: &TreeExternals, block_kind: BlockKind, ) -> Result - where - PSDP: PostStateDataProvider, - DB: Database, - C: Consensus, - EF: ExecutorFactory, - { - if block_kind.extends_canonical_head() { - Self::validate_and_execute_canonical_head_descendant( - block, - parent_block, - post_state_data_provider, - externals, - ) - } else { - Self::validate_and_execute_sidechain( - block, - parent_block, - post_state_data_provider, - externals, - ) - } - } - - /// Validate and execute the given block that _extends the canonical chain_, validating its - /// state root after execution. - fn validate_and_execute_canonical_head_descendant( - block: SealedBlockWithSenders, - parent_block: &SealedHeader, - post_state_data_provider: PSDP, - externals: &TreeExternals, - ) -> Result where PSDP: PostStateDataProvider, DB: Database, @@ -237,19 +206,45 @@ impl AppendableChain { let mut executor = externals.executor_factory.with_sp(&provider); let post_state = executor.execute_and_verify_receipt(&block, U256::MAX, Some(senders))?; - // check state root - let state_root = provider.state_root(post_state.clone())?; - if block.state_root != state_root { - return Err(ConsensusError::BodyStateRootDiff { - got: state_root, - expected: block.state_root, + // check state root if the block extends the canonical chain. + if block_kind.extends_canonical_head() { + // check state root + let state_root = provider.state_root(post_state.clone())?; + if block.state_root != state_root { + return Err(ConsensusError::BodyStateRootDiff { + got: state_root, + expected: block.state_root, + } + .into()) } - .into()) } Ok(post_state) } + /// Validate and execute the given block that _extends the canonical chain_, validating its + /// state root after execution. + fn validate_and_execute_canonical_head_descendant( + block: SealedBlockWithSenders, + parent_block: &SealedHeader, + post_state_data_provider: PSDP, + externals: &TreeExternals, + ) -> Result + where + PSDP: PostStateDataProvider, + DB: Database, + C: Consensus, + EF: ExecutorFactory, + { + Self::validate_and_execute( + block, + parent_block, + post_state_data_provider, + externals, + BlockKind::ExtendsCanonicalHead, + ) + } + /// Validate and execute the given sidechain block, skipping state root validation. fn validate_and_execute_sidechain( block: SealedBlockWithSenders, @@ -263,23 +258,13 @@ impl AppendableChain { C: Consensus, EF: ExecutorFactory, { - // ensure the block is a valid descendant of the parent, according to consensus rules - externals.consensus.validate_header_against_parent(&block, parent_block)?; - - let (block, senders) = block.into_components(); - let block = block.unseal(); - - // get the state provider. - let db = externals.database(); - let canonical_fork = post_state_data_provider.canonical_fork(); - let state_provider = db.history_by_block_number(canonical_fork.number)?; - - let provider = PostStateProvider::new(state_provider, post_state_data_provider); - - let mut executor = externals.executor_factory.with_sp(&provider); - let post_state = executor.execute_and_verify_receipt(&block, U256::MAX, Some(senders))?; - - Ok(post_state) + Self::validate_and_execute( + block, + parent_block, + post_state_data_provider, + externals, + BlockKind::ForksHistoricalBlock, + ) } /// Validate and execute the given block, and append it to this chain. From 6e2fa845d8c7777cdf1f3187e2cc620e394a3dee Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 28 Jun 2023 19:53:52 +0100 Subject: [PATCH 212/216] feat(bin): process & jemalloc metrics (#3435) --- Cargo.lock | 101 +++++++++++++++++++++- bin/reth/Cargo.toml | 4 +- bin/reth/src/node/mod.rs | 4 +- bin/reth/src/prometheus_exporter.rs | 127 +++++++++++++++++++++++++--- bin/reth/src/stage/run.rs | 7 +- 5 files changed, 228 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab30c41beef0..a17d38d149de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -387,6 +387,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2 1.0.60", + "quote 1.0.28", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + [[package]] name = "bindgen" version = "0.65.1" @@ -3157,6 +3177,17 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "jemalloc-ctl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1891c671f3db85d8ea8525dd43ab147f9977041911d24a03e5a36187a7bfde9" +dependencies = [ + "jemalloc-sys", + "libc", + "paste", +] + [[package]] name = "jemalloc-sys" version = "0.5.3+5.3.0-patched" @@ -3431,6 +3462,17 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +[[package]] +name = "libproc" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b18cbf29f8ff3542ba22bdce9ac610fcb75d74bb4e2b306b2a2762242025b4f" +dependencies = [ + "bindgen 0.64.0", + "errno 0.2.8", + "libc", +] + [[package]] name = "lifetimed-bytes" version = "0.1.0" @@ -3537,6 +3579,15 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "match_cfg" version = "0.1.0" @@ -3623,6 +3674,21 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "metrics-process" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99eab79be9f7c18565e889d6eaed6f1ebdafb2b6a88aef446d2fee5e7796ed10" +dependencies = [ + "libproc", + "mach2", + "metrics", + "once_cell", + "procfs", + "rlimit", + "windows", +] + [[package]] name = "metrics-util" version = "0.14.0" @@ -4478,6 +4544,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "procfs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "943ca7f9f29bab5844ecd8fdb3992c5969b6622bb9609b9502fef9b4310e3f1f" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "hex", + "lazy_static", + "rustix 0.36.11", +] + [[package]] name = "proptest" version = "1.1.0" @@ -4851,8 +4930,10 @@ dependencies = [ "human_bytes", "humantime", "hyper", + "jemalloc-ctl", "jemallocator", "metrics-exporter-prometheus", + "metrics-process", "metrics-util", "pin-project", "pretty_assertions", @@ -5264,7 +5345,7 @@ dependencies = [ name = "reth-mdbx-sys" version = "0.1.0-alpha.1" dependencies = [ - "bindgen", + "bindgen 0.65.1", "cc", "libc", ] @@ -5910,6 +5991,15 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "rlimit" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e" +dependencies = [ + "libc", +] + [[package]] name = "rlp" version = "0.5.2" @@ -7819,6 +7909,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" version = "0.42.0" diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index 29470c9d1a86..d9955fa8910d 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -38,6 +38,7 @@ reth-basic-payload-builder = { path = "../../crates/payload/basic" } reth-discv4 = { path = "../../crates/net/discv4" } reth-metrics = { workspace = true } jemallocator = { version = "0.5.0", optional = true } +jemalloc-ctl = { version = "0.5.0", optional = true } # crypto secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recovery"] } @@ -57,6 +58,7 @@ toml = { version = "0.7", features = ["display"] } # metrics metrics-exporter-prometheus = "0.11.0" metrics-util = "0.14.0" +metrics-process = "1.0.9" # test vectors generation proptest = "1.0" @@ -86,7 +88,7 @@ pretty_assertions = "1.3.0" humantime = "2.1.0" [features] -jemalloc = ["dep:jemallocator"] +jemalloc = ["dep:jemallocator", "dep:jemalloc-ctl"] jemalloc-prof = ["jemalloc", "jemallocator?/profiling"] min-error-logs = ["tracing/release_max_level_error"] min-warn-logs = ["tracing/release_max_level_warn"] diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index 02c160f3aec3..d7436e152558 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -481,8 +481,8 @@ impl Command { async fn start_metrics_endpoint(&self, db: Arc>) -> eyre::Result<()> { if let Some(listen_addr) = self.metrics { info!(target: "reth::cli", addr = %listen_addr, "Starting metrics endpoint"); - - prometheus_exporter::initialize_with_db_metrics(listen_addr, db).await?; + prometheus_exporter::initialize(listen_addr, db, metrics_process::Collector::default()) + .await?; } Ok(()) diff --git a/bin/reth/src/prometheus_exporter.rs b/bin/reth/src/prometheus_exporter.rs index 0c9c3bb48159..2dba64b12b55 100644 --- a/bin/reth/src/prometheus_exporter.rs +++ b/bin/reth/src/prometheus_exporter.rs @@ -14,19 +14,24 @@ use reth_db::{ use reth_metrics::metrics::{self, absolute_counter, describe_counter, Unit}; use std::{convert::Infallible, net::SocketAddr, sync::Arc}; -/// Installs Prometheus as the metrics recorder and serves it over HTTP with a hook. +pub(crate) trait Hook: Fn() + Send + Sync {} +impl Hook for T {} + +/// Installs Prometheus as the metrics recorder and serves it over HTTP with hooks. /// -/// The hook is called every time the metrics are requested at the given endpoint, and can be used +/// The hooks are called every time the metrics are requested at the given endpoint, and can be used /// to record values for pull-style metrics, i.e. metrics that are not automatically updated. -pub(crate) async fn initialize_with_hook( +pub(crate) async fn initialize_with_hooks( listen_addr: SocketAddr, - hook: F, + hooks: impl IntoIterator, ) -> eyre::Result<()> { let recorder = PrometheusBuilder::new().build_recorder(); let handle = recorder.handle(); + let hooks: Vec<_> = hooks.into_iter().collect(); + // Start endpoint - start_endpoint(listen_addr, handle, Arc::new(hook)) + start_endpoint(listen_addr, handle, Arc::new(move || hooks.iter().for_each(|hook| hook()))) .await .wrap_err("Could not start Prometheus endpoint")?; @@ -40,7 +45,7 @@ pub(crate) async fn initialize_with_hook( } /// Starts an endpoint at the given address to serve Prometheus metrics. -async fn start_endpoint( +async fn start_endpoint( listen_addr: SocketAddr, handle: PrometheusHandle, hook: Arc, @@ -64,14 +69,16 @@ async fn start_endpoint( Ok(()) } -/// Installs Prometheus as the metrics recorder and serves it over HTTP with database metrics. -pub(crate) async fn initialize_with_db_metrics( +/// Installs Prometheus as the metrics recorder and serves it over HTTP with database and process +/// metrics. +pub(crate) async fn initialize( listen_addr: SocketAddr, db: Arc>, + process: metrics_process::Collector, ) -> eyre::Result<()> { let db_stats = move || { // TODO: A generic stats abstraction for other DB types to deduplicate this and `reth db - // stats` + // stats` let _ = db.view(|tx| { for table in tables::Tables::ALL.iter().map(|table| table.name()) { let table_db = @@ -99,12 +106,112 @@ pub(crate) async fn initialize_with_db_metrics( }); }; - initialize_with_hook(listen_addr, db_stats).await?; + // Clone `process` to move it into the hook and use the original `process` for describe below. + let cloned_process = process.clone(); + let hooks: Vec>> = vec![ + Box::new(db_stats), + Box::new(move || cloned_process.collect()), + Box::new(collect_memory_stats), + ]; + initialize_with_hooks(listen_addr, hooks).await?; // We describe the metrics after the recorder is installed, otherwise this information is not // registered describe_counter!("db.table_size", Unit::Bytes, "The size of a database table (in bytes)"); describe_counter!("db.table_pages", "The number of database pages for a table"); + process.describe(); + describe_memory_stats(); Ok(()) } + +#[cfg(feature = "jemalloc")] +fn collect_memory_stats() { + use jemalloc_ctl::{epoch, stats}; + use reth_metrics::metrics::gauge; + use tracing::error; + + if epoch::advance().map_err(|error| error!(?error, "Failed to advance jemalloc epoch")).is_err() + { + return + } + + if let Ok(value) = stats::active::read() + .map_err(|error| error!(?error, "Failed to read jemalloc.stats.active")) + { + gauge!("jemalloc.active", value as f64); + } + + if let Ok(value) = stats::allocated::read() + .map_err(|error| error!(?error, "Failed to read jemalloc.stats.allocated")) + { + gauge!("jemalloc.allocated", value as f64); + } + + if let Ok(value) = stats::mapped::read() + .map_err(|error| error!(?error, "Failed to read jemalloc.stats.mapped")) + { + gauge!("jemalloc.mapped", value as f64); + } + + if let Ok(value) = stats::metadata::read() + .map_err(|error| error!(?error, "Failed to read jemalloc.stats.metadata")) + { + gauge!("jemalloc.metadata", value as f64); + } + + if let Ok(value) = stats::resident::read() + .map_err(|error| error!(?error, "Failed to read jemalloc.stats.resident")) + { + gauge!("jemalloc.resident", value as f64); + } + + if let Ok(value) = stats::retained::read() + .map_err(|error| error!(?error, "Failed to read jemalloc.stats.retained")) + { + gauge!("jemalloc.retained", value as f64); + } +} + +#[cfg(feature = "jemalloc")] +fn describe_memory_stats() { + use reth_metrics::metrics::describe_gauge; + + describe_gauge!( + "jemalloc.active", + Unit::Bytes, + "Total number of bytes in active pages allocated by the application" + ); + describe_gauge!( + "jemalloc.allocated", + Unit::Bytes, + "Total number of bytes allocated by the application" + ); + describe_gauge!( + "jemalloc.mapped", + Unit::Bytes, + "Total number of bytes in active extents mapped by the allocator" + ); + describe_gauge!( + "jemalloc.metadata", + Unit::Bytes, + "Total number of bytes dedicated to jemalloc metadata" + ); + describe_gauge!( + "jemalloc.resident", + Unit::Bytes, + "Total number of bytes in physically resident data pages mapped by the allocator" + ); + describe_gauge!( + "jemalloc.retained", + Unit::Bytes, + "Total number of bytes in virtual memory mappings that were retained rather than \ + being returned to the operating system via e.g. munmap(2)" + ); +} + +#[cfg(not(feature = "jemalloc"))] +fn collect_memory_stats() {} + +#[cfg(not(feature = "jemalloc"))] +fn describe_memory_stats() {} diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 1150b20f9c13..02d90514eff2 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -127,7 +127,12 @@ impl Command { if let Some(listen_addr) = self.metrics { info!(target: "reth::cli", "Starting metrics endpoint at {}", listen_addr); - prometheus_exporter::initialize_with_db_metrics(listen_addr, Arc::clone(&db)).await?; + prometheus_exporter::initialize( + listen_addr, + Arc::clone(&db), + metrics_process::Collector::default(), + ) + .await?; } let batch_size = self.batch_size.unwrap_or(self.to - self.from + 1); From a53af3a0f28a1cfeceb1d5dd5bc5440fd6f2ae69 Mon Sep 17 00:00:00 2001 From: joshieDo <93316087+joshieDo@users.noreply.github.com> Date: Wed, 28 Jun 2023 22:03:00 +0100 Subject: [PATCH 213/216] refactor: move `init_db` to `reth_db` and add `DatabaseEnv` (#3450) --- Cargo.lock | 2 + bin/reth/src/chain/import.rs | 4 +- bin/reth/src/chain/init.rs | 3 +- bin/reth/src/db/mod.rs | 4 +- bin/reth/src/debug_cmd/execution.rs | 9 +- bin/reth/src/debug_cmd/merkle.rs | 3 +- bin/reth/src/node/mod.rs | 15 ++-- bin/reth/src/prometheus_exporter.rs | 8 +- bin/reth/src/stage/drop.rs | 7 +- bin/reth/src/stage/dump/mod.rs | 4 +- bin/reth/src/stage/run.rs | 2 +- crates/blockchain-tree/src/blockchain_tree.rs | 7 +- crates/consensus/beacon/src/engine/mod.rs | 10 +-- .../interfaces/src/blockchain_tree/error.rs | 1 + crates/interfaces/src/error.rs | 3 + .../net/downloaders/src/bodies/test_utils.rs | 3 +- crates/staged-sync/src/utils/init.rs | 76 ++-------------- crates/stages/benches/criterion.rs | 6 +- crates/stages/benches/setup/mod.rs | 6 +- crates/stages/src/stage.rs | 6 +- crates/stages/src/stages/bodies.rs | 6 +- crates/stages/src/stages/execution.rs | 5 +- crates/stages/src/stages/sender_recovery.rs | 13 ++- crates/stages/src/stages/tx_lookup.rs | 8 +- crates/stages/src/test_utils/runner.rs | 7 +- crates/stages/src/test_utils/test_db.rs | 12 +-- crates/storage/db/Cargo.toml | 1 + crates/storage/db/benches/hash_keys.rs | 14 +-- crates/storage/db/benches/utils.rs | 3 +- crates/storage/db/src/lib.rs | 86 +++++++++++++++++++ crates/storage/provider/Cargo.toml | 1 + crates/storage/provider/src/post_state/mod.rs | 13 +-- .../provider/src/providers/database/mod.rs | 40 ++++++++- .../src/providers/database/provider.rs | 14 +-- crates/trie/src/trie.rs | 7 +- 35 files changed, 234 insertions(+), 175 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a17d38d149de..06036c4a16af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5108,6 +5108,7 @@ dependencies = [ "bytes", "criterion", "derive_more", + "eyre", "futures", "heapless", "iai", @@ -5539,6 +5540,7 @@ dependencies = [ "reth-revm-primitives", "reth-rlp", "reth-trie", + "tempfile", "tokio", "tokio-stream", "tracing", diff --git a/bin/reth/src/chain/import.rs b/bin/reth/src/chain/import.rs index 6a1a79e693d2..ef430a81d8c4 100644 --- a/bin/reth/src/chain/import.rs +++ b/bin/reth/src/chain/import.rs @@ -11,14 +11,14 @@ use reth_provider::{ProviderFactory, StageCheckpointReader}; use crate::args::utils::genesis_value_parser; use reth_config::Config; -use reth_db::database::Database; +use reth_db::{database::Database, init_db}; use reth_downloaders::{ bodies::bodies::BodiesDownloaderBuilder, headers::reverse_headers::ReverseHeadersDownloaderBuilder, test_utils::FileClient, }; use reth_interfaces::consensus::Consensus; use reth_primitives::{stage::StageId, ChainSpec, H256}; -use reth_staged_sync::utils::init::{init_db, init_genesis}; +use reth_staged_sync::utils::init::init_genesis; use reth_stages::{ prelude::*, stages::{ diff --git a/bin/reth/src/chain/init.rs b/bin/reth/src/chain/init.rs index 9be1ae16310e..1a920f78084d 100644 --- a/bin/reth/src/chain/init.rs +++ b/bin/reth/src/chain/init.rs @@ -3,8 +3,9 @@ use crate::{ dirs::{DataDirPath, MaybePlatformPath}, }; use clap::Parser; +use reth_db::init_db; use reth_primitives::ChainSpec; -use reth_staged_sync::utils::init::{init_db, init_genesis}; +use reth_staged_sync::utils::init::init_genesis; use std::sync::Arc; use tracing::info; diff --git a/bin/reth/src/db/mod.rs b/bin/reth/src/db/mod.rs index 8940a1b54496..d44a977aba8d 100644 --- a/bin/reth/src/db/mod.rs +++ b/bin/reth/src/db/mod.rs @@ -12,7 +12,7 @@ use reth_db::{ database::Database, mdbx::{Env, NoWriteMap, WriteMap}, version::{get_db_version, DatabaseVersionError, DB_VERSION}, - Tables, + DatabaseEnv, Tables, }; use reth_primitives::ChainSpec; use std::{path::Path, sync::Arc}; @@ -178,7 +178,7 @@ fn read_only_db(path: &Path) -> eyre::Result> { .with_context(|| format!("Could not open database at path: {}", path.display())) } -fn read_write_db(path: &Path) -> eyre::Result> { +fn read_write_db(path: &Path) -> eyre::Result { Env::::open(path, reth_db::mdbx::EnvKind::RW) .with_context(|| format!("Could not open database at path: {}", path.display())) } diff --git a/bin/reth/src/debug_cmd/execution.rs b/bin/reth/src/debug_cmd/execution.rs index 76fef4a28463..fa3b8502a1ae 100644 --- a/bin/reth/src/debug_cmd/execution.rs +++ b/bin/reth/src/debug_cmd/execution.rs @@ -10,10 +10,7 @@ use clap::Parser; use futures::{stream::select as stream_select, StreamExt}; use reth_beacon_consensus::BeaconConsensus; use reth_config::Config; -use reth_db::{ - database::Database, - mdbx::{Env, WriteMap}, -}; +use reth_db::{database::Database, init_db, DatabaseEnv}; use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_downloaders::{ bodies::bodies::BodiesDownloaderBuilder, @@ -27,7 +24,7 @@ use reth_network::NetworkHandle; use reth_network_api::NetworkInfo; use reth_primitives::{stage::StageId, BlockHashOrNumber, BlockNumber, ChainSpec, H256}; use reth_provider::{BlockExecutionWriter, ProviderFactory, StageCheckpointReader}; -use reth_staged_sync::utils::init::{init_db, init_genesis}; +use reth_staged_sync::utils::init::init_genesis; use reth_stages::{ sets::DefaultStages, stages::{ @@ -153,7 +150,7 @@ impl Command { &self, config: &Config, task_executor: TaskExecutor, - db: Arc>, + db: Arc, network_secret_path: PathBuf, default_peers_path: PathBuf, ) -> eyre::Result { diff --git a/bin/reth/src/debug_cmd/merkle.rs b/bin/reth/src/debug_cmd/merkle.rs index d1aaf291d881..9b186506b1ff 100644 --- a/bin/reth/src/debug_cmd/merkle.rs +++ b/bin/reth/src/debug_cmd/merkle.rs @@ -4,13 +4,12 @@ use crate::{ dirs::{DataDirPath, MaybePlatformPath}, }; use clap::Parser; -use reth_db::{cursor::DbCursorRO, tables, transaction::DbTx}; +use reth_db::{cursor::DbCursorRO, init_db, tables, transaction::DbTx}; use reth_primitives::{ stage::{StageCheckpoint, StageId}, ChainSpec, }; use reth_provider::{ProviderFactory, StageCheckpointReader}; -use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ AccountHashingStage, ExecutionStage, ExecutionStageThresholds, MerkleStage, diff --git a/bin/reth/src/node/mod.rs b/bin/reth/src/node/mod.rs index d7436e152558..aaecdda514eb 100644 --- a/bin/reth/src/node/mod.rs +++ b/bin/reth/src/node/mod.rs @@ -20,10 +20,7 @@ use reth_blockchain_tree::{ config::BlockchainTreeConfig, externals::TreeExternals, BlockchainTree, ShareableBlockchainTree, }; use reth_config::Config; -use reth_db::{ - database::Database, - mdbx::{Env, WriteMap}, -}; +use reth_db::{database::Database, init_db, DatabaseEnv}; use reth_discv4::DEFAULT_DISCOVERY_PORT; use reth_downloaders::{ bodies::bodies::BodiesDownloaderBuilder, @@ -47,7 +44,7 @@ use reth_provider::{ use reth_revm::Factory; use reth_revm_inspectors::stack::Hook; use reth_rpc_engine_api::EngineApi; -use reth_staged_sync::utils::init::{init_db, init_genesis}; +use reth_staged_sync::utils::init::init_genesis; use reth_stages::{ prelude::*, stages::{ @@ -478,7 +475,7 @@ impl Command { } } - async fn start_metrics_endpoint(&self, db: Arc>) -> eyre::Result<()> { + async fn start_metrics_endpoint(&self, db: Arc) -> eyre::Result<()> { if let Some(listen_addr) = self.metrics { info!(target: "reth::cli", addr = %listen_addr, "Starting metrics endpoint"); prometheus_exporter::initialize(listen_addr, db, metrics_process::Collector::default()) @@ -519,7 +516,7 @@ impl Command { Ok(handle) } - fn lookup_head(&self, db: Arc>) -> Result { + fn lookup_head(&self, db: Arc) -> Result { let factory = ProviderFactory::new(db, self.chain.clone()); let provider = factory.provider()?; @@ -604,12 +601,12 @@ impl Command { fn load_network_config( &self, config: &Config, - db: Arc>, + db: Arc, executor: TaskExecutor, head: Head, secret_key: SecretKey, default_peers_path: PathBuf, - ) -> NetworkConfig>>> { + ) -> NetworkConfig>> { self.network .network_config(config, self.chain.clone(), secret_key, default_peers_path) .with_task_executor(Box::new(executor)) diff --git a/bin/reth/src/prometheus_exporter.rs b/bin/reth/src/prometheus_exporter.rs index 2dba64b12b55..f5635c88d28e 100644 --- a/bin/reth/src/prometheus_exporter.rs +++ b/bin/reth/src/prometheus_exporter.rs @@ -6,11 +6,7 @@ use hyper::{ }; use metrics_exporter_prometheus::{PrometheusBuilder, PrometheusHandle}; use metrics_util::layers::{PrefixLayer, Stack}; -use reth_db::{ - database::Database, - mdbx::{Env, WriteMap}, - tables, -}; +use reth_db::{database::Database, tables, DatabaseEnv}; use reth_metrics::metrics::{self, absolute_counter, describe_counter, Unit}; use std::{convert::Infallible, net::SocketAddr, sync::Arc}; @@ -73,7 +69,7 @@ async fn start_endpoint( /// metrics. pub(crate) async fn initialize( listen_addr: SocketAddr, - db: Arc>, + db: Arc, process: metrics_process::Collector, ) -> eyre::Result<()> { let db_stats = move || { diff --git a/bin/reth/src/stage/drop.rs b/bin/reth/src/stage/drop.rs index 8b4bf28365c8..4c2ed15857b6 100644 --- a/bin/reth/src/stage/drop.rs +++ b/bin/reth/src/stage/drop.rs @@ -10,6 +10,7 @@ use reth_db::{ mdbx::{Env, WriteMap}, tables, transaction::DbTxMut, + DatabaseEnv, }; use reth_primitives::{stage::StageId, ChainSpec}; use reth_staged_sync::utils::init::{insert_genesis_header, insert_genesis_state}; @@ -70,7 +71,7 @@ impl Command { tx.clear::()?; tx.clear::()?; tx.put::(StageId::Bodies.to_string(), Default::default())?; - insert_genesis_header::>(tx, self.chain)?; + insert_genesis_header::(tx, self.chain)?; } StageEnum::Senders => { tx.clear::()?; @@ -90,7 +91,7 @@ impl Command { StageId::Execution.to_string(), Default::default(), )?; - insert_genesis_state::>(tx, self.chain.genesis())?; + insert_genesis_state::(tx, self.chain.genesis())?; } StageEnum::AccountHashing => { tx.clear::()?; @@ -155,7 +156,7 @@ impl Command { StageId::TotalDifficulty.to_string(), Default::default(), )?; - insert_genesis_header::>(tx, self.chain)?; + insert_genesis_header::(tx, self.chain)?; } _ => { info!("Nothing to do for stage {:?}", self.stage); diff --git a/bin/reth/src/stage/dump/mod.rs b/bin/reth/src/stage/dump/mod.rs index 4ceffba6f1b4..38add81dabae 100644 --- a/bin/reth/src/stage/dump/mod.rs +++ b/bin/reth/src/stage/dump/mod.rs @@ -5,10 +5,10 @@ use crate::{ }; use clap::Parser; use reth_db::{ - cursor::DbCursorRO, database::Database, table::TableImporter, tables, transaction::DbTx, + cursor::DbCursorRO, database::Database, init_db, table::TableImporter, tables, + transaction::DbTx, }; use reth_primitives::ChainSpec; -use reth_staged_sync::utils::init::init_db; use std::{path::PathBuf, sync::Arc}; use tracing::info; diff --git a/bin/reth/src/stage/run.rs b/bin/reth/src/stage/run.rs index 02d90514eff2..95760bd7294c 100644 --- a/bin/reth/src/stage/run.rs +++ b/bin/reth/src/stage/run.rs @@ -10,10 +10,10 @@ use crate::{ use clap::Parser; use reth_beacon_consensus::BeaconConsensus; use reth_config::Config; +use reth_db::init_db; use reth_downloaders::bodies::bodies::BodiesDownloaderBuilder; use reth_primitives::ChainSpec; use reth_provider::{ProviderFactory, StageCheckpointReader}; -use reth_staged_sync::utils::init::init_db; use reth_stages::{ stages::{ AccountHashingStage, BodyStage, ExecutionStage, ExecutionStageThresholds, diff --git a/crates/blockchain-tree/src/blockchain_tree.rs b/crates/blockchain-tree/src/blockchain_tree.rs index 6b52ec098609..d951939f4986 100644 --- a/crates/blockchain-tree/src/blockchain_tree.rs +++ b/crates/blockchain-tree/src/blockchain_tree.rs @@ -1085,10 +1085,7 @@ mod tests { use crate::block_buffer::BufferedBlocks; use assert_matches::assert_matches; use linked_hash_set::LinkedHashSet; - use reth_db::{ - mdbx::{test_utils::create_test_rw_db, Env, WriteMap}, - transaction::DbTxMut, - }; + use reth_db::{mdbx::test_utils::create_test_rw_db, transaction::DbTxMut, DatabaseEnv}; use reth_interfaces::test_utils::TestConsensus; use reth_primitives::{ proofs::EMPTY_ROOT, stage::StageCheckpoint, ChainSpecBuilder, H256, MAINNET, @@ -1102,7 +1099,7 @@ mod tests { fn setup_externals( exec_res: Vec, - ) -> TreeExternals>, Arc, TestExecutorFactory> { + ) -> TreeExternals, Arc, TestExecutorFactory> { let db = create_test_rw_db(); let consensus = Arc::new(TestConsensus::default()); let chain_spec = Arc::new( diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 70e9fc00722a..96fc9ca0c933 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -1361,7 +1361,7 @@ mod tests { config::BlockchainTreeConfig, externals::TreeExternals, post_state::PostState, BlockchainTree, ShareableBlockchainTree, }; - use reth_db::mdbx::{test_utils::create_test_rw_db, Env, WriteMap}; + use reth_db::{mdbx::test_utils::create_test_rw_db, DatabaseEnv}; use reth_interfaces::{ sync::NoopSyncStateUpdater, test_utils::{NoopFullBlockClient, TestConsensus}, @@ -1381,10 +1381,10 @@ mod tests { }; type TestBeaconConsensusEngine = BeaconConsensusEngine< - Arc>, + Arc, BlockchainProvider< - Arc>, - ShareableBlockchainTree>, TestConsensus, TestExecutorFactory>, + Arc, + ShareableBlockchainTree, TestConsensus, TestExecutorFactory>, >, NoopFullBlockClient, >; @@ -1498,7 +1498,7 @@ mod tests { } /// Builds the test consensus engine into a `TestConsensusEngine` and `TestEnv`. - fn build(self) -> (TestBeaconConsensusEngine, TestEnv>>) { + fn build(self) -> (TestBeaconConsensusEngine, TestEnv>) { reth_tracing::init_test_tracing(); let db = create_test_rw_db(); let consensus = TestConsensus::default(); diff --git a/crates/interfaces/src/blockchain_tree/error.rs b/crates/interfaces/src/blockchain_tree/error.rs index c8913911cb8d..edcdb569baef 100644 --- a/crates/interfaces/src/blockchain_tree/error.rs +++ b/crates/interfaces/src/blockchain_tree/error.rs @@ -272,6 +272,7 @@ impl From for InsertBlockErrorKind { Error::Database(err) => InsertBlockErrorKind::Internal(Box::new(err)), Error::Provider(err) => InsertBlockErrorKind::Internal(Box::new(err)), Error::Network(err) => InsertBlockErrorKind::Internal(Box::new(err)), + Error::Custom(err) => InsertBlockErrorKind::Internal(err.into()), } } } diff --git a/crates/interfaces/src/error.rs b/crates/interfaces/src/error.rs index 7e6e2d618829..b8b72fc92faa 100644 --- a/crates/interfaces/src/error.rs +++ b/crates/interfaces/src/error.rs @@ -19,4 +19,7 @@ pub enum Error { #[error(transparent)] Network(#[from] reth_network_api::NetworkError), + + #[error("{0}")] + Custom(std::string::String), } diff --git a/crates/net/downloaders/src/bodies/test_utils.rs b/crates/net/downloaders/src/bodies/test_utils.rs index 1a77745e490a..6aefe2e066ba 100644 --- a/crates/net/downloaders/src/bodies/test_utils.rs +++ b/crates/net/downloaders/src/bodies/test_utils.rs @@ -5,6 +5,7 @@ use reth_db::{ mdbx::{Env, WriteMap}, tables, transaction::DbTxMut, + DatabaseEnv, }; use reth_interfaces::{db, p2p::bodies::response::BlockResponse}; use reth_primitives::{Block, BlockBody, SealedBlock, SealedHeader, H256}; @@ -46,7 +47,7 @@ pub(crate) fn create_raw_bodies<'a>( } #[inline] -pub(crate) fn insert_headers(db: &Env, headers: &[SealedHeader]) { +pub(crate) fn insert_headers(db: &DatabaseEnv, headers: &[SealedHeader]) { db.update(|tx| -> Result<(), db::DatabaseError> { for header in headers { tx.put::(header.number, header.hash())?; diff --git a/crates/staged-sync/src/utils/init.rs b/crates/staged-sync/src/utils/init.rs index aadf2d6d90d4..b80554e142e5 100644 --- a/crates/staged-sync/src/utils/init.rs +++ b/crates/staged-sync/src/utils/init.rs @@ -1,40 +1,14 @@ -use eyre::WrapErr; use reth_db::{ cursor::DbCursorRO, database::{Database, DatabaseGAT}, - is_database_empty, - mdbx::{Env, WriteMap}, tables, transaction::{DbTx, DbTxMut}, - version::{check_db_version_file, create_db_version_file, DatabaseVersionError}, }; use reth_primitives::{stage::StageId, Account, Bytecode, ChainSpec, StorageEntry, H256, U256}; use reth_provider::{DatabaseProviderRW, HashingWriter, HistoryWriter, PostState, ProviderFactory}; -use std::{collections::BTreeMap, fs, path::Path, sync::Arc}; +use std::{collections::BTreeMap, sync::Arc}; use tracing::debug; -/// Opens up an existing database or creates a new one at the specified path. -pub fn init_db>(path: P) -> eyre::Result> { - if is_database_empty(&path) { - fs::create_dir_all(&path).wrap_err_with(|| { - format!("Could not create database directory {}", path.as_ref().display()) - })?; - create_db_version_file(&path)?; - } else { - match check_db_version_file(&path) { - Ok(_) => (), - Err(DatabaseVersionError::MissingFile) => create_db_version_file(&path)?, - Err(err) => return Err(err.into()), - } - } - - let db = Env::::open(path.as_ref(), reth_db::mdbx::EnvKind::RW)?; - - db.create_tables()?; - - Ok(db) -} - /// Database initialization error type. #[derive(Debug, thiserror::Error, PartialEq, Eq, Clone)] pub enum InitDatabaseError { @@ -202,19 +176,18 @@ pub fn insert_genesis_header( #[cfg(test)] mod tests { use super::*; - use assert_matches::assert_matches; + use reth_db::{ mdbx::test_utils::create_test_rw_db, models::{storage_sharded_key::StorageShardedKey, ShardedKey}, table::Table, - version::db_version_file_path, + DatabaseEnv, }; use reth_primitives::{ Address, Chain, ForkTimestamps, Genesis, GenesisAccount, IntegerList, GOERLI, GOERLI_GENESIS, MAINNET, MAINNET_GENESIS, SEPOLIA, SEPOLIA_GENESIS, }; use std::collections::HashMap; - use tempfile::tempdir; fn collect_table_entries( tx: &>::TX, @@ -305,7 +278,7 @@ mod tests { let tx = db.tx().expect("failed to init tx"); assert_eq!( - collect_table_entries::>, tables::AccountHistory>(&tx) + collect_table_entries::, tables::AccountHistory>(&tx) .expect("failed to collect"), vec![ (ShardedKey::new(address_with_balance, u64::MAX), IntegerList::new([0]).unwrap()), @@ -314,7 +287,7 @@ mod tests { ); assert_eq!( - collect_table_entries::>, tables::StorageHistory>(&tx) + collect_table_entries::, tables::StorageHistory>(&tx) .expect("failed to collect"), vec![( StorageShardedKey::new(address_with_storage, storage_key, u64::MAX), @@ -322,43 +295,4 @@ mod tests { )], ); } - - #[test] - fn db_version() { - let path = tempdir().unwrap(); - - // Database is empty - { - let db = init_db(&path); - assert_matches!(db, Ok(_)); - } - - // Database is not empty, current version is the same as in the file - { - let db = init_db(&path); - assert_matches!(db, Ok(_)); - } - - // Database is not empty, version file is malformed - { - fs::write(path.path().join(db_version_file_path(&path)), "invalid-version").unwrap(); - let db = init_db(&path); - assert!(db.is_err()); - assert_matches!( - db.unwrap_err().downcast_ref::(), - Some(DatabaseVersionError::MalformedFile) - ) - } - - // Database is not empty, version file contains not matching version - { - fs::write(path.path().join(db_version_file_path(&path)), "0").unwrap(); - let db = init_db(&path); - assert!(db.is_err()); - assert_matches!( - db.unwrap_err().downcast_ref::(), - Some(DatabaseVersionError::VersionMismatch { version: 0 }) - ) - } - } } diff --git a/crates/stages/benches/criterion.rs b/crates/stages/benches/criterion.rs index 2e9fca48d8e5..8fce2e37035e 100644 --- a/crates/stages/benches/criterion.rs +++ b/crates/stages/benches/criterion.rs @@ -3,7 +3,7 @@ use criterion::{ BenchmarkGroup, Criterion, }; use pprof::criterion::{Output, PProfProfiler}; -use reth_db::mdbx::{Env, WriteMap}; +use reth_db::DatabaseEnv; use reth_interfaces::test_utils::TestConsensus; use reth_primitives::{stage::StageCheckpoint, MAINNET}; use reth_provider::ProviderFactory; @@ -122,7 +122,7 @@ fn measure_stage_with_path( stage_range: StageRange, label: String, ) where - S: Clone + Stage>, + S: Clone + Stage, F: Fn(S, &TestTransaction, StageRange), { let tx = TestTransaction::new(&path); @@ -152,7 +152,7 @@ fn measure_stage( block_interval: std::ops::Range, label: String, ) where - S: Clone + Stage>, + S: Clone + Stage, F: Fn(S, &TestTransaction, StageRange), { let path = setup::txs_testdata(block_interval.end); diff --git a/crates/stages/benches/setup/mod.rs b/crates/stages/benches/setup/mod.rs index f3af8b075c52..0c2a6dc19172 100644 --- a/crates/stages/benches/setup/mod.rs +++ b/crates/stages/benches/setup/mod.rs @@ -1,9 +1,9 @@ use itertools::concat; use reth_db::{ cursor::DbCursorRO, - mdbx::{Env, WriteMap}, tables, transaction::{DbTx, DbTxMut}, + DatabaseEnv, }; use reth_interfaces::test_utils::{ generators, @@ -32,7 +32,7 @@ pub use account_hashing::*; pub(crate) type StageRange = (ExecInput, UnwindInput); -pub(crate) fn stage_unwind>>( +pub(crate) fn stage_unwind>( stage: S, tx: &TestTransaction, range: StageRange, @@ -60,7 +60,7 @@ pub(crate) fn stage_unwind>>( }); } -pub(crate) fn unwind_hashes>>( +pub(crate) fn unwind_hashes>( stage: S, tx: &TestTransaction, range: StageRange, diff --git a/crates/stages/src/stage.rs b/crates/stages/src/stage.rs index 09519d34b273..7580c6bab422 100644 --- a/crates/stages/src/stage.rs +++ b/crates/stages/src/stage.rs @@ -5,7 +5,7 @@ use reth_primitives::{ stage::{StageCheckpoint, StageId}, BlockNumber, TxNumber, }; -use reth_provider::DatabaseProviderRW; +use reth_provider::{BlockReader, DatabaseProviderRW, ProviderError}; use std::{ cmp::{max, min}, ops::RangeInclusive, @@ -79,7 +79,9 @@ impl ExecInput { tx_threshold: u64, ) -> Result<(RangeInclusive, RangeInclusive, bool), StageError> { let start_block = self.next_block(); - let start_block_body = provider.block_body_indices(start_block)?; + let start_block_body = provider + .block_body_indices(start_block)? + .ok_or(ProviderError::BlockBodyIndicesNotFound(start_block))?; let target_block = self.target(); diff --git a/crates/stages/src/stages/bodies.rs b/crates/stages/src/stages/bodies.rs index 14f6302d87e7..e1a003dce7f7 100644 --- a/crates/stages/src/stages/bodies.rs +++ b/crates/stages/src/stages/bodies.rs @@ -456,10 +456,10 @@ mod tests { use reth_db::{ cursor::DbCursorRO, database::Database, - mdbx::{Env, WriteMap}, models::{StoredBlockBodyIndices, StoredBlockOmmers}, tables, transaction::{DbTx, DbTxMut}, + DatabaseEnv, }; use reth_interfaces::{ p2p::{ @@ -740,7 +740,7 @@ mod tests { /// A [BodyDownloader] that is backed by an internal [HashMap] for testing. #[derive(Debug)] pub(crate) struct TestBodyDownloader { - db: Arc>, + db: Arc, responses: HashMap, headers: VecDeque, batch_size: u64, @@ -748,7 +748,7 @@ mod tests { impl TestBodyDownloader { pub(crate) fn new( - db: Arc>, + db: Arc, responses: HashMap, batch_size: u64, ) -> Self { diff --git a/crates/stages/src/stages/execution.rs b/crates/stages/src/stages/execution.rs index 68038cc73003..06440410c3dd 100644 --- a/crates/stages/src/stages/execution.rs +++ b/crates/stages/src/stages/execution.rs @@ -353,7 +353,10 @@ impl Stage for ExecutionStage { } // Look up the start index for the transaction range - let first_tx_num = provider.block_body_indices(*range.start())?.first_tx_num(); + let first_tx_num = provider + .block_body_indices(*range.start())? + .ok_or(ProviderError::BlockBodyIndicesNotFound(*range.start()))? + .first_tx_num(); let mut stage_checkpoint = input.checkpoint.execution_stage_checkpoint(); diff --git a/crates/stages/src/stages/sender_recovery.rs b/crates/stages/src/stages/sender_recovery.rs index 26def0568253..482ed1eb43f5 100644 --- a/crates/stages/src/stages/sender_recovery.rs +++ b/crates/stages/src/stages/sender_recovery.rs @@ -13,7 +13,7 @@ use reth_primitives::{ stage::{EntitiesCheckpoint, StageCheckpoint, StageId}, TransactionSignedNoHash, TxNumber, H160, }; -use reth_provider::{DatabaseProviderRW, HeaderProvider, ProviderError}; +use reth_provider::{BlockReader, DatabaseProviderRW, HeaderProvider, ProviderError}; use std::fmt::Debug; use thiserror::Error; use tokio::sync::mpsc; @@ -173,7 +173,10 @@ impl Stage for SenderRecoveryStage { let (_, unwind_to, _) = input.unwind_block_range_with_threshold(self.commit_threshold); // Lookup latest tx id that we should unwind to - let latest_tx_id = provider.block_body_indices(unwind_to)?.last_tx_num(); + let latest_tx_id = provider + .block_body_indices(unwind_to)? + .ok_or(ProviderError::BlockBodyIndicesNotFound(unwind_to))? + .last_tx_num(); provider.unwind_table_by_num::(latest_tx_id)?; Ok(UnwindOutput { @@ -386,7 +389,11 @@ mod tests { /// 2. If the is no requested block entry in the bodies table, but [tables::TxSenders] is /// not empty. fn ensure_no_senders_by_block(&self, block: BlockNumber) -> Result<(), TestRunnerError> { - let body_result = self.tx.inner_rw().block_body_indices(block); + let body_result = self + .tx + .inner_rw() + .block_body_indices(block)? + .ok_or(ProviderError::BlockBodyIndicesNotFound(block)); match body_result { Ok(body) => self .tx diff --git a/crates/stages/src/stages/tx_lookup.rs b/crates/stages/src/stages/tx_lookup.rs index 2d32db03d3d3..f572a7f27b6f 100644 --- a/crates/stages/src/stages/tx_lookup.rs +++ b/crates/stages/src/stages/tx_lookup.rs @@ -203,7 +203,7 @@ mod tests { generators::{random_block, random_block_range}, }; use reth_primitives::{stage::StageUnitCheckpoint, BlockNumber, SealedBlock, H256}; - use reth_provider::TransactionsProvider; + use reth_provider::{BlockReader, ProviderError, TransactionsProvider}; // Implement stage test suite. stage_test_suite_ext!(TransactionLookupTestRunner, transaction_lookup); @@ -345,7 +345,11 @@ mod tests { /// 2. If the is no requested block entry in the bodies table, but [tables::TxHashNumber] is /// not empty. fn ensure_no_hash_by_block(&self, number: BlockNumber) -> Result<(), TestRunnerError> { - let body_result = self.tx.inner_rw().block_body_indices(number); + let body_result = self + .tx + .inner_rw() + .block_body_indices(number)? + .ok_or(ProviderError::BlockBodyIndicesNotFound(number)); match body_result { Ok(body) => self.tx.ensure_no_entry_above_by_value::( body.last_tx_num(), diff --git a/crates/stages/src/test_utils/runner.rs b/crates/stages/src/test_utils/runner.rs index 83d97a0388b9..e726948e1cc6 100644 --- a/crates/stages/src/test_utils/runner.rs +++ b/crates/stages/src/test_utils/runner.rs @@ -1,6 +1,9 @@ use super::TestTransaction; use crate::{ExecInput, ExecOutput, Stage, StageError, UnwindInput, UnwindOutput}; -use reth_db::mdbx::{Env, WriteMap}; +use reth_db::{ + mdbx::{Env, WriteMap}, + DatabaseEnv, +}; use reth_primitives::MAINNET; use reth_provider::ProviderFactory; use std::{borrow::Borrow, sync::Arc}; @@ -19,7 +22,7 @@ pub(crate) enum TestRunnerError { /// A generic test runner for stages. #[async_trait::async_trait] pub(crate) trait StageTestRunner { - type S: Stage> + 'static; + type S: Stage + 'static; /// Return a reference to the database. fn tx(&self) -> &TestTransaction; diff --git a/crates/stages/src/test_utils/test_db.rs b/crates/stages/src/test_utils/test_db.rs index 6d4a62595113..ef2c04c5ae7f 100644 --- a/crates/stages/src/test_utils/test_db.rs +++ b/crates/stages/src/test_utils/test_db.rs @@ -10,7 +10,7 @@ use reth_db::{ table::Table, tables, transaction::{DbTx, DbTxMut}, - DatabaseError as DbError, + DatabaseEnv, DatabaseError as DbError, }; use reth_primitives::{ keccak256, Account, Address, BlockNumber, SealedBlock, SealedHeader, StorageEntry, H256, @@ -35,9 +35,9 @@ use std::{ #[derive(Debug)] pub struct TestTransaction { /// WriteMap DB - pub tx: Arc>, + pub tx: Arc, pub path: Option, - pub factory: ProviderFactory>>, + pub factory: ProviderFactory>, } impl Default for TestTransaction { @@ -59,17 +59,17 @@ impl TestTransaction { } /// Return a database wrapped in [DatabaseProviderRW]. - pub fn inner_rw(&self) -> DatabaseProviderRW<'_, Arc>> { + pub fn inner_rw(&self) -> DatabaseProviderRW<'_, Arc> { self.factory.provider_rw().expect("failed to create db container") } /// Return a database wrapped in [DatabaseProviderRO]. - pub fn inner(&self) -> DatabaseProviderRO<'_, Arc>> { + pub fn inner(&self) -> DatabaseProviderRO<'_, Arc> { self.factory.provider().expect("failed to create db container") } /// Get a pointer to an internal database. - pub fn inner_raw(&self) -> Arc> { + pub fn inner_raw(&self) -> Arc { self.tx.clone() } diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 5254d69bb6a9..5b5987b31569 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -38,6 +38,7 @@ thiserror = { workspace = true } tempfile = { version = "3.3.0", optional = true } parking_lot = "0.12" derive_more = "0.99" +eyre = "0.6.8" # arbitrary utils arbitrary = { version = "1.1.7", features = ["derive"], optional = true } diff --git a/crates/storage/db/benches/hash_keys.rs b/crates/storage/db/benches/hash_keys.rs index 078ae5bee462..7b8fe3dc1ebf 100644 --- a/crates/storage/db/benches/hash_keys.rs +++ b/crates/storage/db/benches/hash_keys.rs @@ -162,10 +162,7 @@ where (preload, input) } -fn append( - db: Env, - input: Vec<(::Key, ::Value)>, -) -> Env +fn append(db: DatabaseEnv, input: Vec<(::Key, ::Value)>) -> DatabaseEnv where T: Table + Default, { @@ -183,10 +180,7 @@ where db } -fn insert( - db: Env, - input: Vec<(::Key, ::Value)>, -) -> Env +fn insert(db: DatabaseEnv, input: Vec<(::Key, ::Value)>) -> DatabaseEnv where T: Table + Default, { @@ -204,7 +198,7 @@ where db } -fn put(db: Env, input: Vec<(::Key, ::Value)>) -> Env +fn put(db: DatabaseEnv, input: Vec<(::Key, ::Value)>) -> DatabaseEnv where T: Table + Default, { @@ -231,7 +225,7 @@ struct TableStats { size: usize, } -fn get_table_stats(db: Env) +fn get_table_stats(db: DatabaseEnv) where T: Table + Default, { diff --git a/crates/storage/db/benches/utils.rs b/crates/storage/db/benches/utils.rs index 8f159ec1daa6..30f8f6ac4985 100644 --- a/crates/storage/db/benches/utils.rs +++ b/crates/storage/db/benches/utils.rs @@ -1,3 +1,4 @@ +use reth_db::DatabaseEnv; #[allow(unused_imports)] use reth_db::{ database::Database, @@ -51,7 +52,7 @@ where fn set_up_db( bench_db_path: &Path, pair: &Vec<(::Key, bytes::Bytes, ::Value, bytes::Bytes)>, -) -> reth_db::mdbx::Env +) -> DatabaseEnv where T: Table + Default, T::Key: Default + Clone, diff --git a/crates/storage/db/src/lib.rs b/crates/storage/db/src/lib.rs index 49bdad2061be..c2b5d539dcb7 100644 --- a/crates/storage/db/src/lib.rs +++ b/crates/storage/db/src/lib.rs @@ -87,3 +87,89 @@ pub use abstraction::*; pub use reth_interfaces::db::DatabaseError; pub use tables::*; pub use utils::is_database_empty; + +#[cfg(feature = "mdbx")] +use mdbx::{Env, EnvKind, WriteMap}; + +#[cfg(feature = "mdbx")] +/// Alias type for the database engine in use. +pub type DatabaseEnv = Env; + +/// Opens up an existing database or creates a new one at the specified path. +pub fn init_db>(path: P) -> eyre::Result { + use crate::version::{check_db_version_file, create_db_version_file, DatabaseVersionError}; + use eyre::WrapErr; + + let rpath = path.as_ref(); + if is_database_empty(rpath) { + std::fs::create_dir_all(rpath) + .wrap_err_with(|| format!("Could not create database directory {}", rpath.display()))?; + create_db_version_file(rpath)?; + } else { + match check_db_version_file(rpath) { + Ok(_) => (), + Err(DatabaseVersionError::MissingFile) => create_db_version_file(rpath)?, + Err(err) => return Err(err.into()), + } + } + #[cfg(feature = "mdbx")] + { + let db = Env::::open(rpath, EnvKind::RW)?; + db.create_tables()?; + Ok(db) + } + #[cfg(not(feature = "mdbx"))] + { + unimplemented!(); + } +} + +#[cfg(test)] +mod tests { + use crate::{ + init_db, + version::{db_version_file_path, DatabaseVersionError}, + }; + use assert_matches::assert_matches; + use tempfile::tempdir; + + #[test] + fn db_version() { + let path = tempdir().unwrap(); + + // Database is empty + { + let db = init_db(&path); + assert_matches!(db, Ok(_)); + } + + // Database is not empty, current version is the same as in the file + { + let db = init_db(&path); + assert_matches!(db, Ok(_)); + } + + // Database is not empty, version file is malformed + { + std::fs::write(path.path().join(db_version_file_path(&path)), "invalid-version") + .unwrap(); + let db = init_db(&path); + assert!(db.is_err()); + assert_matches!( + db.unwrap_err().downcast_ref::(), + Some(DatabaseVersionError::MalformedFile) + ) + } + + // Database is not empty, version file contains not matching version + { + std::fs::write(path.path().join(db_version_file_path(&path)), "0").unwrap(); + let db = init_db(&path); + assert!(db.is_err()); + assert_matches!( + db.unwrap_err().downcast_ref::(), + Some(DatabaseVersionError::VersionMismatch { version: 0 }) + ) + } + } +} diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 25b7e9f4c830..07a2a9a02f20 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -39,6 +39,7 @@ reth-primitives = { workspace = true, features = ["arbitrary", "test-utils"] } reth-rlp = { workspace = true } reth-trie = { path = "../../trie", features = ["test-utils"] } parking_lot = "0.12" +tempfile = "3.3" [features] test-utils = ["reth-rlp"] diff --git a/crates/storage/provider/src/post_state/mod.rs b/crates/storage/provider/src/post_state/mod.rs index e9eb216f1e14..1ab52b1b9347 100644 --- a/crates/storage/provider/src/post_state/mod.rs +++ b/crates/storage/provider/src/post_state/mod.rs @@ -643,8 +643,9 @@ mod tests { use crate::{AccountReader, ProviderFactory}; use reth_db::{ database::Database, - mdbx::{test_utils, Env, EnvKind, WriteMap}, + mdbx::{test_utils, EnvKind}, transaction::DbTx, + DatabaseEnv, }; use reth_primitives::{proofs::EMPTY_ROOT, MAINNET}; use reth_trie::test_utils::state_root; @@ -1066,7 +1067,7 @@ mod tests { #[test] fn write_to_db_account_info() { - let db: Arc> = test_utils::create_test_db(EnvKind::RW); + let db: Arc = test_utils::create_test_db(EnvKind::RW); let factory = ProviderFactory::new(db, MAINNET.clone()); let provider = factory.provider_rw().unwrap(); @@ -1135,7 +1136,7 @@ mod tests { #[test] fn write_to_db_storage() { - let db: Arc> = test_utils::create_test_db(EnvKind::RW); + let db: Arc = test_utils::create_test_db(EnvKind::RW); let tx = db.tx_mut().expect("Could not get database tx"); let mut post_state = PostState::new(); @@ -1271,7 +1272,7 @@ mod tests { #[test] fn write_to_db_multiple_selfdestructs() { - let db: Arc> = test_utils::create_test_db(EnvKind::RW); + let db: Arc = test_utils::create_test_db(EnvKind::RW); let tx = db.tx_mut().expect("Could not get database tx"); let address1 = Address::random(); @@ -1820,7 +1821,7 @@ mod tests { #[test] fn empty_post_state_state_root() { - let db: Arc> = test_utils::create_test_db(EnvKind::RW); + let db: Arc = test_utils::create_test_db(EnvKind::RW); let tx = db.tx().unwrap(); let post_state = PostState::new(); @@ -1839,7 +1840,7 @@ mod tests { }) .collect(); - let db: Arc> = test_utils::create_test_db(EnvKind::RW); + let db: Arc = test_utils::create_test_db(EnvKind::RW); // insert initial state to the database db.update(|tx| { diff --git a/crates/storage/provider/src/providers/database/mod.rs b/crates/storage/provider/src/providers/database/mod.rs index ebfefd57c1e4..acfdff010c37 100644 --- a/crates/storage/provider/src/providers/database/mod.rs +++ b/crates/storage/provider/src/providers/database/mod.rs @@ -4,7 +4,7 @@ use crate::{ BlockHashReader, BlockNumReader, BlockReader, EvmEnvProvider, HeaderProvider, ProviderError, StageCheckpointReader, StateProviderBox, TransactionsProvider, WithdrawalsProvider, }; -use reth_db::{database::Database, models::StoredBlockBodyIndices}; +use reth_db::{database::Database, init_db, models::StoredBlockBodyIndices, DatabaseEnv}; use reth_interfaces::Result; use reth_primitives::{ stage::{StageCheckpoint, StageId}, @@ -54,6 +54,20 @@ impl ProviderFactory { } } +impl ProviderFactory { + /// create new database provider by passing a path. [`ProviderFactory`] will own the database + /// instance. + pub fn new_with_database_path>( + path: P, + chain_spec: Arc, + ) -> Result> { + Ok(ProviderFactory:: { + db: init_db(path).map_err(|e| reth_interfaces::Error::Custom(e.to_string()))?, + chain_spec, + }) + } +} + impl Clone for ProviderFactory { fn clone(&self) -> Self { Self { db: self.db.clone(), chain_spec: Arc::clone(&self.chain_spec) } @@ -333,7 +347,13 @@ impl EvmEnvProvider for ProviderFactory { mod tests { use super::ProviderFactory; use crate::{BlockHashReader, BlockNumReader}; - use reth_db::mdbx::{test_utils::create_test_db, EnvKind, WriteMap}; + use reth_db::{ + mdbx::{ + test_utils::{create_test_db, ERROR_TEMPDIR}, + EnvKind, WriteMap, + }, + DatabaseEnv, + }; use reth_primitives::{ChainSpecBuilder, H256}; use std::sync::Arc; @@ -368,4 +388,20 @@ mod tests { provider_rw.block_hash(0).unwrap(); provider.block_hash(0).unwrap(); } + + #[test] + fn provider_factory_with_database_path() { + let chain_spec = ChainSpecBuilder::mainnet().build(); + let factory = ProviderFactory::::new_with_database_path( + tempfile::TempDir::new().expect(ERROR_TEMPDIR).into_path(), + Arc::new(chain_spec), + ) + .unwrap(); + + let provider = factory.provider().unwrap(); + provider.block_hash(0).unwrap(); + let provider_rw = factory.provider_rw().unwrap(); + provider_rw.block_hash(0).unwrap(); + provider.block_hash(0).unwrap(); + } } diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index d0842f6c5b12..8ce2855e965a 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -560,15 +560,6 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> DatabaseProvider<'this, TX> { Ok(blocks) } - /// Query the block body by number. - pub fn block_body_indices(&self, number: BlockNumber) -> Result { - let body = self - .tx - .get::(number)? - .ok_or(ProviderError::BlockBodyIndicesNotFound(number))?; - Ok(body) - } - /// Unwind table by some number key. /// Returns number of rows unwound. /// @@ -1695,9 +1686,8 @@ impl<'this, TX: DbTxMut<'this> + DbTx<'this>> BlockWriter for DatabaseProvider<' block.difficulty } else { let parent_block_number = block.number - 1; - let parent_ttd = - self.tx.get::(parent_block_number)?.unwrap_or_default(); - parent_ttd.0 + block.difficulty + let parent_ttd = self.header_td_by_number(parent_block_number)?.unwrap_or_default(); + parent_ttd + block.difficulty }; self.tx.put::(block.number, ttd.into())?; diff --git a/crates/trie/src/trie.rs b/crates/trie/src/trie.rs index ccf9b6ebe138..679d98a764b7 100644 --- a/crates/trie/src/trie.rs +++ b/crates/trie/src/trie.rs @@ -514,9 +514,10 @@ mod tests { use proptest::{prelude::ProptestConfig, proptest}; use reth_db::{ cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, - mdbx::{test_utils::create_test_rw_db, Env, WriteMap}, + mdbx::test_utils::create_test_rw_db, tables, transaction::DbTxMut, + DatabaseEnv, }; use reth_primitives::{ hex_literal::hex, @@ -1278,7 +1279,7 @@ mod tests { } fn extension_node_storage_trie( - tx: &DatabaseProviderRW<'_, &Env>, + tx: &DatabaseProviderRW<'_, &DatabaseEnv>, hashed_address: H256, ) -> (H256, HashMap) { let value = U256::from(1); @@ -1304,7 +1305,7 @@ mod tests { (root, updates) } - fn extension_node_trie(tx: &DatabaseProviderRW<'_, &Env>) -> H256 { + fn extension_node_trie(tx: &DatabaseProviderRW<'_, &DatabaseEnv>) -> H256 { let a = Account { nonce: 0, balance: U256::from(1u64), bytecode_hash: Some(H256::random()) }; let val = encode_account(a, None); From 10a5737a59dae49bf5e2cb7347dbadbd167b7956 Mon Sep 17 00:00:00 2001 From: Ryan Schneider Date: Wed, 28 Jun 2023 17:52:59 -0400 Subject: [PATCH 214/216] fix: Use random port for port reuse test (#3457) --- crates/net/network/tests/it/startup.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/net/network/tests/it/startup.rs b/crates/net/network/tests/it/startup.rs index 0d36867fc8fd..8a0695903dcb 100644 --- a/crates/net/network/tests/it/startup.rs +++ b/crates/net/network/tests/it/startup.rs @@ -1,4 +1,4 @@ -use reth_discv4::{Discv4Config, DEFAULT_DISCOVERY_PORT}; +use reth_discv4::Discv4Config; use reth_network::{ error::{NetworkError, ServiceKind}, Discovery, NetworkConfigBuilder, NetworkManager, @@ -10,6 +10,7 @@ use std::{ io, net::{Ipv4Addr, SocketAddr, SocketAddrV4}, }; +use tokio::net::TcpListener; fn is_addr_in_use_kind(err: &NetworkError, kind: ServiceKind) -> bool { match err { @@ -51,7 +52,10 @@ async fn test_listener_addr_in_use() { async fn test_discovery_addr_in_use() { let secret_key = SecretKey::new(&mut rand::thread_rng()); let disc_config = Discv4Config::default(); - let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, DEFAULT_DISCOVERY_PORT)); + let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)); + let any_port_listener = TcpListener::bind(addr).await.unwrap(); + let port = any_port_listener.local_addr().unwrap().port(); + let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, port)); let _discovery = Discovery::new(addr, secret_key, Some(disc_config), None).await.unwrap(); let disc_config = Discv4Config::default(); let result = Discovery::new(addr, secret_key, Some(disc_config), None).await; From 41ac27bd413ad787db7a2deacaf676ef15dcca74 Mon Sep 17 00:00:00 2001 From: Alexey Shekhirin Date: Wed, 28 Jun 2023 23:49:24 +0100 Subject: [PATCH 215/216] chore: jemalloc and process metrics in Grafana dashboard (#3455) --- etc/grafana/dashboards/overview.json | 546 ++++++++++++++++++++++++--- 1 file changed, 487 insertions(+), 59 deletions(-) diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 5a7824ed943a..d1855a239170 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -27,7 +27,7 @@ "type": "grafana", "id": "grafana", "name": "Grafana", - "version": "10.0.0" + "version": "10.0.1" }, { "type": "panel", @@ -159,7 +159,7 @@ "showThresholdLabels": false, "showThresholdMarkers": true }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.0.1", "targets": [ { "datasource": { @@ -226,7 +226,7 @@ "showUnfilled": true, "valueMode": "color" }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.0.1", "targets": [ { "datasource": { @@ -492,8 +492,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -609,7 +608,7 @@ "unit": "percentunit" } }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.0.1", "targets": [ { "datasource": { @@ -748,8 +747,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -886,8 +884,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1006,7 +1003,7 @@ }, "showHeader": true }, - "pluginVersion": "10.0.0", + "pluginVersion": "10.0.1", "targets": [ { "datasource": { @@ -1087,8 +1084,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1228,8 +1224,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1321,8 +1316,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1439,8 +1433,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1752,8 +1745,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1907,8 +1899,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2024,8 +2015,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2144,8 +2134,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2335,8 +2324,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -2448,8 +2436,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2553,8 +2540,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2687,8 +2673,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2804,8 +2789,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2921,8 +2905,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3038,8 +3021,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3132,8 +3114,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3242,8 +3223,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3336,8 +3316,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3430,8 +3409,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3539,8 +3517,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3632,8 +3609,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3737,8 +3713,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3855,7 +3830,7 @@ "h": 8, "w": 11, "x": 0, - "y": 17 + "y": 33 }, "id": 60, "options": { @@ -3947,7 +3922,7 @@ "h": 8, "w": 13, "x": 11, - "y": 17 + "y": 33 }, "id": 62, "options": { @@ -4039,7 +4014,7 @@ "h": 7, "w": 11, "x": 0, - "y": 25 + "y": 41 }, "id": 64, "options": { @@ -4075,6 +4050,459 @@ "repeatDirection": "h", "title": "Payload Builder ($instance)", "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 324 + }, + "id": 97, + "panels": [], + "title": "Process", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 325 + }, + "id": 98, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_jemalloc_active", + "instant": false, + "legendFormat": "Active", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_jemalloc_allocated", + "hide": false, + "instant": false, + "legendFormat": "Allocated", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_jemalloc_mapped", + "hide": false, + "instant": false, + "legendFormat": "Mapped", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_jemalloc_metadata", + "hide": false, + "instant": false, + "legendFormat": "Metadata", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_jemalloc_resident", + "hide": false, + "instant": false, + "legendFormat": "Resident", + "range": true, + "refId": "E" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "builder", + "expr": "reth_jemalloc_retained", + "hide": false, + "instant": false, + "legendFormat": "Retained", + "range": true, + "refId": "F" + } + ], + "title": "Jemalloc Memory", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 325 + }, + "id": 101, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_process_resident_memory_bytes", + "instant": false, + "legendFormat": "Resident", + "range": true, + "refId": "A" + } + ], + "title": "Memory", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "100% = 1 core", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 333 + }, + "id": 99, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "avg(rate(reth_process_cpu_seconds_total[1m]))", + "instant": false, + "legendFormat": "Process", + "range": true, + "refId": "A" + } + ], + "title": "CPU", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "100% = 1 core", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 333 + }, + "id": 100, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "reth_process_open_fds", + "instant": false, + "legendFormat": "Open", + "range": true, + "refId": "A" + } + ], + "title": "File Descriptors", + "type": "timeseries" } ], "refresh": "30s", @@ -4116,6 +4544,6 @@ "timezone": "", "title": "reth", "uid": "2k8BXz24x", - "version": 7, + "version": 8, "weekStart": "" } \ No newline at end of file From b7c977765c5491186e041086d8199cc25a559f0f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 29 Jun 2023 06:23:15 +0200 Subject: [PATCH 216/216] perf: check distance to downloaded block if finalized (#3451) --- crates/consensus/beacon/src/engine/mod.rs | 36 ++++++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/crates/consensus/beacon/src/engine/mod.rs b/crates/consensus/beacon/src/engine/mod.rs index 96fc9ca0c933..7dcbba6f6898 100644 --- a/crates/consensus/beacon/src/engine/mod.rs +++ b/crates/consensus/beacon/src/engine/mod.rs @@ -397,6 +397,12 @@ where self.handle.clone() } + /// Returns true if the distance from the local tip to the block is greater than the configured + /// threshold + fn exceeds_pipeline_run_threshold(&self, local_tip: u64, block: u64) -> bool { + block > local_tip && block - local_tip > self.pipeline_run_threshold + } + /// If validation fails, the response MUST contain the latest valid hash: /// /// - The block hash of the ancestor of the invalid payload satisfying the following two @@ -1031,7 +1037,7 @@ where /// chain is invalid, which means the FCU that triggered the download is invalid. Here we can /// stop because there's nothing to do here and the engine needs to wait for another FCU. fn on_downloaded_block(&mut self, block: SealedBlock) { - let num_hash = block.num_hash(); + let downloaded_num_hash = block.num_hash(); trace!(target: "consensus::engine", hash=?block.hash, number=%block.number, "Downloaded full block"); // check if the block's parent is already marked as invalid if self.check_invalid_ancestor_with_head(block.parent_hash, block.hash).is_some() { @@ -1044,23 +1050,37 @@ where match status { InsertPayloadOk::Inserted(BlockStatus::Valid) => { // block is connected to the current canonical head and is valid. - self.try_make_sync_target_canonical(num_hash); + self.try_make_sync_target_canonical(downloaded_num_hash); } InsertPayloadOk::Inserted(BlockStatus::Accepted) => { // block is connected to the canonical chain, but not the current head - self.try_make_sync_target_canonical(num_hash); + self.try_make_sync_target_canonical(downloaded_num_hash); } InsertPayloadOk::Inserted(BlockStatus::Disconnected { missing_parent }) => { // compare the missing parent with the canonical tip let canonical_tip_num = self.blockchain.canonical_tip().number; + let sync_target_state = self.forkchoice_state_tracker.sync_target_state(); + + let mut requires_pipeline = self.exceeds_pipeline_run_threshold( + canonical_tip_num, + missing_parent.number, + ); + + // check if the downloaded block is the tracked finalized block + if let Some(ref state) = sync_target_state { + if downloaded_num_hash.hash == state.finalized_block_hash { + // we downloaded the finalized block + requires_pipeline = self.exceeds_pipeline_run_threshold( + canonical_tip_num, + downloaded_num_hash.number, + ); + } + } // if the number of missing blocks is greater than the max, run the // pipeline - if missing_parent.number >= canonical_tip_num && - missing_parent.number - canonical_tip_num > - self.pipeline_run_threshold - { - if let Some(state) = self.forkchoice_state_tracker.sync_target_state() { + if requires_pipeline { + if let Some(state) = sync_target_state { // if we have already canonicalized the finalized block, we should // skip the pipeline run if Ok(None) ==