diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index a3b6fe02fc..264b1059ab 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -11,6 +11,7 @@ env: CARGO_TERM_COLOR: always BASELINE: base SEED: reth + RUSTC_WRAPPER: "sccache" name: bench jobs: @@ -22,6 +23,7 @@ jobs: submodules: true - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable + - uses: mozilla-actions/sccache-action@v0.0.9 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index 3fbf89560a..c52a5007ad 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -10,9 +10,12 @@ on: types: [opened, reopened, synchronize, closed] merge_group: +env: + RUSTC_WRAPPER: "sccache" + jobs: build: - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest-8 timeout-minutes: 90 steps: - name: Checkout @@ -33,6 +36,8 @@ jobs: - name: Install Rust nightly uses: dtolnay/rust-toolchain@nightly + - uses: mozilla-actions/sccache-action@v0.0.9 + - name: Build docs run: cd docs/vocs && bash scripts/build-cargo-docs.sh diff --git a/.github/workflows/compact.yml b/.github/workflows/compact.yml index ef3f251b0a..9293d52b80 100644 --- a/.github/workflows/compact.yml +++ b/.github/workflows/compact.yml @@ -13,11 +13,12 @@ on: env: CARGO_TERM_COLOR: always + RUSTC_WRAPPER: "sccache" name: compact-codec jobs: compact-codec: - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest strategy: matrix: bin: @@ -26,6 +27,7 @@ jobs: steps: - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable + - uses: mozilla-actions/sccache-action@v0.0.9 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index d5ee430774..f31fefed35 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -11,6 +11,7 @@ on: env: CARGO_TERM_COLOR: always SEED: rustethereumethereumrust + RUSTC_WRAPPER: "sccache" concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -19,13 +20,14 @@ concurrency: jobs: test: name: e2e-testsuite - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest-4 env: RUST_BACKTRACE: 1 timeout-minutes: 90 steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable + - uses: mozilla-actions/sccache-action@v0.0.9 - uses: taiki-e/install-action@nextest - uses: Swatinem/rust-cache@v2 with: diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml index d9fc6dd1ec..220542504e 100644 --- a/.github/workflows/hive.yml +++ b/.github/workflows/hive.yml @@ -245,7 +245,7 @@ jobs: notify-on-error: needs: test if: failure() - runs-on: depot-ubuntu-latest + runs-on: ubuntu-latest steps: - name: Slack Webhook Action uses: rtCamp/action-slack-notify@v2 diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ad7cf05077..46f5670c72 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -24,7 +24,7 @@ jobs: test: name: test / ${{ matrix.network }} if: github.event_name != 'schedule' - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest-4 env: RUST_BACKTRACE: 1 strategy: diff --git a/.github/workflows/kurtosis-op.yml b/.github/workflows/kurtosis-op.yml index 5172d2976c..8cca9a3879 100644 --- a/.github/workflows/kurtosis-op.yml +++ b/.github/workflows/kurtosis-op.yml @@ -85,7 +85,7 @@ jobs: notify-on-error: needs: test if: failure() - runs-on: depot-ubuntu-latest + runs-on: ubuntu-latest steps: - name: Slack Webhook Action uses: rtCamp/action-slack-notify@v2 diff --git a/.github/workflows/kurtosis.yml b/.github/workflows/kurtosis.yml index 28a7328dc9..0f65ca346e 100644 --- a/.github/workflows/kurtosis.yml +++ b/.github/workflows/kurtosis.yml @@ -58,7 +58,7 @@ jobs: notify-on-error: needs: test if: failure() - runs-on: depot-ubuntu-latest + runs-on: ubuntu-latest steps: - name: Slack Webhook Action uses: rtCamp/action-slack-notify@v2 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 6dab07f812..950d882bbc 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -42,7 +42,7 @@ jobs: clippy: name: clippy - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v6 @@ -98,7 +98,7 @@ jobs: crate-checks: name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }}) - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest-4 strategy: matrix: partition: [1, 2, 3] @@ -140,7 +140,7 @@ jobs: docs: name: docs - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest-4 timeout-minutes: 30 steps: - uses: actions/checkout@v6 @@ -172,7 +172,7 @@ jobs: udeps: name: udeps - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v6 @@ -187,7 +187,7 @@ jobs: book: name: book - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest timeout-minutes: 30 steps: - uses: actions/checkout@v6 @@ -246,7 +246,7 @@ jobs: # Checks that selected crates can compile with power set of features features: name: features (${{ matrix.partition }}/${{ matrix.total_partitions }}) - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest strategy: matrix: partition: [1, 2] diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2ab09bc6ce..d1b6842081 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,6 +22,7 @@ env: CARGO_TERM_COLOR: always DOCKER_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/reth DOCKER_OP_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/op-reth + RUSTC_WRAPPER: "sccache" jobs: dry-run: @@ -51,6 +52,7 @@ jobs: steps: - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable + - uses: mozilla-actions/sccache-action@v0.0.9 - name: Verify crate version matches tag # Check that the Cargo version starts with the tag, # so that Cargo version 1.4.8 can be matched against both v1.4.8 and v1.4.8-rc.1 @@ -104,6 +106,7 @@ jobs: - uses: dtolnay/rust-toolchain@stable with: target: ${{ matrix.configs.target }} + - uses: mozilla-actions/sccache-action@v0.0.9 - name: Install cross main id: cross_main run: | diff --git a/.github/workflows/sync-era.yml b/.github/workflows/sync-era.yml index 97d93c25b7..e6ce12f962 100644 --- a/.github/workflows/sync-era.yml +++ b/.github/workflows/sync-era.yml @@ -9,6 +9,7 @@ on: env: CARGO_TERM_COLOR: always + RUSTC_WRAPPER: "sccache" concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -41,6 +42,7 @@ jobs: - uses: actions/checkout@v6 - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable + - uses: mozilla-actions/sccache-action@v0.0.9 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index 89367ec6f2..c1c5794fd0 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -9,6 +9,7 @@ on: env: CARGO_TERM_COLOR: always + RUSTC_WRAPPER: "sccache" concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} @@ -41,6 +42,7 @@ jobs: - uses: actions/checkout@v6 - uses: rui314/setup-mold@v1 - uses: dtolnay/rust-toolchain@stable + - uses: mozilla-actions/sccache-action@v0.0.9 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 31ac626515..d85429d24d 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -20,7 +20,7 @@ concurrency: jobs: test: name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }}) - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest-4 env: RUST_BACKTRACE: 1 strategy: @@ -101,7 +101,7 @@ jobs: doc: name: doc tests - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest env: RUST_BACKTRACE: 1 timeout-minutes: 30 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index ee9064ba65..9bcadad6b8 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -14,7 +14,7 @@ env: jobs: check-reth: - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest timeout-minutes: 60 steps: @@ -34,7 +34,7 @@ jobs: run: cargo check --target x86_64-pc-windows-gnu check-op-reth: - runs-on: depot-ubuntu-latest-16 + runs-on: depot-ubuntu-latest timeout-minutes: 60 steps: diff --git a/Cargo.lock b/Cargo.lock index 6d4f935e03..3eaec41f8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,18 @@ dependencies = [ "thiserror 2.0.17", ] +[[package]] +name = "alloy-eip7928" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926b2c0d34e641cf8b17bf54ce50fda16715b9f68ad878fa6128bae410c6f890" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + [[package]] name = "alloy-eips" version = "1.1.3" @@ -266,9 +278,9 @@ dependencies = [ [[package]] name = "alloy-evm" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70cd39002a40b8d528f4a3f8ecc7e59dc2204d9bfae249296d7d379f291f9cba" +checksum = "3e7b4fb2418490bca9978e74208215ed5fcb21a10aba7eea487abaa60fd588db" dependencies = [ "alloy-consensus", "alloy-eips", @@ -383,9 +395,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.25.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bec5d35135e72ab8096f9b7713840725dc0cd0f9e20ed283156808874da76f4" +checksum = "56afbe3c3b66435c7c3846fe639a60e4cdbc31b9263596eb2f382408b1b160c4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8208,6 +8220,7 @@ name = "reth-engine-tree" version = "1.9.3" dependencies = [ "alloy-consensus", + "alloy-eip7928", "alloy-eips", "alloy-evm", "alloy-primitives", @@ -9232,6 +9245,7 @@ dependencies = [ "alloy-sol-types", "eyre", "futures", + "jsonrpsee-core", "rand 0.9.2", "reth-chainspec", "reth-db", @@ -9267,6 +9281,7 @@ dependencies = [ "serde", "serde_json", "similar-asserts", + "tempfile", "tokio", ] @@ -10157,6 +10172,7 @@ dependencies = [ "reth-db-api", "reth-engine-primitives", "reth-errors", + "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-evm", "reth-evm-ethereum", @@ -10219,7 +10235,9 @@ dependencies = [ "reth-network-peers", "reth-rpc-eth-api", "reth-trie-common", + "serde", "serde_json", + "tokio", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index cfe5aa014a..cbf1f64e87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -376,11 +376,11 @@ reth-era-utils = { path = "crates/era-utils" } reth-errors = { path = "crates/errors" } reth-eth-wire = { path = "crates/net/eth-wire" } reth-eth-wire-types = { path = "crates/net/eth-wire-types" } +reth-ethereum-payload-builder = { path = "crates/ethereum/payload" } reth-ethereum-cli = { path = "crates/ethereum/cli", default-features = false } reth-ethereum-consensus = { path = "crates/ethereum/consensus", default-features = false } reth-ethereum-engine-primitives = { path = "crates/ethereum/engine-primitives", default-features = false } reth-ethereum-forks = { path = "crates/ethereum/hardforks", default-features = false } -reth-ethereum-payload-builder = { path = "crates/ethereum/payload" } reth-ethereum-primitives = { path = "crates/ethereum/primitives", default-features = false } reth-ethereum = { path = "crates/ethereum/reth" } reth-etl = { path = "crates/etl" } @@ -487,7 +487,8 @@ revm-inspectors = "0.33.1" alloy-chains = { version = "0.2.5", default-features = false } alloy-dyn-abi = "1.4.1" alloy-eip2124 = { version = "0.2.0", default-features = false } -alloy-evm = { version = "0.25.0", default-features = false } +alloy-eip7928 = { version = "0.1.0" } +alloy-evm = { version = "0.25.1", default-features = false } alloy-primitives = { version = "1.4.1", default-features = false, features = ["map-foldhash"] } alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } alloy-sol-macro = "1.4.1" diff --git a/crates/cli/commands/src/db/settings.rs b/crates/cli/commands/src/db/settings.rs index c89d144c2b..e8a4152075 100644 --- a/crates/cli/commands/src/db/settings.rs +++ b/crates/cli/commands/src/db/settings.rs @@ -93,6 +93,7 @@ impl Command { transaction_senders_in_static_files: _, storages_history_in_rocksdb: _, transaction_hash_numbers_in_rocksdb: _, + account_history_in_rocksdb: _, } = settings.unwrap_or_else(StorageSettings::legacy); // Update the setting based on the key diff --git a/crates/consensus/debug-client/src/providers/rpc.rs b/crates/consensus/debug-client/src/providers/rpc.rs index 7a8c38ba47..22767d0db5 100644 --- a/crates/consensus/debug-client/src/providers/rpc.rs +++ b/crates/consensus/debug-client/src/providers/rpc.rs @@ -1,5 +1,5 @@ use crate::BlockProvider; -use alloy_provider::{ConnectionConfig, Network, Provider, ProviderBuilder}; +use alloy_provider::{ConnectionConfig, Network, Provider, ProviderBuilder, WebSocketConfig}; use alloy_transport::TransportResult; use futures::{Stream, StreamExt}; use reth_node_api::Block; @@ -29,7 +29,12 @@ impl RpcBlockProvider { ProviderBuilder::default() .connect_with_config( rpc_url, - ConnectionConfig::default().with_max_retries(u32::MAX), + ConnectionConfig::default().with_max_retries(u32::MAX).with_ws_config( + WebSocketConfig::default() + // allow larger messages/frames for big blocks + .max_frame_size(Some(128 * 1024 * 1024)) + .max_message_size(Some(128 * 1024 * 1024)), + ), ) .await?, ), diff --git a/crates/engine/primitives/src/config.rs b/crates/engine/primitives/src/config.rs index f7ed7e8f28..c69689af9f 100644 --- a/crates/engine/primitives/src/config.rs +++ b/crates/engine/primitives/src/config.rs @@ -1,5 +1,7 @@ //! Engine tree configuration. +use alloy_eips::merge::EPOCH_SLOTS; + /// Triggers persistence when the number of canonical blocks in memory exceeds this threshold. pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2; @@ -40,7 +42,7 @@ pub const DEFAULT_RESERVED_CPU_CORES: usize = 1; /// Default maximum concurrency for prewarm task. pub const DEFAULT_PREWARM_MAX_CONCURRENCY: usize = 16; -const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = 256; +const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = EPOCH_SLOTS as u32 * 2; const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256; const DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE: usize = 4; const DEFAULT_CROSS_BLOCK_CACHE_SIZE: u64 = 4 * 1024 * 1024 * 1024; diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index ba99898a84..2b281b90b2 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -39,6 +39,7 @@ reth-trie.workspace = true alloy-evm.workspace = true alloy-consensus.workspace = true alloy-eips.workspace = true +alloy-eip7928.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true alloy-rpc-types-engine.workspace = true diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index f5cd58dcb8..be3de53695 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -11,6 +11,7 @@ use crate::tree::{ StateProviderDatabase, TreeConfig, }; use alloy_consensus::transaction::Either; +use alloy_eip7928::BlockAccessList; use alloy_eips::{eip1898::BlockWithParent, NumHash}; use alloy_evm::Evm; use alloy_primitives::B256; @@ -1243,4 +1244,10 @@ impl BlockOrPayload { Self::Block(_) => "block", } } + + /// Returns the block access list if available. + pub const fn block_access_list(&self) -> Option> { + // TODO decode and return `BlockAccessList` + None + } } diff --git a/crates/ethereum/evm/src/build.rs b/crates/ethereum/evm/src/build.rs index 85d4cae311..2530fc6179 100644 --- a/crates/ethereum/evm/src/build.rs +++ b/crates/ethereum/evm/src/build.rs @@ -5,7 +5,6 @@ use alloy_consensus::{ }; use alloy_eips::merge::BEACON_NONCE; use alloy_evm::{block::BlockExecutorFactory, eth::EthBlockExecutionCtx}; -use alloy_primitives::Bytes; use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_evm::execute::{BlockAssembler, BlockAssemblerInput, BlockExecutionError}; use reth_execution_types::BlockExecutionResult; @@ -17,14 +16,12 @@ use revm::context::Block as _; pub struct EthBlockAssembler { /// The chainspec. pub chain_spec: Arc, - /// Extra data to use for the blocks. - pub extra_data: Bytes, } impl EthBlockAssembler { /// Creates a new [`EthBlockAssembler`]. - pub fn new(chain_spec: Arc) -> Self { - Self { chain_spec, extra_data: Default::default() } + pub const fn new(chain_spec: Arc) -> Self { + Self { chain_spec } } } @@ -110,7 +107,7 @@ where gas_limit: evm_env.block_env.gas_limit(), difficulty: evm_env.block_env.difficulty(), gas_used: *gas_used, - extra_data: self.extra_data.clone(), + extra_data: ctx.extra_data, parent_beacon_block_root: ctx.parent_beacon_block_root, blob_gas_used: block_blob_gas_used, excess_blob_gas, diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index 3a7473d776..094400f918 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -116,12 +116,6 @@ impl EthEvmConfig { pub const fn chain_spec(&self) -> &Arc { self.executor_factory.spec() } - - /// Sets the extra data for the block assembler. - pub fn with_extra_data(mut self, extra_data: Bytes) -> Self { - self.block_assembler.extra_data = extra_data; - self - } } impl ConfigureEvm for EthEvmConfig @@ -193,6 +187,7 @@ where parent_beacon_block_root: block.header().parent_beacon_block_root, ommers: &block.body().ommers, withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed), + extra_data: block.header().extra_data.clone(), }) } @@ -206,6 +201,7 @@ where parent_beacon_block_root: attributes.parent_beacon_block_root, ommers: &[], withdrawals: attributes.withdrawals.map(Cow::Owned), + extra_data: attributes.extra_data, }) } } @@ -282,6 +278,7 @@ where parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(), ommers: &[], withdrawals: payload.payload.withdrawals().map(|w| Cow::Owned(w.clone().into())), + extra_data: payload.payload.as_v1().extra_data.clone(), }) } diff --git a/crates/ethereum/node/Cargo.toml b/crates/ethereum/node/Cargo.toml index 4ea3b8ccb1..0209dcb70a 100644 --- a/crates/ethereum/node/Cargo.toml +++ b/crates/ethereum/node/Cargo.toml @@ -61,6 +61,8 @@ reth-node-core.workspace = true reth-e2e-test-utils.workspace = true reth-tasks.workspace = true reth-testing-utils.workspace = true +tempfile.workspace = true +jsonrpsee-core.workspace = true alloy-primitives.workspace = true alloy-provider.workspace = true diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 5b60f85b52..bdf03404aa 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -32,15 +32,15 @@ use reth_node_builder::{ EngineValidatorBuilder, EthApiBuilder, EthApiCtx, Identity, PayloadValidatorBuilder, RethRpcAddOns, RpcAddOns, RpcHandle, }, - BuilderContext, DebugNode, Node, NodeAdapter, PayloadBuilderConfig, + BuilderContext, DebugNode, Node, NodeAdapter, }; use reth_payload_primitives::PayloadTypes; use reth_provider::{providers::ProviderFactoryBuilder, EthStorage}; use reth_rpc::{ eth::core::{EthApiFor, EthRpcConverterFor}, - ValidationApi, + TestingApi, ValidationApi, }; -use reth_rpc_api::servers::BlockSubmissionValidationApiServer; +use reth_rpc_api::servers::{BlockSubmissionValidationApiServer, TestingApiServer}; use reth_rpc_builder::{config::RethRpcServerConfig, middleware::RethRpcMiddleware}; use reth_rpc_eth_api::{ helpers::{ @@ -313,6 +313,17 @@ where .modules .merge_if_module_configured(RethRpcModule::Eth, eth_config.into_rpc())?; + // testing_buildBlockV1: only wire when the hidden testing module is explicitly + // requested on any transport. Default stays disabled to honor security guidance. + let testing_api = TestingApi::new( + container.registry.eth_api().clone(), + container.registry.evm_config().clone(), + ) + .into_rpc(); + container + .modules + .merge_if_module_configured(RethRpcModule::Testing, testing_api)?; + Ok(()) }) .await @@ -426,9 +437,7 @@ where type EVM = EthEvmConfig; async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { - let evm_config = EthEvmConfig::new(ctx.chain_spec()) - .with_extra_data(ctx.payload_builder_config().extra_data_bytes()); - Ok(evm_config) + Ok(EthEvmConfig::new(ctx.chain_spec())) } } diff --git a/crates/ethereum/node/src/payload.rs b/crates/ethereum/node/src/payload.rs index bd9bb68ebd..644078bc96 100644 --- a/crates/ethereum/node/src/payload.rs +++ b/crates/ethereum/node/src/payload.rs @@ -54,7 +54,8 @@ where evm_config, EthereumBuilderConfig::new() .with_gas_limit(gas_limit) - .with_max_blobs_per_block(conf.max_blobs_per_block()), + .with_max_blobs_per_block(conf.max_blobs_per_block()) + .with_extra_data(conf.extra_data_bytes()), )) } } diff --git a/crates/ethereum/node/tests/it/main.rs b/crates/ethereum/node/tests/it/main.rs index 0f85adda31..7aeec57ada 100644 --- a/crates/ethereum/node/tests/it/main.rs +++ b/crates/ethereum/node/tests/it/main.rs @@ -2,5 +2,6 @@ mod builder; mod exex; +mod testing; const fn main() {} diff --git a/crates/ethereum/node/tests/it/testing.rs b/crates/ethereum/node/tests/it/testing.rs new file mode 100644 index 0000000000..16f26a8c6a --- /dev/null +++ b/crates/ethereum/node/tests/it/testing.rs @@ -0,0 +1,84 @@ +//! E2E tests for the testing RPC namespace. + +use alloy_primitives::{Address, B256}; +use alloy_rpc_types_engine::ExecutionPayloadEnvelopeV4; +use jsonrpsee_core::client::ClientT; +use reth_db::test_utils::create_test_rw_db; +use reth_ethereum_engine_primitives::EthPayloadAttributes; +use reth_node_builder::{NodeBuilder, NodeConfig}; +use reth_node_core::{ + args::DatadirArgs, + dirs::{DataDirPath, MaybePlatformPath}, +}; +use reth_node_ethereum::{node::EthereumAddOns, EthereumNode}; +use reth_rpc_api::TestingBuildBlockRequestV1; +use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection}; +use reth_tasks::TaskManager; +use std::str::FromStr; +use tempfile::tempdir; +use tokio::sync::oneshot; + +#[tokio::test(flavor = "multi_thread")] +async fn testing_rpc_build_block_works() -> eyre::Result<()> { + let tasks = TaskManager::current(); + let mut rpc_args = reth_node_core::args::RpcServerArgs::default().with_http(); + rpc_args.http_api = Some(RpcModuleSelection::from_iter([RethRpcModule::Testing])); + let tempdir = tempdir().expect("temp datadir"); + let datadir_args = DatadirArgs { + datadir: MaybePlatformPath::::from_str(tempdir.path().to_str().unwrap()) + .expect("valid datadir"), + static_files_path: Some(tempdir.path().join("static")), + }; + let config = NodeConfig::test().with_datadir_args(datadir_args).with_rpc(rpc_args); + let db = create_test_rw_db(); + + let (tx, rx): ( + oneshot::Sender>, + oneshot::Receiver>, + ) = oneshot::channel(); + + let builder = NodeBuilder::new(config) + .with_database(db) + .with_launch_context(tasks.executor()) + .with_types::() + .with_components(EthereumNode::components()) + .with_add_ons(EthereumAddOns::default()) + .on_rpc_started(move |ctx, handles| { + let Some(client) = handles.rpc.http_client() else { return Ok(()) }; + + let chain = ctx.config().chain.clone(); + let parent_block_hash = chain.genesis_hash(); + let payload_attributes = EthPayloadAttributes { + timestamp: chain.genesis().timestamp + 1, + prev_randao: B256::ZERO, + suggested_fee_recipient: Address::ZERO, + withdrawals: None, + parent_beacon_block_root: None, + }; + + let request = TestingBuildBlockRequestV1 { + parent_block_hash, + payload_attributes, + transactions: vec![], + extra_data: None, + }; + + tokio::spawn(async move { + let res: eyre::Result = + client.request("testing_buildBlockV1", [request]).await.map_err(Into::into); + let _ = tx.send(res); + }); + + Ok(()) + }); + + // Launch the node with the default engine launcher. + let launcher = builder.engine_api_launcher(); + let _node = builder.launch_with(launcher).await?; + + // Wait for the testing RPC call to return. + let res = rx.await.expect("testing_buildBlockV1 response"); + assert!(res.is_ok(), "testing_buildBlockV1 failed: {:?}", res.err()); + + Ok(()) +} diff --git a/crates/ethereum/payload/src/config.rs b/crates/ethereum/payload/src/config.rs index 00e9a50125..6e429b61f0 100644 --- a/crates/ethereum/payload/src/config.rs +++ b/crates/ethereum/payload/src/config.rs @@ -1,4 +1,5 @@ use alloy_eips::eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M; +use alloy_primitives::Bytes; use reth_primitives_traits::constants::GAS_LIMIT_BOUND_DIVISOR; /// Settings for the Ethereum builder. @@ -13,6 +14,8 @@ pub struct EthereumBuilderConfig { /// /// If `None`, defaults to the protocol maximum. pub max_blobs_per_block: Option, + /// Extra data for built blocks. + pub extra_data: Bytes, } impl Default for EthereumBuilderConfig { @@ -28,6 +31,7 @@ impl EthereumBuilderConfig { desired_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M, await_payload_on_missing: true, max_blobs_per_block: None, + extra_data: Bytes::new(), } } @@ -49,6 +53,12 @@ impl EthereumBuilderConfig { self.max_blobs_per_block = max_blobs_per_block; self } + + /// Set the extra data for built blocks. + pub fn with_extra_data(mut self, extra_data: Bytes) -> Self { + self.extra_data = extra_data; + self + } } impl EthereumBuilderConfig { diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index d092267c4b..d23b21e6e9 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -168,6 +168,7 @@ where gas_limit: builder_config.gas_limit(parent_header.gas_limit), parent_beacon_block_root: attributes.parent_beacon_block_root(), withdrawals: Some(attributes.withdrawals().clone()), + extra_data: builder_config.extra_data, }, ) .map_err(PayloadBuilderError::other)?; diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index e2101fd915..aca8726b24 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -28,7 +28,7 @@ use alloy_evm::{ block::{BlockExecutorFactory, BlockExecutorFor}, precompiles::PrecompilesMap, }; -use alloy_primitives::{Address, B256}; +use alloy_primitives::{Address, Bytes, B256}; use core::{error::Error, fmt::Debug}; use execute::{BasicBlockExecutor, BlockAssembler, BlockBuilder}; use reth_execution_errors::BlockExecutionError; @@ -501,6 +501,8 @@ pub struct NextBlockEnvAttributes { pub parent_beacon_block_root: Option, /// Withdrawals pub withdrawals: Option, + /// Optional extra data. + pub extra_data: Bytes, } /// Abstraction over transaction environment. diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 4e40b5b051..130e5eb675 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -859,8 +859,8 @@ impl BuilderContext { .request_handler(self.provider().clone()) .split_with_handle(); - self.executor.spawn_critical("p2p txpool", Box::pin(txpool)); - self.executor.spawn_critical("p2p eth request handler", Box::pin(eth)); + self.executor.spawn_critical_blocking("p2p txpool", Box::pin(txpool)); + self.executor.spawn_critical_blocking("p2p eth request handler", Box::pin(eth)); let default_peers_path = self.config().datadir().known_peers(); let known_peers_file = self.config().network.persistent_peers_file(default_peers_path); diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index d620474385..e1dfb58d13 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -1179,6 +1179,7 @@ impl<'a, N: FullNodeComponents = OnceLock::new(); + /// Default max number of subscriptions per connection. pub(crate) const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024; @@ -37,76 +41,442 @@ pub(crate) const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15; pub(crate) const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 160; /// Default number of incoming connections. +/// +/// This restricts how many active connections (http, ws) the server accepts. +/// Once exceeded, the server can reject new connections. pub(crate) const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 500; +/// Default values for RPC server that can be customized +/// +/// Global defaults can be set via [`DefaultRpcServerArgs::try_init`]. +#[derive(Debug, Clone)] +pub struct DefaultRpcServerArgs { + http: bool, + http_addr: IpAddr, + http_port: u16, + http_disable_compression: bool, + http_api: Option, + http_corsdomain: Option, + ws: bool, + ws_addr: IpAddr, + ws_port: u16, + ws_allowed_origins: Option, + ws_api: Option, + ipcdisable: bool, + ipcpath: String, + ipc_socket_permissions: Option, + auth_addr: IpAddr, + auth_port: u16, + auth_jwtsecret: Option, + auth_ipc: bool, + auth_ipc_path: String, + disable_auth_server: bool, + rpc_jwtsecret: Option, + rpc_max_request_size: MaxU32, + rpc_max_response_size: MaxU32, + rpc_max_subscriptions_per_connection: MaxU32, + rpc_max_connections: MaxU32, + rpc_max_tracing_requests: usize, + rpc_max_blocking_io_requests: usize, + rpc_max_trace_filter_blocks: u64, + rpc_max_blocks_per_filter: ZeroAsNoneU64, + rpc_max_logs_per_response: ZeroAsNoneU64, + rpc_gas_cap: u64, + rpc_evm_memory_limit: u64, + rpc_tx_fee_cap: u128, + rpc_max_simulate_blocks: u64, + rpc_eth_proof_window: u64, + rpc_proof_permits: usize, + rpc_pending_block: PendingBlockKind, + rpc_forwarder: Option, + builder_disallow: Option>, + rpc_state_cache: RpcStateCacheArgs, + gas_price_oracle: GasPriceOracleArgs, + rpc_send_raw_transaction_sync_timeout: Duration, +} + +impl DefaultRpcServerArgs { + /// Initialize the global RPC server defaults with this configuration + pub fn try_init(self) -> Result<(), Self> { + RPC_SERVER_DEFAULTS.set(self) + } + + /// Get a reference to the global RPC server defaults + pub fn get_global() -> &'static Self { + RPC_SERVER_DEFAULTS.get_or_init(Self::default) + } + + /// Set the default HTTP enabled state + pub const fn with_http(mut self, v: bool) -> Self { + self.http = v; + self + } + + /// Set the default HTTP address + pub const fn with_http_addr(mut self, v: IpAddr) -> Self { + self.http_addr = v; + self + } + + /// Set the default HTTP port + pub const fn with_http_port(mut self, v: u16) -> Self { + self.http_port = v; + self + } + + /// Set whether to disable HTTP compression by default + pub const fn with_http_disable_compression(mut self, v: bool) -> Self { + self.http_disable_compression = v; + self + } + + /// Set the default HTTP API modules + pub fn with_http_api(mut self, v: Option) -> Self { + self.http_api = v; + self + } + + /// Set the default HTTP CORS domain + pub fn with_http_corsdomain(mut self, v: Option) -> Self { + self.http_corsdomain = v; + self + } + + /// Set the default WS enabled state + pub const fn with_ws(mut self, v: bool) -> Self { + self.ws = v; + self + } + + /// Set the default WS address + pub const fn with_ws_addr(mut self, v: IpAddr) -> Self { + self.ws_addr = v; + self + } + + /// Set the default WS port + pub const fn with_ws_port(mut self, v: u16) -> Self { + self.ws_port = v; + self + } + + /// Set the default WS allowed origins + pub fn with_ws_allowed_origins(mut self, v: Option) -> Self { + self.ws_allowed_origins = v; + self + } + + /// Set the default WS API modules + pub fn with_ws_api(mut self, v: Option) -> Self { + self.ws_api = v; + self + } + + /// Set whether to disable IPC by default + pub const fn with_ipcdisable(mut self, v: bool) -> Self { + self.ipcdisable = v; + self + } + + /// Set the default IPC path + pub fn with_ipcpath(mut self, v: String) -> Self { + self.ipcpath = v; + self + } + + /// Set the default IPC socket permissions + pub fn with_ipc_socket_permissions(mut self, v: Option) -> Self { + self.ipc_socket_permissions = v; + self + } + + /// Set the default auth server address + pub const fn with_auth_addr(mut self, v: IpAddr) -> Self { + self.auth_addr = v; + self + } + + /// Set the default auth server port + pub const fn with_auth_port(mut self, v: u16) -> Self { + self.auth_port = v; + self + } + + /// Set the default auth JWT secret path + pub fn with_auth_jwtsecret(mut self, v: Option) -> Self { + self.auth_jwtsecret = v; + self + } + + /// Set the default auth IPC enabled state + pub const fn with_auth_ipc(mut self, v: bool) -> Self { + self.auth_ipc = v; + self + } + + /// Set the default auth IPC path + pub fn with_auth_ipc_path(mut self, v: String) -> Self { + self.auth_ipc_path = v; + self + } + + /// Set whether to disable the auth server by default + pub const fn with_disable_auth_server(mut self, v: bool) -> Self { + self.disable_auth_server = v; + self + } + + /// Set the default RPC JWT secret + pub const fn with_rpc_jwtsecret(mut self, v: Option) -> Self { + self.rpc_jwtsecret = v; + self + } + + /// Set the default max request size + pub const fn with_rpc_max_request_size(mut self, v: MaxU32) -> Self { + self.rpc_max_request_size = v; + self + } + + /// Set the default max response size + pub const fn with_rpc_max_response_size(mut self, v: MaxU32) -> Self { + self.rpc_max_response_size = v; + self + } + + /// Set the default max subscriptions per connection + pub const fn with_rpc_max_subscriptions_per_connection(mut self, v: MaxU32) -> Self { + self.rpc_max_subscriptions_per_connection = v; + self + } + + /// Set the default max connections + pub const fn with_rpc_max_connections(mut self, v: MaxU32) -> Self { + self.rpc_max_connections = v; + self + } + + /// Set the default max tracing requests + pub const fn with_rpc_max_tracing_requests(mut self, v: usize) -> Self { + self.rpc_max_tracing_requests = v; + self + } + + /// Set the default max blocking IO requests + pub const fn with_rpc_max_blocking_io_requests(mut self, v: usize) -> Self { + self.rpc_max_blocking_io_requests = v; + self + } + + /// Set the default max trace filter blocks + pub const fn with_rpc_max_trace_filter_blocks(mut self, v: u64) -> Self { + self.rpc_max_trace_filter_blocks = v; + self + } + + /// Set the default max blocks per filter + pub const fn with_rpc_max_blocks_per_filter(mut self, v: ZeroAsNoneU64) -> Self { + self.rpc_max_blocks_per_filter = v; + self + } + + /// Set the default max logs per response + pub const fn with_rpc_max_logs_per_response(mut self, v: ZeroAsNoneU64) -> Self { + self.rpc_max_logs_per_response = v; + self + } + + /// Set the default gas cap + pub const fn with_rpc_gas_cap(mut self, v: u64) -> Self { + self.rpc_gas_cap = v; + self + } + + /// Set the default EVM memory limit + pub const fn with_rpc_evm_memory_limit(mut self, v: u64) -> Self { + self.rpc_evm_memory_limit = v; + self + } + + /// Set the default tx fee cap + pub const fn with_rpc_tx_fee_cap(mut self, v: u128) -> Self { + self.rpc_tx_fee_cap = v; + self + } + + /// Set the default max simulate blocks + pub const fn with_rpc_max_simulate_blocks(mut self, v: u64) -> Self { + self.rpc_max_simulate_blocks = v; + self + } + + /// Set the default eth proof window + pub const fn with_rpc_eth_proof_window(mut self, v: u64) -> Self { + self.rpc_eth_proof_window = v; + self + } + + /// Set the default proof permits + pub const fn with_rpc_proof_permits(mut self, v: usize) -> Self { + self.rpc_proof_permits = v; + self + } + + /// Set the default pending block kind + pub const fn with_rpc_pending_block(mut self, v: PendingBlockKind) -> Self { + self.rpc_pending_block = v; + self + } + + /// Set the default RPC forwarder + pub fn with_rpc_forwarder(mut self, v: Option) -> Self { + self.rpc_forwarder = v; + self + } + + /// Set the default builder disallow addresses + pub fn with_builder_disallow(mut self, v: Option>) -> Self { + self.builder_disallow = v; + self + } + + /// Set the default RPC state cache args + pub const fn with_rpc_state_cache(mut self, v: RpcStateCacheArgs) -> Self { + self.rpc_state_cache = v; + self + } + + /// Set the default gas price oracle args + pub const fn with_gas_price_oracle(mut self, v: GasPriceOracleArgs) -> Self { + self.gas_price_oracle = v; + self + } + + /// Set the default send raw transaction sync timeout + pub const fn with_rpc_send_raw_transaction_sync_timeout(mut self, v: Duration) -> Self { + self.rpc_send_raw_transaction_sync_timeout = v; + self + } +} + +impl Default for DefaultRpcServerArgs { + fn default() -> Self { + Self { + http: false, + http_addr: Ipv4Addr::LOCALHOST.into(), + http_port: constants::DEFAULT_HTTP_RPC_PORT, + http_disable_compression: false, + http_api: None, + http_corsdomain: None, + ws: false, + ws_addr: Ipv4Addr::LOCALHOST.into(), + ws_port: constants::DEFAULT_WS_RPC_PORT, + ws_allowed_origins: None, + ws_api: None, + ipcdisable: false, + ipcpath: constants::DEFAULT_IPC_ENDPOINT.to_string(), + ipc_socket_permissions: None, + auth_addr: Ipv4Addr::LOCALHOST.into(), + auth_port: constants::DEFAULT_AUTH_PORT, + auth_jwtsecret: None, + auth_ipc: false, + auth_ipc_path: constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string(), + disable_auth_server: false, + rpc_jwtsecret: None, + rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB.into(), + rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB.into(), + rpc_max_subscriptions_per_connection: RPC_DEFAULT_MAX_SUBS_PER_CONN.into(), + rpc_max_connections: RPC_DEFAULT_MAX_CONNECTIONS.into(), + rpc_max_tracing_requests: constants::default_max_tracing_requests(), + rpc_max_blocking_io_requests: constants::DEFAULT_MAX_BLOCKING_IO_REQUEST, + rpc_max_trace_filter_blocks: constants::DEFAULT_MAX_TRACE_FILTER_BLOCKS, + rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(), + rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(), + rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP, + rpc_evm_memory_limit: (1 << 32) - 1, + rpc_tx_fee_cap: constants::DEFAULT_TX_FEE_CAP_WEI, + rpc_max_simulate_blocks: constants::DEFAULT_MAX_SIMULATE_BLOCKS, + rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW, + rpc_proof_permits: constants::DEFAULT_PROOF_PERMITS, + rpc_pending_block: PendingBlockKind::Full, + rpc_forwarder: None, + builder_disallow: None, + rpc_state_cache: RpcStateCacheArgs::default(), + gas_price_oracle: GasPriceOracleArgs::default(), + rpc_send_raw_transaction_sync_timeout: + constants::RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS, + } + } +} + /// Parameters for configuring the rpc more granularity via CLI #[derive(Debug, Clone, Args, PartialEq, Eq)] #[command(next_help_heading = "RPC")] pub struct RpcServerArgs { /// Enable the HTTP-RPC server - #[arg(long, default_value_if("dev", "true", "true"))] + #[arg(long, default_value_if("dev", "true", "true"), default_value_t = DefaultRpcServerArgs::get_global().http)] pub http: bool, /// Http server address to listen on - #[arg(long = "http.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + #[arg(long = "http.addr", default_value_t = DefaultRpcServerArgs::get_global().http_addr)] pub http_addr: IpAddr, /// Http server port to listen on - #[arg(long = "http.port", default_value_t = constants::DEFAULT_HTTP_RPC_PORT)] + #[arg(long = "http.port", default_value_t = DefaultRpcServerArgs::get_global().http_port)] pub http_port: u16, /// Disable compression for HTTP responses - #[arg(long = "http.disable-compression", default_value_t = false)] + #[arg(long = "http.disable-compression", default_value_t = DefaultRpcServerArgs::get_global().http_disable_compression)] pub http_disable_compression: bool, /// Rpc Modules to be configured for the HTTP server - #[arg(long = "http.api", value_parser = RpcModuleSelectionValueParser::default())] + #[arg(long = "http.api", value_parser = RpcModuleSelectionValueParser::default(), default_value = Resettable::from(DefaultRpcServerArgs::get_global().http_api.as_ref().map(|v| v.to_string().into())))] pub http_api: Option, /// Http Corsdomain to allow request from - #[arg(long = "http.corsdomain")] + #[arg(long = "http.corsdomain", default_value = Resettable::from(DefaultRpcServerArgs::get_global().http_corsdomain.as_ref().map(|v| v.to_string().into())))] pub http_corsdomain: Option, /// Enable the WS-RPC server - #[arg(long)] + #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().ws)] pub ws: bool, /// Ws server address to listen on - #[arg(long = "ws.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + #[arg(long = "ws.addr", default_value_t = DefaultRpcServerArgs::get_global().ws_addr)] pub ws_addr: IpAddr, /// Ws server port to listen on - #[arg(long = "ws.port", default_value_t = constants::DEFAULT_WS_RPC_PORT)] + #[arg(long = "ws.port", default_value_t = DefaultRpcServerArgs::get_global().ws_port)] pub ws_port: u16, /// Origins from which to accept `WebSocket` requests - #[arg(id = "ws.origins", long = "ws.origins", alias = "ws.corsdomain")] + #[arg(id = "ws.origins", long = "ws.origins", alias = "ws.corsdomain", default_value = Resettable::from(DefaultRpcServerArgs::get_global().ws_allowed_origins.as_ref().map(|v| v.to_string().into())))] pub ws_allowed_origins: Option, /// Rpc Modules to be configured for the WS server - #[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default())] + #[arg(long = "ws.api", value_parser = RpcModuleSelectionValueParser::default(), default_value = Resettable::from(DefaultRpcServerArgs::get_global().ws_api.as_ref().map(|v| v.to_string().into())))] pub ws_api: Option, /// Disable the IPC-RPC server - #[arg(long)] + #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().ipcdisable)] pub ipcdisable: bool, /// Filename for IPC socket/pipe within the datadir - #[arg(long, default_value_t = constants::DEFAULT_IPC_ENDPOINT.to_string())] + #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().ipcpath.clone())] pub ipcpath: String, /// Set the permissions for the IPC socket file, in octal format. /// /// If not specified, the permissions will be set by the system's umask. - #[arg(long = "ipc.permissions")] + #[arg(long = "ipc.permissions", default_value = Resettable::from(DefaultRpcServerArgs::get_global().ipc_socket_permissions.as_ref().map(|v| v.to_string().into())))] pub ipc_socket_permissions: Option, /// Auth server address to listen on - #[arg(long = "authrpc.addr", default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + #[arg(long = "authrpc.addr", default_value_t = DefaultRpcServerArgs::get_global().auth_addr)] pub auth_addr: IpAddr, /// Auth server port to listen on - #[arg(long = "authrpc.port", default_value_t = constants::DEFAULT_AUTH_PORT)] + #[arg(long = "authrpc.port", default_value_t = DefaultRpcServerArgs::get_global().auth_port)] pub auth_port: u16, /// Path to a JWT secret to use for the authenticated engine-API RPC server. @@ -115,22 +485,22 @@ pub struct RpcServerArgs { /// /// If no path is provided, a secret will be generated and stored in the datadir under /// `//jwt.hex`. For mainnet this would be `~/.reth/mainnet/jwt.hex` by default. - #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false)] + #[arg(long = "authrpc.jwtsecret", value_name = "PATH", global = true, required = false, default_value = Resettable::from(DefaultRpcServerArgs::get_global().auth_jwtsecret.as_ref().map(|v| v.to_string_lossy().into())))] pub auth_jwtsecret: Option, /// Enable auth engine API over IPC - #[arg(long)] + #[arg(long, default_value_t = DefaultRpcServerArgs::get_global().auth_ipc)] pub auth_ipc: bool, /// Filename for auth IPC socket/pipe within the datadir - #[arg(long = "auth-ipc.path", default_value_t = constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string())] + #[arg(long = "auth-ipc.path", default_value_t = DefaultRpcServerArgs::get_global().auth_ipc_path.clone())] pub auth_ipc_path: String, /// Disable the auth/engine API server. /// /// This will prevent the authenticated engine-API server from starting. Use this if you're /// running a node that doesn't need to serve engine API requests. - #[arg(long = "disable-auth-server", alias = "disable-engine-api")] + #[arg(long = "disable-auth-server", alias = "disable-engine-api", default_value_t = DefaultRpcServerArgs::get_global().disable_auth_server)] pub disable_auth_server: bool, /// Hex encoded JWT secret to authenticate the regular RPC server(s), see `--http.api` and @@ -138,23 +508,23 @@ pub struct RpcServerArgs { /// /// This is __not__ used for the authenticated engine-API RPC server, see /// `--authrpc.jwtsecret`. - #[arg(long = "rpc.jwtsecret", value_name = "HEX", global = true, required = false)] + #[arg(long = "rpc.jwtsecret", value_name = "HEX", global = true, required = false, default_value = Resettable::from(DefaultRpcServerArgs::get_global().rpc_jwtsecret.as_ref().map(|v| format!("{:?}", v).into())))] pub rpc_jwtsecret: Option, /// Set the maximum RPC request payload size for both HTTP and WS in megabytes. - #[arg(long = "rpc.max-request-size", alias = "rpc-max-request-size", default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB.into())] + #[arg(long = "rpc.max-request-size", alias = "rpc-max-request-size", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_request_size)] pub rpc_max_request_size: MaxU32, /// Set the maximum RPC response payload size for both HTTP and WS in megabytes. - #[arg(long = "rpc.max-response-size", alias = "rpc-max-response-size", visible_alias = "rpc.returndata.limit", default_value_t = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB.into())] + #[arg(long = "rpc.max-response-size", alias = "rpc-max-response-size", visible_alias = "rpc.returndata.limit", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_response_size)] pub rpc_max_response_size: MaxU32, /// Set the maximum concurrent subscriptions per connection. - #[arg(long = "rpc.max-subscriptions-per-connection", alias = "rpc-max-subscriptions-per-connection", default_value_t = RPC_DEFAULT_MAX_SUBS_PER_CONN.into())] + #[arg(long = "rpc.max-subscriptions-per-connection", alias = "rpc-max-subscriptions-per-connection", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_subscriptions_per_connection)] pub rpc_max_subscriptions_per_connection: MaxU32, /// Maximum number of RPC server connections. - #[arg(long = "rpc.max-connections", alias = "rpc-max-connections", value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS.into())] + #[arg(long = "rpc.max-connections", alias = "rpc-max-connections", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_connections)] pub rpc_max_connections: MaxU32, /// Maximum number of concurrent tracing requests. @@ -163,19 +533,27 @@ pub struct RpcServerArgs { /// Tracing requests are generally CPU bound. /// Choosing a value that is higher than the available CPU cores can have a negative impact on /// the performance of the node and affect the node's ability to maintain sync. - #[arg(long = "rpc.max-tracing-requests", alias = "rpc-max-tracing-requests", value_name = "COUNT", default_value_t = constants::default_max_tracing_requests())] + #[arg(long = "rpc.max-tracing-requests", alias = "rpc-max-tracing-requests", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_tracing_requests)] pub rpc_max_tracing_requests: usize, + /// Maximum number of concurrent blocking IO requests. + /// + /// Blocking IO requests include `eth_call`, `eth_estimateGas`, and similar methods that + /// require EVM execution. These are spawned as blocking tasks to avoid blocking the async + /// runtime. + #[arg(long = "rpc.max-blocking-io-requests", alias = "rpc-max-blocking-io-requests", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_blocking_io_requests)] + pub rpc_max_blocking_io_requests: usize, + /// Maximum number of blocks for `trace_filter` requests. - #[arg(long = "rpc.max-trace-filter-blocks", alias = "rpc-max-trace-filter-blocks", value_name = "COUNT", default_value_t = constants::DEFAULT_MAX_TRACE_FILTER_BLOCKS)] + #[arg(long = "rpc.max-trace-filter-blocks", alias = "rpc-max-trace-filter-blocks", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_trace_filter_blocks)] pub rpc_max_trace_filter_blocks: u64, /// Maximum number of blocks that could be scanned per filter request. (0 = entire chain) - #[arg(long = "rpc.max-blocks-per-filter", alias = "rpc-max-blocks-per-filter", value_name = "COUNT", default_value_t = ZeroAsNoneU64::new(constants::DEFAULT_MAX_BLOCKS_PER_FILTER))] + #[arg(long = "rpc.max-blocks-per-filter", alias = "rpc-max-blocks-per-filter", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_blocks_per_filter)] pub rpc_max_blocks_per_filter: ZeroAsNoneU64, /// Maximum number of logs that can be returned in a single response. (0 = no limit) - #[arg(long = "rpc.max-logs-per-response", alias = "rpc-max-logs-per-response", value_name = "COUNT", default_value_t = ZeroAsNoneU64::new(constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64))] + #[arg(long = "rpc.max-logs-per-response", alias = "rpc-max-logs-per-response", value_name = "COUNT", default_value_t = DefaultRpcServerArgs::get_global().rpc_max_logs_per_response)] pub rpc_max_logs_per_response: ZeroAsNoneU64, /// Maximum gas limit for `eth_call` and call tracing RPC methods. @@ -184,7 +562,7 @@ pub struct RpcServerArgs { alias = "rpc-gascap", value_name = "GAS_CAP", value_parser = MaxOr::new(RangedU64ValueParser::::new().range(1..)), - default_value_t = constants::gas_oracle::RPC_DEFAULT_GAS_CAP + default_value_t = DefaultRpcServerArgs::get_global().rpc_gas_cap )] pub rpc_gas_cap: u64, @@ -194,7 +572,7 @@ pub struct RpcServerArgs { alias = "rpc-evm-memory-limit", value_name = "MEMORY_LIMIT", value_parser = MaxOr::new(RangedU64ValueParser::::new().range(1..)), - default_value_t = (1 << 32) - 1 + default_value_t = DefaultRpcServerArgs::get_global().rpc_evm_memory_limit )] pub rpc_evm_memory_limit: u64, @@ -212,7 +590,7 @@ pub struct RpcServerArgs { #[arg( long = "rpc.max-simulate-blocks", value_name = "BLOCKS_COUNT", - default_value_t = constants::DEFAULT_MAX_SIMULATE_BLOCKS + default_value_t = DefaultRpcServerArgs::get_global().rpc_max_simulate_blocks )] pub rpc_max_simulate_blocks: u64, @@ -221,7 +599,7 @@ pub struct RpcServerArgs { /// configured number of blocks from current tip (up to `tip - window`). #[arg( long = "rpc.eth-proof-window", - default_value_t = constants::DEFAULT_ETH_PROOF_WINDOW, + default_value_t = DefaultRpcServerArgs::get_global().rpc_eth_proof_window, value_parser = RangedU64ValueParser::::new().range(..=constants::MAX_ETH_PROOF_WINDOW) )] pub rpc_eth_proof_window: u64, @@ -243,7 +621,7 @@ pub struct RpcServerArgs { /// Path to file containing disallowed addresses, json-encoded list of strings. Block /// validation API will reject blocks containing transactions from these addresses. - #[arg(long = "builder.disallow", value_name = "PATH", value_parser = reth_cli_util::parsers::read_json_from_file::>)] + #[arg(long = "builder.disallow", value_name = "PATH", value_parser = reth_cli_util::parsers::read_json_from_file::>, default_value = Resettable::from(DefaultRpcServerArgs::get_global().builder_disallow.as_ref().map(|v| format!("{:?}", v).into())))] pub builder_disallow: Option>, /// State cache configuration. @@ -387,49 +765,93 @@ impl RpcServerArgs { impl Default for RpcServerArgs { fn default() -> Self { + let DefaultRpcServerArgs { + http, + http_addr, + http_port, + http_disable_compression, + http_api, + http_corsdomain, + ws, + ws_addr, + ws_port, + ws_allowed_origins, + ws_api, + ipcdisable, + ipcpath, + ipc_socket_permissions, + auth_addr, + auth_port, + auth_jwtsecret, + auth_ipc, + auth_ipc_path, + disable_auth_server, + rpc_jwtsecret, + rpc_max_request_size, + rpc_max_response_size, + rpc_max_subscriptions_per_connection, + rpc_max_connections, + rpc_max_tracing_requests, + rpc_max_blocking_io_requests, + rpc_max_trace_filter_blocks, + rpc_max_blocks_per_filter, + rpc_max_logs_per_response, + rpc_gas_cap, + rpc_evm_memory_limit, + rpc_tx_fee_cap, + rpc_max_simulate_blocks, + rpc_eth_proof_window, + rpc_proof_permits, + rpc_pending_block, + rpc_forwarder, + builder_disallow, + rpc_state_cache, + gas_price_oracle, + rpc_send_raw_transaction_sync_timeout, + } = DefaultRpcServerArgs::get_global().clone(); Self { - http: false, - http_addr: Ipv4Addr::LOCALHOST.into(), - http_port: constants::DEFAULT_HTTP_RPC_PORT, - http_disable_compression: false, - http_api: None, - http_corsdomain: None, - ws: false, - ws_addr: Ipv4Addr::LOCALHOST.into(), - ws_port: constants::DEFAULT_WS_RPC_PORT, - ws_allowed_origins: None, - ws_api: None, - ipcdisable: false, - ipcpath: constants::DEFAULT_IPC_ENDPOINT.to_string(), - ipc_socket_permissions: None, - auth_addr: Ipv4Addr::LOCALHOST.into(), - auth_port: constants::DEFAULT_AUTH_PORT, - auth_jwtsecret: None, - auth_ipc: false, - auth_ipc_path: constants::DEFAULT_ENGINE_API_IPC_ENDPOINT.to_string(), - disable_auth_server: false, - rpc_jwtsecret: None, - rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB.into(), - rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB.into(), - rpc_max_subscriptions_per_connection: RPC_DEFAULT_MAX_SUBS_PER_CONN.into(), - rpc_max_connections: RPC_DEFAULT_MAX_CONNECTIONS.into(), - rpc_max_tracing_requests: constants::default_max_tracing_requests(), - rpc_max_trace_filter_blocks: constants::DEFAULT_MAX_TRACE_FILTER_BLOCKS, - rpc_max_blocks_per_filter: constants::DEFAULT_MAX_BLOCKS_PER_FILTER.into(), - rpc_max_logs_per_response: (constants::DEFAULT_MAX_LOGS_PER_RESPONSE as u64).into(), - rpc_gas_cap: constants::gas_oracle::RPC_DEFAULT_GAS_CAP, - rpc_evm_memory_limit: (1 << 32) - 1, - rpc_tx_fee_cap: constants::DEFAULT_TX_FEE_CAP_WEI, - rpc_max_simulate_blocks: constants::DEFAULT_MAX_SIMULATE_BLOCKS, - rpc_eth_proof_window: constants::DEFAULT_ETH_PROOF_WINDOW, - rpc_pending_block: PendingBlockKind::Full, - gas_price_oracle: GasPriceOracleArgs::default(), - rpc_state_cache: RpcStateCacheArgs::default(), - rpc_proof_permits: constants::DEFAULT_PROOF_PERMITS, - rpc_forwarder: None, - builder_disallow: Default::default(), - rpc_send_raw_transaction_sync_timeout: - constants::RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS, + http, + http_addr, + http_port, + http_disable_compression, + http_api, + http_corsdomain, + ws, + ws_addr, + ws_port, + ws_allowed_origins, + ws_api, + ipcdisable, + ipcpath, + ipc_socket_permissions, + auth_addr, + auth_port, + auth_jwtsecret, + auth_ipc, + auth_ipc_path, + disable_auth_server, + rpc_jwtsecret, + rpc_max_request_size, + rpc_max_response_size, + rpc_max_subscriptions_per_connection, + rpc_max_connections, + rpc_max_tracing_requests, + rpc_max_blocking_io_requests, + rpc_max_trace_filter_blocks, + rpc_max_blocks_per_filter, + rpc_max_logs_per_response, + rpc_gas_cap, + rpc_evm_memory_limit, + rpc_tx_fee_cap, + rpc_max_simulate_blocks, + rpc_eth_proof_window, + rpc_proof_permits, + rpc_pending_block, + rpc_forwarder, + builder_disallow, + rpc_state_cache, + gas_price_oracle, + rpc_send_raw_transaction_sync_timeout, } } } @@ -542,4 +964,159 @@ mod tests { let expected = 1_000_000_000_000_000_000u128; assert_eq!(args.rpc_tx_fee_cap, expected); // 1 ETH default cap } + + #[test] + fn test_rpc_server_args() { + let args = RpcServerArgs { + http: true, + http_addr: "127.0.0.1".parse().unwrap(), + http_port: 8545, + http_disable_compression: false, + http_api: Some(RpcModuleSelection::try_from_selection(["eth", "admin"]).unwrap()), + http_corsdomain: Some("*".to_string()), + ws: true, + ws_addr: "127.0.0.1".parse().unwrap(), + ws_port: 8546, + ws_allowed_origins: Some("*".to_string()), + ws_api: Some(RpcModuleSelection::try_from_selection(["eth", "admin"]).unwrap()), + ipcdisable: false, + ipcpath: "reth.ipc".to_string(), + ipc_socket_permissions: Some("0o666".to_string()), + auth_addr: "127.0.0.1".parse().unwrap(), + auth_port: 8551, + auth_jwtsecret: Some(std::path::PathBuf::from("/tmp/jwt.hex")), + auth_ipc: false, + auth_ipc_path: "engine.ipc".to_string(), + disable_auth_server: false, + rpc_jwtsecret: Some( + JwtSecret::from_hex( + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + ) + .unwrap(), + ), + rpc_max_request_size: 15u32.into(), + rpc_max_response_size: 160u32.into(), + rpc_max_subscriptions_per_connection: 1024u32.into(), + rpc_max_connections: 500u32.into(), + rpc_max_tracing_requests: 16, + rpc_max_blocking_io_requests: 256, + rpc_max_trace_filter_blocks: 4000, + rpc_max_blocks_per_filter: 1000u64.into(), + rpc_max_logs_per_response: 10000u64.into(), + rpc_gas_cap: 50_000_000, + rpc_evm_memory_limit: 256, + rpc_tx_fee_cap: 2_000_000_000_000_000_000u128, + rpc_max_simulate_blocks: 256, + rpc_eth_proof_window: 100_000, + rpc_proof_permits: 16, + rpc_pending_block: PendingBlockKind::Full, + rpc_forwarder: Some("http://localhost:8545".parse().unwrap()), + builder_disallow: None, + rpc_state_cache: RpcStateCacheArgs { + max_blocks: 5000, + max_receipts: 2000, + max_headers: 1000, + max_concurrent_db_requests: 512, + }, + gas_price_oracle: GasPriceOracleArgs { + blocks: 20, + ignore_price: 2, + max_price: 500_000_000_000, + percentile: 60, + default_suggested_fee: None, + }, + rpc_send_raw_transaction_sync_timeout: std::time::Duration::from_secs(30), + }; + + let parsed_args = CommandParser::::parse_from([ + "reth", + "--http", + "--http.addr", + "127.0.0.1", + "--http.port", + "8545", + "--http.api", + "eth,admin", + "--http.corsdomain", + "*", + "--ws", + "--ws.addr", + "127.0.0.1", + "--ws.port", + "8546", + "--ws.origins", + "*", + "--ws.api", + "eth,admin", + "--ipcpath", + "reth.ipc", + "--ipc.permissions", + "0o666", + "--authrpc.addr", + "127.0.0.1", + "--authrpc.port", + "8551", + "--authrpc.jwtsecret", + "/tmp/jwt.hex", + "--auth-ipc.path", + "engine.ipc", + "--rpc.jwtsecret", + "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", + "--rpc.max-request-size", + "15", + "--rpc.max-response-size", + "160", + "--rpc.max-subscriptions-per-connection", + "1024", + "--rpc.max-connections", + "500", + "--rpc.max-tracing-requests", + "16", + "--rpc.max-blocking-io-requests", + "256", + "--rpc.max-trace-filter-blocks", + "4000", + "--rpc.max-blocks-per-filter", + "1000", + "--rpc.max-logs-per-response", + "10000", + "--rpc.gascap", + "50000000", + "--rpc.evm-memory-limit", + "256", + "--rpc.txfeecap", + "2.0", + "--rpc.max-simulate-blocks", + "256", + "--rpc.eth-proof-window", + "100000", + "--rpc.proof-permits", + "16", + "--rpc.pending-block", + "full", + "--rpc.forwarder", + "http://localhost:8545", + "--rpc-cache.max-blocks", + "5000", + "--rpc-cache.max-receipts", + "2000", + "--rpc-cache.max-headers", + "1000", + "--rpc-cache.max-concurrent-db-requests", + "512", + "--gpo.blocks", + "20", + "--gpo.ignoreprice", + "2", + "--gpo.maxprice", + "500000000000", + "--gpo.percentile", + "60", + "--rpc.send-raw-transaction-sync-timeout", + "30s", + ]) + .args; + + assert_eq!(parsed_args, args); + } } diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index d5f42a5473..16389b5e91 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -272,6 +272,11 @@ where fn tracing_task_guard(&self) -> &BlockingTaskGuard { self.inner.eth_api.blocking_task_guard() } + + #[inline] + fn blocking_io_task_guard(&self) -> &Arc { + self.inner.eth_api.blocking_io_request_semaphore() + } } impl LoadFee for OpEthApi diff --git a/crates/payload/primitives/src/payload.rs b/crates/payload/primitives/src/payload.rs index 709a37768f..37edaaae39 100644 --- a/crates/payload/primitives/src/payload.rs +++ b/crates/payload/primitives/src/payload.rs @@ -3,7 +3,7 @@ use crate::{MessageValidationKind, PayloadAttributes}; use alloc::vec::Vec; use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, eip7685::Requests, BlockNumHash}; -use alloy_primitives::B256; +use alloy_primitives::{Bytes, B256}; use alloy_rpc_types_engine::ExecutionData; use core::fmt::Debug; use serde::{de::DeserializeOwned, Serialize}; @@ -40,6 +40,11 @@ pub trait ExecutionPayload: /// Returns `None` for pre-Shanghai blocks. fn withdrawals(&self) -> Option<&Vec>; + /// Returns the access list included in this payload. + /// + /// Returns `None` for pre-Amsterdam blocks. + fn block_access_list(&self) -> Option<&Bytes>; + /// Returns the beacon block root associated with this payload. /// /// Returns `None` for pre-merge payloads. @@ -69,6 +74,10 @@ impl ExecutionPayload for ExecutionData { self.payload.withdrawals() } + fn block_access_list(&self) -> Option<&Bytes> { + None + } + fn parent_beacon_block_root(&self) -> Option { self.sidecar.parent_beacon_block_root() } @@ -172,6 +181,10 @@ impl ExecutionPayload for op_alloy_rpc_types_engine::OpExecutionData { self.payload.as_v2().map(|p| &p.withdrawals) } + fn block_access_list(&self) -> Option<&Bytes> { + None + } + fn parent_beacon_block_root(&self) -> Option { self.sidecar.parent_beacon_block_root() } diff --git a/crates/rpc/rpc-api/Cargo.toml b/crates/rpc/rpc-api/Cargo.toml index ebd748d183..e2c2d00155 100644 --- a/crates/rpc/rpc-api/Cargo.toml +++ b/crates/rpc/rpc-api/Cargo.toml @@ -35,6 +35,7 @@ alloy-serde.workspace = true alloy-rpc-types-beacon.workspace = true alloy-rpc-types-engine.workspace = true alloy-genesis.workspace = true +serde = { workspace = true, features = ["derive"] } # misc jsonrpsee = { workspace = true, features = ["server", "macros"] } @@ -46,3 +47,8 @@ client = [ "jsonrpsee/async-client", "reth-rpc-eth-api/client", ] + +[dev-dependencies] +serde_json = { workspace = true } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +jsonrpsee = { workspace = true, features = ["client", "async-client", "http-client"] } diff --git a/crates/rpc/rpc-api/src/lib.rs b/crates/rpc/rpc-api/src/lib.rs index 89e21c80b0..9c3a4baa03 100644 --- a/crates/rpc/rpc-api/src/lib.rs +++ b/crates/rpc/rpc-api/src/lib.rs @@ -25,11 +25,14 @@ mod net; mod otterscan; mod reth; mod rpc; +mod testing; mod trace; mod txpool; mod validation; mod web3; +pub use testing::{TestingBuildBlockRequestV1, TESTING_BUILD_BLOCK_V1}; + /// re-export of all server traits pub use servers::*; @@ -45,6 +48,7 @@ pub mod servers { otterscan::OtterscanServer, reth::RethApiServer, rpc::RpcApiServer, + testing::TestingApiServer, trace::TraceApiServer, txpool::TxPoolApiServer, validation::BlockSubmissionValidationApiServer, @@ -75,6 +79,7 @@ pub mod clients { otterscan::OtterscanClient, reth::RethApiClient, rpc::RpcApiServer, + testing::TestingApiClient, trace::TraceApiClient, txpool::TxPoolApiClient, validation::BlockSubmissionValidationApiClient, diff --git a/crates/rpc/rpc-api/src/testing.rs b/crates/rpc/rpc-api/src/testing.rs new file mode 100644 index 0000000000..f49380058e --- /dev/null +++ b/crates/rpc/rpc-api/src/testing.rs @@ -0,0 +1,45 @@ +//! Testing namespace for building a block in a single call. +//! +//! This follows the `testing_buildBlockV1` specification. **Highly sensitive:** +//! testing-only, powerful enough to include arbitrary transactions; must stay +//! disabled by default and never be exposed on public-facing RPC without an +//! explicit operator flag. + +use alloy_primitives::{Bytes, B256}; +use alloy_rpc_types_engine::{ + ExecutionPayloadEnvelopeV5, PayloadAttributes as EthPayloadAttributes, +}; +use jsonrpsee::proc_macros::rpc; +use serde::{Deserialize, Serialize}; + +/// Capability string for `testing_buildBlockV1`. +pub const TESTING_BUILD_BLOCK_V1: &str = "testing_buildBlockV1"; + +/// Request payload for `testing_buildBlockV1`. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TestingBuildBlockRequestV1 { + /// Parent block hash of the block to build. + pub parent_block_hash: B256, + /// Payload attributes (Cancun version). + pub payload_attributes: EthPayloadAttributes, + /// Raw signed transactions to force-include in order. + pub transactions: Vec, + /// Optional extra data for the block header. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub extra_data: Option, +} + +/// Testing RPC interface for building a block in a single call. +#[cfg_attr(not(feature = "client"), rpc(server, namespace = "testing"))] +#[cfg_attr(feature = "client", rpc(server, client, namespace = "testing"))] +pub trait TestingApi { + /// Builds a block using the provided parent, payload attributes, and transactions. + /// + /// See + #[method(name = "buildBlockV1")] + async fn build_block_v1( + &self, + request: TestingBuildBlockRequestV1, + ) -> jsonrpsee::core::RpcResult; +} diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index 6198b0ee0a..c9bb5af8c2 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -94,6 +94,7 @@ impl RethRpcServerConfig for RpcServerArgs { fn eth_config(&self) -> EthConfig { EthConfig::default() .max_tracing_requests(self.rpc_max_tracing_requests) + .max_blocking_io_requests(self.rpc_max_blocking_io_requests) .max_trace_filter_blocks(self.rpc_max_trace_filter_blocks) .max_blocks_per_filter(self.rpc_max_blocks_per_filter.unwrap_or_max()) .max_logs_per_response(self.rpc_max_logs_per_response.unwrap_or_max() as usize) diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 89935ca747..644f5dd496 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -52,6 +52,7 @@ use reth_rpc_eth_api::{ }; use reth_rpc_eth_types::{receipt::EthReceiptConverter, EthConfig, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, CompressionLayer, JwtAuthValidator, JwtSecret}; +pub use reth_rpc_server_types::RethRpcModule; use reth_storage_api::{ AccountReader, BlockReader, ChangeSetReader, FullRpcProvider, NodePrimitivesProvider, StateProviderFactory, @@ -76,7 +77,7 @@ use jsonrpsee::server::ServerConfigBuilder; pub use reth_ipc::server::{ Builder as IpcServerBuilder, RpcServiceBuilder as IpcRpcServiceBuilder, }; -pub use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection}; +pub use reth_rpc_server_types::{constants, RpcModuleSelection}; pub use tower::layer::util::{Identity, Stack}; /// Auth server utilities. @@ -561,8 +562,8 @@ where } } -impl - RpcRegistryInner +impl + RpcRegistryInner where EthApi: EthApiTypes, { @@ -591,6 +592,11 @@ where &self.provider } + /// Returns a reference to the evm config + pub const fn evm_config(&self) -> &Evm { + &self.evm_config + } + /// Returns all installed methods pub fn methods(&self) -> Vec { self.modules.values().cloned().collect() @@ -992,18 +998,18 @@ where .into_rpc() .into() } - // only relevant for Ethereum and configured in `EthereumAddOns` - // implementation - // TODO: can we get rid of this here? - // Custom modules are not handled here - they should be registered via - // extend_rpc_modules - RethRpcModule::Flashbots | RethRpcModule::Other(_) => Default::default(), RethRpcModule::Miner => MinerApi::default().into_rpc().into(), RethRpcModule::Mev => { EthSimBundle::new(eth_api.clone(), self.blocking_pool_guard.clone()) .into_rpc() .into() } + // these are implementation specific and need to be handled during + // initialization and should be registered via extend_rpc_modules in the + // nodebuilder rpc addon stack + RethRpcModule::Flashbots | + RethRpcModule::Testing | + RethRpcModule::Other(_) => Default::default(), }) .clone() }) diff --git a/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs b/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs index 886ff63914..c174cd9bde 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/blocking_task.rs @@ -7,18 +7,29 @@ use reth_tasks::{ pool::{BlockingTaskGuard, BlockingTaskPool}, TaskSpawner, }; -use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit}; +use std::sync::Arc; +use tokio::sync::{oneshot, AcquireError, OwnedSemaphorePermit, Semaphore}; use crate::EthApiTypes; -/// Executes code on a blocking thread. +/// Helpers for spawning blocking operations. +/// +/// Operations can be blocking because they require lots of CPU work and/or IO. +/// +/// This differentiates between workloads that are primarily CPU bound and heavier in general (such +/// as tracing tasks) and tasks that have a more balanced profile (io and cpu), such as `eth_call` +/// and alike. +/// +/// This provides access to semaphores that permit how many of those are permitted concurrently. +/// It's expected that tracing related tasks are configured with a lower threshold, because not only +/// are they CPU heavy but they can also accumulate more memory for the traces. pub trait SpawnBlocking: EthApiTypes + Clone + Send + Sync + 'static { /// Returns a handle for spawning IO heavy blocking tasks. /// /// Runtime access in default trait method implementations. fn io_task_spawner(&self) -> impl TaskSpawner; - /// Returns a handle for spawning CPU heavy blocking tasks. + /// Returns a handle for spawning __CPU heavy__ blocking tasks, such as tracing requests. /// /// Thread pool access in default trait method implementations. fn tracing_task_pool(&self) -> &BlockingTaskPool; @@ -26,21 +37,121 @@ pub trait SpawnBlocking: EthApiTypes + Clone + Send + Sync + 'static { /// Returns handle to semaphore for pool of CPU heavy blocking tasks. fn tracing_task_guard(&self) -> &BlockingTaskGuard; + /// Returns handle to semaphore for blocking IO tasks. + /// + /// This semaphore is used to limit concurrent blocking IO operations like `eth_call`, + /// `eth_estimateGas`, and similar methods that require EVM execution. + fn blocking_io_task_guard(&self) -> &Arc; + + /// Acquires a permit from the tracing task semaphore. + /// + /// This should be used for __CPU heavy__ operations like `debug_traceTransaction`, + /// `debug_traceCall`, and similar tracing methods. These tasks are typically: + /// - Primarily CPU bound with intensive computation + /// - Can accumulate significant memory for trace results + /// - Expected to have lower concurrency limits than general blocking IO tasks + /// + /// For blocking IO tasks like `eth_call` or `eth_estimateGas`, use + /// [`acquire_owned_blocking_io`](Self::acquire_owned_blocking_io) instead. + /// /// See also [`Semaphore::acquire_owned`](`tokio::sync::Semaphore::acquire_owned`). - fn acquire_owned( + fn acquire_owned_tracing( &self, ) -> impl Future> + Send { self.tracing_task_guard().clone().acquire_owned() } + /// Acquires multiple permits from the tracing task semaphore. + /// + /// This should be used for particularly heavy tracing operations that require more resources + /// than a standard trace. The permit count should reflect the expected resource consumption + /// relative to a standard tracing operation. + /// + /// Like [`acquire_owned_tracing`](Self::acquire_owned_tracing), this is specifically for + /// CPU-intensive tracing tasks, not general blocking IO operations. + /// /// See also [`Semaphore::acquire_many_owned`](`tokio::sync::Semaphore::acquire_many_owned`). - fn acquire_many_owned( + fn acquire_many_owned_tracing( &self, n: u32, ) -> impl Future> + Send { self.tracing_task_guard().clone().acquire_many_owned(n) } + /// Acquires a permit from the blocking IO request semaphore. + /// + /// This should be used for operations like `eth_call`, `eth_estimateGas`, and similar methods + /// that require EVM execution and are spawned as blocking tasks. + /// + /// See also [`Semaphore::acquire_owned`](`tokio::sync::Semaphore::acquire_owned`). + fn acquire_owned_blocking_io( + &self, + ) -> impl Future> + Send { + self.blocking_io_task_guard().clone().acquire_owned() + } + + /// Acquires multiple permits from the blocking IO request semaphore. + /// + /// This should be used for operations that may require more resources than a single permit + /// allows. + /// + /// See also [`Semaphore::acquire_many_owned`](`tokio::sync::Semaphore::acquire_many_owned`). + fn acquire_many_owned_blocking_io( + &self, + n: u32, + ) -> impl Future> + Send { + self.blocking_io_task_guard().clone().acquire_many_owned(n) + } + + /// Acquires permits from the blocking IO request semaphore based on a calculated weight. + /// + /// The weight determines the maximum number of concurrent requests of this type that can run. + /// For example, if the semaphore has 256 total permits and `weight=10`, then at most 10 + /// concurrent requests of this type are allowed. + /// + /// The permits acquired per request is calculated as `total_permits / weight`, with an + /// adjustment: if this result is even, we add 1 to ensure that `weight - 1` permits are + /// always available for other tasks, preventing complete semaphore exhaustion. + /// + /// This should be used to explicitly limit concurrent requests based on their expected + /// resource consumption: + /// + /// - **Block range queries**: Higher weight for larger ranges (fewer concurrent requests) + /// - **Complex calls**: Higher weight for expensive operations + /// - **Batch operations**: Higher weight for larger batches + /// - **Historical queries**: Higher weight for deeper history lookups + /// + /// # Examples + /// + /// ```ignore + /// // For a heavy request, use higher weight to limit concurrency + /// let weight = 20; // Allow at most 20 concurrent requests of this type + /// let _permit = self.acquire_weighted_blocking_io(weight).await?; + /// ``` + /// + /// This helps prevent resource exhaustion from concurrent expensive operations while allowing + /// many cheap operations to run in parallel. + /// + /// See also [`Semaphore::acquire_many_owned`](`tokio::sync::Semaphore::acquire_many_owned`). + fn acquire_weighted_blocking_io( + &self, + weight: u32, + ) -> impl Future> + Send { + let guard = self.blocking_io_task_guard(); + let total_permits = guard.available_permits().max(1) as u32; + let weight = weight.max(1); + let mut permits_to_acquire = (total_permits / weight).max(1); + + // If total_permits divides evenly by weight, add 1 to ensure that when `weight` + // concurrent requests are running, at least `weight - 1` permits remain available + // for other tasks + if total_permits.is_multiple_of(weight) { + permits_to_acquire += 1; + } + + guard.clone().acquire_many_owned(permits_to_acquire) + } + /// Executes the future on a new blocking task. /// /// Note: This is expected for futures that are dominated by blocking IO operations, for tracing diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index 78225dd9b7..ea0fdce5ed 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -212,6 +212,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA overrides: EvmOverrides, ) -> impl Future> + Send { async move { + let _permit = self.acquire_owned_blocking_io().await; let res = self.transact_call_at(request, block_number.unwrap_or_default(), overrides).await?; diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index e368f309f3..56690a3bfb 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -420,6 +420,7 @@ impl BuildPendingEnv for NextBlockEnvAttributes { gas_limit: parent.gas_limit(), parent_beacon_block_root: parent.parent_beacon_block_root(), withdrawals: parent.withdrawals_root().map(|_| Default::default()), + extra_data: parent.extra_data().clone(), } } } diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index 1b3dbfcdee..9f55bae972 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -16,7 +16,8 @@ use reth_rpc_eth_types::{ error::FromEvmError, EthApiError, PendingBlockEnv, RpcInvalidTransactionError, }; use reth_storage_api::{ - BlockIdReader, BlockNumReader, StateProvider, StateProviderBox, StateProviderFactory, + BlockIdReader, BlockNumReader, BlockReaderIdExt, StateProvider, StateProviderBox, + StateProviderFactory, }; use reth_transaction_pool::TransactionPool; @@ -96,7 +97,7 @@ pub trait EthState: LoadState + SpawnBlocking { { Ok(async move { let _permit = self - .acquire_owned() + .acquire_owned_tracing() .await .map_err(RethError::other) .map_err(EthApiError::Internal)?; @@ -273,21 +274,20 @@ pub trait LoadState: let PendingBlockEnv { evm_env, origin } = self.pending_block_env_and_cfg()?; Ok((evm_env, origin.state_block_id())) } else { - // Use cached values if there is no pending block - let block_hash = RpcNodeCore::provider(self) - .block_hash_for_id(at) + // we can assume that the blockid will be predominantly `Latest` (e.g. for + // `eth_call`) and if requested by number or hash we can quickly fetch just the + // header + let header = RpcNodeCore::provider(self) + .sealed_header_by_id(at) .map_err(Self::Error::from_eth_err)? - .ok_or(EthApiError::HeaderNotFound(at))?; - - let header = - self.cache().get_header(block_hash).await.map_err(Self::Error::from_eth_err)?; + .ok_or_else(|| EthApiError::HeaderNotFound(at))?; let evm_env = self .evm_config() .evm_env(&header) .map_err(RethError::other) .map_err(Self::Error::from_eth_err)?; - Ok((evm_env, block_hash.into())) + Ok((evm_env, header.hash().into())) } } } diff --git a/crates/rpc/rpc-eth-types/src/builder/config.rs b/crates/rpc/rpc-eth-types/src/builder/config.rs index d537302b02..7b09a3144a 100644 --- a/crates/rpc/rpc-eth-types/src/builder/config.rs +++ b/crates/rpc/rpc-eth-types/src/builder/config.rs @@ -8,9 +8,10 @@ use crate::{ }; use reqwest::Url; use reth_rpc_server_types::constants::{ - default_max_tracing_requests, DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKS_PER_FILTER, - DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_MAX_TRACE_FILTER_BLOCKS, - DEFAULT_PROOF_PERMITS, RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS, + default_max_tracing_requests, DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKING_IO_REQUEST, + DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_MAX_SIMULATE_BLOCKS, + DEFAULT_MAX_TRACE_FILTER_BLOCKS, DEFAULT_PROOF_PERMITS, + RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS, }; use serde::{Deserialize, Serialize}; @@ -68,6 +69,15 @@ pub struct EthConfig { pub eth_proof_window: u64, /// The maximum number of tracing calls that can be executed in concurrently. pub max_tracing_requests: usize, + /// The maximum number of blocking IO calls that can be executed in concurrently. + /// + /// Requests such as `eth_call`, `eth_estimateGas` and alike require evm execution, which is + /// considered blocking since it's usually more heavy on the IO side but also CPU constrained. + /// It is expected that these are spawned as short lived blocking tokio tasks. This config + /// determines how many can be spawned concurrently, to avoid a build up in the tokio's + /// blocking pool queue since there's only a limited number of threads available. This setting + /// restricts how many tasks are spawned concurrently. + pub max_blocking_io_requests: usize, /// Maximum number of blocks for `trace_filter` requests. pub max_trace_filter_blocks: u64, /// Maximum number of blocks that could be scanned per filter request in `eth_getLogs` calls. @@ -116,6 +126,7 @@ impl Default for EthConfig { gas_oracle: GasPriceOracleConfig::default(), eth_proof_window: DEFAULT_ETH_PROOF_WINDOW, max_tracing_requests: default_max_tracing_requests(), + max_blocking_io_requests: DEFAULT_MAX_BLOCKING_IO_REQUEST, max_trace_filter_blocks: DEFAULT_MAX_TRACE_FILTER_BLOCKS, max_blocks_per_filter: DEFAULT_MAX_BLOCKS_PER_FILTER, max_logs_per_response: DEFAULT_MAX_LOGS_PER_RESPONSE, @@ -152,6 +163,12 @@ impl EthConfig { self } + /// Configures the maximum number of blocking IO requests + pub const fn max_blocking_io_requests(mut self, max_requests: usize) -> Self { + self.max_blocking_io_requests = max_requests; + self + } + /// Configures the maximum block length to scan per `eth_getLogs` request pub const fn max_blocks_per_filter(mut self, max_blocks: u64) -> Self { self.max_blocks_per_filter = max_blocks; diff --git a/crates/rpc/rpc-server-types/src/constants.rs b/crates/rpc/rpc-server-types/src/constants.rs index 8861af7b54..acf5294fe9 100644 --- a/crates/rpc/rpc-server-types/src/constants.rs +++ b/crates/rpc/rpc-server-types/src/constants.rs @@ -18,6 +18,20 @@ pub const DEFAULT_MAX_LOGS_PER_RESPONSE: usize = 20_000; /// The default maximum number of blocks for `trace_filter` requests. pub const DEFAULT_MAX_TRACE_FILTER_BLOCKS: u64 = 100; +/// Setting for how many concurrent (heavier) _blocking_ IO requests are allowed. +/// +/// What is considered a blocking IO request can depend on the RPC method. In general anything that +/// requires IO is considered blocking and should be spawned as blocking. This setting is however, +/// primarily intended for heavier blocking requests that require evm execution for example, +/// `eth_call` and alike. This is intended to be used with a semaphore that must be acquired before +/// a new task is spawned to avoid unnecessary pooling if the number of inflight requests exceeds +/// the available threads in the pool. +/// +/// tokio's blocking pool, has a default of 512 and could grow unbounded, since requests like +/// `eth_call` also require a lot of cpu which will occupy the thread, we can set this to a lower +/// value. +pub const DEFAULT_MAX_BLOCKING_IO_REQUEST: usize = 256; + /// The default maximum number tracing requests we're allowing concurrently. /// Tracing is mostly CPU bound so we're limiting the number of concurrent requests to something /// lower that the number of cores, in order to minimize the impact on the rest of the system. diff --git a/crates/rpc/rpc-server-types/src/module.rs b/crates/rpc/rpc-server-types/src/module.rs index 14e44cb7c0..d41e10bb73 100644 --- a/crates/rpc/rpc-server-types/src/module.rs +++ b/crates/rpc/rpc-server-types/src/module.rs @@ -323,6 +323,8 @@ pub enum RethRpcModule { Miner, /// `mev_` module Mev, + /// `testing_` module + Testing, /// Custom RPC module not part of the standard set #[strum(default)] #[serde(untagged)] @@ -347,6 +349,7 @@ impl RethRpcModule { Self::Flashbots, Self::Miner, Self::Mev, + Self::Testing, ]; /// Returns the number of standard variants (excludes Other) @@ -406,6 +409,7 @@ impl AsRef for RethRpcModule { Self::Flashbots => "flashbots", Self::Miner => "miner", Self::Mev => "mev", + Self::Testing => "testing", } } } @@ -428,6 +432,7 @@ impl FromStr for RethRpcModule { "flashbots" => Self::Flashbots, "miner" => Self::Miner, "mev" => Self::Mev, + "testing" => Self::Testing, // Any unknown module becomes Other other => Self::Other(other.to_string()), }) diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index a47fa5ebcd..80c89e6027 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -38,6 +38,8 @@ reth-rpc-server-types.workspace = true reth-network-types.workspace = true reth-consensus.workspace = true reth-consensus-common.workspace = true +reth-ethereum-primitives.workspace = true +reth-ethereum-engine-primitives.workspace = true reth-node-api.workspace = true reth-trie-common.workspace = true diff --git a/crates/rpc/rpc/src/eth/builder.rs b/crates/rpc/rpc/src/eth/builder.rs index ff01903736..9642ca97be 100644 --- a/crates/rpc/rpc/src/eth/builder.rs +++ b/crates/rpc/rpc/src/eth/builder.rs @@ -15,7 +15,8 @@ use reth_rpc_eth_types::{ FeeHistoryCacheConfig, ForwardConfig, GasCap, GasPriceOracle, GasPriceOracleConfig, }; use reth_rpc_server_types::constants::{ - DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS, + DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKING_IO_REQUEST, DEFAULT_MAX_SIMULATE_BLOCKS, + DEFAULT_PROOF_PERMITS, }; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor}; use std::{sync::Arc, time::Duration}; @@ -41,6 +42,7 @@ pub struct EthApiBuilder { task_spawner: Box, next_env: NextEnv, max_batch_size: usize, + max_blocking_io_requests: usize, pending_block_kind: PendingBlockKind, raw_tx_forwarder: ForwardConfig, send_raw_transaction_sync_timeout: Duration, @@ -92,6 +94,7 @@ impl EthApiBuilder { task_spawner, next_env, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder, send_raw_transaction_sync_timeout, @@ -113,6 +116,7 @@ impl EthApiBuilder { task_spawner, next_env, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder, send_raw_transaction_sync_timeout, @@ -145,6 +149,7 @@ where eth_state_cache_config: Default::default(), next_env: Default::default(), max_batch_size: 1, + max_blocking_io_requests: DEFAULT_MAX_BLOCKING_IO_REQUEST, pending_block_kind: PendingBlockKind::Full, raw_tx_forwarder: ForwardConfig::default(), send_raw_transaction_sync_timeout: Duration::from_secs(30), @@ -184,6 +189,7 @@ where gas_oracle_config, next_env, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder, send_raw_transaction_sync_timeout, @@ -205,6 +211,7 @@ where gas_oracle_config, next_env, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder, send_raw_transaction_sync_timeout, @@ -233,6 +240,7 @@ where gas_oracle_config, next_env: _, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder, send_raw_transaction_sync_timeout, @@ -254,6 +262,7 @@ where gas_oracle_config, next_env, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder, send_raw_transaction_sync_timeout, @@ -335,6 +344,12 @@ where self } + /// Sets the maximum number of concurrent blocking IO requests. + pub const fn max_blocking_io_requests(mut self, max_blocking_io_requests: usize) -> Self { + self.max_blocking_io_requests = max_blocking_io_requests; + self + } + /// Sets the pending block kind pub const fn pending_block_kind(mut self, pending_block_kind: PendingBlockKind) -> Self { self.pending_block_kind = pending_block_kind; @@ -482,6 +497,7 @@ where task_spawner, next_env, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder, send_raw_transaction_sync_timeout, @@ -523,6 +539,7 @@ where rpc_converter, next_env, max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder.forwarder_client(), send_raw_transaction_sync_timeout, diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index 5b16efd6b4..04216f49fd 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -33,7 +33,7 @@ use reth_transaction_pool::{ blobstore::BlobSidecarConverter, noop::NoopTransactionPool, AddedTransactionOutcome, BatchTxProcessor, BatchTxRequest, TransactionPool, }; -use tokio::sync::{broadcast, mpsc, Mutex}; +use tokio::sync::{broadcast, mpsc, Mutex, Semaphore}; const DEFAULT_BROADCAST_CAPACITY: usize = 2000; @@ -152,6 +152,7 @@ where proof_permits: usize, rpc_converter: Rpc, max_batch_size: usize, + max_blocking_io_requests: usize, pending_block_kind: PendingBlockKind, raw_tx_forwarder: ForwardConfig, send_raw_transaction_sync_timeout: Duration, @@ -171,6 +172,7 @@ where rpc_converter, (), max_batch_size, + max_blocking_io_requests, pending_block_kind, raw_tx_forwarder.forwarder_client(), send_raw_transaction_sync_timeout, @@ -263,6 +265,11 @@ where fn tracing_task_guard(&self) -> &BlockingTaskGuard { self.inner.blocking_task_guard() } + + #[inline] + fn blocking_io_task_guard(&self) -> &std::sync::Arc { + self.inner.blocking_io_request_semaphore() + } } /// Container type `EthApi` @@ -296,6 +303,9 @@ pub struct EthApiInner { /// Guard for getproof calls blocking_task_guard: BlockingTaskGuard, + /// Semaphore to limit concurrent blocking IO requests (`eth_call`, `eth_estimateGas`, etc.) + blocking_io_request_semaphore: Arc, + /// Transaction broadcast channel raw_tx_sender: broadcast::Sender, @@ -346,6 +356,7 @@ where converter: Rpc, next_env: impl PendingEnvBuilder, max_batch_size: usize, + max_blocking_io_requests: usize, pending_block_kind: PendingBlockKind, raw_tx_forwarder: Option, send_raw_transaction_sync_timeout: Duration, @@ -384,6 +395,7 @@ where blocking_task_pool, fee_history_cache, blocking_task_guard: BlockingTaskGuard::new(proof_permits), + blocking_io_request_semaphore: Arc::new(Semaphore::new(max_blocking_io_requests)), raw_tx_sender, raw_tx_forwarder, converter, @@ -440,6 +452,8 @@ where } /// Returns a handle to the blocking thread pool. + /// + /// This is intended for tasks that are CPU bound. #[inline] pub const fn blocking_task_pool(&self) -> &BlockingTaskPool { &self.blocking_task_pool @@ -525,7 +539,7 @@ where /// Returns the transaction batch sender #[inline] - const fn tx_batch_sender( + pub const fn tx_batch_sender( &self, ) -> &mpsc::UnboundedSender::Transaction>> { &self.tx_batch_sender @@ -576,6 +590,12 @@ where pub const fn evm_memory_limit(&self) -> u64 { self.evm_memory_limit } + + /// Returns a reference to the blocking IO request semaphore. + #[inline] + pub const fn blocking_io_request_semaphore(&self) -> &Arc { + &self.blocking_io_request_semaphore + } } #[cfg(test)] diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index b5a20c19cf..816b39f485 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -42,6 +42,7 @@ mod net; mod otterscan; mod reth; mod rpc; +mod testing; mod trace; mod txpool; mod validation; @@ -58,6 +59,7 @@ pub use otterscan::OtterscanApi; pub use reth::RethApi; pub use reth_rpc_convert::RpcTypes; pub use rpc::RPCApi; +pub use testing::TestingApi; pub use trace::TraceApi; pub use txpool::TxPoolApi; pub use validation::{ValidationApi, ValidationApiConfig}; diff --git a/crates/rpc/rpc/src/testing.rs b/crates/rpc/rpc/src/testing.rs new file mode 100644 index 0000000000..833f0749e2 --- /dev/null +++ b/crates/rpc/rpc/src/testing.rs @@ -0,0 +1,127 @@ +//! Implementation of the `testing` namespace. +//! +//! This exposes `testing_buildBlockV1`, intended for non-production/debug use. + +use alloy_consensus::{Header, Transaction}; +use alloy_evm::Evm; +use alloy_primitives::U256; +use alloy_rpc_types_engine::ExecutionPayloadEnvelopeV5; +use async_trait::async_trait; +use jsonrpsee::core::RpcResult; +use reth_errors::RethError; +use reth_ethereum_engine_primitives::EthBuiltPayload; +use reth_ethereum_primitives::EthPrimitives; +use reth_evm::{execute::BlockBuilder, ConfigureEvm, NextBlockEnvAttributes}; +use reth_primitives_traits::{AlloyBlockHeader as BlockTrait, Recovered, TxTy}; +use reth_revm::{database::StateProviderDatabase, db::State}; +use reth_rpc_api::{TestingApiServer, TestingBuildBlockRequestV1}; +use reth_rpc_eth_api::{helpers::Call, FromEthApiError}; +use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; +use reth_storage_api::{BlockReader, HeaderProvider}; +use revm::context::Block; +use std::sync::Arc; + +/// Testing API handler. +#[derive(Debug, Clone)] +pub struct TestingApi { + eth_api: Eth, + evm_config: Evm, +} + +impl TestingApi { + /// Create a new testing API handler. + pub const fn new(eth_api: Eth, evm_config: Evm) -> Self { + Self { eth_api, evm_config } + } +} + +impl TestingApi +where + Eth: Call>, + Evm: ConfigureEvm + + 'static, +{ + async fn build_block_v1( + &self, + request: TestingBuildBlockRequestV1, + ) -> Result { + let evm_config = self.evm_config.clone(); + self.eth_api + .spawn_with_state_at_block(request.parent_block_hash, move |eth_api, state| { + let state = state.database.0; + let mut db = State::builder() + .with_bundle_update() + .with_database(StateProviderDatabase::new(&state)) + .build(); + let parent = eth_api + .provider() + .sealed_header_by_hash(request.parent_block_hash)? + .ok_or_else(|| { + EthApiError::HeaderNotFound(request.parent_block_hash.into()) + })?; + + let env_attrs = NextBlockEnvAttributes { + timestamp: request.payload_attributes.timestamp, + suggested_fee_recipient: request.payload_attributes.suggested_fee_recipient, + prev_randao: request.payload_attributes.prev_randao, + gas_limit: parent.gas_limit(), + parent_beacon_block_root: request.payload_attributes.parent_beacon_block_root, + withdrawals: request.payload_attributes.withdrawals.map(Into::into), + extra_data: request.extra_data.unwrap_or_default(), + }; + + let mut builder = evm_config + .builder_for_next_block(&mut db, &parent, env_attrs) + .map_err(RethError::other) + .map_err(Eth::Error::from_eth_err)?; + builder.apply_pre_execution_changes().map_err(Eth::Error::from_eth_err)?; + + let mut total_fees = U256::ZERO; + let base_fee = builder.evm_mut().block().basefee(); + + for tx in request.transactions { + let tx: Recovered> = recover_raw_transaction(&tx)?; + let tip = tx.effective_tip_per_gas(base_fee).unwrap_or_default(); + let gas_used = + builder.execute_transaction(tx).map_err(Eth::Error::from_eth_err)?; + + total_fees += U256::from(tip) * U256::from(gas_used); + } + let outcome = builder.finish(&state).map_err(Eth::Error::from_eth_err)?; + + let requests = outcome + .block + .requests_hash() + .is_some() + .then_some(outcome.execution_result.requests); + + EthBuiltPayload::new( + alloy_rpc_types_engine::PayloadId::default(), + Arc::new(outcome.block.into_sealed_block()), + total_fees, + requests, + ) + .try_into_v5() + .map_err(RethError::other) + .map_err(Eth::Error::from_eth_err) + }) + .await + } +} + +#[async_trait] +impl TestingApiServer for TestingApi +where + Eth: Call>, + Evm: ConfigureEvm + + 'static, +{ + /// Handles `testing_buildBlockV1` by gating concurrency via a semaphore and offloading heavy + /// work to the blocking pool to avoid stalling the async runtime. + async fn build_block_v1( + &self, + request: TestingBuildBlockRequestV1, + ) -> RpcResult { + self.build_block_v1(request).await.map_err(Into::into) + } +} diff --git a/crates/storage/db-api/src/models/metadata.rs b/crates/storage/db-api/src/models/metadata.rs index 67ef25a5ea..60862e0df6 100644 --- a/crates/storage/db-api/src/models/metadata.rs +++ b/crates/storage/db-api/src/models/metadata.rs @@ -25,6 +25,9 @@ pub struct StorageSettings { /// Whether `TransactionHashNumbers` is stored in `RocksDB`. #[serde(default)] pub transaction_hash_numbers_in_rocksdb: bool, + /// Whether `AccountsHistory` is stored in `RocksDB`. + #[serde(default)] + pub account_history_in_rocksdb: bool, } impl StorageSettings { @@ -39,6 +42,7 @@ impl StorageSettings { transaction_senders_in_static_files: false, storages_history_in_rocksdb: false, transaction_hash_numbers_in_rocksdb: false, + account_history_in_rocksdb: false, } } @@ -65,4 +69,10 @@ impl StorageSettings { self.transaction_hash_numbers_in_rocksdb = value; self } + + /// Sets the `account_history_in_rocksdb` flag to the provided value. + pub const fn with_account_history_in_rocksdb(mut self, value: bool) -> Self { + self.account_history_in_rocksdb = value; + self + } } diff --git a/docs/design/database.md b/docs/design/database.md index e0874c2155..0d22bb3f9a 100644 --- a/docs/design/database.md +++ b/docs/design/database.md @@ -2,8 +2,8 @@ ## Abstractions -- We created a [Database trait abstraction](https://github.com/paradigmxyz/reth/blob/main/crates/cli/commands/src/db/mod.rs) using Rust Stable GATs which frees us from being bound to a single database implementation. We currently use MDBX, but are exploring [redb](https://github.com/cberner/redb) as an alternative. -- We then iterated on [`Transaction`](https://github.com/paradigmxyz/reth/blob/main/crates/storage/errors/src/db.rs) as a non-leaky abstraction with helpers for strictly-typed and unit-tested higher-level database abstractions. +- We created a [Database trait abstraction](https://github.com/paradigmxyz/reth/blob/main/crates/storage/db-api/src/database.rs) using Rust Stable GATs which frees us from being bound to a single database implementation. We currently use MDBX, but are exploring [redb](https://github.com/cberner/redb) as an alternative. +- We then iterated on [`Transaction`](https://github.com/paradigmxyz/reth/blob/main/crates/storage/db-api/src/transaction.rs) as a non-leaky abstraction with helpers for strictly-typed and unit-tested higher-level database abstractions. ## Codecs diff --git a/docs/repo/layout.md b/docs/repo/layout.md index 22d13ffd01..95dd4d9f95 100644 --- a/docs/repo/layout.md +++ b/docs/repo/layout.md @@ -40,10 +40,19 @@ All binaries are stored in [`bin`](../../bin). These crates are related to the database. - [`storage/codecs`](../../crates/storage/codecs): Different storage codecs. +- [`storage/codecs/derive`](../../crates/storage/codecs/derive): Derive macros for storage codecs. - [`storage/libmdbx-rs`](../../crates/storage/libmdbx-rs): Rust bindings for [libmdbx](https://github.com/erthink/libmdbx). A fork of an earlier Apache-licensed version of [libmdbx-rs][libmdbx-rs]. - [`storage/db`](../../crates/storage/db): Strongly typed Database abstractions (transactions, cursors, tables) over lower level database backends. - Implemented backends: mdbx +- [`storage/db-api`](../../crates/storage/db-api): High-level database access traits used across storage crates. +- [`storage/db-common`](../../crates/storage/db-common): Shared database helpers and utilities. +- [`storage/db-models`](../../crates/storage/db-models): Typed database models for on-disk tables. +- [`storage/storage-api`](../../crates/storage/storage-api): Storage-facing APIs used by higher-level components. - [`storage/provider`](../../crates/storage/provider): Traits which provide a higher level api over the database to access the Ethereum state and historical data (transactions, blocks etc.) +- [`storage/rpc-provider`](../../crates/storage/rpc-provider): Storage provider implementations tailored for RPC access patterns. +- [`storage/errors`](../../crates/storage/errors): Common error types used by storage crates. +- [`storage/nippy-jar`](../../crates/storage/nippy-jar): Compressed columnar storage for historical data. +- [`storage/zstd-compressors`](../../crates/storage/zstd-compressors): Zstandard-based compressors used by storage components. ### Networking @@ -62,16 +71,21 @@ The networking component mainly lives in [`net/network`](../../crates/net/networ - Contains: Peer banlist. - [`net/network-api`](../../crates/net/network-api): Contains traits that define the networking component as a whole. Other components that interface with the network stack only need to depend on this crate for the relevant types. - [`net/nat`](../../crates/net/nat): A small helper crate that resolves the external IP of the running node using various methods (such as a manually provided IP, using UPnP etc.) +- [`net/network-types`](../../crates/net/network-types): Common networking types (peer identifiers, capabilities, messages, etc.). +- [`net/p2p`](../../crates/net/p2p): Higher-level P2P networking helpers and utilities. +- [`net/peers`](../../crates/net/peers): Peer set management, scoring and reputation support. #### Discovery - [`net/discv4`](../../crates/net/discv4): An implementation of the [discv4][discv4] protocol +- [`net/discv5`](../../crates/net/discv5): An implementation of the discv5 node discovery protocol. - [`net/dns`](../../crates/net/dns): An implementation of node discovery via DNS ([EIP-1459][eip-1459]) #### Protocol - [`net/eth-wire`](../../crates/net/eth-wire): Implements the `eth` wire protocol and the ``RLPx`` networking stack. - [`net/ecies`](../../crates/net/ecies): Implementation of the Elliptic Curve Integrated Encryption Scheme used in the ``RLPx`` handshake. +- [`net/eth-wire-types`](../../crates/net/eth-wire-types): Common types used by the `eth` wire protocol and RLPx networking stack. #### Downloaders @@ -81,7 +95,9 @@ The networking component mainly lives in [`net/network`](../../crates/net/networ Different consensus mechanisms. -- [`consensus/common`](../../crates/consensus/common): Common consensus functions and traits (e.g. fee calculation) +- [`consensus/common`](../../crates/consensus/common): Common consensus functions and traits (e.g. fee calculation). +- [`consensus/consensus`](../../crates/consensus/consensus): Core consensus engine interfaces and implementations. +- [`consensus/debug-client`](../../crates/consensus/debug-client): Utilities for interacting with the consensus engine in debugging and testing scenarios. ### Execution @@ -96,7 +112,9 @@ Crates related to transaction execution. These crates implement the main syncing drivers of reth. -- [`stages`](../../crates/stages): A pipelined sync, including implementation of various stages. This is used during initial sync and is faster than the tree-like structure for longer sync ranges. +- [`stages/api`](../../crates/stages/api): Public API for the staged sync pipeline. +- [`stages/stages`](../../crates/stages/stages): Implementations of the individual sync stages and the pipeline driver. This is used during initial sync and is faster than the tree-like structure for longer sync ranges. +- [`stages/types`](../../crates/stages/types): Shared types used by the staged sync pipeline. ### RPC @@ -146,6 +164,10 @@ Crates related to building and validating payloads (blocks). - [`transaction-pool`](../../crates/transaction-pool): An in-memory pending transactions pool. - [`payload/builder`](../../crates/payload/builder): Abstractions for payload building and a payload builder service that works with multiple kinds of payload resolvers. - [`payload/basic`](../../crates/payload/basic): A basic payload generator. +- [`payload/builder-primitives`](../../crates/payload/builder-primitives): Common primitives used by payload builders. +- [`payload/primitives`](../../crates/payload/primitives): Shared types used when building and validating payloads. +- [`payload/util`](../../crates/payload/util): Utility helpers used by payload building and validation logic. +- [`payload/validator`](../../crates/payload/validator): Payload validation helpers and utilities. ### Primitives @@ -169,6 +191,12 @@ Small utility crates. - [`metrics/metrics-derive`](https://github.com/rkrasiuk/metrics-derive): A derive-style API for creating metrics - [`metrics/reth-node-metrics`](../../crates/node/metrics/): The implementation of metrics server, recorder, hooks. - [`tracing`](../../crates/tracing): A small utility crate to install a uniform [`tracing`][tracing] subscriber +- [`fs-util`](../../crates/fs-util): Small filesystem utilities shared across the node. +- [`tokio-util`](../../crates/tokio-util): Tokio-related utilities used by reth. +- [`static-file`](../../crates/static-file): Utilities for bundling and serving static files. +- [`tracing-otlp`](../../crates/tracing-otlp): Exporter for sending [`tracing`][tracing] spans to OTLP/OTel backends. +- [`errors`](../../crates/errors): Common error types shared across multiple crates. +- [`e2e-test-utils`](../../crates/e2e-test-utils): Helpers for end-to-end tests of the node. [libmdbx-rs]: https://crates.io/crates/libmdbx [discv4]: https://github.com/ethereum/devp2p/blob/master/discv4.md diff --git a/docs/vocs/docs/pages/cli/op-reth/node.mdx b/docs/vocs/docs/pages/cli/op-reth/node.mdx index 2a6450d344..fc09b7a8cd 100644 --- a/docs/vocs/docs/pages/cli/op-reth/node.mdx +++ b/docs/vocs/docs/pages/cli/op-reth/node.mdx @@ -302,7 +302,7 @@ RPC: --http.api Rpc Modules to be configured for the HTTP server - [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev] + [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev, testing] --http.corsdomain Http Corsdomain to allow request from @@ -326,7 +326,7 @@ RPC: --ws.api Rpc Modules to be configured for the WS server - [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev] + [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev, testing] --ipcdisable Disable the IPC-RPC server @@ -404,6 +404,13 @@ RPC: [default: ] + --rpc.max-blocking-io-requests + Maximum number of concurrent blocking IO requests. + + Blocking IO requests include `eth_call`, `eth_estimateGas`, and similar methods that require EVM execution. These are spawned as blocking tasks to avoid blocking the async runtime. + + [default: 256] + --rpc.max-trace-filter-blocks Maximum number of blocks for `trace_filter` requests diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 6542d0c5bf..8746a770e1 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -302,7 +302,7 @@ RPC: --http.api Rpc Modules to be configured for the HTTP server - [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev] + [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev, testing] --http.corsdomain Http Corsdomain to allow request from @@ -326,7 +326,7 @@ RPC: --ws.api Rpc Modules to be configured for the WS server - [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev] + [possible values: admin, debug, eth, net, trace, txpool, web3, rpc, reth, ots, flashbots, miner, mev, testing] --ipcdisable Disable the IPC-RPC server @@ -404,6 +404,13 @@ RPC: [default: ] + --rpc.max-blocking-io-requests + Maximum number of concurrent blocking IO requests. + + Blocking IO requests include `eth_call`, `eth_estimateGas`, and similar methods that require EVM execution. These are spawned as blocking tasks to avoid blocking the async runtime. + + [default: 256] + --rpc.max-trace-filter-blocks Maximum number of blocks for `trace_filter` requests diff --git a/docs/vocs/docs/pages/exex/how-it-works.mdx b/docs/vocs/docs/pages/exex/how-it-works.mdx index 21162a7562..c6fcc2bebd 100644 --- a/docs/vocs/docs/pages/exex/how-it-works.mdx +++ b/docs/vocs/docs/pages/exex/how-it-works.mdx @@ -4,6 +4,28 @@ description: How Execution Extensions (ExExes) work in Reth. # How do ExExes work? +## Architecture + +```mermaid +sequenceDiagram + participant Reth + participant ExEx + + Note over Reth,ExEx: Normal Flow + Reth->>ExEx: ChainCommit Notification + activate ExEx + ExEx->>ExEx: Process Block Data + ExEx->>Reth: FinishedHeight Event + deactivate ExEx + + Note over Reth,ExEx: Reorg Flow + Reth->>ExEx: ChainReorg Notification + activate ExEx + ExEx->>ExEx: Rollback & Re-process + ExEx->>Reth: New FinishedHeight Event + deactivate ExEx +``` + ExExes are just [Futures](https://doc.rust-lang.org/std/future/trait.Future.html) that run indefinitely alongside Reth – as simple as that. diff --git a/docs/vocs/docs/pages/exex/overview.mdx b/docs/vocs/docs/pages/exex/overview.mdx index abfcc8f3b8..85ad5f259e 100644 --- a/docs/vocs/docs/pages/exex/overview.mdx +++ b/docs/vocs/docs/pages/exex/overview.mdx @@ -17,6 +17,27 @@ initiated by Reth. Read more about things you can build with Execution Extensions in the [Paradigm blog](https://www.paradigm.xyz/2024/05/reth-exex). +## Architecture + +```mermaid +graph LR + subgraph "Reth Process" + Reth[Reth Core] + + Reth -->|Notifications| ExEx1[ExEx 1] + Reth -->|Notifications| ExEx2[ExEx 2] + Reth -->|Notifications| ExEx3[ExEx N] + + ExEx1 -->|Events| Reth + ExEx2 -->|Events| Reth + ExEx3 -->|Events| Reth + end + + ExEx1 --> External1[External System 1] + ExEx2 --> External2[External System 2] + ExEx3 --> External3[External System N] +``` + ## What Execution Extensions are not Execution Extensions are not separate processes that connect to the main Reth node process. diff --git a/docs/vocs/docs/pages/run/faq/ports.mdx b/docs/vocs/docs/pages/run/faq/ports.mdx index f9a3ba9950..469eb73e00 100644 --- a/docs/vocs/docs/pages/run/faq/ports.mdx +++ b/docs/vocs/docs/pages/run/faq/ports.mdx @@ -13,6 +13,13 @@ This section provides essential information about the ports used by the system, - **Purpose:** Peering with other nodes for synchronization of blockchain data. Nodes communicate through this port to maintain network consensus and share updated information. - **Exposure Recommendation:** This port should be exposed to enable seamless interaction and synchronization with other nodes in the network. +## Discovery v5 Port + +- **Port:** `9200` +- **Protocol:** UDP +- **Purpose:** Used for discv5 peer discovery protocol. This is a newer discovery protocol that can be enabled with `--enable-discv5-discovery`. It operates independently from the legacy discv4 discovery on port 30303. +- **Exposure Recommendation:** This port should be exposed if discv5 discovery is enabled to allow peer discovery. + ## Metrics Port - **Port:** `9001` diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index 89c400fc7a..ac65f47932 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -2757,11 +2757,11 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0.5\"}", + "expr": "reth_engine_rpc_get_blobs_v2{$instance_label=\"$instance\", quantile=\"0.5\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, - "legendFormat": "engine_getBlobsV1 p50", + "legendFormat": "engine_getBlobsV2 p50", "range": true, "refId": "A", "useBackend": false @@ -2773,11 +2773,11 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0.95\"}", + "expr": "reth_engine_rpc_get_blobs_v2{$instance_label=\"$instance\", quantile=\"0.95\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, - "legendFormat": "engine_getBlobsV1 p95", + "legendFormat": "engine_getBlobsV2 p95", "range": true, "refId": "B", "useBackend": false @@ -2789,11 +2789,11 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0.99\"}", + "expr": "reth_engine_rpc_get_blobs_v2{$instance_label=\"$instance\", quantile=\"0.99\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, - "legendFormat": "engine_getBlobsV1 p99", + "legendFormat": "engine_getBlobsV2 p99", "range": true, "refId": "C", "useBackend": false @@ -2805,11 +2805,11 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"0\"}", + "expr": "reth_engine_rpc_get_blobs_v2{$instance_label=\"$instance\", quantile=\"0\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, - "legendFormat": "engine_getBlobsV1 min", + "legendFormat": "engine_getBlobsV2 min", "range": true, "refId": "D", "useBackend": false @@ -2821,11 +2821,11 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_engine_rpc_get_blobs_v1{$instance_label=\"$instance\", quantile=\"1\"}", + "expr": "reth_engine_rpc_get_blobs_v2{$instance_label=\"$instance\", quantile=\"1\"}", "fullMetaSearch": false, "hide": false, "includeNullMetadata": true, - "legendFormat": "engine_getBlobsV1 max", + "legendFormat": "engine_getBlobsV2 max", "range": true, "refId": "E", "useBackend": false diff --git a/examples/custom-engine-types/src/main.rs b/examples/custom-engine-types/src/main.rs index 0eef796291..a26ce1594a 100644 --- a/examples/custom-engine-types/src/main.rs +++ b/examples/custom-engine-types/src/main.rs @@ -41,7 +41,7 @@ use reth_ethereum::{ builder::{ components::{BasicPayloadServiceBuilder, ComponentsBuilder, PayloadBuilderBuilder}, rpc::{PayloadValidatorBuilder, RpcAddOns}, - BuilderContext, Node, NodeAdapter, NodeBuilder, + BuilderContext, Node, NodeAdapter, NodeBuilder, PayloadBuilderConfig, }, core::{args::RpcServerArgs, node_config::NodeConfig}, node::{ @@ -337,7 +337,8 @@ where ctx.provider().clone(), pool, evm_config, - EthereumBuilderConfig::new(), + EthereumBuilderConfig::new() + .with_extra_data(ctx.payload_builder_config().extra_data_bytes()), ), }; Ok(payload_builder) diff --git a/examples/custom-node/src/engine.rs b/examples/custom-node/src/engine.rs index fceace2d2e..3db35bb363 100644 --- a/examples/custom-node/src/engine.rs +++ b/examples/custom-node/src/engine.rs @@ -5,6 +5,7 @@ use crate::{ CustomNode, }; use alloy_eips::eip2718::WithEncoded; +use alloy_primitives::Bytes; use op_alloy_rpc_types_engine::{OpExecutionData, OpExecutionPayload}; use reth_engine_primitives::EngineApiValidator; use reth_ethereum::{ @@ -54,6 +55,10 @@ impl ExecutionPayload for CustomExecutionData { None } + fn block_access_list(&self) -> Option<&Bytes> { + None + } + fn parent_beacon_block_root(&self) -> Option { self.inner.parent_beacon_block_root() } diff --git a/examples/custom-payload-builder/src/main.rs b/examples/custom-payload-builder/src/main.rs index c38b46a5b9..3de8297090 100644 --- a/examples/custom-payload-builder/src/main.rs +++ b/examples/custom-payload-builder/src/main.rs @@ -62,7 +62,8 @@ where ctx.provider().clone(), pool, evm_config, - EthereumBuilderConfig::new(), + EthereumBuilderConfig::new() + .with_extra_data(ctx.payload_builder_config().extra_data_bytes()), ); let conf = ctx.payload_builder_config(); diff --git a/examples/exex-hello-world/src/main.rs b/examples/exex-hello-world/src/main.rs index 2c89fb7262..3e86ee785a 100644 --- a/examples/exex-hello-world/src/main.rs +++ b/examples/exex-hello-world/src/main.rs @@ -81,7 +81,7 @@ where let _eth_pubsub = rpc_handle.eth_handlers().pubsub.clone(); // The TraceApi type that provides all the trace_ handlers let _trace_api = rpc_handle.trace_api(); - // The DebugApi type that provides all the trace_ handlers + // The DebugApi type that provides all the debug_ handlers let _debug_api = rpc_handle.debug_api(); while let Some(notification) = ctx.notifications.try_next().await? {