Compare commits

..

4 Commits

Author SHA1 Message Date
Dan Cline
af0e2d0f01 fix(db-common): compute state root when initializing from genesis (#17731) 2025-08-06 09:30:42 -07:00
Dan Cline
29f7aa6fe8 deps: bump revm patch for modexp price increase 2025-07-07 21:28:52 -04:00
Dan Cline
1b6522f74d deps: bump revm patch 2025-07-01 14:09:23 -04:00
Roman Krasiuk
1533cd0633 deps: patch revm 2025-06-25 15:10:32 +02:00
1486 changed files with 41798 additions and 125494 deletions

3
.codespellrc Normal file
View File

@@ -0,0 +1,3 @@
[codespell]
skip = .git,target,./crates/storage/libmdbx-rs/mdbx-sys/libmdbx,Cargo.toml,Cargo.lock
ignore-words-list = crate,ser,ratatui

View File

@@ -5,22 +5,3 @@ slow-timeout = { period = "30s", terminate-after = 4 }
[[profile.default.overrides]]
filter = "test(general_state_tests)"
slow-timeout = { period = "1m", terminate-after = 10 }
[[profile.default.overrides]]
filter = "test(eest_fixtures)"
slow-timeout = { period = "2m", terminate-after = 10 }
# E2E tests using the testsuite framework from crates/e2e-test-utils
# These tests are located in tests/e2e-testsuite/ directories across various crates
[[profile.default.overrides]]
filter = "binary(e2e_testsuite)"
slow-timeout = { period = "2m", terminate-after = 3 }
[[profile.default.overrides]]
filter = "package(reth-era) and binary(it)"
slow-timeout = { period = "2m", terminate-after = 10 }
# Allow slower ethereum node e2e tests (p2p + blobs) to run up to 5 minutes.
[[profile.default.overrides]]
filter = "package(reth-node-ethereum) and binary(e2e)"
slow-timeout = { period = "1m", terminate-after = 5 }

View File

@@ -12,7 +12,7 @@ workflows:
# Check that `A` activates the features of `B`.
"propagate-feature",
# These are the features to check:
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat,min-error-logs,min-warn-logs,min-info-logs,min-debug-logs,min-trace-logs,otlp,js-tracer,portable",
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat",
# Do not try to add a new section to `[features]` of `A` only because `B` exposes that feature. There are edge-cases where this is still needed, but we can add them manually.
"--left-side-feature-missing=ignore",
# Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on.

View File

@@ -4,7 +4,6 @@
# include source files
!/bin
!/crates
!/pkg
!/testing
!book.toml
!Cargo.lock
@@ -12,7 +11,6 @@
!Cross.toml
!deny.toml
!Makefile
!README.md
# include for vergen constants
!/.git

2
.gitattributes vendored
View File

@@ -2,5 +2,3 @@ book/cli/**/*.md linguist-vendored
book/cli/cli.md -linguist-vendored
crates/storage/libmdbx-rs/mdbx-sys/libmdbx/** linguist-vendored
bun.lock linguist-language=JSON-with-Comments

45
.github/CODEOWNERS vendored
View File

@@ -1,45 +1,50 @@
* @gakonst
crates/blockchain-tree-api/ @rakita @mattsse @Rjected
crates/blockchain-tree/ @rakita @mattsse @Rjected
crates/chain-state/ @fgimenez @mattsse
bin/ @onbjerg
crates/blockchain-tree-api/ @rakita @rkrasiuk @mattsse @Rjected
crates/blockchain-tree/ @rakita @rkrasiuk @mattsse @Rjected
crates/chain-state/ @fgimenez @mattsse @rkrasiuk
crates/chainspec/ @Rjected @joshieDo @mattsse
crates/cli/ @mattsse
crates/consensus/ @mattsse @Rjected
crates/e2e-test-utils/ @mattsse @Rjected @klkvr @fgimenez
crates/engine/ @mattsse @Rjected @fgimenez @mediocregopher @yongkangc
crates/cli/ @onbjerg @mattsse
crates/config/ @onbjerg
crates/consensus/ @rkrasiuk @mattsse @Rjected
crates/e2e-test-utils/ @mattsse @Rjected
crates/engine @rkrasiuk @mattsse @Rjected
crates/engine/ @rkrasiuk @mattsse @Rjected @fgimenez
crates/era/ @mattsse @RomanHodulak
crates/errors/ @mattsse
crates/ethereum-forks/ @mattsse @Rjected
crates/ethereum/ @mattsse @Rjected
crates/etl/ @joshieDo @shekhirin
crates/evm/ @rakita @mattsse @Rjected
crates/exex/ @shekhirin
crates/exex/ @onbjerg @shekhirin
crates/fs-util/ @onbjerg
crates/metrics/ @onbjerg
crates/net/ @mattsse @Rjected
crates/net/downloaders/ @Rjected
crates/node/ @mattsse @Rjected @klkvr
crates/net/downloaders/ @onbjerg @rkrasiuk
crates/node/ @mattsse @Rjected @onbjerg @klkvr
crates/optimism/ @mattsse @Rjected @fgimenez
crates/payload/ @mattsse @Rjected
crates/primitives-traits/ @Rjected @RomanHodulak @mattsse @klkvr
crates/primitives/ @Rjected @mattsse @klkvr
crates/prune/ @shekhirin @joshieDo
crates/ress @shekhirin @Rjected
crates/ress @rkrasiuk
crates/revm/ @mattsse @rakita
crates/rpc/ @mattsse @Rjected @RomanHodulak
crates/stages/ @shekhirin @mediocregopher
crates/stages/ @onbjerg @rkrasiuk @shekhirin
crates/static-file/ @joshieDo @shekhirin
crates/storage/codecs/ @joshieDo
crates/storage/db-api/ @joshieDo @rakita
crates/storage/db-common/ @Rjected
crates/storage/db-common/ @Rjected @onbjerg
crates/storage/db/ @joshieDo @rakita
crates/storage/errors/ @rakita
crates/storage/errors/ @rakita @onbjerg
crates/storage/libmdbx-rs/ @rakita @shekhirin
crates/storage/nippy-jar/ @joshieDo @shekhirin
crates/storage/provider/ @rakita @joshieDo @shekhirin
crates/storage/storage-api/ @joshieDo
crates/storage/storage-api/ @joshieDo @rkrasiuk
crates/tasks/ @mattsse
crates/tokio-util/ @fgimenez
crates/transaction-pool/ @mattsse @yongkangc
crates/trie/ @Rjected @shekhirin @mediocregopher
bin/reth-bench-compare/ @mediocregopher @shekhirin @yongkangc
etc/ @Rjected @shekhirin
.github/ @gakonst @DaniPopes
crates/tracing/ @onbjerg
crates/transaction-pool/ @mattsse
crates/trie/ @rkrasiuk @Rjected @shekhirin @mediocregopher
etc/ @Rjected @onbjerg @shekhirin
.github/ @onbjerg @gakonst @DaniPopes

View File

@@ -1,7 +0,0 @@
self-hosted-runner:
labels:
- depot-ubuntu-latest
- depot-ubuntu-latest-2
- depot-ubuntu-latest-4
- depot-ubuntu-latest-8
- depot-ubuntu-latest-16

View File

@@ -11,7 +11,6 @@ exclude_crates=(
# The following require investigation if they can be fixed
reth-basic-payload-builder
reth-bench
reth-bench-compare
reth-cli
reth-cli-commands
reth-cli-runner
@@ -41,7 +40,6 @@ exclude_crates=(
reth-node-events
reth-node-metrics
reth-optimism-cli
reth-optimism-flashblocks
reth-optimism-node
reth-optimism-payload-builder
reth-optimism-rpc
@@ -50,8 +48,6 @@ exclude_crates=(
reth-rpc-api
reth-rpc-api-testing-util
reth-rpc-builder
reth-rpc-convert
reth-rpc-e2e-tests
reth-rpc-engine-api
reth-rpc-eth-api
reth-rpc-eth-types
@@ -62,14 +58,13 @@ exclude_crates=(
reth-ress-provider
# The following are not supposed to be working
reth # all of the crates below
reth-storage-rpc-provider
reth-alloy-provider
reth-invalid-block-hooks # reth-provider
reth-libmdbx # mdbx
reth-mdbx-sys # mdbx
reth-payload-builder # reth-metrics
reth-provider # tokio
reth-prune # tokio
reth-prune-static-files # reth-provider
reth-stages-api # reth-provider, reth-prune
reth-static-file # tokio
reth-transaction-pool # c-kzg
@@ -81,7 +76,6 @@ exclude_crates=(
reth-era-downloader # tokio
reth-era-utils # tokio
reth-tracing-otlp
reth-node-ethstats
)
# Array to hold the results

View File

@@ -11,8 +11,7 @@ go build .
# Run each hive command in the background for each simulator and wait
echo "Building images"
# TODO: test code has been moved from https://github.com/ethereum/execution-spec-tests to https://github.com/ethereum/execution-specs we need to pin eels branch with `--sim.buildarg branch=<release-branch-name>` once we have the fusaka release tagged on the new repo
./hive -client reth --sim "ethereum/eels" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/eest" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v4.4.0/fixtures_develop.tar.gz --sim.buildarg branch=v4.4.0 -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/engine" -sim.timelimit 1s || true &
./hive -client reth --sim "devp2p" -sim.timelimit 1s || true &
./hive -client reth --sim "ethereum/rpc-compat" -sim.timelimit 1s || true &
@@ -28,8 +27,8 @@ docker save hive/hiveproxy:latest -o ../hive_assets/hiveproxy.tar & saving_pids+
docker save hive/simulators/devp2p:latest -o ../hive_assets/devp2p.tar & saving_pids+=( $! )
docker save hive/simulators/ethereum/engine:latest -o ../hive_assets/engine.tar & saving_pids+=( $! )
docker save hive/simulators/ethereum/rpc-compat:latest -o ../hive_assets/rpc_compat.tar & saving_pids+=( $! )
docker save hive/simulators/ethereum/eels/consume-engine:latest -o ../hive_assets/eels_engine.tar & saving_pids+=( $! )
docker save hive/simulators/ethereum/eels/consume-rlp:latest -o ../hive_assets/eels_rlp.tar & saving_pids+=( $! )
docker save hive/simulators/ethereum/eest/consume-engine:latest -o ../hive_assets/eest_engine.tar & saving_pids+=( $! )
docker save hive/simulators/ethereum/eest/consume-rlp:latest -o ../hive_assets/eest_rlp.tar & saving_pids+=( $! )
docker save hive/simulators/smoke/genesis:latest -o ../hive_assets/smoke_genesis.tar & saving_pids+=( $! )
docker save hive/simulators/smoke/network:latest -o ../hive_assets/smoke_network.tar & saving_pids+=( $! )
docker save hive/simulators/ethereum/sync:latest -o ../hive_assets/ethereum_sync.tar & saving_pids+=( $! )

View File

@@ -6,8 +6,12 @@ rpc-compat:
- debug_getRawReceipts/get-block-n (reth)
- debug_getRawTransaction/get-invalid-hash (reth)
- eth_call/call-callenv (reth)
- eth_getStorageAt/get-storage-invalid-key-too-large (reth)
- eth_getStorageAt/get-storage-invalid-key (reth)
- eth_getTransactionReceipt/get-access-list (reth)
- eth_getTransactionReceipt/get-blob-tx (reth)
- eth_getTransactionReceipt/get-dynamic-fee (reth)
- eth_getTransactionReceipt/get-legacy-contract (reth)
- eth_getTransactionReceipt/get-legacy-input (reth)
- eth_getTransactionReceipt/get-legacy-receipt (reth)
@@ -28,47 +32,35 @@ engine-withdrawals:
- Withdraw zero amount (Paris) (reth)
- Empty Withdrawals (Paris) (reth)
- Corrupted Block Hash Payload (INVALID) (Paris) (reth)
- Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload (Paris) (reth)
- Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org (Paris) (reth)
engine-api: [ ]
engine-api: []
# no fix due to https://github.com/paradigmxyz/reth/issues/8732
engine-cancun:
- Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth)
# the test fails with older versions of the code for which it passed before, probably related to changes
# the test fails with older verions of the code for which it passed before, probably related to changes
# in hive or its dependencies
- Blob Transaction Ordering, Multiple Clients (Cancun) (reth)
sync: [ ]
sync: []
engine-auth: [ ]
# https://github.com/ethereum/hive/issues/1277
engine-auth:
- "JWT Authentication: No time drift, correct secret (Paris) (reth)"
- "JWT Authentication: Negative time drift, within limit, correct secret (Paris) (reth)"
- "JWT Authentication: Positive time drift, within limit, correct secret (Paris) (reth)"
# EIP-7610 related tests (Revert creation in case of non-empty storage):
#
# tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage
# The test artificially creates an empty account with storage, then tests EIP-7610's behavior.
# On mainnet, ~25 such accounts exist as contract addresses (derived from keccak(prefix, caller,
# nonce/salt), not from public keys). No private key exists for contract addresses. To trigger
# this with EIP-7702, you'd need to recover a private key from one of the already deployed contract addresses - mathematically impossible.
#
# tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_*
# Requires hash collision on create2 address to target already deployed accounts with storage.
# ~20-30 such accounts exist from before the state-clear EIP. Creating new accounts targeting
# these requires hash collision - mathematically impossible to trigger on mainnet.
# ref: https://github.com/ethereum/go-ethereum/pull/28666#issuecomment-1891997143
#
# System contract tests (already fixed and deployed):
#
# tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout and test_invalid_log_length
# System contract is already fixed and deployed; tests cover scenarios where contract is
# malformed which can't happen retroactively. No point in adding checks.
#
# tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment
# tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment
# Post-fork system contract deployment tests. Should fix for spec compliance but not realistic
# on mainnet as these contracts are already deployed at the correct addresses.
eels/consume-engine:
# 7702 test - no fix: its too expensive to check whether the storage is empty on each creation
# 6110 related tests - may start passing when fixtures improve
# 7002 related tests - post-fork test, should fix for spec compliance but not
# realistic on mainnet
# 7251 related tests - modified contract, not necessarily practical on mainnet,
# worth re-visiting when more of these related tests are passing
eest/consume-engine:
- tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage[fork_Prague-blockchain_test_engine-zero_nonce]-reth
- tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py::test_system_contract_errors[fork_Prague-blockchain_test_engine-system_contract_reaches_gas_limit-system_contract_0x0000bbddc7ce488642fb579f8b00f3a590007251]-reth
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
@@ -76,6 +68,7 @@ eels/consume-engine:
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_index_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
- tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py::test_system_contract_errors[fork_Prague-blockchain_test_engine-system_contract_reaches_gas_limit-system_contract_0x00000961ef480eb55e80d19ad83579a64c007002]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
@@ -85,76 +78,11 @@ eels/consume-engine:
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_False]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_True]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_False]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_True]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
# Blob limit tests:
#
# tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py::test_max_blobs_per_tx_fork_transition[fork_PragueToOsakaAtTime15k-blob_count_7-blockchain_test]
# this test inserts a chain via chain.rlp where the last block is invalid, but expects import to stop there, this doesn't work properly with our pipeline import approach hence the import fails when the invalid block is detected.
#. In other words, if this test fails, this means we're correctly rejecting the block.
#. The same test exists in the consume-engine simulator where it is passing as expected
eels/consume-rlp:
# the next test expects a concrete new format in the error message, there is no spec for this message, so it is ok to ignore
- tests/cancun/eip4844_blobs/test_blob_txs.py::test_blob_type_tx_pre_fork[fork_ShanghaiToCancunAtTime15k-blockchain_test_engine_from_state_test-one_blob_tx]-reth
# 7702 test - no fix: its too expensive to check whether the storage is empty on each creation
# rest of tests - see above
eest/consume-rlp:
- tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage[fork_Prague-blockchain_test-zero_nonce]-reth
- tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py::test_system_contract_errors[fork_Prague-blockchain_test_engine-system_contract_reaches_gas_limit-system_contract_0x0000bbddc7ce488642fb579f8b00f3a590007251]-reth
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
@@ -170,74 +98,13 @@ eels/consume-rlp:
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_False]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_True]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_False]-reth
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_True]-reth
- tests/osaka/eip7594_peerdas/test_max_blob_per_tx.py::test_max_blobs_per_tx_fork_transition[fork_PragueToOsakaAtTime15k-blob_count_7-blockchain_test]-reth
- tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py::test_system_contract_errors[fork_Prague-blockchain_test-system_contract_reaches_gas_limit-system_contract_0x0000bbddc7ce488642fb579f8b00f3a590007251]-reth
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-nonzero_balance]-reth
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-zero_balance]-reth
- tests/prague/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py::test_system_contract_errors[fork_Prague-blockchain_test-system_contract_reaches_gas_limit-system_contract_0x00000961ef480eb55e80d19ad83579a64c007002]-reth
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-nonzero_balance]-reth
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-zero_balance]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Cancun-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Osaka-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Paris-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Prague-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Shanghai-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Cancun-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth

View File

@@ -1,36 +0,0 @@
# Ignored Tests Configuration
#
# This file contains tests that should be ignored for various reasons (flaky, known issues, etc).
# These tests will be IGNORED in the CI results - they won't cause the build to fail
# regardless of whether they pass or fail.
#
# Format
# test_suite:
# - "test name 1"
# - "test name 2"
#
# When a test should no longer be ignored, remove it from this list.
# flaky
engine-withdrawals:
- Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload (Paris) (reth)
- Withdrawals Fork on Block 8 - 10 Block Re-Org NewPayload (Paris) (reth)
- Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org (Paris) (reth)
- Sync after 128 blocks - Withdrawals on Block 2 - Multiple Withdrawal Accounts (Paris) (reth)
engine-cancun:
- Transaction Re-Org, New Payload on Revert Back (Cancun) (reth)
- Transaction Re-Org, Re-Org to Different Block (Cancun) (reth)
- Transaction Re-Org, Re-Org Out (Cancun) (reth)
- Invalid Missing Ancestor ReOrg, StateRoot, EmptyTxs=False, Invalid P9 (Cancun) (reth)
- Multiple New Payloads Extending Canonical Chain, Wait for Canonical Payload (Cancun) (reth)
engine-api:
- Transaction Re-Org, Re-Org Out (Paris) (reth)
- Transaction Re-Org, Re-Org to Different Block (Paris) (reth)
- Transaction Re-Org, New Payload on Revert Back (Paris) (reth)
- Transaction Re-Org, Re-Org to Different Block (Paris) (reth)
- Invalid Missing Ancestor Syncing ReOrg, Transaction Nonce, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth)
- Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=True, Invalid P9 (Paris) (reth)
- Invalid Missing Ancestor Syncing ReOrg, Transaction Signature, EmptyTxs=False, CanonicalReOrg=False, Invalid P9 (Paris) (reth)
- Invalid Missing Ancestor ReOrg, StateRoot, EmptyTxs=True, Invalid P10 (Paris) (reth)
- Multiple New Payloads Extending Canonical Chain, Wait for Canonical Payload (Paris) (reth)
- Multiple New Payloads Extending Canonical Chain, Set Head to First Payload Received (Paris) (reth)

View File

@@ -11,8 +11,8 @@ IMAGES=(
"/tmp/smoke_genesis.tar"
"/tmp/smoke_network.tar"
"/tmp/ethereum_sync.tar"
"/tmp/eels_engine.tar"
"/tmp/eels_rlp.tar"
"/tmp/eest_engine.tar"
"/tmp/eest_rlp.tar"
"/tmp/reth_image.tar"
)

View File

@@ -7,7 +7,6 @@ import argparse
parser = argparse.ArgumentParser(description="Check for unexpected test results based on an exclusion list.")
parser.add_argument("report_json", help="Path to the hive report JSON file.")
parser.add_argument("--exclusion", required=True, help="Path to the exclusion YAML file.")
parser.add_argument("--ignored", required=True, help="Path to the ignored tests YAML file.")
args = parser.parse_args()
# Load hive JSON
@@ -19,30 +18,13 @@ with open(args.exclusion, 'r') as file:
exclusion_data = yaml.safe_load(file)
exclusions = exclusion_data.get(report['name'], [])
# Load ignored tests YAML
with open(args.ignored, 'r') as file:
ignored_data = yaml.safe_load(file)
ignored_tests = ignored_data.get(report['name'], [])
# Collect unexpected failures and passes
unexpected_failures = []
unexpected_passes = []
ignored_results = {'passed': [], 'failed': []}
for test in report['testCases'].values():
test_name = test['name']
test_pass = test['summaryResult']['pass']
# Check if this is an ignored test
if test_name in ignored_tests:
# Track ignored test results for informational purposes
if test_pass:
ignored_results['passed'].append(test_name)
else:
ignored_results['failed'].append(test_name)
continue # Skip this test - don't count it as unexpected
# Check against expected failures
if test_name in exclusions:
if test_pass:
unexpected_passes.append(test_name)
@@ -50,19 +32,6 @@ for test in report['testCases'].values():
if not test_pass:
unexpected_failures.append(test_name)
# Print summary of ignored tests if any were ignored
if ignored_results['passed'] or ignored_results['failed']:
print("Ignored Tests:")
if ignored_results['passed']:
print(f" Passed ({len(ignored_results['passed'])} tests):")
for test in ignored_results['passed']:
print(f" {test}")
if ignored_results['failed']:
print(f" Failed ({len(ignored_results['failed'])} tests):")
for test in ignored_results['failed']:
print(f" {test}")
print()
# Check if there are any unexpected failures or passes and exit with error
if unexpected_failures or unexpected_passes:
if unexpected_failures:

View File

@@ -7,7 +7,7 @@ sim="${1}"
limit="${2}"
run_hive() {
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 16 --client reth 2>&1 | tee /tmp/log || true
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 8 --client reth 2>&1 | tee /tmp/log || true
}
check_log() {

View File

@@ -18,19 +18,12 @@ ethereum_package:
}'
optimism_package:
chains:
chain0:
participants:
node0:
el:
type: op-geth
cl:
type: op-node
node1:
el:
type: op-reth
image: "ghcr.io/paradigmxyz/op-reth:kurtosis-ci"
cl:
type: op-node
- participants:
- el_type: op-geth
cl_type: op-node
- el_type: op-reth
cl_type: op-node
el_image: "ghcr.io/paradigmxyz/op-reth:kurtosis-ci"
network_params:
holocene_time_offset: 0
isthmus_time_offset: 0

View File

@@ -11,19 +11,18 @@ env:
CARGO_TERM_COLOR: always
BASELINE: base
SEED: reth
RUSTC_WRAPPER: "sccache"
name: bench
jobs:
codspeed:
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
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
@@ -34,8 +33,7 @@ jobs:
- name: Build the benchmark target(s)
run: ./.github/scripts/codspeed-build.sh
- name: Run the benchmarks
uses: CodSpeedHQ/action@v4
uses: CodSpeedHQ/action@v3
with:
run: cargo codspeed run --workspace
mode: instrumentation
token: ${{ secrets.CODSPEED_TOKEN }}

View File

@@ -10,49 +10,36 @@ on:
types: [opened, reopened, synchronize, closed]
merge_group:
env:
RUSTC_WRAPPER: "sccache"
jobs:
build:
runs-on: depot-ubuntu-latest-8
timeout-minutes: 90
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install bun
uses: oven-sh/setup-bun@v2
with:
bun-version: v1.2.23
- name: Install Playwright browsers
# Required for rehype-mermaid to render Mermaid diagrams during build
run: |
cd docs/vocs/
cd book/vocs/
bun i
npx playwright install --with-deps chromium
- 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
- name: Build Vocs
run: |
cd docs/vocs/ && bun run build
cd book/vocs/ && bun run build
echo "Vocs Build Complete"
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
uses: actions/upload-pages-artifact@v3
with:
path: "./docs/vocs/docs/dist"
path: "./book/vocs/docs/dist"
deploy:
# Only deploy if a push to main

View File

@@ -13,12 +13,12 @@ on:
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
name: compact-codec
jobs:
compact-codec:
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
strategy:
matrix:
bin:
@@ -27,12 +27,11 @@ 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
- name: Checkout base
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
ref: ${{ github.base_ref || 'main' }}
# On `main` branch, generates test vectors and serializes them to disk using `Compact`.
@@ -40,7 +39,7 @@ jobs:
run: |
${{ matrix.bin }} -- test-vectors compact --write
- name: Checkout PR
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
clean: false
# On incoming merge try to read and decode previously generated vectors with `Compact`

View File

@@ -33,7 +33,7 @@ jobs:
- name: 'Build and push the git-sha-tagged op-reth image'
command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME GIT_SHA=$GIT_SHA PROFILE=maxperf op-docker-build-push-git-sha'
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2

View File

@@ -35,7 +35,7 @@ jobs:
- name: 'Build and push the nightly profiling op-reth image'
command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=profiling op-docker-build-push-nightly-profiling'
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Remove bloatware
uses: laverdet/remove-bloatware@v1.0.0
with:

View File

@@ -1,73 +0,0 @@
# Tag a specific Docker release version as latest
name: docker-tag-latest
on:
workflow_dispatch:
inputs:
version:
description: 'Release version to tag as latest (e.g., v1.8.4)'
required: true
type: string
tag_reth:
description: 'Tag reth image as latest'
required: false
type: boolean
default: true
tag_op_reth:
description: 'Tag op-reth image as latest'
required: false
type: boolean
default: false
env:
DOCKER_USERNAME: ${{ github.actor }}
jobs:
tag-reth-latest:
name: Tag reth as latest
runs-on: ubuntu-24.04
if: ${{ inputs.tag_reth }}
permissions:
packages: write
contents: read
steps:
- name: Log in to Docker
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
- name: Pull reth release image
run: |
docker pull ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }}
- name: Tag reth as latest
run: |
docker tag ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/reth:latest
- name: Push reth latest tag
run: |
docker push ghcr.io/${{ github.repository_owner }}/reth:latest
tag-op-reth-latest:
name: Tag op-reth as latest
runs-on: ubuntu-24.04
if: ${{ inputs.tag_op_reth }}
permissions:
packages: write
contents: read
steps:
- name: Log in to Docker
run: |
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
- name: Pull op-reth release image
run: |
docker pull ghcr.io/${{ github.repository_owner }}/op-reth:${{ inputs.version }}
- name: Tag op-reth as latest
run: |
docker tag ghcr.io/${{ github.repository_owner }}/op-reth:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/op-reth:latest
- name: Push op-reth latest tag
run: |
docker push ghcr.io/${{ github.repository_owner }}/op-reth:latest

View File

@@ -32,7 +32,7 @@ jobs:
- name: "Build and push op-reth image"
command: "make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
@@ -68,7 +68,7 @@ jobs:
- name: "Build and push op-reth image"
command: "make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push-latest"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2

View File

@@ -1,46 +0,0 @@
# Runs e2e tests using the testsuite framework
name: e2e
on:
pull_request:
merge_group:
push:
branches: [main]
env:
CARGO_TERM_COLOR: always
SEED: rustethereumethereumrust
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
test:
name: e2e-testsuite
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:
cache-on-failure: true
- name: Run e2e tests
run: |
cargo nextest run \
--locked --features "asm-keccak" \
--workspace \
--exclude 'example-*' \
--exclude 'exex-subscription' \
--exclude 'reth-bench' \
--exclude 'ef-tests' \
--exclude 'op-reth' \
--exclude 'reth' \
-E 'binary(e2e_testsuite)'

View File

@@ -1,21 +0,0 @@
name: grafana
on:
pull_request:
merge_group:
push:
branches: [main]
jobs:
check-dashboard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check for ${DS_PROMETHEUS} in overview.json
run: |
if grep -Fn '${DS_PROMETHEUS}' etc/grafana/dashboards/overview.json; then
echo "Error: overview.json contains '\${DS_PROMETHEUS}' placeholder"
echo "Please replace it with '\${datasource}'"
exit 1
fi
echo "✓ overview.json does not contain '\${DS_PROMETHEUS}' placeholder"

View File

@@ -24,55 +24,33 @@ jobs:
prepare-hive:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: depot-ubuntu-latest-16
runs-on:
group: Reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Checkout hive tests
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
repository: ethereum/hive
# TODO: unpin when https://github.com/ethereum/hive/issues/1306 is fixed
ref: edd9969338dd1798ba2e61f049c7e3a15cef53e6
path: hivetests
- name: Get hive commit hash
id: hive-commit
run: echo "hash=$(cd hivetests && git rev-parse HEAD)" >> $GITHUB_OUTPUT
- uses: actions/setup-go@v6
- uses: actions/setup-go@v5
with:
go-version: "^1.13.1"
- run: go version
- name: Restore hive assets cache
id: cache-hive
uses: actions/cache@v5
with:
path: ./hive_assets
key: hive-assets-${{ steps.hive-commit.outputs.hash }}-${{ hashFiles('.github/assets/hive/build_simulators.sh') }}
- name: Build hive assets
if: steps.cache-hive.outputs.cache-hit != 'true'
run: .github/assets/hive/build_simulators.sh
- name: Load cached Docker images
if: steps.cache-hive.outputs.cache-hit == 'true'
run: |
cd hive_assets
for tar_file in *.tar; do
if [ -f "$tar_file" ]; then
echo "Loading $tar_file..."
docker load -i "$tar_file"
fi
done
# Make hive binary executable
chmod +x hive
- name: Upload hive assets
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: hive_assets
path: ./hive_assets
test:
timeout-minutes: 120
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
@@ -136,64 +114,57 @@ jobs:
- debug_
# consume-engine
- sim: ethereum/eels/consume-engine
limit: .*tests/osaka.*
- sim: ethereum/eels/consume-engine
- sim: ethereum/eest/consume-engine
limit: .*tests/prague.*
- sim: ethereum/eels/consume-engine
- sim: ethereum/eest/consume-engine
limit: .*tests/cancun.*
- sim: ethereum/eels/consume-engine
- sim: ethereum/eest/consume-engine
limit: .*tests/shanghai.*
- sim: ethereum/eels/consume-engine
- sim: ethereum/eest/consume-engine
limit: .*tests/berlin.*
- sim: ethereum/eels/consume-engine
- sim: ethereum/eest/consume-engine
limit: .*tests/istanbul.*
- sim: ethereum/eels/consume-engine
- sim: ethereum/eest/consume-engine
limit: .*tests/homestead.*
- sim: ethereum/eels/consume-engine
- sim: ethereum/eest/consume-engine
limit: .*tests/frontier.*
- sim: ethereum/eels/consume-engine
limit: .*tests/paris.*
# consume-rlp
- sim: ethereum/eels/consume-rlp
limit: .*tests/osaka.*
- sim: ethereum/eels/consume-rlp
- sim: ethereum/eest/consume-rlp
limit: .*tests/prague.*
- sim: ethereum/eels/consume-rlp
- sim: ethereum/eest/consume-rlp
limit: .*tests/cancun.*
- sim: ethereum/eels/consume-rlp
- sim: ethereum/eest/consume-rlp
limit: .*tests/shanghai.*
- sim: ethereum/eels/consume-rlp
- sim: ethereum/eest/consume-rlp
limit: .*tests/berlin.*
- sim: ethereum/eels/consume-rlp
- sim: ethereum/eest/consume-rlp
limit: .*tests/istanbul.*
- sim: ethereum/eels/consume-rlp
- sim: ethereum/eest/consume-rlp
limit: .*tests/homestead.*
- sim: ethereum/eels/consume-rlp
- sim: ethereum/eest/consume-rlp
limit: .*tests/frontier.*
- sim: ethereum/eels/consume-rlp
limit: .*tests/paris.*
needs:
- prepare-reth
- prepare-hive
name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
runs-on: depot-ubuntu-latest-16
runs-on:
group: Reth
permissions:
issues: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download hive assets
uses: actions/download-artifact@v7
uses: actions/download-artifact@v4
with:
name: hive_assets
path: /tmp
- name: Download reth image
uses: actions/download-artifact@v7
uses: actions/download-artifact@v4
with:
name: artifacts
path: /tmp
@@ -207,7 +178,7 @@ jobs:
chmod +x /usr/local/bin/hive
- name: Checkout hive tests
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
repository: ethereum/hive
ref: master
@@ -231,7 +202,7 @@ jobs:
- name: Parse hive output
run: |
find hivetests/workspace/logs -type f -name "*.json" ! -name "hive.json" | xargs -I {} python .github/assets/hive/parse.py {} --exclusion .github/assets/hive/expected_failures.yaml --ignored .github/assets/hive/ignored_tests.yaml
find hivetests/workspace/logs -type f -name "*.json" ! -name "hive.json" | xargs -I {} python .github/assets/hive/parse.py {} --exclusion .github/assets/hive/expected_failures.yaml
- name: Print simulator output
if: ${{ failure() }}
@@ -245,7 +216,8 @@ jobs:
notify-on-error:
needs: test
if: failure()
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

@@ -14,7 +14,6 @@ on:
env:
CARGO_TERM_COLOR: always
SEED: rustethereumethereumrust
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -24,7 +23,8 @@ jobs:
test:
name: test / ${{ matrix.network }}
if: github.event_name != 'schedule'
runs-on: depot-ubuntu-latest-4
runs-on:
group: Reth
env:
RUST_BACKTRACE: 1
strategy:
@@ -32,13 +32,12 @@ jobs:
network: ["ethereum", "optimism"]
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- name: Install Geth
run: .github/assets/install_geth.sh
- uses: taiki-e/install-action@nextest
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -48,7 +47,7 @@ jobs:
cargo nextest run \
--locked --features "asm-keccak ${{ matrix.network }}" \
--workspace --exclude ef-tests \
-E "kind(test) and not binary(e2e_testsuite)"
-E "kind(test)"
- if: matrix.network == 'optimism'
name: Run tests
run: |
@@ -72,13 +71,12 @@ jobs:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: run era1 files integration tests
run: cargo nextest run --release --package reth-era --test it -- --ignored
run: cargo nextest run --package reth-era --test it -- --ignored

View File

@@ -9,7 +9,7 @@ on:
push:
tags:
- "*"
- '*'
env:
CARGO_TERM_COLOR: always
@@ -32,16 +32,17 @@ jobs:
strategy:
fail-fast: false
name: run kurtosis
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
needs:
- prepare-reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download reth image
uses: actions/download-artifact@v7
uses: actions/download-artifact@v4
with:
name: artifacts
path: /tmp
@@ -61,10 +62,12 @@ jobs:
sudo apt update
sudo apt install kurtosis-cli
kurtosis engine start
kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package --args-file .github/assets/kurtosis_op_network_params.yaml
# TODO: unpin optimism-package when https://github.com/ethpandaops/optimism-package/issues/340 is fixed
# kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package --args-file .github/assets/kurtosis_op_network_params.yaml
kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package@452133367b693e3ba22214a6615c86c60a1efd5e --args-file .github/assets/kurtosis_op_network_params.yaml
ENCLAVE_ID=$(curl http://127.0.0.1:9779/api/enclaves | jq --raw-output 'keys[0]')
GETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-node0-op-geth".public_ports.rpc.number')
RETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-node1-op-reth".public_ports.rpc.number')
GETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-1-op-geth-op-node-op-kurtosis".public_ports.rpc.number')
RETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-2-op-reth-op-node-op-kurtosis".public_ports.rpc.number')
echo "GETH_RPC=http://127.0.0.1:$GETH_PORT" >> $GITHUB_ENV
echo "RETH_RPC=http://127.0.0.1:$RETH_PORT" >> $GITHUB_ENV
@@ -82,10 +85,12 @@ jobs:
kurtosis service logs -a op-devnet op-cl-2151908-2-op-node-op-reth-op-kurtosis
exit 1
notify-on-error:
needs: test
if: failure()
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

@@ -9,7 +9,7 @@ on:
push:
tags:
- "*"
- '*'
env:
CARGO_TERM_COLOR: always
@@ -30,16 +30,17 @@ jobs:
strategy:
fail-fast: false
name: run kurtosis
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
needs:
- prepare-reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download reth image
uses: actions/download-artifact@v7
uses: actions/download-artifact@v4
with:
name: artifacts
path: /tmp
@@ -53,12 +54,13 @@ jobs:
- name: Run kurtosis
uses: ethpandaops/kurtosis-assertoor-github-action@v1
with:
ethereum_package_args: ".github/assets/kurtosis_network_params.yaml"
ethereum_package_args: '.github/assets/kurtosis_network_params.yaml'
notify-on-error:
needs: test
if: failure()
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

@@ -11,12 +11,12 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Label PRs
uses: actions/github-script@v8
uses: actions/github-script@v7
with:
script: |
const label_pr = require('./.github/assets/label_pr.js')

View File

@@ -12,7 +12,7 @@ jobs:
actionlint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Download actionlint
id: get_actionlint
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)

View File

@@ -8,12 +8,11 @@ on:
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
jobs:
clippy-binaries:
name: clippy binaries / ${{ matrix.type }}
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
@@ -22,12 +21,11 @@ jobs:
args: --workspace --lib --examples --tests --benches --locked
features: "ethereum asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@clippy
with:
components: clippy
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -42,15 +40,14 @@ jobs:
clippy:
name: clippy
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
with:
components: clippy
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -59,16 +56,15 @@ jobs:
RUSTFLAGS: -D warnings
wasm:
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
target: wasm32-wasip1
- uses: taiki-e/install-action@cargo-hack
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -79,16 +75,15 @@ jobs:
.github/assets/check_wasm.sh
riscv:
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
target: riscv32imac-unknown-none-elf
- uses: taiki-e/install-action@cargo-hack
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -97,27 +92,21 @@ jobs:
run: .github/assets/check_rv32imac.sh
crate-checks:
name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }})
runs-on: depot-ubuntu-latest-4
strategy:
matrix:
partition: [1, 2, 3]
total_partitions: [3]
timeout-minutes: 60
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-hack
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo hack check --workspace --partition ${{ matrix.partition }}/${{ matrix.total_partitions }}
- run: cargo hack check --workspace
msrv:
name: MSRV
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
@@ -125,12 +114,11 @@ jobs:
- binary: reth
- binary: op-reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.88" # MSRV
- uses: mozilla-actions/sccache-action@v0.0.9
toolchain: "1.86" # MSRV
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -140,13 +128,12 @@ jobs:
docs:
name: docs
runs-on: depot-ubuntu-latest-4
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -158,27 +145,25 @@ jobs:
fmt:
name: fmt
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
with:
components: rustfmt
- uses: mozilla-actions/sccache-action@v0.0.9
- name: Run fmt
run: cargo fmt --all --check
udeps:
name: udeps
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -187,37 +172,37 @@ jobs:
book:
name: book
runs-on: depot-ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo build --bin reth --workspace
- run: cargo build --bin op-reth --workspace
env:
RUSTFLAGS: -D warnings
- run: ./docs/cli/update.sh target/debug/reth target/debug/op-reth
- name: Check docs changes
run: git diff --exit-code
typos:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: crate-ci/typos@v1
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo build --bin reth --workspace --features ethereum
env:
RUSTFLAGS: -D warnings
- run: ./book/cli/update.sh target/debug/reth
- name: Check book changes
run: git diff --exit-code
codespell:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: codespell-project/actions-codespell@v2
with:
skip: "*.json"
check-toml:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Run dprint
uses: dprint/check@v2.3
with:
@@ -227,7 +212,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Check dashboard JSON with jq
uses: sergeysova/jq-action@v2
with:
@@ -237,52 +222,45 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- name: Ensure no arbitrary or proptest dependency on default build
run: cargo tree --package reth -e=features,no-dev | grep -Eq "arbitrary|proptest" && exit 1 || exit 0
# Checks that selected crates can compile with power set of features
# Checks that selected rates can compile with power set of features
features:
name: features
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@clippy
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: cargo install cargo-hack
uses: taiki-e/install-action@cargo-hack
- run: |
cargo hack check \
--package reth-codecs \
--package reth-primitives-traits \
--package reth-primitives \
--feature-powerset \
--depth 2
- run: make check-features
env:
RUSTFLAGS: -D warnings
# Check crates correctly propagate features
feature-propagation:
runs-on: depot-ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: rui314/setup-mold@v1
- uses: taiki-e/cache-cargo-install-action@v2
with:
tool: zepter
- name: Eagerly pull dependencies
run: cargo metadata --format-version=1 --locked > /dev/null
- run: zepter run check
- uses: actions/checkout@v4
- name: fetch deps
run: |
# Eagerly pull dependencies
time cargo metadata --format-version=1 --locked > /dev/null
- name: run zepter
run: |
cargo install zepter -f --locked
zepter --version
time zepter run check
deny:
uses: ithacaxyz/ci/.github/workflows/deny.yml@main
@@ -300,7 +278,7 @@ jobs:
- fmt
- udeps
- book
- typos
- codespell
- grafana
- no-test-deps
- features

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Check title
id: lint_pr_title
uses: amannn/action-semantic-pull-request@v6
uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:

View File

@@ -26,9 +26,10 @@ jobs:
prepare-reth:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- run: mkdir artifacts
- name: Set up Docker Buildx
@@ -50,7 +51,7 @@ jobs:
- name: Upload reth image
id: upload
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: artifacts
path: ./artifacts

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Update Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@v7
uses: dawidd6/action-homebrew-bump-formula@v5
with:
token: ${{ secrets.HOMEBREW }}
no_fork: true

View File

@@ -1,11 +1,11 @@
# This workflow is for building and pushing reproducible artifacts for releases
# This workflow is for building and pushing reproducible Docker images for releases.
name: release-reproducible
on:
workflow_run:
workflows: [release]
types: [completed]
push:
tags:
- v*
env:
DOCKER_REPRODUCIBLE_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth-reproducible
@@ -13,41 +13,23 @@ env:
jobs:
extract-version:
name: extract version
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Extract version from triggering tag
- name: Extract version
run: echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT
id: extract_version
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Get the tag that points to the head SHA of the triggering workflow
TAG=$(gh api /repos/${{ github.repository }}/git/refs/tags \
--jq '.[] | select(.object.sha == "${{ github.event.workflow_run.head_sha }}") | .ref' \
| head -1 \
| sed 's|refs/tags/||')
if [ -z "$TAG" ]; then
echo "No tag found for SHA ${{ github.event.workflow_run.head_sha }}"
exit 1
fi
echo "VERSION=$TAG" >> $GITHUB_OUTPUT
outputs:
VERSION: ${{ steps.extract_version.outputs.VERSION }}
build-reproducible:
name: build and push reproducible image and binaries
name: build and push reproducible image
runs-on: ubuntu-latest
needs: [extract-version]
needs: extract-version
permissions:
packages: write
contents: write
contents: read
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.extract-version.outputs.VERSION }}
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
@@ -58,37 +40,12 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Rust version
id: rust_version
run: |
RUST_TOOLCHAIN=$(rustc --version | cut -d' ' -f2)
echo "RUST_TOOLCHAIN=$RUST_TOOLCHAIN" >> $GITHUB_OUTPUT
- name: Build reproducible artifacts
uses: docker/build-push-action@v6
id: docker_build
with:
context: .
file: ./Dockerfile.reproducible
build-args: |
RUST_TOOLCHAIN=${{ steps.rust_version.outputs.RUST_TOOLCHAIN }}
VERSION=${{ needs.extract-version.outputs.VERSION }}
target: artifacts
outputs: type=local,dest=./docker-artifacts
cache-from: type=gha
cache-to: type=gha,mode=max
env:
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Build and push final image
- name: Build and push reproducible image
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile.reproducible
push: true
build-args: |
RUST_TOOLCHAIN=${{ steps.rust_version.outputs.RUST_TOOLCHAIN }}
VERSION=${{ needs.extract-version.outputs.VERSION }}
tags: |
${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${{ needs.extract-version.outputs.VERSION }}
${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:latest
@@ -97,30 +54,3 @@ jobs:
provenance: false
env:
DOCKER_BUILD_RECORD_UPLOAD: false
- name: Prepare artifacts from Docker build
run: |
mkdir reproducible-artifacts
cp docker-artifacts/reth reproducible-artifacts/reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu
cp docker-artifacts/*.deb reproducible-artifacts/reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu-reproducible.deb
- name: Configure GPG and create artifacts
env:
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
run: |
export GPG_TTY=$(tty)
echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --batch --import
cd reproducible-artifacts
tar -czf reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu --remove-files
echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab reth-reproducible-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu.tar.gz
echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab reth-${{ needs.extract-version.outputs.VERSION }}-x86_64-unknown-linux-gnu-reproducible.deb
- name: Upload reproducible artifacts to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload ${{ needs.extract-version.outputs.VERSION }} \
reproducible-artifacts/*

View File

@@ -20,9 +20,8 @@ env:
OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth
REPRODUCIBLE_IMAGE_NAME: ${{ github.repository_owner }}/reth-reproducible
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"
DOCKER_IMAGE_NAME_URL: ghcr.io/${{ github.repository_owner }}/reth
DOCKER_OP_IMAGE_NAME_URL: ghcr.io/${{ github.repository_owner }}/op-reth
jobs:
dry-run:
@@ -50,9 +49,8 @@ jobs:
needs: extract-version
if: ${{ github.event.inputs.dry_run != 'true' }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- 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
@@ -80,7 +78,7 @@ jobs:
profile: maxperf
allow_fail: false
- target: x86_64-apple-darwin
os: macos-14
os: macos-13
profile: maxperf
allow_fail: false
- target: aarch64-apple-darwin
@@ -101,12 +99,11 @@ jobs:
- command: op-build
binary: op-reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- 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: |
@@ -144,14 +141,14 @@ jobs:
- name: Upload artifact
if: ${{ github.event.inputs.dry_run != 'true' }}
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
- name: Upload signature
if: ${{ github.event.inputs.dry_run != 'true' }}
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
@@ -169,11 +166,11 @@ jobs:
steps:
# This is necessary for generating the changelog.
# It has to come before "Download Artifacts" or else it deletes the artifacts.
- uses: actions/checkout@v6
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download artifacts
uses: actions/download-artifact@v7
uses: actions/download-artifact@v4
- name: Generate full changelog
id: changelog
run: |
@@ -226,7 +223,7 @@ jobs:
| Payload Builders | <TODO> |
| Non-Payload Builders | <TODO> |
*See [Update Priorities](https://reth.rs/installation/priorities) for more information about this table.*
*See [Update Priorities](https://paradigmxyz.github.io/reth/installation/priorities.html) for more information about this table.*
## All Changes
@@ -234,7 +231,7 @@ jobs:
## Binaries
[See pre-built binaries documentation.](https://reth.rs/installation/binaries)
[See pre-built binaries documentation.](https://paradigmxyz.github.io/reth/installation/binaries.html)
The binaries are signed with the PGP key: `50FB 7CC5 5B2E 8AFA 59FE 03B7 AA5E D56A 7FBF 253E`

View File

@@ -8,73 +8,31 @@ on:
jobs:
build:
name: build reproducible binaries
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- runner: ubuntu-latest
machine: machine-1
- runner: ubuntu-22.04
machine: machine-2
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
target: x86_64-unknown-linux-gnu
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build reproducible binary with Docker
- name: Install cross main
run: |
RUST_TOOLCHAIN=$(rustc --version | cut -d' ' -f2)
docker build \
--build-arg "RUST_TOOLCHAIN=${RUST_TOOLCHAIN}" \
-f Dockerfile.reproducible -t reth:release \
--target artifacts \
--output type=local,dest=./target .
- name: Calculate SHA256
id: sha256
cargo install cross --git https://github.com/cross-rs/cross
- name: Install cargo-cache
run: |
sha256sum target/reth > checksum.sha256
echo "Binaries SHA256 on ${{ matrix.machine }}: $(cat checksum.sha256)"
- name: Upload the hash
uses: actions/upload-artifact@v6
cargo install cargo-cache
- uses: Swatinem/rust-cache@v2
with:
name: checksum-${{ matrix.machine }}
path: |
checksum.sha256
retention-days: 1
compare:
name: compare reproducible binaries
needs: build
runs-on: ubuntu-latest
steps:
- name: Download artifacts from machine-1
uses: actions/download-artifact@v7
with:
name: checksum-machine-1
path: machine-1/
- name: Download artifacts from machine-2
uses: actions/download-artifact@v7
with:
name: checksum-machine-2
path: machine-2/
- name: Compare SHA256 hashes
cache-on-failure: true
- name: Build Reth
run: |
echo "=== SHA256 Comparison ==="
echo "Machine 1 hash:"
cat machine-1/checksum.sha256
echo "Machine 2 hash:"
cat machine-2/checksum.sha256
if cmp -s machine-1/checksum.sha256 machine-2/checksum.sha256; then
echo "✅ SUCCESS: Binaries are identical (reproducible build verified)"
else
echo "❌ FAILURE: Binaries differ (reproducible build failed)"
exit 1
fi
make build-reproducible
mv target/x86_64-unknown-linux-gnu/release/reth reth-build-1
- name: Clean cache
run: make clean && cargo cache -a
- name: Build Reth again
run: |
make build-reproducible
mv target/x86_64-unknown-linux-gnu/release/reth reth-build-2
- name: Compare binaries
run: cmp reth-build-1 reth-build-2

View File

@@ -12,7 +12,6 @@ env:
CARGO_TERM_COLOR: always
FROM_BLOCK: 0
TO_BLOCK: 50000
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -23,16 +22,16 @@ jobs:
name: stage-run-test
# Only run stage commands test in merge groups
if: github.event_name == 'merge_group'
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- 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

View File

@@ -14,7 +14,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/stale@v10
- uses: actions/stale@v9
with:
days-before-stale: 21
days-before-close: 7

View File

@@ -9,7 +9,6 @@ on:
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -18,7 +17,8 @@ concurrency:
jobs:
sync:
name: sync (${{ matrix.chain.bin }})
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
@@ -39,10 +39,9 @@ jobs:
block: 10000
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- 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
@@ -54,7 +53,7 @@ jobs:
--chain ${{ matrix.chain.chain }} \
--debug.tip ${{ matrix.chain.tip }} \
--debug.max-block ${{ matrix.chain.block }} \
--debug.terminate \
--debug.terminate
--era.enable
- name: Verify the target block hash
run: |
@@ -65,4 +64,4 @@ jobs:
${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }}
- name: Run stage unwind to block hash
run: |
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}

View File

@@ -9,7 +9,6 @@ on:
env:
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -18,7 +17,8 @@ concurrency:
jobs:
sync:
name: sync (${{ matrix.chain.bin }})
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
@@ -39,10 +39,9 @@ jobs:
block: 10000
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- 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
@@ -64,4 +63,4 @@ jobs:
${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }}
- name: Run stage unwind to block hash
run: |
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}

View File

@@ -11,7 +11,6 @@ on:
env:
CARGO_TERM_COLOR: always
SEED: rustethereumethereumrust
RUSTC_WRAPPER: "sccache"
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@@ -20,7 +19,8 @@ concurrency:
jobs:
test:
name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }})
runs-on: depot-ubuntu-latest-4
runs-on:
group: Reth
env:
RUST_BACKTRACE: 1
strategy:
@@ -44,10 +44,9 @@ jobs:
total_partitions: 2
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- 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
@@ -62,38 +61,29 @@ jobs:
${{ matrix.args }} --workspace \
--exclude ef-tests --no-tests=warn \
--partition hash:${{ matrix.partition }}/2 \
-E "!kind(test) and not binary(e2e_testsuite)"
-E "!kind(test)"
state:
name: Ethereum state tests
runs-on: depot-ubuntu-latest-4
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Checkout ethereum/tests
uses: actions/checkout@v6
uses: actions/checkout@v4
with:
repository: ethereum/tests
ref: 81862e4848585a438d64f911a19b3825f0f4cd95
path: testing/ef-tests/ethereum-tests
submodules: recursive
fetch-depth: 1
- name: Download & extract EEST fixtures (public)
shell: bash
env:
EEST_TESTS_TAG: v4.5.0
run: |
set -euo pipefail
mkdir -p testing/ef-tests/execution-spec-tests
URL="https://github.com/ethereum/execution-spec-tests/releases/download/${EEST_TESTS_TAG}/fixtures_stable.tar.gz"
curl -L "$URL" | tar -xz --strip-components=1 -C testing/ef-tests/execution-spec-tests
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -101,15 +91,15 @@ jobs:
doc:
name: doc tests
runs-on: depot-ubuntu-latest
runs-on:
group: Reth
env:
RUST_BACKTRACE: 1
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- 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

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: Install required tools
run: |
@@ -27,7 +27,7 @@ jobs:
./fetch_superchain_config.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@v8
uses: peter-evans/create-pull-request@v7
with:
commit-message: "chore: update superchain config"
title: "chore: update superchain config"

View File

@@ -9,22 +9,18 @@ on:
branches: [main]
merge_group:
env:
RUSTC_WRAPPER: "sccache"
jobs:
check-reth:
runs-on: depot-ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
target: x86_64-pc-windows-gnu
- uses: taiki-e/install-action@cross
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
@@ -34,17 +30,16 @@ jobs:
run: cargo check --target x86_64-pc-windows-gnu
check-op-reth:
runs-on: depot-ubuntu-latest
runs-on: ubuntu-24.04
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
target: x86_64-pc-windows-gnu
- uses: taiki-e/install-action@cross
- uses: mozilla-actions/sccache-action@v0.0.9
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true

15
.gitignore vendored
View File

@@ -55,20 +55,7 @@ rustc-ice-*
book/sources/Cargo.lock
# vocs node_modules
docs/vocs/node_modules
book/vocs/node_modules
# Cargo chef recipe file
recipe.json
_
# broken links report
links-report.json
# Python cache
__pycache__/
*.py[cod]
*$py.class
# direnv
.envrc
.direnv/

View File

@@ -162,7 +162,6 @@ Based on PR patterns, avoid:
2. **Mixing unrelated changes**: One logical change per PR
3. **Ignoring CI failures**: All checks must pass
4. **Incomplete implementations**: Finish features before submitting
5. **Modifying libmdbx sources**: Never modify files in `crates/storage/libmdbx-rs/mdbx-sys/libmdbx/` - this is vendored third-party code
### CI Requirements
@@ -182,7 +181,6 @@ Label PRs appropriately, first check the available labels and then apply the rel
* when changes are docs related, add C-docs label
* when changes are optimism related (e.g. new feature or exclusive changes to crates/optimism), add A-op-reth label
* ... and so on, check the available labels for more options.
* if being tasked to open a pr, ensure that all changes are properly formatted: `cargo +nightly fmt --all`
If changes in reth include changes to dependencies, run commands `zepter` and `make lint-toml` before finalizing the pr. Assume `zepter` binary is installed.
@@ -236,85 +234,6 @@ Common refactoring pattern:
- Add trait bounds for flexibility
- Enable reuse across different chain types (Ethereum, Optimism)
#### When to Comment
Write comments that remain valuable after the PR is merged. Future readers won't have PR context - they only see the current code.
##### ✅ DO: Add Value
**Explain WHY and non-obvious behavior:**
```rust
// Process must handle allocations atomically to prevent race conditions
// between dealloc on drop and concurrent limit checks
unsafe impl GlobalAlloc for LimitedAllocator { ... }
// Binary search requires sorted input. Panics on unsorted slices.
fn find_index(items: &[Item], target: &Item) -> Option
// Timeout set to 5s to match EVM block processing limits
const TRACER_TIMEOUT: Duration = Duration::from_secs(5);
```
**Document constraints and assumptions:**
```rust
/// Returns heap size estimate.
///
/// Note: May undercount shared references (Rc/Arc). For precise
/// accounting, combine with an allocator-based approach.
fn deep_size_of(&self) -> usize
```
**Explain complex logic:**
```rust
// We reset limits at task start because tokio reuses threads in
// spawn_blocking pool. Without reset, second task inherits first
// task's allocation count and immediately hits limit.
THREAD_ALLOCATED.with(|allocated| allocated.set(0));
```
##### ❌ DON'T: Describe Changes
```rust
// ❌ BAD - Describes the change, not the code
// Changed from Vec to HashMap for O(1) lookups
// ✅ GOOD - Explains the decision
// HashMap provides O(1) symbol lookups during trace replay
```
```rust
// ❌ BAD - PR-specific context
// Fix for issue #234 where memory wasn't freed
// ✅ GOOD - Documents the actual behavior
// Explicitly drop allocations before limit check to ensure
// accurate accounting
```
```rust
// ❌ BAD - States the obvious
// Increment counter
counter += 1;
// ✅ GOOD - Explains non-obvious purpose
// Track allocations across all threads for global limit enforcement
GLOBAL_COUNTER.fetch_add(1, Ordering::SeqCst);
```
✅ **Comment when:**
- Non-obvious behavior or edge cases
- Performance trade-offs
- Safety requirements (unsafe blocks must always be documented)
- Limitations or gotchas
- Why simpler alternatives don't work
❌ **Don't comment when:**
- Code is self-explanatory
- Just restating the code in English
- Describing what changed in this PR
##### The Test: "Will this make sense in 6 months?"
Before adding a comment, ask: Would someone reading just the current code (no PR, no history) find this helpful?
### Example Contribution Workflow
Let's say you want to fix a bug where external IP resolution fails on startup:

View File

@@ -3,7 +3,7 @@
Thanks for your interest in improving Reth!
There are multiple opportunities to contribute at any level. It doesn't matter if you are just getting started with Rust
or if you are already the most weathered expert, we can use your help.
or are the most weathered expert, we can use your help.
**No contribution is too small and all contributions are valued.**
@@ -55,7 +55,7 @@ If you have reviewed existing documentation and still have questions, or you are
*opening a discussion**. This repository comes with a discussions board where you can also ask for help. Click the "
Discussions" tab at the top.
As Reth is still in heavy development, the documentation can be a bit scattered. The [Reth Docs][reth-docs] is our
As Reth is still in heavy development, the documentation can be a bit scattered. The [Reth Book][reth-book] is our
current best-effort attempt at keeping up-to-date information.
### Submitting a bug report
@@ -235,7 +235,7 @@ _Adapted from the [Foundry contributing guide][foundry-contributing]_.
[dev-tg]: https://t.me/paradigm_reth
[reth-docs]: https://github.com/paradigmxyz/reth/tree/main/docs
[reth-book]: https://github.com/paradigmxyz/reth/tree/main/book
[mcve]: https://stackoverflow.com/help/mcve

4342
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
[workspace.package]
version = "1.9.3"
edition = "2024"
rust-version = "1.88"
version = "1.4.8"
edition = "2021"
rust-version = "1.86"
license = "MIT OR Apache-2.0"
homepage = "https://paradigmxyz.github.io/reth"
repository = "https://github.com/paradigmxyz/reth"
@@ -10,9 +10,8 @@ exclude = [".github/"]
[workspace]
members = [
"bin/reth-bench/",
"bin/reth-bench-compare/",
"bin/reth/",
"crates/storage/rpc-provider/",
"crates/alloy-provider/",
"crates/chain-state/",
"crates/chainspec/",
"crates/cli/cli/",
@@ -68,7 +67,6 @@ members = [
"crates/node/api/",
"crates/node/builder/",
"crates/node/core/",
"crates/node/ethstats",
"crates/node/events/",
"crates/node/metrics",
"crates/node/types",
@@ -77,7 +75,6 @@ members = [
"crates/optimism/cli",
"crates/optimism/consensus",
"crates/optimism/evm/",
"crates/optimism/flashblocks/",
"crates/optimism/hardforks/",
"crates/optimism/node/",
"crates/optimism/payload/",
@@ -94,7 +91,6 @@ members = [
"crates/payload/util/",
"crates/primitives-traits/",
"crates/primitives/",
"crates/prune/db",
"crates/prune/prune",
"crates/prune/types",
"crates/ress/protocol",
@@ -109,7 +105,6 @@ members = [
"crates/rpc/rpc-layer",
"crates/rpc/rpc-server-types/",
"crates/rpc/rpc-testing-util/",
"crates/rpc/rpc-e2e-tests/",
"crates/rpc/rpc-convert/",
"crates/rpc/rpc/",
"crates/stages/api/",
@@ -148,27 +143,22 @@ members = [
"examples/custom-node/",
"examples/custom-engine-types/",
"examples/custom-evm/",
"examples/custom-hardforks/",
"examples/custom-inspector/",
"examples/custom-node-components/",
"examples/custom-payload-builder/",
"examples/custom-rlpx-subprotocol",
"examples/custom-rpc-middleware",
"examples/custom-node",
"examples/db-access",
"examples/engine-api-access",
"examples/exex-hello-world",
"examples/exex-subscription",
"examples/exex-test",
"examples/full-contract-state",
"examples/manual-p2p/",
"examples/network-txpool/",
"examples/network/",
"examples/network-proxy/",
"examples/node-builder-api/",
"examples/node-custom-rpc/",
"examples/node-event-hooks/",
"examples/op-db-access/",
"examples/polygon-p2p/",
"examples/rpc-db/",
"examples/precompile-cache/",
@@ -176,11 +166,10 @@ members = [
"examples/custom-beacon-withdrawals",
"testing/ef-tests/",
"testing/testing-utils",
"testing/runner",
"crates/tracing-otlp",
]
default-members = ["bin/reth"]
exclude = ["docs/cli"]
exclude = ["book/sources", "book/cli"]
# Explicitly set the resolver to version 2, which is the default for packages with edition >= 2021
# https://doc.rust-lang.org/edition-guide/rust-2021/default-cargo-resolver.html
@@ -192,7 +181,6 @@ rust.missing_docs = "warn"
rust.rust_2018_idioms = { level = "deny", priority = -1 }
rust.unreachable_pub = "warn"
rust.unused_must_use = "deny"
rust.rust_2024_incompatible_pat = "warn"
rustdoc.all = "warn"
# rust.unnameable-types = "warn"
@@ -329,20 +317,13 @@ inherits = "release"
lto = "fat"
codegen-units = 1
[profile.reproducible]
inherits = "release"
panic = "abort"
codegen-units = 1
incremental = false
[workspace.dependencies]
# reth
op-reth = { path = "crates/optimism/bin" }
reth = { path = "bin/reth" }
reth-storage-rpc-provider = { path = "crates/storage/rpc-provider" }
reth-alloy-provider = { path = "crates/alloy-provider" }
reth-basic-payload-builder = { path = "crates/payload/basic" }
reth-bench = { path = "bin/reth-bench" }
reth-bench-compare = { path = "bin/reth-bench-compare" }
reth-chain-state = { path = "crates/chain-state" }
reth-chainspec = { path = "crates/chainspec", default-features = false }
reth-cli = { path = "crates/cli/cli" }
@@ -376,11 +357,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-cli = { path = "crates/ethereum/cli" }
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" }
@@ -410,14 +391,13 @@ reth-node-api = { path = "crates/node/api" }
reth-node-builder = { path = "crates/node/builder" }
reth-node-core = { path = "crates/node/core" }
reth-node-ethereum = { path = "crates/ethereum/node" }
reth-node-ethstats = { path = "crates/node/ethstats" }
reth-node-events = { path = "crates/node/events" }
reth-node-metrics = { path = "crates/node/metrics" }
reth-optimism-node = { path = "crates/optimism/node" }
reth-node-types = { path = "crates/node/types" }
reth-op = { path = "crates/optimism/reth", default-features = false }
reth-optimism-chainspec = { path = "crates/optimism/chainspec", default-features = false }
reth-optimism-cli = { path = "crates/optimism/cli", default-features = false }
reth-optimism-cli = { path = "crates/optimism/cli" }
reth-optimism-consensus = { path = "crates/optimism/consensus", default-features = false }
reth-optimism-forks = { path = "crates/optimism/hardforks", default-features = false }
reth-optimism-payload-builder = { path = "crates/optimism/payload" }
@@ -440,18 +420,16 @@ reth-rpc = { path = "crates/rpc/rpc" }
reth-rpc-api = { path = "crates/rpc/rpc-api" }
reth-rpc-api-testing-util = { path = "crates/rpc/rpc-testing-util" }
reth-rpc-builder = { path = "crates/rpc/rpc-builder" }
reth-rpc-e2e-tests = { path = "crates/rpc/rpc-e2e-tests" }
reth-rpc-engine-api = { path = "crates/rpc/rpc-engine-api" }
reth-rpc-eth-api = { path = "crates/rpc/rpc-eth-api" }
reth-rpc-eth-types = { path = "crates/rpc/rpc-eth-types", default-features = false }
reth-rpc-layer = { path = "crates/rpc/rpc-layer" }
reth-optimism-flashblocks = { path = "crates/optimism/flashblocks" }
reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" }
reth-rpc-convert = { path = "crates/rpc/rpc-convert" }
reth-stages = { path = "crates/stages/stages" }
reth-stages-api = { path = "crates/stages/api" }
reth-stages-types = { path = "crates/stages/types", default-features = false }
reth-stateless = { path = "crates/stateless", default-features = false }
reth-stateless = { path = "crates/stateless" }
reth-static-file = { path = "crates/static-file/static-file" }
reth-static-file-types = { path = "crates/static-file/types", default-features = false }
reth-storage-api = { path = "crates/storage/storage-api", default-features = false }
@@ -459,8 +437,7 @@ reth-storage-errors = { path = "crates/storage/errors", default-features = false
reth-tasks = { path = "crates/tasks" }
reth-testing-utils = { path = "testing/testing-utils" }
reth-tokio-util = { path = "crates/tokio-util" }
reth-tracing = { path = "crates/tracing", default-features = false }
reth-tracing-otlp = { path = "crates/tracing-otlp" }
reth-tracing = { path = "crates/tracing" }
reth-transaction-pool = { path = "crates/transaction-pool" }
reth-trie = { path = "crates/trie/trie" }
reth-trie-common = { path = "crates/trie/common", default-features = false }
@@ -473,79 +450,79 @@ reth-ress-protocol = { path = "crates/ress/protocol" }
reth-ress-provider = { path = "crates/ress/provider" }
# revm
revm = { version = "33.1.0", default-features = false }
revm-bytecode = { version = "7.1.1", default-features = false }
revm-database = { version = "9.0.5", default-features = false }
revm-state = { version = "8.1.1", default-features = false }
revm-primitives = { version = "21.0.2", default-features = false }
revm-interpreter = { version = "31.1.0", default-features = false }
revm-database-interface = { version = "8.0.5", default-features = false }
op-revm = { version = "14.1.0", default-features = false }
revm-inspectors = "0.33.2"
revm = { version = "26.0.1", default-features = false }
revm-bytecode = { version = "5.0.0", default-features = false }
revm-database = { version = "6.0.0", default-features = false }
revm-state = { version = "6.0.0", default-features = false }
revm-primitives = { version = "20.0.0", default-features = false }
revm-interpreter = { version = "22.0.1", default-features = false }
revm-inspector = { version = "7.0.1", default-features = false }
revm-context = { version = "7.0.1", default-features = false }
revm-context-interface = { version = "7.0.0", default-features = false }
revm-database-interface = { version = "6.0.0", default-features = false }
op-revm = { version = "7.0.1", default-features = false }
revm-inspectors = "0.25.0"
# eth
alloy-chains = { version = "0.2.5", default-features = false }
alloy-dyn-abi = "1.4.1"
alloy-chains = { version = "0.2.0", default-features = false }
alloy-dyn-abi = "1.2.0"
alloy-eip2124 = { version = "0.2.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-evm = { version = "0.12", default-features = false }
alloy-primitives = { version = "1.2.0", default-features = false, features = ["map-foldhash"] }
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
alloy-sol-macro = "1.4.1"
alloy-sol-types = { version = "1.4.1", default-features = false }
alloy-trie = { version = "0.9.1", default-features = false }
alloy-sol-macro = "1.2.0"
alloy-sol-types = { version = "1.2.0", default-features = false }
alloy-trie = { version = "0.9.0", default-features = false }
alloy-hardforks = "0.4.5"
alloy-hardforks = "0.2.7"
alloy-consensus = { version = "1.1.3", default-features = false }
alloy-contract = { version = "1.1.3", default-features = false }
alloy-eips = { version = "1.1.3", default-features = false }
alloy-genesis = { version = "1.1.3", default-features = false }
alloy-json-rpc = { version = "1.1.3", default-features = false }
alloy-network = { version = "1.1.3", default-features = false }
alloy-network-primitives = { version = "1.1.3", default-features = false }
alloy-provider = { version = "1.1.3", features = ["reqwest", "debug-api"], default-features = false }
alloy-pubsub = { version = "1.1.3", default-features = false }
alloy-rpc-client = { version = "1.1.3", default-features = false }
alloy-rpc-types = { version = "1.1.3", features = ["eth"], default-features = false }
alloy-rpc-types-admin = { version = "1.1.3", default-features = false }
alloy-rpc-types-anvil = { version = "1.1.3", default-features = false }
alloy-rpc-types-beacon = { version = "1.1.3", default-features = false }
alloy-rpc-types-debug = { version = "1.1.3", default-features = false }
alloy-rpc-types-engine = { version = "1.1.3", default-features = false }
alloy-rpc-types-eth = { version = "1.1.3", default-features = false }
alloy-rpc-types-mev = { version = "1.1.3", default-features = false }
alloy-rpc-types-trace = { version = "1.1.3", default-features = false }
alloy-rpc-types-txpool = { version = "1.1.3", default-features = false }
alloy-serde = { version = "1.1.3", default-features = false }
alloy-signer = { version = "1.1.3", default-features = false }
alloy-signer-local = { version = "1.1.3", default-features = false }
alloy-transport = { version = "1.1.3" }
alloy-transport-http = { version = "1.1.3", features = ["reqwest-rustls-tls"], default-features = false }
alloy-transport-ipc = { version = "1.1.3", default-features = false }
alloy-transport-ws = { version = "1.1.3", default-features = false }
alloy-consensus = { version = "1.0.12", default-features = false }
alloy-contract = { version = "1.0.12", default-features = false }
alloy-eips = { version = "1.0.12", default-features = false }
alloy-genesis = { version = "1.0.12", default-features = false }
alloy-json-rpc = { version = "1.0.12", default-features = false }
alloy-network = { version = "1.0.12", default-features = false }
alloy-network-primitives = { version = "1.0.12", default-features = false }
alloy-provider = { version = "1.0.12", features = ["reqwest"], default-features = false }
alloy-pubsub = { version = "1.0.12", default-features = false }
alloy-rpc-client = { version = "1.0.12", default-features = false }
alloy-rpc-types = { version = "1.0.12", features = ["eth"], default-features = false }
alloy-rpc-types-admin = { version = "1.0.12", default-features = false }
alloy-rpc-types-anvil = { version = "1.0.12", default-features = false }
alloy-rpc-types-beacon = { version = "1.0.12", default-features = false }
alloy-rpc-types-debug = { version = "1.0.12", default-features = false }
alloy-rpc-types-engine = { version = "1.0.12", default-features = false }
alloy-rpc-types-eth = { version = "1.0.12", default-features = false }
alloy-rpc-types-mev = { version = "1.0.12", default-features = false }
alloy-rpc-types-trace = { version = "1.0.12", default-features = false }
alloy-rpc-types-txpool = { version = "1.0.12", default-features = false }
alloy-serde = { version = "1.0.12", default-features = false }
alloy-signer = { version = "1.0.12", default-features = false }
alloy-signer-local = { version = "1.0.12", default-features = false }
alloy-transport = { version = "1.0.12" }
alloy-transport-http = { version = "1.0.12", features = ["reqwest-rustls-tls"], default-features = false }
alloy-transport-ipc = { version = "1.0.12", default-features = false }
alloy-transport-ws = { version = "1.0.12", default-features = false }
# op
alloy-op-evm = { version = "0.25.0", default-features = false }
alloy-op-hardforks = "0.4.4"
op-alloy-rpc-types = { version = "0.23.1", default-features = false }
op-alloy-rpc-types-engine = { version = "0.23.1", default-features = false }
op-alloy-network = { version = "0.23.1", default-features = false }
op-alloy-consensus = { version = "0.23.1", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.23.1", default-features = false }
alloy-op-evm = { version = "0.12", default-features = false }
alloy-op-hardforks = "0.2.2"
op-alloy-rpc-types = { version = "0.18.6", default-features = false }
op-alloy-rpc-types-engine = { version = "0.18.6", default-features = false }
op-alloy-network = { version = "0.18.6", default-features = false }
op-alloy-consensus = { version = "0.18.6", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.18.6", default-features = false }
op-alloy-flz = { version = "0.13.1", default-features = false }
# misc
either = { version = "1.15.0", default-features = false }
arrayvec = { version = "0.7.6", default-features = false }
aquamarine = "0.6"
auto_impl = "1"
backon = { version = "1.2", default-features = false, features = ["std-blocking-sleep", "tokio-sleep"] }
bincode = "1.3"
bitflags = "2.4"
blake3 = "1.5.5"
boyer-moore-magiclen = "0.2.16"
bytes = { version = "1.5", default-features = false }
brotli = "8"
cfg-if = "1.0"
clap = "4"
dashmap = "6.0"
@@ -554,6 +531,7 @@ dirs-next = "2.0.0"
dyn-clone = "1.0.17"
eyre = "0.6"
fdlimit = "0.3.0"
generic-array = "0.14"
humantime = "2.1"
humantime-serde = "1.1"
itertools = { version = "0.14", default-features = false }
@@ -561,7 +539,7 @@ linked_hash_set = "0.1"
lz4 = "1.28.1"
modular-bitfield = "0.11.2"
notify = { version = "8.0.0", default-features = false, features = ["macos_fsevent"] }
nybbles = { version = "0.4.2", default-features = false }
nybbles = { version = "0.4.0", default-features = false }
once_cell = { version = "1.19", default-features = false, features = ["critical-section"] }
parking_lot = "0.12"
paste = "1.0"
@@ -574,7 +552,6 @@ serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
serde_with = { version = "3", default-features = false, features = ["macros"] }
sha2 = { version = "0.10", default-features = false }
shellexpand = "3.0.0"
shlex = "1.3"
smallvec = "1"
strum = { version = "0.27", default-features = false }
strum_macros = "0.27"
@@ -589,7 +566,6 @@ byteorder = "1"
mini-moka = "0.10"
tar-no-std = { version = "0.3.2", default-features = false }
miniz_oxide = { version = "0.8.4", default-features = false }
chrono = "0.4.41"
# metrics
metrics = "0.24.0"
@@ -605,11 +581,9 @@ quote = "1.0"
# tokio
tokio = { version = "1.44.2", default-features = false }
tokio-stream = "0.1.11"
tokio-tungstenite = "0.26.2"
tokio-util = { version = "0.7.4", features = ["codec"] }
# async
async-compression = { version = "0.4", default-features = false }
async-stream = "0.3"
async-trait = "0.1.68"
futures = "0.3"
@@ -624,15 +598,15 @@ tower = "0.5"
tower-http = "0.6"
# p2p
discv5 = "0.10"
if-addrs = "0.14"
discv5 = "0.9"
if-addrs = "0.13"
# rpc
jsonrpsee = "0.26.0"
jsonrpsee-core = "0.26.0"
jsonrpsee-server = "0.26.0"
jsonrpsee-http-client = "0.26.0"
jsonrpsee-types = "0.26.0"
jsonrpsee = "0.25.1"
jsonrpsee-core = "0.25.1"
jsonrpsee-server = "0.25.1"
jsonrpsee-http-client = "0.25.1"
jsonrpsee-types = "0.25.1"
# http
http = "1.0"
@@ -649,26 +623,15 @@ secp256k1 = { version = "0.30", default-features = false, features = ["global-co
rand_08 = { package = "rand", version = "0.8" }
# for eip-4844
c-kzg = "2.1.5"
c-kzg = "2.1.1"
# config
toml = "0.8"
# rocksdb
rocksdb = { version = "0.24" }
# otlp obs
opentelemetry_sdk = "0.31"
opentelemetry = "0.31"
opentelemetry-otlp = "0.31"
opentelemetry-semantic-conventions = "0.31"
tracing-opentelemetry = "0.32"
# misc-testing
arbitrary = "1.3"
assert_matches = "1.5.0"
criterion = { package = "codspeed-criterion-compat", version = "2.7" }
insta = "1.41"
proptest = "1.7"
proptest-derive = "0.5"
similar-asserts = { version = "1.5.0", features = ["serde"] }
@@ -687,19 +650,24 @@ tikv-jemallocator = "0.6"
tracy-client = "0.18.0"
snmalloc-rs = { version = "0.3.7", features = ["build_cc"] }
# TODO: When we build for a windows target on an ubuntu runner, crunchy tries to
# get the wrong path, update this when the workflow has been updated
#
# See: https://github.com/eira-fransham/crunchy/issues/13
crunchy = "=0.2.2"
aes = "0.8.1"
ahash = "0.8"
anyhow = "1.0"
bindgen = { version = "0.71", default-features = false }
bindgen = { version = "0.70", default-features = false }
block-padding = "0.3.2"
cc = "=1.2.15"
cipher = "0.4.3"
comfy-table = "7.0"
concat-kdf = "0.1.0"
convert_case = "0.7.0"
crossbeam-channel = "0.5.13"
crossterm = "0.28.0"
csv = "1.3.0"
ctrlc = "3.4"
ctr = "0.9.2"
data-encoding = "2"
delegate = "0.13"
@@ -737,37 +705,45 @@ visibility = "0.1.1"
walkdir = "2.3.3"
vergen-git2 = "1.0.5"
# networking
ipnet = "2.11"
[patch.crates-io]
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "08fa016ed950b6e65f810fc9cdef7cf38fbc63f6" }
# [patch.crates-io]
# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
revm = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-bytecode = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-database = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-state = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-interpreter = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-inspector = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-context = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-context-interface = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
revm-database-interface = { git = "https://github.com/bluealloy/revm", branch = "performance-modexp" }
# op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
# op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", rev = "a79d6fc" }
@@ -782,6 +758,3 @@ ipnet = "2.11"
# jsonrpsee-server = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
# jsonrpsee-http-client = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
# jsonrpsee-types = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "a69f0b45a6b0286e16072cb8399e02ce6ceca353" }
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "a69f0b45a6b0286e16072cb8399e02ce6ceca353" }

View File

@@ -7,7 +7,7 @@ LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth
LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0"
# Install system dependencies
RUN apt-get update && apt-get install -y libclang-dev pkg-config
RUN apt-get update && apt-get -y upgrade && apt-get install -y libclang-dev pkg-config
# Builds a cargo-chef plan
FROM chef AS planner

View File

@@ -1,25 +1,20 @@
ARG RUST_TOOLCHAIN=1.89.0
FROM docker.io/rust:$RUST_TOOLCHAIN-trixie AS builder
# Use the Rust 1.86 image based on Debian Bookworm
FROM rust:1.86-bookworm AS builder
ARG PROFILE
ARG VERSION
# Switch to snapshot repository to pin dependencies
RUN sed -i '/^# http/{N;s|^# \(http[^ ]*\)\nURIs: .*|# \1\nURIs: \1|}' /etc/apt/sources.list.d/debian.sources
RUN apt-get -o Acquire::Check-Valid-Until=false update && \
apt-get install -y \
libjemalloc-dev \
libclang-dev \
mold
# Install specific version of libclang-dev
RUN apt-get update && apt-get install -y libclang-dev=1:14.0-55.7~deb12u1
# Copy the project to the container
COPY ./ /app
WORKDIR /app
COPY . .
RUN RUSTFLAGS_REPRODUCIBLE_EXTRA="-Clink-arg=-fuse-ld=mold" make build-reth-reproducible && \
PROFILE=${PROFILE:-reproducible} VERSION=$VERSION make build-deb-x86_64-unknown-linux-gnu
FROM scratch AS artifacts
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/reth /reth
COPY --from=builder /app/target/x86_64-unknown-linux-gnu/reproducible/*.deb /
# Build the project with the reproducible settings
RUN make build-reproducible
FROM gcr.io/distroless/cc-debian13:nonroot-239cdd2c8a6b275b6a6f6ed1428c57de2fff3e50
COPY --from=artifacts /reth /reth
RUN mv /app/target/x86_64-unknown-linux-gnu/release/reth /reth
# Create a minimal final image with just the binary
FROM gcr.io/distroless/cc-debian12:nonroot-6755e21ccd99ddead6edc8106ba03888cbeed41a
COPY --from=builder /reth /reth
EXPOSE 30303 30303/udp 9001 8545 8546
ENTRYPOINT [ "/reth" ]

View File

@@ -17,13 +17,7 @@ RUN apt-get update && apt-get install --assume-yes --no-install-recommends git
RUN git clone https://github.com/cross-rs/cross /cross
WORKDIR /cross/docker
RUN git checkout baf457efc2555225af47963475bd70e8d2f5993f
# xargo doesn't work with Rust 1.89 and higher: https://github.com/cross-rs/cross/issues/1701.
#
# When this PR https://github.com/cross-rs/cross/pull/1580 is merged,
# we can update the checkout above and remove this replacement.
RUN sed -i 's|sh rustup-init.sh -y --no-modify-path --profile minimal|sh rustup-init.sh -y --no-modify-path --profile minimal --default-toolchain=1.88.0|' xargo.sh
RUN git checkout 9e2298e17170655342d3248a9c8ac37ef92ba38f
RUN cp common.sh lib.sh / && /common.sh
RUN cp cmake.sh / && /cmake.sh

View File

@@ -6,13 +6,13 @@ LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0"
RUN apt-get update && apt-get -y upgrade && apt-get install -y libclang-dev pkg-config
# Builds a cargo-chef plan
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
COPY . .
ARG BUILD_PROFILE=release
ENV BUILD_PROFILE=$BUILD_PROFILE
@@ -20,18 +20,15 @@ ENV BUILD_PROFILE=$BUILD_PROFILE
ARG RUSTFLAGS=""
ENV RUSTFLAGS="$RUSTFLAGS"
ARG FEATURES=""
ENV FEATURES=$FEATURES
RUN cargo chef cook --profile $BUILD_PROFILE --features "$FEATURES" --recipe-path recipe.json --manifest-path /app/crates/optimism/bin/Cargo.toml
RUN cargo chef cook --profile $BUILD_PROFILE --recipe-path recipe.json --manifest-path /app/crates/optimism/bin/Cargo.toml
COPY . .
RUN cargo build --profile $BUILD_PROFILE --features "$FEATURES" --bin op-reth --manifest-path /app/crates/optimism/bin/Cargo.toml
RUN cargo build --profile $BUILD_PROFILE --bin op-reth --manifest-path /app/crates/optimism/bin/Cargo.toml
RUN ls -la /app/target/$BUILD_PROFILE/op-reth
RUN cp /app/target/$BUILD_PROFILE/op-reth /app/op-reth
FROM ubuntu AS runtime
FROM ubuntu:22.04 AS runtime
RUN apt-get update && \
apt-get install -y ca-certificates libssl-dev pkg-config strace && \

View File

@@ -30,7 +30,7 @@
Opstack tries to be as close to the L1 engine API as much as possible. Isthmus (Prague equivalent) introduced the first
deviation from the L1 engine API with an additional field in the `ExecutionPayload`. For this reason the op engine API
has its own server traits `OpEngineApi`.
has it's own server traits `OpEngineApi`.
Adding a new versioned endpoint requires the same changes as for L1 just for the dedicated OP types.
### Hardforks

107
Makefile
View File

@@ -30,11 +30,6 @@ EF_TESTS_TAG := v17.0
EF_TESTS_URL := https://github.com/ethereum/tests/archive/refs/tags/$(EF_TESTS_TAG).tar.gz
EF_TESTS_DIR := ./testing/ef-tests/ethereum-tests
# The release tag of https://github.com/ethereum/execution-spec-tests to use for EEST tests
EEST_TESTS_TAG := v4.5.0
EEST_TESTS_URL := https://github.com/ethereum/execution-spec-tests/releases/download/$(EEST_TESTS_TAG)/fixtures_stable.tar.gz
EEST_TESTS_DIR := ./testing/ef-tests/execution-spec-tests
# The docker image name
DOCKER_IMAGE_NAME ?= ghcr.io/paradigmxyz/reth
@@ -47,14 +42,14 @@ help: ## Display this help.
##@ Build
.PHONY: install
install: ## Build and install the reth binary under `$(CARGO_HOME)/bin`.
install: ## Build and install the reth binary under `~/.cargo/bin`.
cargo install --path bin/reth --bin reth --force --locked \
--features "$(FEATURES)" \
--profile "$(PROFILE)" \
$(CARGO_INSTALL_EXTRA_FLAGS)
.PHONY: install-op
install-op: ## Build and install the op-reth binary under `$(CARGO_HOME)/bin`.
install-op: ## Build and install the op-reth binary under `~/.cargo/bin`.
cargo install --path crates/optimism/bin --bin op-reth --force --locked \
--features "$(FEATURES)" \
--profile "$(PROFILE)" \
@@ -65,31 +60,37 @@ build: ## Build the reth binary into `target` directory.
cargo build --bin reth --features "$(FEATURES)" --profile "$(PROFILE)"
# Environment variables for reproducible builds
# Initialize RUSTFLAGS
RUST_BUILD_FLAGS =
# Enable static linking to ensure reproducibility across builds
RUST_BUILD_FLAGS += --C target-feature=+crt-static
# Set the linker to use static libgcc to ensure reproducibility across builds
RUST_BUILD_FLAGS += -C link-arg=-static-libgcc
# Remove build ID from the binary to ensure reproducibility across builds
RUST_BUILD_FLAGS += -C link-arg=-Wl,--build-id=none
# Remove metadata hash from symbol names to ensure reproducible builds
RUST_BUILD_FLAGS += -C metadata=''
# Set timestamp from last git commit for reproducible builds
SOURCE_DATE ?= $(shell git log -1 --pretty=%ct)
# Disable incremental compilation to avoid non-deterministic artifacts
CARGO_INCREMENTAL_VAL = 0
# Set C locale for consistent string handling and sorting
LOCALE_VAL = C
# Set UTC timezone for consistent time handling across builds
TZ_VAL = UTC
# Extra RUSTFLAGS for reproducible builds. Can be overridden via the environment.
RUSTFLAGS_REPRODUCIBLE_EXTRA ?=
# `reproducible` only supports reth on x86_64-unknown-linux-gnu
build-%-reproducible:
@if [ "$*" != "reth" ]; then \
echo "Error: Reproducible builds are only supported for reth, not $*"; \
exit 1; \
fi
.PHONY: build-reproducible
build-reproducible: ## Build the reth binary into `target` directory with reproducible builds. Only works for x86_64-unknown-linux-gnu currently
SOURCE_DATE_EPOCH=$(SOURCE_DATE) \
RUSTFLAGS="-C symbol-mangling-version=v0 -C strip=none -C link-arg=-Wl,--build-id=none -C metadata='' --remap-path-prefix $$(pwd)=. $(RUSTFLAGS_REPRODUCIBLE_EXTRA)" \
LC_ALL=C \
TZ=UTC \
JEMALLOC_OVERRIDE=/usr/lib/x86_64-linux-gnu/libjemalloc.a \
cargo build --bin reth --features "$(FEATURES) jemalloc-unprefixed" --profile "reproducible" --locked --target x86_64-unknown-linux-gnu
RUSTFLAGS="${RUST_BUILD_FLAGS} --remap-path-prefix $$(pwd)=." \
CARGO_INCREMENTAL=${CARGO_INCREMENTAL_VAL} \
LC_ALL=${LOCALE_VAL} \
TZ=${TZ_VAL} \
cargo build --bin reth --features "$(FEATURES)" --profile "release" --locked --target x86_64-unknown-linux-gnu
.PHONY: build-debug
build-debug: ## Build the reth binary into `target/debug` directory.
cargo build --bin reth --features "$(FEATURES)"
.PHONY: build-debug-op
build-debug-op: ## Build the op-reth binary into `target/debug` directory.
cargo build --bin op-reth --features "$(FEATURES)" --manifest-path crates/optimism/bin/Cargo.toml
.PHONY: build-op
build-op: ## Build the op-reth binary into `target` directory.
@@ -149,22 +150,6 @@ op-build-x86_64-apple-darwin:
op-build-aarch64-apple-darwin:
$(MAKE) op-build-native-aarch64-apple-darwin
build-deb-%:
@case "$*" in \
x86_64-unknown-linux-gnu|aarch64-unknown-linux-gnu|riscv64gc-unknown-linux-gnu) \
echo "Building debian package for $*"; \
;; \
*) \
echo "Error: Debian packages are only supported for x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, and riscv64gc-unknown-linux-gnu, not $*"; \
exit 1; \
;; \
esac
cargo install cargo-deb@3.6.0 --locked
cargo deb --profile $(PROFILE) --no-build --no-dbgsym --no-strip \
--target $* \
$(if $(VERSION),--deb-version "1~$(VERSION)") \
$(if $(VERSION),--output "target/$*/$(PROFILE)/reth-$(VERSION)-$*-$(PROFILE).deb")
# Create a `.tar.gz` containing a binary for a specific target.
define tarball_release_binary
cp $(CARGO_TARGET_DIR)/$(1)/$(PROFILE)/$(2) $(BIN_DIR)/$(2)
@@ -217,18 +202,9 @@ $(EF_TESTS_DIR):
tar -xzf ethereum-tests.tar.gz --strip-components=1 -C $(EF_TESTS_DIR)
rm ethereum-tests.tar.gz
# Downloads and unpacks EEST tests in the `$(EEST_TESTS_DIR)` directory.
#
# Requires `wget` and `tar`
$(EEST_TESTS_DIR):
mkdir $(EEST_TESTS_DIR)
wget $(EEST_TESTS_URL) -O execution-spec-tests.tar.gz
tar -xzf execution-spec-tests.tar.gz --strip-components=1 -C $(EEST_TESTS_DIR)
rm execution-spec-tests.tar.gz
.PHONY: ef-tests
ef-tests: $(EF_TESTS_DIR) $(EEST_TESTS_DIR) ## Runs Legacy and EEST tests.
cargo nextest run -p ef-tests --release --features ef-tests
ef-tests: $(EF_TESTS_DIR) ## Runs Ethereum Foundation tests.
cargo nextest run -p ef-tests --features ef-tests
##@ reth-bench
@@ -236,8 +212,8 @@ ef-tests: $(EF_TESTS_DIR) $(EEST_TESTS_DIR) ## Runs Legacy and EEST tests.
reth-bench: ## Build the reth-bench binary into the `target` directory.
cargo build --manifest-path bin/reth-bench/Cargo.toml --features "$(FEATURES)" --profile "$(PROFILE)"
.PHONY: install-reth-bench
install-reth-bench: ## Build and install the reth binary under `$(CARGO_HOME)/bin`.
.PHONY: install-reth-bech
install-reth-bench: ## Build and install the reth binary under `~/.cargo/bin`.
cargo install --path bin/reth-bench --bin reth-bench --force --locked \
--features "$(FEATURES)" \
--profile "$(PROFILE)"
@@ -390,9 +366,9 @@ db-tools: ## Compile MDBX debugging tools.
@echo "Run \"$(DB_TOOLS_DIR)/mdbx_chk\" for the MDBX db file integrity check."
.PHONY: update-book-cli
update-book-cli: build-debug build-debug-op## Update book cli documentation.
update-book-cli: build-debug ## Update book cli documentation.
@echo "Updating book cli doc..."
@./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth $(CARGO_TARGET_DIR)/debug/op-reth
@./book/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth
.PHONY: profiling
profiling: ## Builds `reth` with optimisations, but also symbols.
@@ -439,12 +415,12 @@ clippy-op-dev:
--locked \
--all-features
lint-typos: ensure-typos
typos
lint-codespell: ensure-codespell
codespell --skip "*.json" --skip "./testing/ef-tests/ethereum-tests"
ensure-typos:
@if ! command -v typos &> /dev/null; then \
echo "typos not found. Please install it by running the command 'cargo install typos-cli' or refer to the following link for more information: https://github.com/crate-ci/typos"; \
ensure-codespell:
@if ! command -v codespell &> /dev/null; then \
echo "codespell not found. Please install it by running the command `pip install codespell` or refer to the following link for more information: https://github.com/codespell-project/codespell" \
exit 1; \
fi
@@ -463,14 +439,14 @@ lint-toml: ensure-dprint
ensure-dprint:
@if ! command -v dprint &> /dev/null; then \
echo "dprint not found. Please install it by running the command 'cargo install --locked dprint' or refer to the following link for more information: https://github.com/dprint/dprint"; \
echo "dprint not found. Please install it by running the command `cargo install --locked dprint` or refer to the following link for more information: https://github.com/dprint/dprint" \
exit 1; \
fi
lint:
make fmt && \
make clippy && \
make lint-typos && \
make lint-codespell && \
make lint-toml
clippy-fix:
@@ -521,3 +497,10 @@ pr:
make update-book-cli && \
cargo docs --document-private-items && \
make test
check-features:
cargo hack check \
--package reth-codecs \
--package reth-primitives-traits \
--package reth-primitives \
--feature-powerset

View File

@@ -10,7 +10,7 @@
![](./assets/reth-prod.png)
**[Install](https://paradigmxyz.github.io/reth/installation/installation.html)**
| [User Docs](https://reth.rs)
| [User Book](https://reth.rs)
| [Developer Docs](./docs)
| [Crate Docs](https://reth.rs/docs)
@@ -20,7 +20,7 @@
## What is Reth?
Reth (short for Rust Ethereum, [pronunciation](https://x.com/kelvinfichter/status/1597653609411268608)) is a new Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient. Reth is an Execution Layer (EL) and is compatible with all Ethereum Consensus Layer (CL) implementations that support the [Engine API](https://github.com/ethereum/execution-apis/tree/a0d03086564ab1838b462befbc083f873dcf0c0f/src/engine). It is originally built and driven forward by [Paradigm](https://paradigm.xyz/), and is licensed under the Apache and MIT licenses.
Reth (short for Rust Ethereum, [pronunciation](https://twitter.com/kelvinfichter/status/1597653609411268608)) is a new Ethereum full node implementation that is focused on being user-friendly, highly modular, as well as being fast and efficient. Reth is an Execution Layer (EL) and is compatible with all Ethereum Consensus Layer (CL) implementations that support the [Engine API](https://github.com/ethereum/execution-apis/tree/a0d03086564ab1838b462befbc083f873dcf0c0f/src/engine). It is originally built and driven forward by [Paradigm](https://paradigm.xyz/), and is licensed under the Apache and MIT licenses.
## Goals
@@ -29,7 +29,7 @@ As a full Ethereum node, Reth allows users to connect to the Ethereum network an
More concretely, our goals are:
1. **Modularity**: Every component of Reth is built to be used as a library: well-tested, heavily documented and benchmarked. We envision that developers will import the node's crates, mix and match, and innovate on top of them. Examples of such usage include but are not limited to spinning up standalone P2P networks, talking directly to a node's database, or "unbundling" the node into the components you need. To achieve that, we are licensing Reth under the Apache/MIT permissive license. You can learn more about the project's components [here](./docs/repo/layout.md).
2. **Performance**: Reth aims to be fast, so we use Rust and the [Erigon staged-sync](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) node architecture. We also use our Ethereum libraries (including [Alloy](https://github.com/alloy-rs/alloy/) and [revm](https://github.com/bluealloy/revm/)) which we've battle-tested and optimized via [Foundry](https://github.com/foundry-rs/foundry/).
2. **Performance**: Reth aims to be fast, so we used Rust and the [Erigon staged-sync](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) node architecture. We also use our Ethereum libraries (including [Alloy](https://github.com/alloy-rs/alloy/) and [revm](https://github.com/bluealloy/revm/)) which weve battle-tested and optimized via [Foundry](https://github.com/foundry-rs/foundry/).
3. **Free for anyone to use any way they want**: Reth is free open source software, built for the community, by the community. By licensing the software under the Apache/MIT license, we want developers to use it without being bound by business licenses, or having to think about the implications of GPL-like licenses.
4. **Client Diversity**: The Ethereum protocol becomes more antifragile when no node implementation dominates. This ensures that if there's a software bug, the network does not finalize a bad block. By building a new client, we hope to contribute to Ethereum's antifragility.
5. **Support as many EVM chains as possible**: We aspire that Reth can full-sync not only Ethereum, but also other chains like Optimism, Polygon, BNB Smart Chain, and more. If you're working on any of these projects, please reach out.
@@ -40,18 +40,17 @@ More concretely, our goals are:
Reth is production ready, and suitable for usage in mission-critical environments such as staking or high-uptime services. We also actively recommend professional node operators to switch to Reth in production for performance and cost reasons in use cases where high performance with great margins is required such as RPC, MEV, Indexing, Simulations, and P2P activities.
More historical context below:
- We released 1.0 "production-ready" stable Reth in June 2024.
- Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v2.pdf).
- Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://x.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon.
- We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3, 2024,the last beta release.
- We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4, 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives.
- We shipped iterative improvements until the last alpha release on February 28, 2024, [0.1.0-alpha.21](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.21).
- We [initially announced](https://www.paradigm.xyz/2023/06/reth-alpha) [0.1.0-alpha.1](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.1) on June 20, 2023.
* We released 1.0 "production-ready" stable Reth in June 2024.
* Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v2.pdf).
* Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon.
* We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3rd 2024 the last beta release.
* We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4th 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives.
* We shipped iterative improvements until the last alpha release on February 28th 2024, [0.1.0-alpha.21](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.21).
* We [initially announced](https://www.paradigm.xyz/2023/06/reth-alpha) [0.1.0-alpha.1](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.1) in June 20th 2023.
### Database compatibility
We do not have any breaking database changes since beta.1, and we do not plan any in the near future.
We do not have any breaking database changes since beta.1, and do not plan any in the near future.
Reth [v0.2.0-beta.1](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) includes
a [set of breaking database changes](https://github.com/paradigmxyz/reth/pull/5191) that makes it impossible to use database files produced by earlier versions.
@@ -61,7 +60,7 @@ If you had a database produced by alpha versions of Reth, you need to drop it wi
## For Users
See the [Reth documentation](https://reth.rs/) for instructions on how to install and run Reth.
See the [Reth Book](https://paradigmxyz.github.io/reth) for instructions on how to install and run Reth.
## For Developers
@@ -69,7 +68,7 @@ See the [Reth documentation](https://reth.rs/) for instructions on how to instal
You can use individual crates of reth in your project.
The crate docs can be found [here](https://reth.rs/docs/).
The crate docs can be found [here](https://paradigmxyz.github.io/reth/docs).
For a general overview of the crates, see [Project Layout](./docs/repo/layout.md).
@@ -77,20 +76,21 @@ For a general overview of the crates, see [Project Layout](./docs/repo/layout.md
If you want to contribute, or follow along with contributor discussion, you can use our [main telegram](https://t.me/paradigm_reth) to chat with us about the development of Reth!
- Our contributor guidelines can be found in [`CONTRIBUTING.md`](./CONTRIBUTING.md).
- See our [contributor docs](./docs) for more information on the project. A good starting point is [Project Layout](./docs/repo/layout.md).
- Our contributor guidelines can be found in [`CONTRIBUTING.md`](./CONTRIBUTING.md).
- See our [contributor docs](./docs) for more information on the project. A good starting point is [Project Layout](./docs/repo/layout.md).
### Building and testing
<!--
When updating this, also update:
- clippy.toml
- Cargo.toml
- .github/workflows/lint.yml
-->
The Minimum Supported Rust Version (MSRV) of this project is [1.88.0](https://blog.rust-lang.org/2025/06/26/Rust-1.88.0/).
The Minimum Supported Rust Version (MSRV) of this project is [1.86.0](https://blog.rust-lang.org/2025/04/03/Rust-1.86.0/).
See the docs for detailed instructions on how to [build from source](https://reth.rs/installation/source/).
See the book for detailed instructions on how to [build from source](https://paradigmxyz.github.io/reth/installation/source.html).
To fully test Reth, you will need to have [Geth installed](https://geth.ethereum.org/docs/getting-started/installing-geth), but it is possible to run a subset of tests without Geth.
@@ -119,13 +119,13 @@ Using `cargo test` to run tests may work fine, but this is not tested and does n
## Getting Help
If you have any questions, first see if the answer to your question can be found in the [docs][book].
If you have any questions, first see if the answer to your question can be found in the [book][book].
If the answer is not there:
- Join the [Telegram][tg-url] to get help, or
- Open a [discussion](https://github.com/paradigmxyz/reth/discussions/new) with your question, or
- Open an issue with [the bug](https://github.com/paradigmxyz/reth/issues/new?assignees=&labels=C-bug%2CS-needs-triage&projects=&template=bug.yml)
- Join the [Telegram][tg-url] to get help, or
- Open a [discussion](https://github.com/paradigmxyz/reth/discussions/new) with your question, or
- Open an issue with [the bug](https://github.com/paradigmxyz/reth/issues/new?assignees=&labels=C-bug%2CS-needs-triage&projects=&template=bug.yml)
## Security
@@ -137,13 +137,13 @@ Reth is a new implementation of the Ethereum protocol. In the process of develop
None of this would have been possible without them, so big shoutout to the teams below:
- [Geth](https://github.com/ethereum/go-ethereum/): We would like to express our heartfelt gratitude to the go-ethereum team for their outstanding contributions to Ethereum over the years. Their tireless efforts and dedication have helped to shape the Ethereum ecosystem and make it the vibrant and innovative community it is today. Thank you for your hard work and commitment to the project.
- [Erigon](https://github.com/ledgerwatch/erigon) (fka Turbo-Geth): Erigon pioneered the ["Staged Sync" architecture](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) that Reth is using, as well as [introduced MDBX](https://github.com/ledgerwatch/erigon/wiki/Choice-of-storage-engine) as the database of choice. We thank Erigon for pushing the state of the art research on the performance limits of Ethereum nodes.
- [Akula](https://github.com/akula-bft/akula/): Reth uses forks of the Apache versions of Akula's [MDBX Bindings](https://github.com/paradigmxyz/reth/pull/132), [FastRLP](https://github.com/paradigmxyz/reth/pull/63) and [ECIES](https://github.com/paradigmxyz/reth/pull/80). Given that these packages were already released under the Apache License, and they implement standardized solutions, we decided not to reimplement them to iterate faster. We thank the Akula team for their contributions to the Rust Ethereum ecosystem and for publishing these packages.
- [Geth](https://github.com/ethereum/go-ethereum/): We would like to express our heartfelt gratitude to the go-ethereum team for their outstanding contributions to Ethereum over the years. Their tireless efforts and dedication have helped to shape the Ethereum ecosystem and make it the vibrant and innovative community it is today. Thank you for your hard work and commitment to the project.
- [Erigon](https://github.com/ledgerwatch/erigon) (fka Turbo-Geth): Erigon pioneered the ["Staged Sync" architecture](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) that Reth is using, as well as [introduced MDBX](https://github.com/ledgerwatch/erigon/wiki/Choice-of-storage-engine) as the database of choice. We thank Erigon for pushing the state of the art research on the performance limits of Ethereum nodes.
- [Akula](https://github.com/akula-bft/akula/): Reth uses forks of the Apache versions of Akula's [MDBX Bindings](https://github.com/paradigmxyz/reth/pull/132), [FastRLP](https://github.com/paradigmxyz/reth/pull/63) and [ECIES](https://github.com/paradigmxyz/reth/pull/80) . Given that these packages were already released under the Apache License, and they implement standardized solutions, we decided not to reimplement them to iterate faster. We thank the Akula team for their contributions to the Rust Ethereum ecosystem and for publishing these packages.
## Warning
The `NippyJar` and `Compact` encoding formats and their implementations are designed for storing and retrieving data internally. They are not hardened to safely read potentially malicious data.
[book]: https://reth.rs/
[book]: https://paradigmxyz.github.io/reth/
[tg-url]: https://t.me/paradigm_reth

View File

@@ -1,96 +0,0 @@
[package]
name = "reth-bench-compare"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
description = "Automated reth benchmark comparison between git references"
[lints]
workspace = true
[[bin]]
name = "reth-bench-compare"
path = "src/main.rs"
[dependencies]
# reth
reth-cli-runner.workspace = true
reth-cli-util.workspace = true
reth-node-core.workspace = true
reth-tracing.workspace = true
reth-chainspec.workspace = true
# alloy
alloy-provider = { workspace = true, features = ["reqwest-rustls-tls"], default-features = false }
alloy-rpc-types-eth.workspace = true
alloy-primitives.workspace = true
# CLI and argument parsing
clap = { workspace = true, features = ["derive", "env"] }
eyre.workspace = true
# Async runtime
tokio = { workspace = true, features = ["full"] }
tracing.workspace = true
# Serialization
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
# Time handling
chrono = { workspace = true, features = ["serde"] }
# Path manipulation
shellexpand.workspace = true
# CSV handling
csv.workspace = true
# Process management
ctrlc.workspace = true
shlex.workspace = true
[target.'cfg(unix)'.dependencies]
nix = { version = "0.29", features = ["signal", "process"] }
[features]
default = ["jemalloc"]
asm-keccak = [
"reth-node-core/asm-keccak",
"alloy-primitives/asm-keccak",
]
jemalloc = [
"reth-cli-util/jemalloc",
"reth-node-core/jemalloc",
]
jemalloc-prof = ["reth-cli-util/jemalloc-prof"]
tracy-allocator = ["reth-cli-util/tracy-allocator"]
min-error-logs = [
"tracing/release_max_level_error",
"reth-node-core/min-error-logs",
]
min-warn-logs = [
"tracing/release_max_level_warn",
"reth-node-core/min-warn-logs",
]
min-info-logs = [
"tracing/release_max_level_info",
"reth-node-core/min-info-logs",
]
min-debug-logs = [
"tracing/release_max_level_debug",
"reth-node-core/min-debug-logs",
]
min-trace-logs = [
"tracing/release_max_level_trace",
"reth-node-core/min-trace-logs",
]
# no-op feature flag for switching between the `optimism` and default functionality in CI matrices
ethereum = []

View File

@@ -1,298 +0,0 @@
//! Benchmark execution using reth-bench.
use crate::cli::Args;
use eyre::{eyre, Result, WrapErr};
use std::{
path::Path,
sync::{Arc, Mutex},
};
use tokio::{
fs::File as AsyncFile,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
process::Command,
};
use tracing::{debug, error, info, warn};
/// Manages benchmark execution using reth-bench
pub(crate) struct BenchmarkRunner {
rpc_url: String,
jwt_secret: String,
wait_time: Option<String>,
warmup_blocks: u64,
}
impl BenchmarkRunner {
/// Create a new `BenchmarkRunner` from CLI arguments
pub(crate) fn new(args: &Args) -> Self {
Self {
rpc_url: args.get_rpc_url(),
jwt_secret: args.jwt_secret_path().to_string_lossy().to_string(),
wait_time: args.wait_time.clone(),
warmup_blocks: args.get_warmup_blocks(),
}
}
/// Clear filesystem caches (page cache, dentries, and inodes)
pub(crate) async fn clear_fs_caches() -> Result<()> {
info!("Clearing filesystem caches...");
// First sync to ensure all pending writes are flushed
let sync_output =
Command::new("sync").output().await.wrap_err("Failed to execute sync command")?;
if !sync_output.status.success() {
return Err(eyre!("sync command failed"));
}
// Drop caches - requires sudo/root permissions
// 3 = drop pagecache, dentries, and inodes
let drop_caches_cmd = Command::new("sudo")
.args(["-n", "sh", "-c", "echo 3 > /proc/sys/vm/drop_caches"])
.output()
.await;
match drop_caches_cmd {
Ok(output) if output.status.success() => {
info!("Successfully cleared filesystem caches");
Ok(())
}
Ok(output) => {
let stderr = String::from_utf8_lossy(&output.stderr);
if stderr.contains("sudo: a password is required") {
warn!("Unable to clear filesystem caches: sudo password required");
warn!(
"For optimal benchmarking, configure passwordless sudo for cache clearing:"
);
warn!(" echo '$USER ALL=(ALL) NOPASSWD: /bin/sh -c echo\\\\ [0-9]\\\\ \\\\>\\\\ /proc/sys/vm/drop_caches' | sudo tee /etc/sudoers.d/drop_caches");
Ok(())
} else {
Err(eyre!("Failed to clear filesystem caches: {}", stderr))
}
}
Err(e) => {
warn!("Unable to clear filesystem caches: {}", e);
Ok(())
}
}
}
/// Run a warmup benchmark for cache warming
pub(crate) async fn run_warmup(&self, from_block: u64) -> Result<()> {
let to_block = from_block + self.warmup_blocks;
info!(
"Running warmup benchmark from block {} to {} ({} blocks)",
from_block, to_block, self.warmup_blocks
);
// Build the reth-bench command for warmup (no output flag)
let mut cmd = Command::new("reth-bench");
cmd.args([
"new-payload-fcu",
"--rpc-url",
&self.rpc_url,
"--jwt-secret",
&self.jwt_secret,
"--from",
&from_block.to_string(),
"--to",
&to_block.to_string(),
]);
// Add wait-time argument if provided
if let Some(ref wait_time) = self.wait_time {
cmd.args(["--wait-time", wait_time]);
}
cmd.env("RUST_LOG_STYLE", "never")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
// Set process group for consistent signal handling
#[cfg(unix)]
{
cmd.process_group(0);
}
debug!("Executing warmup reth-bench command: {:?}", cmd);
// Execute the warmup benchmark
let mut child = cmd.spawn().wrap_err("Failed to start warmup reth-bench process")?;
// Stream output at debug level
if let Some(stdout) = child.stdout.take() {
tokio::spawn(async move {
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[WARMUP] {}", line);
}
});
}
if let Some(stderr) = child.stderr.take() {
tokio::spawn(async move {
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[WARMUP] {}", line);
}
});
}
let status = child.wait().await.wrap_err("Failed to wait for warmup reth-bench")?;
if !status.success() {
return Err(eyre!("Warmup reth-bench failed with exit code: {:?}", status.code()));
}
info!("Warmup completed successfully");
Ok(())
}
/// Run a benchmark for the specified block range
pub(crate) async fn run_benchmark(
&self,
from_block: u64,
to_block: u64,
output_dir: &Path,
) -> Result<()> {
info!(
"Running benchmark from block {} to {} (output: {:?})",
from_block, to_block, output_dir
);
// Ensure output directory exists
std::fs::create_dir_all(output_dir)
.wrap_err_with(|| format!("Failed to create output directory: {output_dir:?}"))?;
// Create log file path for reth-bench output
let log_file_path = output_dir.join("reth_bench.log");
info!("reth-bench logs will be saved to: {:?}", log_file_path);
// Build the reth-bench command
let mut cmd = Command::new("reth-bench");
cmd.args([
"new-payload-fcu",
"--rpc-url",
&self.rpc_url,
"--jwt-secret",
&self.jwt_secret,
"--from",
&from_block.to_string(),
"--to",
&to_block.to_string(),
"--output",
&output_dir.to_string_lossy(),
]);
// Add wait-time argument if provided
if let Some(ref wait_time) = self.wait_time {
cmd.args(["--wait-time", wait_time]);
}
cmd.env("RUST_LOG_STYLE", "never")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
// Set process group for consistent signal handling
#[cfg(unix)]
{
cmd.process_group(0);
}
// Debug log the command
debug!("Executing reth-bench command: {:?}", cmd);
// Execute the benchmark
let mut child = cmd.spawn().wrap_err("Failed to start reth-bench process")?;
// Capture stdout and stderr for error reporting
let stdout_lines = Arc::new(Mutex::new(Vec::new()));
let stderr_lines = Arc::new(Mutex::new(Vec::new()));
// Stream stdout with prefix at debug level, capture for error reporting, and write to log
// file
if let Some(stdout) = child.stdout.take() {
let stdout_lines_clone = stdout_lines.clone();
let log_file = AsyncFile::create(&log_file_path)
.await
.wrap_err(format!("Failed to create log file: {:?}", log_file_path))?;
tokio::spawn(async move {
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
let mut log_file = log_file;
while let Ok(Some(line)) = lines.next_line().await {
debug!("[RETH-BENCH] {}", line);
if let Ok(mut captured) = stdout_lines_clone.lock() {
captured.push(line.clone());
}
// Write to log file (reth-bench output already has timestamps if needed)
let log_line = format!("{}\n", line);
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
debug!("Failed to write to log file: {}", e);
}
}
});
}
// Stream stderr with prefix at debug level, capture for error reporting, and write to log
// file
if let Some(stderr) = child.stderr.take() {
let stderr_lines_clone = stderr_lines.clone();
let log_file = AsyncFile::options()
.create(true)
.append(true)
.open(&log_file_path)
.await
.wrap_err(format!("Failed to open log file for stderr: {:?}", log_file_path))?;
tokio::spawn(async move {
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
let mut log_file = log_file;
while let Ok(Some(line)) = lines.next_line().await {
debug!("[RETH-BENCH] {}", line);
if let Ok(mut captured) = stderr_lines_clone.lock() {
captured.push(line.clone());
}
// Write to log file (reth-bench output already has timestamps if needed)
let log_line = format!("{}\n", line);
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
debug!("Failed to write to log file: {}", e);
}
}
});
}
let status = child.wait().await.wrap_err("Failed to wait for reth-bench")?;
if !status.success() {
// Print all captured output when command fails
error!("reth-bench failed with exit code: {:?}", status.code());
if let Ok(stdout) = stdout_lines.lock() &&
!stdout.is_empty()
{
error!("reth-bench stdout:");
for line in stdout.iter() {
error!(" {}", line);
}
}
if let Ok(stderr) = stderr_lines.lock() &&
!stderr.is_empty()
{
error!("reth-bench stderr:");
for line in stderr.iter() {
error!(" {}", line);
}
}
return Err(eyre!("reth-bench failed with exit code: {:?}", status.code()));
}
info!("Benchmark completed");
Ok(())
}
}

View File

@@ -1,945 +0,0 @@
//! CLI argument parsing and main command orchestration.
use alloy_provider::{Provider, ProviderBuilder};
use clap::Parser;
use eyre::{eyre, Result, WrapErr};
use reth_chainspec::Chain;
use reth_cli_runner::CliContext;
use reth_node_core::args::{DatadirArgs, LogArgs, TraceArgs};
use reth_tracing::FileWorkerGuard;
use std::{net::TcpListener, path::PathBuf, str::FromStr};
use tokio::process::Command;
use tracing::{debug, info, warn};
use crate::{
benchmark::BenchmarkRunner, comparison::ComparisonGenerator, compilation::CompilationManager,
git::GitManager, node::NodeManager,
};
/// Target for disabling the --debug.startup-sync-state-idle flag
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum DisableStartupSyncStateIdle {
/// Disable for baseline and warmup runs
Baseline,
/// Disable for feature runs only
Feature,
/// Disable for all runs
All,
}
impl FromStr for DisableStartupSyncStateIdle {
type Err = String;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"baseline" => Ok(Self::Baseline),
"feature" => Ok(Self::Feature),
"all" => Ok(Self::All),
_ => Err(format!("Invalid value '{}'. Expected 'baseline', 'feature', or 'all'", s)),
}
}
}
impl std::fmt::Display for DisableStartupSyncStateIdle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Baseline => write!(f, "baseline"),
Self::Feature => write!(f, "feature"),
Self::All => write!(f, "all"),
}
}
}
/// Automated reth benchmark comparison between git references
#[derive(Debug, Parser)]
#[command(
name = "reth-bench-compare",
about = "Compare reth performance between two git references (branches or tags)",
version
)]
pub(crate) struct Args {
/// Git reference (branch or tag) to use as baseline for comparison
#[arg(long, value_name = "REF")]
pub baseline_ref: String,
/// Git reference (branch or tag) to compare against the baseline
#[arg(long, value_name = "REF")]
pub feature_ref: String,
#[command(flatten)]
pub datadir: DatadirArgs,
/// Number of blocks to benchmark
#[arg(long, value_name = "N", default_value = "100")]
pub blocks: u64,
/// RPC endpoint for fetching block data
#[arg(long, value_name = "URL")]
pub rpc_url: Option<String>,
/// JWT secret file path
///
/// If not provided, defaults to `<datadir>/<chain>/jwt.hex`.
/// If the file doesn't exist, it will be created automatically.
#[arg(long, value_name = "PATH")]
pub jwt_secret: Option<PathBuf>,
/// Output directory for benchmark results
#[arg(long, value_name = "PATH", default_value = "./reth-bench-compare")]
pub output_dir: String,
/// Skip git branch validation (useful for testing)
#[arg(long)]
pub skip_git_validation: bool,
/// Port for reth metrics endpoint
#[arg(long, value_name = "PORT", default_value = "5005")]
pub metrics_port: u16,
/// The chain this node is running.
///
/// Possible values are either a built-in chain name or numeric chain ID.
#[arg(long, value_name = "CHAIN", default_value = "mainnet", required = false)]
pub chain: Chain,
/// Run reth binary with sudo (for elevated privileges)
#[arg(long)]
pub sudo: bool,
/// Generate comparison charts using Python script
#[arg(long)]
pub draw: bool,
/// Enable CPU profiling with samply during benchmark runs
#[arg(long)]
pub profile: bool,
/// Wait time between engine API calls (passed to reth-bench)
#[arg(long, value_name = "DURATION")]
pub wait_time: Option<String>,
/// Number of blocks to run for cache warmup after clearing caches.
/// If not specified, defaults to the same as --blocks
#[arg(long, value_name = "N")]
pub warmup_blocks: Option<u64>,
/// Disable filesystem cache clearing before warmup phase.
/// By default, filesystem caches are cleared before warmup to ensure consistent benchmarks.
#[arg(long)]
pub no_clear_cache: bool,
#[command(flatten)]
pub logs: LogArgs,
#[command(flatten)]
pub traces: TraceArgs,
/// Maximum queue size for OTLP Batch Span Processor (traces).
/// Higher values prevent trace drops when benchmarking many blocks.
#[arg(
long,
value_name = "OTLP_BUFFER_SIZE",
default_value = "32768",
help_heading = "Tracing"
)]
pub otlp_max_queue_size: usize,
/// Additional arguments to pass to baseline reth node command
///
/// Example: `--baseline-args "--debug.tip 0xabc..."`
#[arg(long, value_name = "ARGS")]
pub baseline_args: Option<String>,
/// Additional arguments to pass to feature reth node command
///
/// Example: `--feature-args "--debug.tip 0xdef..."`
#[arg(long, value_name = "ARGS")]
pub feature_args: Option<String>,
/// Additional arguments to pass to reth node command (applied to both baseline and feature)
///
/// All arguments after `--` will be passed directly to the reth node command.
/// Example: `reth-bench-compare --baseline-ref main --feature-ref pr/123 -- --debug.tip
/// 0xabc...`
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub reth_args: Vec<String>,
/// Comma-separated list of features to enable during reth compilation
///
/// Example: `jemalloc,asm-keccak`
#[arg(long, value_name = "FEATURES", default_value = "jemalloc,asm-keccak")]
pub features: String,
/// Disable automatic --debug.startup-sync-state-idle flag for specific runs.
/// Can be "baseline", "feature", or "all".
/// By default, the flag is passed to warmup, baseline, and feature runs.
/// When "baseline" is specified, the flag is NOT passed to warmup OR baseline.
/// When "feature" is specified, the flag is NOT passed to feature.
/// When "all" is specified, the flag is NOT passed to any run.
#[arg(long, value_name = "TARGET")]
pub disable_startup_sync_state_idle: Option<DisableStartupSyncStateIdle>,
}
impl Args {
/// Initializes tracing with the configured options.
pub(crate) fn init_tracing(&self) -> Result<Option<FileWorkerGuard>> {
let guard = self.logs.init_tracing()?;
Ok(guard)
}
/// Build additional arguments for a specific ref type, conditionally including
/// --debug.startup-sync-state-idle based on the configuration
pub(crate) fn build_additional_args(
&self,
ref_type: &str,
base_args_str: Option<&String>,
) -> Vec<String> {
// Parse the base arguments string if provided
let mut args = base_args_str.map(|s| parse_args_string(s)).unwrap_or_default();
// Determine if we should add the --debug.startup-sync-state-idle flag
let should_add_flag = match self.disable_startup_sync_state_idle {
None => true, // By default, add the flag
Some(DisableStartupSyncStateIdle::All) => false,
Some(DisableStartupSyncStateIdle::Baseline) => {
ref_type != "baseline" && ref_type != "warmup"
}
Some(DisableStartupSyncStateIdle::Feature) => ref_type != "feature",
};
if should_add_flag {
args.push("--debug.startup-sync-state-idle".to_string());
debug!("Adding --debug.startup-sync-state-idle flag for ref_type: {}", ref_type);
} else {
debug!("Skipping --debug.startup-sync-state-idle flag for ref_type: {}", ref_type);
}
args
}
/// Get the default RPC URL for a given chain
const fn get_default_rpc_url(chain: &Chain) -> &'static str {
match chain.id() {
8453 => "https://base-mainnet.rpc.ithaca.xyz", // base
84532 => "https://base-sepolia.rpc.ithaca.xyz", // base-sepolia
27082 => "https://rpc.hoodi.ethpandaops.io", // hoodi
_ => "https://reth-ethereum.ithaca.xyz/rpc", // mainnet and fallback
}
}
/// Get the RPC URL, using chain-specific default if not provided
pub(crate) fn get_rpc_url(&self) -> String {
self.rpc_url.clone().unwrap_or_else(|| Self::get_default_rpc_url(&self.chain).to_string())
}
/// Get the JWT secret path - either provided or derived from datadir
pub(crate) fn jwt_secret_path(&self) -> PathBuf {
match &self.jwt_secret {
Some(path) => {
let jwt_secret_str = path.to_string_lossy();
let expanded = shellexpand::tilde(&jwt_secret_str);
PathBuf::from(expanded.as_ref())
}
None => {
// Use the same logic as reth: <datadir>/<chain>/jwt.hex
let chain_path = self.datadir.clone().resolve_datadir(self.chain);
chain_path.jwt()
}
}
}
/// Get the resolved datadir path using the chain
pub(crate) fn datadir_path(&self) -> PathBuf {
let chain_path = self.datadir.clone().resolve_datadir(self.chain);
chain_path.data_dir().to_path_buf()
}
/// Get the expanded output directory path
pub(crate) fn output_dir_path(&self) -> PathBuf {
let expanded = shellexpand::tilde(&self.output_dir);
PathBuf::from(expanded.as_ref())
}
/// Get the effective warmup blocks value - either specified or defaults to blocks
pub(crate) fn get_warmup_blocks(&self) -> u64 {
self.warmup_blocks.unwrap_or(self.blocks)
}
}
/// Validate that the RPC endpoint chain ID matches the specified chain
async fn validate_rpc_chain_id(rpc_url: &str, expected_chain: &Chain) -> Result<()> {
// Create Alloy provider
let url = rpc_url.parse().map_err(|e| eyre!("Invalid RPC URL '{}': {}", rpc_url, e))?;
let provider = ProviderBuilder::new().connect_http(url);
// Query chain ID using Alloy
let rpc_chain_id = provider
.get_chain_id()
.await
.map_err(|e| eyre!("Failed to get chain ID from RPC endpoint {}: {:?}", rpc_url, e))?;
let expected_chain_id = expected_chain.id();
if rpc_chain_id != expected_chain_id {
return Err(eyre!(
"RPC endpoint chain ID mismatch!\n\
Expected: {} (chain: {})\n\
Found: {} at RPC endpoint: {}\n\n\
Please use an RPC endpoint for the correct network or change the --chain argument.",
expected_chain_id,
expected_chain,
rpc_chain_id,
rpc_url
));
}
info!("Validated RPC endpoint chain ID");
Ok(())
}
/// Main comparison workflow execution
pub(crate) async fn run_comparison(args: Args, _ctx: CliContext) -> Result<()> {
// Create a new process group for this process and all its children
#[cfg(unix)]
{
use nix::unistd::{getpid, setpgid};
if let Err(e) = setpgid(getpid(), getpid()) {
warn!("Failed to create process group: {e}");
}
}
info!(
"Starting benchmark comparison between '{}' and '{}'",
args.baseline_ref, args.feature_ref
);
if args.sudo {
info!("Running in sudo mode - reth commands will use elevated privileges");
}
// Initialize Git manager
let git_manager = GitManager::new()?;
// Fetch all branches, tags, and commits
git_manager.fetch_all()?;
// Initialize compilation manager
let output_dir = args.output_dir_path();
let compilation_manager = CompilationManager::new(
git_manager.repo_root().to_string(),
output_dir.clone(),
git_manager.clone(),
args.features.clone(),
)?;
// Initialize node manager
let mut node_manager = NodeManager::new(&args);
let benchmark_runner = BenchmarkRunner::new(&args);
let mut comparison_generator = ComparisonGenerator::new(&args);
// Set the comparison directory in node manager to align with results directory
node_manager.set_comparison_dir(comparison_generator.get_output_dir());
// Store original git state for restoration
let original_ref = git_manager.get_current_ref()?;
info!("Current git reference: {}", original_ref);
// Validate git state
if !args.skip_git_validation {
git_manager.validate_clean_state()?;
git_manager.validate_refs(&[&args.baseline_ref, &args.feature_ref])?;
}
// Validate RPC endpoint chain ID matches the specified chain
let rpc_url = args.get_rpc_url();
validate_rpc_chain_id(&rpc_url, &args.chain).await?;
// Setup signal handling for cleanup
let git_manager_cleanup = git_manager.clone();
let original_ref_cleanup = original_ref.clone();
ctrlc::set_handler(move || {
eprintln!("Received interrupt signal, cleaning up...");
// Send SIGTERM to entire process group to ensure all children exit
#[cfg(unix)]
{
use nix::{
sys::signal::{kill, Signal},
unistd::Pid,
};
// Send SIGTERM to our process group (negative PID = process group)
let current_pid = std::process::id() as i32;
let pgid = Pid::from_raw(-current_pid);
if let Err(e) = kill(pgid, Signal::SIGTERM) {
eprintln!("Failed to send SIGTERM to process group: {e}");
}
}
// Give a moment for any ongoing git operations to complete
std::thread::sleep(std::time::Duration::from_millis(200));
if let Err(e) = git_manager_cleanup.switch_ref(&original_ref_cleanup) {
eprintln!("Failed to restore original git reference: {e}");
eprintln!("You may need to manually run: git checkout {original_ref_cleanup}");
}
std::process::exit(1);
})?;
let result = run_benchmark_workflow(
&git_manager,
&compilation_manager,
&mut node_manager,
&benchmark_runner,
&mut comparison_generator,
&args,
)
.await;
// Always restore original git reference
info!("Restoring original git reference: {}", original_ref);
git_manager.switch_ref(&original_ref)?;
// Handle any errors from the workflow
result?;
Ok(())
}
/// Parse a string of arguments into a vector of strings
fn parse_args_string(args_str: &str) -> Vec<String> {
shlex::split(args_str).unwrap_or_else(|| {
// Fallback to simple whitespace splitting if shlex fails
args_str.split_whitespace().map(|s| s.to_string()).collect()
})
}
/// Run compilation phase for both baseline and feature binaries
async fn run_compilation_phase(
git_manager: &GitManager,
compilation_manager: &CompilationManager,
args: &Args,
is_optimism: bool,
) -> Result<(String, String)> {
info!("=== Running compilation phase ===");
// Ensure required tools are available (only need to check once)
compilation_manager.ensure_reth_bench_available()?;
if args.profile {
compilation_manager.ensure_samply_available()?;
}
let refs = [&args.baseline_ref, &args.feature_ref];
let ref_types = ["baseline", "feature"];
// First, resolve all refs to commits using a HashMap to avoid race conditions where a ref is
// pushed to mid-run.
let mut ref_commits = std::collections::HashMap::new();
for &git_ref in &refs {
if !ref_commits.contains_key(git_ref) {
git_manager.switch_ref(git_ref)?;
let commit = git_manager.get_current_commit()?;
ref_commits.insert(git_ref.clone(), commit);
info!("Reference {} resolves to commit: {}", git_ref, &ref_commits[git_ref][..8]);
}
}
// Now compile each ref using the resolved commits
for (i, &git_ref) in refs.iter().enumerate() {
let ref_type = ref_types[i];
let commit = &ref_commits[git_ref];
info!(
"Compiling {} binary for reference: {} (commit: {})",
ref_type,
git_ref,
&commit[..8]
);
// Switch to target reference
git_manager.switch_ref(git_ref)?;
// Compile reth (with caching)
compilation_manager.compile_reth(commit, is_optimism)?;
info!("Completed compilation for {} reference", ref_type);
}
let baseline_commit = ref_commits[&args.baseline_ref].clone();
let feature_commit = ref_commits[&args.feature_ref].clone();
info!("Compilation phase completed");
Ok((baseline_commit, feature_commit))
}
/// Run warmup phase to warm up caches before benchmarking
async fn run_warmup_phase(
git_manager: &GitManager,
compilation_manager: &CompilationManager,
node_manager: &mut NodeManager,
benchmark_runner: &BenchmarkRunner,
args: &Args,
is_optimism: bool,
baseline_commit: &str,
) -> Result<()> {
info!("=== Running warmup phase ===");
// Use baseline for warmup
let warmup_ref = &args.baseline_ref;
// Switch to baseline reference
git_manager.switch_ref(warmup_ref)?;
// Get the cached binary path for baseline (should already be compiled)
let binary_path =
compilation_manager.get_cached_binary_path_for_commit(baseline_commit, is_optimism);
// Verify the cached binary exists
if !binary_path.exists() {
return Err(eyre!(
"Cached baseline binary not found at {:?}. Compilation phase should have created it.",
binary_path
));
}
info!("Using cached baseline binary for warmup (commit: {})", &baseline_commit[..8]);
// Build additional args with conditional --debug.startup-sync-state-idle flag
let additional_args = args.build_additional_args("warmup", args.baseline_args.as_ref());
// Start reth node for warmup (command is not stored for warmup phase)
let (mut node_process, _warmup_command) =
node_manager.start_node(&binary_path, warmup_ref, "warmup", &additional_args).await?;
// Wait for node to be ready and get its current tip
let current_tip = node_manager.wait_for_node_ready_and_get_tip().await?;
info!("Warmup node is ready at tip: {}", current_tip);
// Store the tip we'll unwind back to
let original_tip = current_tip;
// Clear filesystem caches before warmup run only (unless disabled)
if args.no_clear_cache {
info!("Skipping filesystem cache clearing (--no-clear-cache flag set)");
} else {
BenchmarkRunner::clear_fs_caches().await?;
}
// Run warmup to warm up caches
benchmark_runner.run_warmup(current_tip).await?;
// Stop node before unwinding (node must be stopped to release database lock)
node_manager.stop_node(&mut node_process).await?;
// Unwind back to starting block after warmup
node_manager.unwind_to_block(original_tip).await?;
info!("Warmup phase completed");
Ok(())
}
/// Execute the complete benchmark workflow for both branches
async fn run_benchmark_workflow(
git_manager: &GitManager,
compilation_manager: &CompilationManager,
node_manager: &mut NodeManager,
benchmark_runner: &BenchmarkRunner,
comparison_generator: &mut ComparisonGenerator,
args: &Args,
) -> Result<()> {
// Detect if this is an Optimism chain once at the beginning
let rpc_url = args.get_rpc_url();
let is_optimism = compilation_manager.detect_optimism_chain(&rpc_url).await?;
// Run compilation phase for both binaries
let (baseline_commit, feature_commit) =
run_compilation_phase(git_manager, compilation_manager, args, is_optimism).await?;
// Run warmup phase before benchmarking (skip if warmup_blocks is 0)
if args.get_warmup_blocks() > 0 {
run_warmup_phase(
git_manager,
compilation_manager,
node_manager,
benchmark_runner,
args,
is_optimism,
&baseline_commit,
)
.await?;
} else {
info!("Skipping warmup phase (warmup_blocks is 0)");
}
let refs = [&args.baseline_ref, &args.feature_ref];
let ref_types = ["baseline", "feature"];
let commits = [&baseline_commit, &feature_commit];
for (i, &git_ref) in refs.iter().enumerate() {
let ref_type = ref_types[i];
let commit = commits[i];
info!("=== Processing {} reference: {} ===", ref_type, git_ref);
// Switch to target reference
git_manager.switch_ref(git_ref)?;
// Get the cached binary path for this git reference (should already be compiled)
let binary_path =
compilation_manager.get_cached_binary_path_for_commit(commit, is_optimism);
// Verify the cached binary exists
if !binary_path.exists() {
return Err(eyre!(
"Cached {} binary not found at {:?}. Compilation phase should have created it.",
ref_type,
binary_path
));
}
info!("Using cached {} binary (commit: {})", ref_type, &commit[..8]);
// Get reference-specific base arguments string
let base_args_str = match ref_type {
"baseline" => args.baseline_args.as_ref(),
"feature" => args.feature_args.as_ref(),
_ => None,
};
// Build additional args with conditional --debug.startup-sync-state-idle flag
let additional_args = args.build_additional_args(ref_type, base_args_str);
// Start reth node and capture the command for reporting
let (mut node_process, reth_command) =
node_manager.start_node(&binary_path, git_ref, ref_type, &additional_args).await?;
// Wait for node to be ready and get its current tip (wherever it is)
let current_tip = node_manager.wait_for_node_ready_and_get_tip().await?;
info!("Node is ready at tip: {}", current_tip);
// Store the tip we'll unwind back to
let original_tip = current_tip;
// Calculate benchmark range
// Note: reth-bench has an off-by-one error where it consumes the first block
// of the range, so we add 1 to compensate and get exactly args.blocks blocks
let from_block = original_tip;
let to_block = original_tip + args.blocks;
// Run benchmark
let output_dir = comparison_generator.get_ref_output_dir(ref_type);
// Capture start timestamp for the benchmark run
let benchmark_start = chrono::Utc::now();
// Run benchmark (comparison logic is handled separately by ComparisonGenerator)
benchmark_runner.run_benchmark(from_block, to_block, &output_dir).await?;
// Capture end timestamp for the benchmark run
let benchmark_end = chrono::Utc::now();
// Stop node
node_manager.stop_node(&mut node_process).await?;
// Unwind back to original tip
node_manager.unwind_to_block(original_tip).await?;
// Store results for comparison
comparison_generator.add_ref_results(ref_type, &output_dir)?;
// Set the benchmark run timestamps and reth command
comparison_generator.set_ref_timestamps(ref_type, benchmark_start, benchmark_end)?;
comparison_generator.set_ref_command(ref_type, reth_command)?;
info!("Completed {} reference benchmark", ref_type);
}
// Generate comparison report
comparison_generator.generate_comparison_report().await?;
// Generate charts if requested
if args.draw {
generate_comparison_charts(comparison_generator).await?;
}
// Start samply servers if profiling was enabled
if args.profile {
start_samply_servers(args).await?;
}
Ok(())
}
/// Generate comparison charts using the Python script
async fn generate_comparison_charts(comparison_generator: &ComparisonGenerator) -> Result<()> {
info!("Generating comparison charts with Python script...");
let baseline_output_dir = comparison_generator.get_ref_output_dir("baseline");
let feature_output_dir = comparison_generator.get_ref_output_dir("feature");
let baseline_csv = baseline_output_dir.join("combined_latency.csv");
let feature_csv = feature_output_dir.join("combined_latency.csv");
// Check if CSV files exist
if !baseline_csv.exists() {
return Err(eyre!("Baseline CSV not found: {:?}", baseline_csv));
}
if !feature_csv.exists() {
return Err(eyre!("Feature CSV not found: {:?}", feature_csv));
}
let output_dir = comparison_generator.get_output_dir();
let chart_output = output_dir.join("latency_comparison.png");
let script_path = "bin/reth-bench/scripts/compare_newpayload_latency.py";
info!("Running Python comparison script with uv...");
let mut cmd = Command::new("uv");
cmd.args([
"run",
script_path,
&baseline_csv.to_string_lossy(),
&feature_csv.to_string_lossy(),
"-o",
&chart_output.to_string_lossy(),
]);
// Set process group for consistent signal handling
#[cfg(unix)]
{
cmd.process_group(0);
}
let output = cmd.output().await.map_err(|e| {
eyre!("Failed to execute Python script with uv: {}. Make sure uv is installed.", e)
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
return Err(eyre!(
"Python script failed with exit code {:?}:\nstdout: {}\nstderr: {}",
output.status.code(),
stdout,
stderr
));
}
let stdout = String::from_utf8_lossy(&output.stdout);
if !stdout.trim().is_empty() {
info!("Python script output:\n{}", stdout);
}
info!("Comparison chart generated: {:?}", chart_output);
Ok(())
}
/// Start samply servers for viewing profiles
async fn start_samply_servers(args: &Args) -> Result<()> {
info!("Starting samply servers for profile viewing...");
let output_dir = args.output_dir_path();
let profiles_dir = output_dir.join("profiles");
// Build profile paths
let baseline_profile = profiles_dir.join("baseline.json.gz");
let feature_profile = profiles_dir.join("feature.json.gz");
// Check if profiles exist
if !baseline_profile.exists() {
warn!("Baseline profile not found: {:?}", baseline_profile);
return Ok(());
}
if !feature_profile.exists() {
warn!("Feature profile not found: {:?}", feature_profile);
return Ok(());
}
// Find two consecutive available ports starting from 3000
let (baseline_port, feature_port) = find_consecutive_ports(3000)?;
info!("Found available ports: {} and {}", baseline_port, feature_port);
// Get samply path
let samply_path = get_samply_path().await?;
// Start baseline server
info!("Starting samply server for baseline '{}' on port {}", args.baseline_ref, baseline_port);
let mut baseline_cmd = Command::new(&samply_path);
baseline_cmd
.args(["load", "--port", &baseline_port.to_string(), &baseline_profile.to_string_lossy()])
.kill_on_drop(true);
// Set process group for consistent signal handling
#[cfg(unix)]
{
baseline_cmd.process_group(0);
}
// Conditionally pipe output based on log level
if tracing::enabled!(tracing::Level::DEBUG) {
baseline_cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::piped());
} else {
baseline_cmd.stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null());
}
// Debug log the command
debug!("Executing samply load command: {:?}", baseline_cmd);
let mut baseline_child =
baseline_cmd.spawn().wrap_err("Failed to start samply server for baseline")?;
// Stream baseline samply output if debug logging is enabled
if tracing::enabled!(tracing::Level::DEBUG) {
if let Some(stdout) = baseline_child.stdout.take() {
tokio::spawn(async move {
use tokio::io::{AsyncBufReadExt, BufReader};
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[SAMPLY-BASELINE] {}", line);
}
});
}
if let Some(stderr) = baseline_child.stderr.take() {
tokio::spawn(async move {
use tokio::io::{AsyncBufReadExt, BufReader};
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[SAMPLY-BASELINE] {}", line);
}
});
}
}
// Start feature server
info!("Starting samply server for feature '{}' on port {}", args.feature_ref, feature_port);
let mut feature_cmd = Command::new(&samply_path);
feature_cmd
.args(["load", "--port", &feature_port.to_string(), &feature_profile.to_string_lossy()])
.kill_on_drop(true);
// Set process group for consistent signal handling
#[cfg(unix)]
{
feature_cmd.process_group(0);
}
// Conditionally pipe output based on log level
if tracing::enabled!(tracing::Level::DEBUG) {
feature_cmd.stdout(std::process::Stdio::piped()).stderr(std::process::Stdio::piped());
} else {
feature_cmd.stdout(std::process::Stdio::null()).stderr(std::process::Stdio::null());
}
// Debug log the command
debug!("Executing samply load command: {:?}", feature_cmd);
let mut feature_child =
feature_cmd.spawn().wrap_err("Failed to start samply server for feature")?;
// Stream feature samply output if debug logging is enabled
if tracing::enabled!(tracing::Level::DEBUG) {
if let Some(stdout) = feature_child.stdout.take() {
tokio::spawn(async move {
use tokio::io::{AsyncBufReadExt, BufReader};
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[SAMPLY-FEATURE] {}", line);
}
});
}
if let Some(stderr) = feature_child.stderr.take() {
tokio::spawn(async move {
use tokio::io::{AsyncBufReadExt, BufReader};
let reader = BufReader::new(stderr);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[SAMPLY-FEATURE] {}", line);
}
});
}
}
// Give servers time to start
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
// Print access information
println!("\n=== SAMPLY PROFILE SERVERS STARTED ===");
println!("Baseline '{}': http://127.0.0.1:{}", args.baseline_ref, baseline_port);
println!("Feature '{}': http://127.0.0.1:{}", args.feature_ref, feature_port);
println!("\nOpen the URLs in your browser to view the profiles.");
println!("Press Ctrl+C to stop the servers and exit.");
println!("=========================================\n");
// Wait for Ctrl+C or process termination
let ctrl_c = tokio::signal::ctrl_c();
let baseline_wait = baseline_child.wait();
let feature_wait = feature_child.wait();
tokio::select! {
_ = ctrl_c => {
info!("Received Ctrl+C, shutting down samply servers...");
}
result = baseline_wait => {
match result {
Ok(status) => info!("Baseline samply server exited with status: {}", status),
Err(e) => warn!("Baseline samply server error: {}", e),
}
}
result = feature_wait => {
match result {
Ok(status) => info!("Feature samply server exited with status: {}", status),
Err(e) => warn!("Feature samply server error: {}", e),
}
}
}
// Ensure both processes are terminated
let _ = baseline_child.kill().await;
let _ = feature_child.kill().await;
info!("Samply servers stopped.");
Ok(())
}
/// Find two consecutive available ports starting from the given port
fn find_consecutive_ports(start_port: u16) -> Result<(u16, u16)> {
for port in start_port..=65533 {
// Check if both port and port+1 are available
if is_port_available(port) && is_port_available(port + 1) {
return Ok((port, port + 1));
}
}
Err(eyre!("Could not find two consecutive available ports starting from {}", start_port))
}
/// Check if a port is available by attempting to bind to it
fn is_port_available(port: u16) -> bool {
TcpListener::bind(("127.0.0.1", port)).is_ok()
}
/// Get the absolute path to samply using 'which' command
async fn get_samply_path() -> Result<String> {
let output = Command::new("which")
.arg("samply")
.output()
.await
.wrap_err("Failed to execute 'which samply' command")?;
if !output.status.success() {
return Err(eyre!("samply not found in PATH"));
}
let samply_path = String::from_utf8(output.stdout)
.wrap_err("samply path is not valid UTF-8")?
.trim()
.to_string();
if samply_path.is_empty() {
return Err(eyre!("which samply returned empty path"));
}
Ok(samply_path)
}

View File

@@ -1,710 +0,0 @@
//! Results comparison and report generation.
use crate::cli::Args;
use chrono::{DateTime, Utc};
use csv::Reader;
use eyre::{eyre, Result, WrapErr};
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
collections::HashMap,
fs,
path::{Path, PathBuf},
};
use tracing::{info, warn};
/// Manages comparison between baseline and feature reference results
pub(crate) struct ComparisonGenerator {
output_dir: PathBuf,
timestamp: String,
baseline_ref_name: String,
feature_ref_name: String,
baseline_results: Option<BenchmarkResults>,
feature_results: Option<BenchmarkResults>,
baseline_command: Option<String>,
feature_command: Option<String>,
}
/// Represents the results from a single benchmark run
#[derive(Debug, Clone)]
pub(crate) struct BenchmarkResults {
pub ref_name: String,
pub combined_latency_data: Vec<CombinedLatencyRow>,
pub summary: BenchmarkSummary,
pub start_timestamp: Option<DateTime<Utc>>,
pub end_timestamp: Option<DateTime<Utc>>,
}
/// Combined latency CSV row structure
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct CombinedLatencyRow {
pub block_number: u64,
pub transaction_count: u64,
pub gas_used: u64,
pub new_payload_latency: u128,
}
/// Total gas CSV row structure
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct TotalGasRow {
pub block_number: u64,
pub transaction_count: u64,
pub gas_used: u64,
pub time: u128,
}
/// Summary statistics for a benchmark run.
///
/// Latencies are derived from per-block `engine_newPayload` timings (converted from µs to ms):
/// - `mean_new_payload_latency_ms`: arithmetic mean latency across blocks.
/// - `median_new_payload_latency_ms`: p50 latency across blocks.
/// - `p90_new_payload_latency_ms` / `p99_new_payload_latency_ms`: tail latencies across blocks.
#[derive(Debug, Clone, Serialize)]
pub(crate) struct BenchmarkSummary {
pub total_blocks: u64,
pub total_gas_used: u64,
pub total_duration_ms: u128,
pub mean_new_payload_latency_ms: f64,
pub median_new_payload_latency_ms: f64,
pub p90_new_payload_latency_ms: f64,
pub p99_new_payload_latency_ms: f64,
pub gas_per_second: f64,
pub blocks_per_second: f64,
pub min_block_number: u64,
pub max_block_number: u64,
}
/// Comparison report between two benchmark runs
#[derive(Debug, Serialize)]
pub(crate) struct ComparisonReport {
pub timestamp: String,
pub baseline: RefInfo,
pub feature: RefInfo,
pub comparison_summary: ComparisonSummary,
pub per_block_comparisons: Vec<BlockComparison>,
}
/// Information about a reference in the comparison
#[derive(Debug, Serialize)]
pub(crate) struct RefInfo {
pub ref_name: String,
pub summary: BenchmarkSummary,
pub start_timestamp: Option<DateTime<Utc>>,
pub end_timestamp: Option<DateTime<Utc>>,
pub reth_command: Option<String>,
}
/// Summary of the comparison between references.
///
/// Percent deltas are `(feature - baseline) / baseline * 100`:
/// - `new_payload_latency_p50_change_percent` / p90 / p99: percent changes of the respective
/// per-block percentiles.
/// - `per_block_latency_change_mean_percent` / `per_block_latency_change_median_percent` are the
/// mean and median of per-block percent deltas (feature vs baseline), capturing block-level
/// drift.
/// - `per_block_latency_change_std_dev_percent`: standard deviation of per-block percent changes,
/// measuring consistency of performance changes across blocks.
/// - `new_payload_total_latency_change_percent` is the percent change of the total newPayload time
/// across the run.
///
/// Positive means slower/higher; negative means faster/lower.
#[derive(Debug, Serialize)]
pub(crate) struct ComparisonSummary {
pub per_block_latency_change_mean_percent: f64,
pub per_block_latency_change_median_percent: f64,
pub per_block_latency_change_std_dev_percent: f64,
pub new_payload_total_latency_change_percent: f64,
pub new_payload_latency_p50_change_percent: f64,
pub new_payload_latency_p90_change_percent: f64,
pub new_payload_latency_p99_change_percent: f64,
pub gas_per_second_change_percent: f64,
pub blocks_per_second_change_percent: f64,
}
/// Per-block comparison data
#[derive(Debug, Serialize)]
pub(crate) struct BlockComparison {
pub block_number: u64,
pub transaction_count: u64,
pub gas_used: u64,
pub baseline_new_payload_latency: u128,
pub feature_new_payload_latency: u128,
pub new_payload_latency_change_percent: f64,
}
impl ComparisonGenerator {
/// Create a new comparison generator
pub(crate) fn new(args: &Args) -> Self {
let now: DateTime<Utc> = Utc::now();
let timestamp = now.format("%Y%m%d_%H%M%S").to_string();
Self {
output_dir: args.output_dir_path(),
timestamp,
baseline_ref_name: args.baseline_ref.clone(),
feature_ref_name: args.feature_ref.clone(),
baseline_results: None,
feature_results: None,
baseline_command: None,
feature_command: None,
}
}
/// Get the output directory for a specific reference
pub(crate) fn get_ref_output_dir(&self, ref_type: &str) -> PathBuf {
self.output_dir.join("results").join(&self.timestamp).join(ref_type)
}
/// Get the main output directory for this comparison run
pub(crate) fn get_output_dir(&self) -> PathBuf {
self.output_dir.join("results").join(&self.timestamp)
}
/// Add benchmark results for a reference
pub(crate) fn add_ref_results(&mut self, ref_type: &str, output_path: &Path) -> Result<()> {
let ref_name = match ref_type {
"baseline" => &self.baseline_ref_name,
"feature" => &self.feature_ref_name,
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
};
let results = self.load_benchmark_results(ref_name, output_path)?;
match ref_type {
"baseline" => self.baseline_results = Some(results),
"feature" => self.feature_results = Some(results),
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
}
info!("Loaded benchmark results for {} reference", ref_type);
Ok(())
}
/// Set the benchmark run timestamps for a reference
pub(crate) fn set_ref_timestamps(
&mut self,
ref_type: &str,
start: DateTime<Utc>,
end: DateTime<Utc>,
) -> Result<()> {
match ref_type {
"baseline" => {
if let Some(ref mut results) = self.baseline_results {
results.start_timestamp = Some(start);
results.end_timestamp = Some(end);
} else {
return Err(eyre!("Baseline results not loaded yet"));
}
}
"feature" => {
if let Some(ref mut results) = self.feature_results {
results.start_timestamp = Some(start);
results.end_timestamp = Some(end);
} else {
return Err(eyre!("Feature results not loaded yet"));
}
}
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
}
Ok(())
}
/// Set the reth command for a reference
pub(crate) fn set_ref_command(&mut self, ref_type: &str, command: String) -> Result<()> {
match ref_type {
"baseline" => {
self.baseline_command = Some(command);
}
"feature" => {
self.feature_command = Some(command);
}
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
}
Ok(())
}
/// Generate the final comparison report
pub(crate) async fn generate_comparison_report(&self) -> Result<()> {
info!("Generating comparison report...");
let baseline =
self.baseline_results.as_ref().ok_or_else(|| eyre!("Baseline results not loaded"))?;
let feature =
self.feature_results.as_ref().ok_or_else(|| eyre!("Feature results not loaded"))?;
let per_block_comparisons = self.calculate_per_block_comparisons(baseline, feature)?;
let comparison_summary = self.calculate_comparison_summary(
&baseline.summary,
&feature.summary,
&per_block_comparisons,
)?;
let report = ComparisonReport {
timestamp: self.timestamp.clone(),
baseline: RefInfo {
ref_name: baseline.ref_name.clone(),
summary: baseline.summary.clone(),
start_timestamp: baseline.start_timestamp,
end_timestamp: baseline.end_timestamp,
reth_command: self.baseline_command.clone(),
},
feature: RefInfo {
ref_name: feature.ref_name.clone(),
summary: feature.summary.clone(),
start_timestamp: feature.start_timestamp,
end_timestamp: feature.end_timestamp,
reth_command: self.feature_command.clone(),
},
comparison_summary,
per_block_comparisons,
};
// Write reports
self.write_comparison_reports(&report).await?;
// Print summary to console
self.print_comparison_summary(&report);
Ok(())
}
/// Load benchmark results from CSV files
fn load_benchmark_results(
&self,
ref_name: &str,
output_path: &Path,
) -> Result<BenchmarkResults> {
let combined_latency_path = output_path.join("combined_latency.csv");
let total_gas_path = output_path.join("total_gas.csv");
let combined_latency_data = self.load_combined_latency_csv(&combined_latency_path)?;
let total_gas_data = self.load_total_gas_csv(&total_gas_path)?;
let summary = self.calculate_summary(&combined_latency_data, &total_gas_data)?;
Ok(BenchmarkResults {
ref_name: ref_name.to_string(),
combined_latency_data,
summary,
start_timestamp: None,
end_timestamp: None,
})
}
/// Load combined latency CSV data
fn load_combined_latency_csv(&self, path: &Path) -> Result<Vec<CombinedLatencyRow>> {
let mut reader = Reader::from_path(path)
.wrap_err_with(|| format!("Failed to open combined latency CSV: {path:?}"))?;
let mut rows = Vec::new();
for result in reader.deserialize() {
let row: CombinedLatencyRow = result
.wrap_err_with(|| format!("Failed to parse combined latency row in {path:?}"))?;
rows.push(row);
}
if rows.is_empty() {
return Err(eyre!("No data found in combined latency CSV: {:?}", path));
}
Ok(rows)
}
/// Load total gas CSV data
fn load_total_gas_csv(&self, path: &Path) -> Result<Vec<TotalGasRow>> {
let mut reader = Reader::from_path(path)
.wrap_err_with(|| format!("Failed to open total gas CSV: {path:?}"))?;
let mut rows = Vec::new();
for result in reader.deserialize() {
let row: TotalGasRow =
result.wrap_err_with(|| format!("Failed to parse total gas row in {path:?}"))?;
rows.push(row);
}
if rows.is_empty() {
return Err(eyre!("No data found in total gas CSV: {:?}", path));
}
Ok(rows)
}
/// Calculate summary statistics for a benchmark run.
///
/// Computes latency statistics from per-block `new_payload_latency` values in `combined_data`
/// (converting from µs to ms), and throughput metrics using the total run duration from
/// `total_gas_data`. Percentiles (p50/p90/p99) use linear interpolation on sorted latencies.
fn calculate_summary(
&self,
combined_data: &[CombinedLatencyRow],
total_gas_data: &[TotalGasRow],
) -> Result<BenchmarkSummary> {
if combined_data.is_empty() || total_gas_data.is_empty() {
return Err(eyre!("Cannot calculate summary for empty data"));
}
let total_blocks = combined_data.len() as u64;
let total_gas_used: u64 = combined_data.iter().map(|r| r.gas_used).sum();
let total_duration_ms = total_gas_data.last().unwrap().time / 1000; // Convert microseconds to milliseconds
let latencies_ms: Vec<f64> =
combined_data.iter().map(|r| r.new_payload_latency as f64 / 1000.0).collect();
let mean_new_payload_latency_ms: f64 =
latencies_ms.iter().sum::<f64>() / total_blocks as f64;
let mut sorted_latencies_ms = latencies_ms;
sorted_latencies_ms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let median_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.5);
let p90_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.9);
let p99_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.99);
let total_duration_seconds = total_duration_ms as f64 / 1000.0;
let gas_per_second = if total_duration_seconds > f64::EPSILON {
total_gas_used as f64 / total_duration_seconds
} else {
0.0
};
let blocks_per_second = if total_duration_seconds > f64::EPSILON {
total_blocks as f64 / total_duration_seconds
} else {
0.0
};
let min_block_number = combined_data.first().unwrap().block_number;
let max_block_number = combined_data.last().unwrap().block_number;
Ok(BenchmarkSummary {
total_blocks,
total_gas_used,
total_duration_ms,
mean_new_payload_latency_ms,
median_new_payload_latency_ms,
p90_new_payload_latency_ms,
p99_new_payload_latency_ms,
gas_per_second,
blocks_per_second,
min_block_number,
max_block_number,
})
}
/// Calculate comparison summary between baseline and feature
fn calculate_comparison_summary(
&self,
baseline: &BenchmarkSummary,
feature: &BenchmarkSummary,
per_block_comparisons: &[BlockComparison],
) -> Result<ComparisonSummary> {
let calc_percent_change = |baseline: f64, feature: f64| -> f64 {
if baseline.abs() > f64::EPSILON {
((feature - baseline) / baseline) * 100.0
} else {
0.0
}
};
// Calculate per-block statistics. "Per-block" means: for each block, compute the percent
// change (feature - baseline) / baseline * 100, then calculate statistics across those
// per-block percent changes. This captures how consistently the feature performs relative
// to baseline across all blocks.
let per_block_percent_changes: Vec<f64> =
per_block_comparisons.iter().map(|c| c.new_payload_latency_change_percent).collect();
let per_block_latency_change_mean_percent = if per_block_percent_changes.is_empty() {
0.0
} else {
per_block_percent_changes.iter().sum::<f64>() / per_block_percent_changes.len() as f64
};
let per_block_latency_change_median_percent = if per_block_percent_changes.is_empty() {
0.0
} else {
let mut sorted = per_block_percent_changes.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
percentile(&sorted, 0.5)
};
let per_block_latency_change_std_dev_percent =
calculate_std_dev(&per_block_percent_changes, per_block_latency_change_mean_percent);
let baseline_total_latency_ms =
baseline.mean_new_payload_latency_ms * baseline.total_blocks as f64;
let feature_total_latency_ms =
feature.mean_new_payload_latency_ms * feature.total_blocks as f64;
let new_payload_total_latency_change_percent =
calc_percent_change(baseline_total_latency_ms, feature_total_latency_ms);
Ok(ComparisonSummary {
per_block_latency_change_mean_percent,
per_block_latency_change_median_percent,
per_block_latency_change_std_dev_percent,
new_payload_total_latency_change_percent,
new_payload_latency_p50_change_percent: calc_percent_change(
baseline.median_new_payload_latency_ms,
feature.median_new_payload_latency_ms,
),
new_payload_latency_p90_change_percent: calc_percent_change(
baseline.p90_new_payload_latency_ms,
feature.p90_new_payload_latency_ms,
),
new_payload_latency_p99_change_percent: calc_percent_change(
baseline.p99_new_payload_latency_ms,
feature.p99_new_payload_latency_ms,
),
gas_per_second_change_percent: calc_percent_change(
baseline.gas_per_second,
feature.gas_per_second,
),
blocks_per_second_change_percent: calc_percent_change(
baseline.blocks_per_second,
feature.blocks_per_second,
),
})
}
/// Calculate per-block comparisons
fn calculate_per_block_comparisons(
&self,
baseline: &BenchmarkResults,
feature: &BenchmarkResults,
) -> Result<Vec<BlockComparison>> {
let mut baseline_map: HashMap<u64, &CombinedLatencyRow> = HashMap::new();
for row in &baseline.combined_latency_data {
baseline_map.insert(row.block_number, row);
}
let mut comparisons = Vec::new();
for feature_row in &feature.combined_latency_data {
if let Some(baseline_row) = baseline_map.get(&feature_row.block_number) {
let calc_percent_change = |baseline: u128, feature: u128| -> f64 {
if baseline > 0 {
((feature as f64 - baseline as f64) / baseline as f64) * 100.0
} else {
0.0
}
};
let comparison = BlockComparison {
block_number: feature_row.block_number,
transaction_count: feature_row.transaction_count,
gas_used: feature_row.gas_used,
baseline_new_payload_latency: baseline_row.new_payload_latency,
feature_new_payload_latency: feature_row.new_payload_latency,
new_payload_latency_change_percent: calc_percent_change(
baseline_row.new_payload_latency,
feature_row.new_payload_latency,
),
};
comparisons.push(comparison);
} else {
warn!("Block {} not found in baseline data", feature_row.block_number);
}
}
Ok(comparisons)
}
/// Write comparison reports to files
async fn write_comparison_reports(&self, report: &ComparisonReport) -> Result<()> {
let report_dir = self.output_dir.join("results").join(&self.timestamp);
fs::create_dir_all(&report_dir)
.wrap_err_with(|| format!("Failed to create report directory: {report_dir:?}"))?;
// Write JSON report
let json_path = report_dir.join("comparison_report.json");
let json_content = serde_json::to_string_pretty(report)
.wrap_err("Failed to serialize comparison report to JSON")?;
fs::write(&json_path, json_content)
.wrap_err_with(|| format!("Failed to write JSON report: {json_path:?}"))?;
// Write CSV report for per-block comparisons
let csv_path = report_dir.join("per_block_comparison.csv");
let mut writer = csv::Writer::from_path(&csv_path)
.wrap_err_with(|| format!("Failed to create CSV writer: {csv_path:?}"))?;
for comparison in &report.per_block_comparisons {
writer.serialize(comparison).wrap_err("Failed to write comparison row to CSV")?;
}
writer.flush().wrap_err("Failed to flush CSV writer")?;
info!("Comparison reports written to: {:?}", report_dir);
Ok(())
}
/// Print comparison summary to console
fn print_comparison_summary(&self, report: &ComparisonReport) {
// Parse and format timestamp nicely
let formatted_timestamp = if let Ok(dt) = chrono::DateTime::parse_from_str(
&format!("{} +0000", report.timestamp.replace('_', " ")),
"%Y%m%d %H%M%S %z",
) {
dt.format("%Y-%m-%d %H:%M:%S UTC").to_string()
} else {
// Fallback to original if parsing fails
report.timestamp.clone()
};
println!("\n=== BENCHMARK COMPARISON SUMMARY ===");
println!("Timestamp: {formatted_timestamp}");
println!("Baseline: {}", report.baseline.ref_name);
println!("Feature: {}", report.feature.ref_name);
println!();
let summary = &report.comparison_summary;
println!("Performance Changes:");
println!(
" NewPayload Latency per-block mean change: {:+.2}%",
summary.per_block_latency_change_mean_percent
);
println!(
" NewPayload Latency per-block median change: {:+.2}%",
summary.per_block_latency_change_median_percent
);
println!(
" NewPayload Latency per-block std dev: {:.2}%",
summary.per_block_latency_change_std_dev_percent
);
println!(
" Total newPayload time change: {:+.2}%",
summary.new_payload_total_latency_change_percent
);
println!(
" NewPayload Latency p50: {:+.2}%",
summary.new_payload_latency_p50_change_percent
);
println!(
" NewPayload Latency p90: {:+.2}%",
summary.new_payload_latency_p90_change_percent
);
println!(
" NewPayload Latency p99: {:+.2}%",
summary.new_payload_latency_p99_change_percent
);
println!(
" Gas/Second: {:+.2}%",
summary.gas_per_second_change_percent
);
println!(
" Blocks/Second: {:+.2}%",
summary.blocks_per_second_change_percent
);
println!();
println!("Baseline Summary:");
let baseline = &report.baseline.summary;
println!(
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
baseline.total_blocks,
baseline.min_block_number,
baseline.max_block_number,
baseline.total_gas_used,
baseline.total_duration_ms as f64 / 1000.0
);
println!(" NewPayload latency (ms):");
println!(
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
baseline.mean_new_payload_latency_ms,
baseline.median_new_payload_latency_ms,
baseline.p90_new_payload_latency_ms,
baseline.p99_new_payload_latency_ms
);
if let (Some(start), Some(end)) =
(&report.baseline.start_timestamp, &report.baseline.end_timestamp)
{
println!(
" Started: {}, Ended: {}",
start.format("%Y-%m-%d %H:%M:%S UTC"),
end.format("%Y-%m-%d %H:%M:%S UTC")
);
}
if let Some(ref cmd) = report.baseline.reth_command {
println!(" Command: {}", cmd);
}
println!();
println!("Feature Summary:");
let feature = &report.feature.summary;
println!(
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
feature.total_blocks,
feature.min_block_number,
feature.max_block_number,
feature.total_gas_used,
feature.total_duration_ms as f64 / 1000.0
);
println!(" NewPayload latency (ms):");
println!(
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
feature.mean_new_payload_latency_ms,
feature.median_new_payload_latency_ms,
feature.p90_new_payload_latency_ms,
feature.p99_new_payload_latency_ms
);
if let (Some(start), Some(end)) =
(&report.feature.start_timestamp, &report.feature.end_timestamp)
{
println!(
" Started: {}, Ended: {}",
start.format("%Y-%m-%d %H:%M:%S UTC"),
end.format("%Y-%m-%d %H:%M:%S UTC")
);
}
if let Some(ref cmd) = report.feature.reth_command {
println!(" Command: {}", cmd);
}
println!();
}
}
/// Calculate standard deviation from a set of values and their mean.
///
/// Computes the population standard deviation using the formula:
/// `sqrt(sum((x - mean)²) / n)`
///
/// Returns 0.0 for empty input.
fn calculate_std_dev(values: &[f64], mean: f64) -> f64 {
if values.is_empty() {
return 0.0;
}
let variance = values
.iter()
.map(|x| {
let diff = x - mean;
diff * diff
})
.sum::<f64>() /
values.len() as f64;
variance.sqrt()
}
/// Calculate percentile using linear interpolation on a sorted slice.
///
/// Computes `rank = percentile × (n - 1)` where n is the array length. If the rank falls
/// between two indices, linearly interpolates between those values. For example, with 100 values,
/// p90 computes rank = 0.9 × 99 = 89.1, then returns `values[89] × 0.9 + values[90] × 0.1`.
///
/// Returns 0.0 for empty input.
fn percentile(sorted_values: &[f64], percentile: f64) -> f64 {
if sorted_values.is_empty() {
return 0.0;
}
let clamped = percentile.clamp(0.0, 1.0);
let max_index = sorted_values.len() - 1;
let rank = clamped * max_index as f64;
let lower = rank.floor() as usize;
let upper = rank.ceil() as usize;
if lower == upper {
sorted_values[lower]
} else {
let weight = rank - lower as f64;
sorted_values[lower].mul_add(1.0 - weight, sorted_values[upper] * weight)
}
}

View File

@@ -1,354 +0,0 @@
//! Compilation operations for reth and reth-bench.
use crate::git::GitManager;
use alloy_primitives::address;
use alloy_provider::{Provider, ProviderBuilder};
use eyre::{eyre, Result, WrapErr};
use std::{fs, path::PathBuf, process::Command};
use tracing::{debug, error, info, warn};
/// Manages compilation operations for reth components
#[derive(Debug)]
pub(crate) struct CompilationManager {
repo_root: String,
output_dir: PathBuf,
git_manager: GitManager,
features: String,
}
impl CompilationManager {
/// Create a new `CompilationManager`
pub(crate) const fn new(
repo_root: String,
output_dir: PathBuf,
git_manager: GitManager,
features: String,
) -> Result<Self> {
Ok(Self { repo_root, output_dir, git_manager, features })
}
/// Detect if the RPC endpoint is an Optimism chain
pub(crate) async fn detect_optimism_chain(&self, rpc_url: &str) -> Result<bool> {
info!("Detecting chain type from RPC endpoint...");
// Create Alloy provider
let url = rpc_url.parse().map_err(|e| eyre!("Invalid RPC URL '{}': {}", rpc_url, e))?;
let provider = ProviderBuilder::new().connect_http(url);
// Check for Optimism predeploy at address 0x420000000000000000000000000000000000000F
let is_optimism = !provider
.get_code_at(address!("0x420000000000000000000000000000000000000F"))
.await?
.is_empty();
if is_optimism {
info!("Detected Optimism chain");
} else {
info!("Detected Ethereum chain");
}
Ok(is_optimism)
}
/// Get the path to the cached binary using explicit commit hash
pub(crate) fn get_cached_binary_path_for_commit(
&self,
commit: &str,
is_optimism: bool,
) -> PathBuf {
let identifier = &commit[..8]; // Use first 8 chars of commit
let binary_name = if is_optimism {
format!("op-reth_{}", identifier)
} else {
format!("reth_{}", identifier)
};
self.output_dir.join("bin").join(binary_name)
}
/// Compile reth using cargo build and cache the binary
pub(crate) fn compile_reth(&self, commit: &str, is_optimism: bool) -> Result<()> {
// Validate that current git commit matches the expected commit
let current_commit = self.git_manager.get_current_commit()?;
if current_commit != commit {
return Err(eyre!(
"Git commit mismatch! Expected: {}, but currently at: {}",
&commit[..8],
&current_commit[..8]
));
}
let cached_path = self.get_cached_binary_path_for_commit(commit, is_optimism);
// Check if cached binary already exists (since path contains commit hash, it's valid)
if cached_path.exists() {
info!("Using cached binary (commit: {})", &commit[..8]);
return Ok(());
}
info!("No cached binary found, compiling (commit: {})...", &commit[..8]);
let binary_name = if is_optimism { "op-reth" } else { "reth" };
info!(
"Compiling {} with profiling configuration (commit: {})...",
binary_name,
&commit[..8]
);
let mut cmd = Command::new("cargo");
cmd.arg("build").arg("--profile").arg("profiling");
// Add features
cmd.arg("--features").arg(&self.features);
info!("Using features: {}", self.features);
// Add bin-specific arguments for optimism
if is_optimism {
cmd.arg("--bin")
.arg("op-reth")
.arg("--manifest-path")
.arg("crates/optimism/bin/Cargo.toml");
}
cmd.current_dir(&self.repo_root);
// Set RUSTFLAGS for native CPU optimization
cmd.env("RUSTFLAGS", "-C target-cpu=native");
// Debug log the command
debug!("Executing cargo command: {:?}", cmd);
let output = cmd.output().wrap_err("Failed to execute cargo build command")?;
// Print stdout and stderr with prefixes at debug level
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stdout.lines() {
if !line.trim().is_empty() {
debug!("[CARGO] {}", line);
}
}
for line in stderr.lines() {
if !line.trim().is_empty() {
debug!("[CARGO] {}", line);
}
}
if !output.status.success() {
// Print all output when compilation fails
error!("Cargo build failed with exit code: {:?}", output.status.code());
if !stdout.trim().is_empty() {
error!("Cargo stdout:");
for line in stdout.lines() {
error!(" {}", line);
}
}
if !stderr.trim().is_empty() {
error!("Cargo stderr:");
for line in stderr.lines() {
error!(" {}", line);
}
}
return Err(eyre!("Compilation failed with exit code: {:?}", output.status.code()));
}
info!("{} compilation completed", binary_name);
// Copy the compiled binary to cache
let source_path =
PathBuf::from(&self.repo_root).join(format!("target/profiling/{}", binary_name));
if !source_path.exists() {
return Err(eyre!("Compiled binary not found at {:?}", source_path));
}
// Create bin directory if it doesn't exist
let bin_dir = self.output_dir.join("bin");
fs::create_dir_all(&bin_dir).wrap_err("Failed to create bin directory")?;
// Copy binary to cache
fs::copy(&source_path, &cached_path).wrap_err("Failed to copy binary to cache")?;
// Make the cached binary executable
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&cached_path)?.permissions();
perms.set_mode(0o755);
fs::set_permissions(&cached_path, perms)?;
}
info!("Cached compiled binary at: {:?}", cached_path);
Ok(())
}
/// Check if reth-bench is available in PATH
pub(crate) fn is_reth_bench_available(&self) -> bool {
match Command::new("which").arg("reth-bench").output() {
Ok(output) => {
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout);
info!("Found reth-bench: {}", path.trim());
true
} else {
false
}
}
Err(_) => false,
}
}
/// Check if samply is available in PATH
pub(crate) fn is_samply_available(&self) -> bool {
match Command::new("which").arg("samply").output() {
Ok(output) => {
if output.status.success() {
let path = String::from_utf8_lossy(&output.stdout);
info!("Found samply: {}", path.trim());
true
} else {
false
}
}
Err(_) => false,
}
}
/// Install samply using cargo
pub(crate) fn install_samply(&self) -> Result<()> {
info!("Installing samply via cargo...");
let mut cmd = Command::new("cargo");
cmd.args(["install", "--locked", "samply"]);
// Debug log the command
debug!("Executing cargo command: {:?}", cmd);
let output = cmd.output().wrap_err("Failed to execute cargo install samply command")?;
// Print stdout and stderr with prefixes at debug level
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stdout.lines() {
if !line.trim().is_empty() {
debug!("[CARGO-SAMPLY] {}", line);
}
}
for line in stderr.lines() {
if !line.trim().is_empty() {
debug!("[CARGO-SAMPLY] {}", line);
}
}
if !output.status.success() {
// Print all output when installation fails
error!("Cargo install samply failed with exit code: {:?}", output.status.code());
if !stdout.trim().is_empty() {
error!("Cargo stdout:");
for line in stdout.lines() {
error!(" {}", line);
}
}
if !stderr.trim().is_empty() {
error!("Cargo stderr:");
for line in stderr.lines() {
error!(" {}", line);
}
}
return Err(eyre!(
"samply installation failed with exit code: {:?}",
output.status.code()
));
}
info!("Samply installation completed");
Ok(())
}
/// Ensure samply is available, installing if necessary
pub(crate) fn ensure_samply_available(&self) -> Result<()> {
if self.is_samply_available() {
Ok(())
} else {
warn!("samply not found in PATH, installing...");
self.install_samply()
}
}
/// Ensure reth-bench is available, compiling if necessary
pub(crate) fn ensure_reth_bench_available(&self) -> Result<()> {
if self.is_reth_bench_available() {
Ok(())
} else {
warn!("reth-bench not found in PATH, compiling and installing...");
self.compile_reth_bench()
}
}
/// Compile and install reth-bench using `make install-reth-bench`
pub(crate) fn compile_reth_bench(&self) -> Result<()> {
info!("Compiling and installing reth-bench...");
let mut cmd = Command::new("make");
cmd.arg("install-reth-bench").current_dir(&self.repo_root);
// Debug log the command
debug!("Executing make command: {:?}", cmd);
let output = cmd.output().wrap_err("Failed to execute make install-reth-bench command")?;
// Print stdout and stderr with prefixes at debug level
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
for line in stdout.lines() {
if !line.trim().is_empty() {
debug!("[MAKE-BENCH] {}", line);
}
}
for line in stderr.lines() {
if !line.trim().is_empty() {
debug!("[MAKE-BENCH] {}", line);
}
}
if !output.status.success() {
// Print all output when compilation fails
error!("Make install-reth-bench failed with exit code: {:?}", output.status.code());
if !stdout.trim().is_empty() {
error!("Make stdout:");
for line in stdout.lines() {
error!(" {}", line);
}
}
if !stderr.trim().is_empty() {
error!("Make stderr:");
for line in stderr.lines() {
error!(" {}", line);
}
}
return Err(eyre!(
"reth-bench compilation failed with exit code: {:?}",
output.status.code()
));
}
info!("Reth-bench compilation completed");
Ok(())
}
}

View File

@@ -1,328 +0,0 @@
//! Git operations for branch management.
use eyre::{eyre, Result, WrapErr};
use std::process::Command;
use tracing::{info, warn};
/// Manages git operations for branch switching
#[derive(Debug, Clone)]
pub(crate) struct GitManager {
repo_root: String,
}
impl GitManager {
/// Create a new `GitManager`, detecting the repository root
pub(crate) fn new() -> Result<Self> {
let output = Command::new("git")
.args(["rev-parse", "--show-toplevel"])
.output()
.wrap_err("Failed to execute git command - is git installed?")?;
if !output.status.success() {
return Err(eyre!("Not in a git repository or git command failed"));
}
let repo_root = String::from_utf8(output.stdout)
.wrap_err("Git output is not valid UTF-8")?
.trim()
.to_string();
let manager = Self { repo_root };
info!(
"Detected git repository at: {}, current reference: {}",
manager.repo_root(),
manager.get_current_ref()?
);
Ok(manager)
}
/// Get the current git branch name
pub(crate) fn get_current_branch(&self) -> Result<String> {
let output = Command::new("git")
.args(["branch", "--show-current"])
.current_dir(&self.repo_root)
.output()
.wrap_err("Failed to get current branch")?;
if !output.status.success() {
return Err(eyre!("Failed to determine current branch"));
}
let branch = String::from_utf8(output.stdout)
.wrap_err("Branch name is not valid UTF-8")?
.trim()
.to_string();
if branch.is_empty() {
return Err(eyre!("Not on a named branch (detached HEAD?)"));
}
Ok(branch)
}
/// Get the current git reference (branch name, tag, or commit hash)
pub(crate) fn get_current_ref(&self) -> Result<String> {
// First try to get branch name
if let Ok(branch) = self.get_current_branch() {
return Ok(branch);
}
// If not on a branch, check if we're on a tag
let tag_output = Command::new("git")
.args(["describe", "--exact-match", "--tags", "HEAD"])
.current_dir(&self.repo_root)
.output()
.wrap_err("Failed to check for tag")?;
if tag_output.status.success() {
let tag = String::from_utf8(tag_output.stdout)
.wrap_err("Tag name is not valid UTF-8")?
.trim()
.to_string();
return Ok(tag);
}
// If not on a branch or tag, return the commit hash
let commit_output = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&self.repo_root)
.output()
.wrap_err("Failed to get current commit")?;
if !commit_output.status.success() {
return Err(eyre!("Failed to get current commit hash"));
}
let commit_hash = String::from_utf8(commit_output.stdout)
.wrap_err("Commit hash is not valid UTF-8")?
.trim()
.to_string();
Ok(commit_hash)
}
/// Check if the git working directory has uncommitted changes to tracked files
pub(crate) fn validate_clean_state(&self) -> Result<()> {
let output = Command::new("git")
.args(["status", "--porcelain"])
.current_dir(&self.repo_root)
.output()
.wrap_err("Failed to check git status")?;
if !output.status.success() {
return Err(eyre!("Git status command failed"));
}
let status_output =
String::from_utf8(output.stdout).wrap_err("Git status output is not valid UTF-8")?;
// Check for uncommitted changes to tracked files
// Status codes: M = modified, A = added, D = deleted, R = renamed, C = copied, U = updated
// ?? = untracked files (we want to ignore these)
let has_uncommitted_changes = status_output.lines().any(|line| {
if line.len() >= 2 {
let status = &line[0..2];
// Ignore untracked files (??) and ignored files (!!)
!matches!(status, "??" | "!!")
} else {
false
}
});
if has_uncommitted_changes {
warn!("Git working directory has uncommitted changes to tracked files:");
for line in status_output.lines() {
if line.len() >= 2 && !matches!(&line[0..2], "??" | "!!") {
warn!(" {}", line);
}
}
return Err(eyre!(
"Git working directory has uncommitted changes to tracked files. Please commit or stash changes before running benchmark comparison."
));
}
// Check if there are untracked files and log them as info
let untracked_files: Vec<&str> =
status_output.lines().filter(|line| line.starts_with("??")).collect();
if !untracked_files.is_empty() {
info!(
"Git working directory has {} untracked files (this is OK)",
untracked_files.len()
);
}
info!("Git working directory is clean (no uncommitted changes to tracked files)");
Ok(())
}
/// Fetch all refs from remote to ensure we have latest branches and tags
pub(crate) fn fetch_all(&self) -> Result<()> {
let output = Command::new("git")
.args(["fetch", "--all", "--tags", "--quiet", "--force"])
.current_dir(&self.repo_root)
.output()
.wrap_err("Failed to fetch latest refs")?;
if output.status.success() {
info!("Fetched latest refs");
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
// Only warn if there's actual error content, not just fetch progress
if !stderr.trim().is_empty() && !stderr.contains("-> origin/") {
warn!("Git fetch encountered issues (continuing anyway): {}", stderr);
}
}
Ok(())
}
/// Validate that the specified git references exist (branches, tags, or commits)
pub(crate) fn validate_refs(&self, refs: &[&str]) -> Result<()> {
for &git_ref in refs {
// Try to resolve the ref similar to `git checkout` by peeling to a commit.
// First try the ref as-is with ^{commit}, then fall back to origin/{ref}^{commit}.
let as_is = format!("{git_ref}^{{commit}}");
let ref_check = Command::new("git")
.args(["rev-parse", "--verify", &as_is])
.current_dir(&self.repo_root)
.output();
let found = if let Ok(output) = ref_check &&
output.status.success()
{
info!("Validated reference exists: {}", git_ref);
true
} else {
// Try remote-only branches via origin/{ref}
let origin_ref = format!("origin/{git_ref}^{{commit}}");
let origin_check = Command::new("git")
.args(["rev-parse", "--verify", &origin_ref])
.current_dir(&self.repo_root)
.output();
if let Ok(output) = origin_check &&
output.status.success()
{
info!("Validated remote reference exists: origin/{}", git_ref);
true
} else {
false
}
};
if !found {
return Err(eyre!(
"Git reference '{}' does not exist as branch, tag, or commit (tried '{}' and 'origin/{}^{{commit}}')",
git_ref,
format!("{git_ref}^{{commit}}"),
git_ref,
));
}
}
Ok(())
}
/// Switch to the specified git reference (branch, tag, or commit)
pub(crate) fn switch_ref(&self, git_ref: &str) -> Result<()> {
// First checkout the reference
let output = Command::new("git")
.args(["checkout", git_ref])
.current_dir(&self.repo_root)
.output()
.wrap_err_with(|| format!("Failed to switch to reference '{git_ref}'"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(eyre!("Failed to switch to reference '{}': {}", git_ref, stderr));
}
// Check if this is a branch that tracks a remote and pull latest changes
let is_branch = Command::new("git")
.args(["show-ref", "--verify", "--quiet", &format!("refs/heads/{git_ref}")])
.current_dir(&self.repo_root)
.status()
.map(|s| s.success())
.unwrap_or(false);
if is_branch {
// Check if the branch tracks a remote
let tracking_output = Command::new("git")
.args([
"rev-parse",
"--abbrev-ref",
"--symbolic-full-name",
&format!("{git_ref}@{{upstream}}"),
])
.current_dir(&self.repo_root)
.output();
if let Ok(output) = tracking_output &&
output.status.success()
{
let upstream = String::from_utf8_lossy(&output.stdout).trim().to_string();
if !upstream.is_empty() && upstream != format!("{git_ref}@{{upstream}}") {
// Branch tracks a remote, pull latest changes
info!("Pulling latest changes for branch: {}", git_ref);
let pull_output = Command::new("git")
.args(["pull", "--ff-only"])
.current_dir(&self.repo_root)
.output()
.wrap_err_with(|| {
format!("Failed to pull latest changes for branch '{git_ref}'")
})?;
if pull_output.status.success() {
info!("Successfully pulled latest changes for branch: {}", git_ref);
} else {
let stderr = String::from_utf8_lossy(&pull_output.stderr);
warn!("Failed to pull latest changes for branch '{}': {}", git_ref, stderr);
// Continue anyway, we'll use whatever version we have
}
}
}
}
// Verify the checkout succeeded by checking the current commit
let current_commit_output = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&self.repo_root)
.output()
.wrap_err("Failed to get current commit")?;
if !current_commit_output.status.success() {
return Err(eyre!("Failed to verify git checkout"));
}
info!("Switched to reference: {}", git_ref);
Ok(())
}
/// Get the current commit hash
pub(crate) fn get_current_commit(&self) -> Result<String> {
let output = Command::new("git")
.args(["rev-parse", "HEAD"])
.current_dir(&self.repo_root)
.output()
.wrap_err("Failed to get current commit")?;
if !output.status.success() {
return Err(eyre!("Failed to get current commit hash"));
}
let commit_hash = String::from_utf8(output.stdout)
.wrap_err("Commit hash is not valid UTF-8")?
.trim()
.to_string();
Ok(commit_hash)
}
/// Get the repository root path
pub(crate) fn repo_root(&self) -> &str {
&self.repo_root
}
}

View File

@@ -1,45 +0,0 @@
//! # reth-bench-compare
//!
//! Automated tool for comparing reth performance between two git branches.
//! This tool automates the complete workflow of compiling, running, and benchmarking
//! reth on different branches to provide meaningful performance comparisons.
#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#[global_allocator]
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
mod benchmark;
mod cli;
mod comparison;
mod compilation;
mod git;
mod node;
use clap::Parser;
use cli::{run_comparison, Args};
use eyre::Result;
use reth_cli_runner::CliRunner;
fn main() -> Result<()> {
// Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided.
if std::env::var_os("RUST_BACKTRACE").is_none() {
unsafe {
std::env::set_var("RUST_BACKTRACE", "1");
}
}
let args = Args::parse();
// Initialize tracing
let _guard = args.init_tracing()?;
// Run until either exit or sigint or sigterm
let runner = CliRunner::try_default_runtime()?;
runner.run_command_until_exit(|ctx| run_comparison(args, ctx))
}

View File

@@ -1,554 +0,0 @@
//! Node management for starting, stopping, and controlling reth instances.
use crate::cli::Args;
use alloy_provider::{Provider, ProviderBuilder};
use alloy_rpc_types_eth::SyncStatus;
use eyre::{eyre, OptionExt, Result, WrapErr};
#[cfg(unix)]
use nix::sys::signal::{killpg, Signal};
#[cfg(unix)]
use nix::unistd::Pid;
use reth_chainspec::Chain;
use std::{fs, path::PathBuf, time::Duration};
use tokio::{
fs::File as AsyncFile,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader as AsyncBufReader},
process::Command,
time::{sleep, timeout},
};
use tracing::{debug, info, warn};
/// Manages reth node lifecycle and operations
pub(crate) struct NodeManager {
datadir: Option<String>,
metrics_port: u16,
chain: Chain,
use_sudo: bool,
binary_path: Option<std::path::PathBuf>,
enable_profiling: bool,
output_dir: PathBuf,
additional_reth_args: Vec<String>,
comparison_dir: Option<PathBuf>,
tracing_endpoint: Option<String>,
otlp_max_queue_size: usize,
}
impl NodeManager {
/// Create a new `NodeManager` with configuration from CLI args
pub(crate) fn new(args: &Args) -> Self {
Self {
datadir: Some(args.datadir_path().to_string_lossy().to_string()),
metrics_port: args.metrics_port,
chain: args.chain,
use_sudo: args.sudo,
binary_path: None,
enable_profiling: args.profile,
output_dir: args.output_dir_path(),
// Filter out empty strings to prevent invalid arguments being passed to reth node
additional_reth_args: args
.reth_args
.iter()
.filter(|s| !s.is_empty())
.cloned()
.collect(),
comparison_dir: None,
tracing_endpoint: args.traces.otlp.as_ref().map(|u| u.to_string()),
otlp_max_queue_size: args.otlp_max_queue_size,
}
}
/// Set the comparison directory path for logging
pub(crate) fn set_comparison_dir(&mut self, dir: PathBuf) {
self.comparison_dir = Some(dir);
}
/// Get the log file path for a given reference type
fn get_log_file_path(&self, ref_type: &str) -> Result<PathBuf> {
let comparison_dir = self
.comparison_dir
.as_ref()
.ok_or_eyre("Comparison directory not set. Call set_comparison_dir first.")?;
// The comparison directory already contains the full path to results/<timestamp>
let log_dir = comparison_dir.join(ref_type);
// Create the directory if it doesn't exist
fs::create_dir_all(&log_dir)
.wrap_err(format!("Failed to create log directory: {:?}", log_dir))?;
let log_file = log_dir.join("reth_node.log");
Ok(log_file)
}
/// Get the perf event max sample rate from the system, capped at 10000
fn get_perf_sample_rate(&self) -> Option<String> {
let perf_rate_file = "/proc/sys/kernel/perf_event_max_sample_rate";
if let Ok(content) = fs::read_to_string(perf_rate_file) {
let rate_str = content.trim();
if !rate_str.is_empty() {
if let Ok(system_rate) = rate_str.parse::<u32>() {
let capped_rate = std::cmp::min(system_rate, 10000);
info!(
"Detected perf_event_max_sample_rate: {}, using: {}",
system_rate, capped_rate
);
return Some(capped_rate.to_string());
}
warn!("Failed to parse perf_event_max_sample_rate: {}", rate_str);
}
}
None
}
/// Get the absolute path to samply using 'which' command
async fn get_samply_path(&self) -> Result<String> {
let output = Command::new("which")
.arg("samply")
.output()
.await
.wrap_err("Failed to execute 'which samply' command")?;
if !output.status.success() {
return Err(eyre!("samply not found in PATH"));
}
let samply_path = String::from_utf8(output.stdout)
.wrap_err("samply path is not valid UTF-8")?
.trim()
.to_string();
if samply_path.is_empty() {
return Err(eyre!("which samply returned empty path"));
}
Ok(samply_path)
}
/// Build reth arguments as a vector of strings
fn build_reth_args(
&self,
binary_path_str: &str,
additional_args: &[String],
ref_type: &str,
) -> (Vec<String>, String) {
let mut reth_args = vec![binary_path_str.to_string(), "node".to_string()];
// Add chain argument (skip for mainnet as it's the default)
let chain_str = self.chain.to_string();
if chain_str != "mainnet" {
reth_args.extend_from_slice(&["--chain".to_string(), chain_str.clone()]);
}
// Add datadir if specified
if let Some(ref datadir) = self.datadir {
reth_args.extend_from_slice(&["--datadir".to_string(), datadir.clone()]);
}
// Add reth-specific arguments
let metrics_arg = format!("0.0.0.0:{}", self.metrics_port);
reth_args.extend_from_slice(&[
"--engine.accept-execution-requests-hash".to_string(),
"--metrics".to_string(),
metrics_arg,
"--http".to_string(),
"--http.api".to_string(),
"eth".to_string(),
"--disable-discovery".to_string(),
"--trusted-only".to_string(),
]);
// Add tracing arguments if OTLP endpoint is configured
if let Some(ref endpoint) = self.tracing_endpoint {
info!("Enabling OTLP tracing export to: {} (service: reth-{})", endpoint, ref_type);
// Endpoint requires equals per clap settings in reth
reth_args.push(format!("--tracing-otlp={}", endpoint));
}
// Add any additional arguments passed via command line (common to both baseline and
// feature)
reth_args.extend_from_slice(&self.additional_reth_args);
// Add reference-specific additional arguments
reth_args.extend_from_slice(additional_args);
(reth_args, chain_str)
}
/// Create a command for profiling mode
async fn create_profiling_command(
&self,
ref_type: &str,
reth_args: &[String],
) -> Result<Command> {
// Create profiles directory if it doesn't exist
let profile_dir = self.output_dir.join("profiles");
fs::create_dir_all(&profile_dir).wrap_err("Failed to create profiles directory")?;
let profile_path = profile_dir.join(format!("{}.json.gz", ref_type));
info!("Starting reth node with samply profiling...");
info!("Profile output: {:?}", profile_path);
// Get absolute path to samply
let samply_path = self.get_samply_path().await?;
let mut cmd = if self.use_sudo {
let mut sudo_cmd = Command::new("sudo");
sudo_cmd.arg(&samply_path);
sudo_cmd
} else {
Command::new(&samply_path)
};
// Add samply arguments
cmd.args(["record", "--save-only", "-o", &profile_path.to_string_lossy()]);
// Add rate argument if available
if let Some(rate) = self.get_perf_sample_rate() {
cmd.args(["--rate", &rate]);
}
// Add separator and complete reth command
cmd.arg("--");
cmd.args(reth_args);
// Set environment variable to disable log styling
cmd.env("RUST_LOG_STYLE", "never");
Ok(cmd)
}
/// Create a command for direct reth execution
fn create_direct_command(&self, reth_args: &[String]) -> Command {
let binary_path = &reth_args[0];
let mut cmd = if self.use_sudo {
info!("Starting reth node with sudo...");
let mut sudo_cmd = Command::new("sudo");
sudo_cmd.args(reth_args);
sudo_cmd
} else {
info!("Starting reth node...");
let mut reth_cmd = Command::new(binary_path);
reth_cmd.args(&reth_args[1..]); // Skip the binary path since it's the command
reth_cmd
};
// Set environment variable to disable log styling
cmd.env("RUST_LOG_STYLE", "never");
cmd
}
/// Start a reth node using the specified binary path and return the process handle
/// along with the formatted reth command string for reporting.
pub(crate) async fn start_node(
&mut self,
binary_path: &std::path::Path,
_git_ref: &str,
ref_type: &str,
additional_args: &[String],
) -> Result<(tokio::process::Child, String)> {
// Store the binary path for later use (e.g., in unwind_to_block)
self.binary_path = Some(binary_path.to_path_buf());
let binary_path_str = binary_path.to_string_lossy();
let (reth_args, _) = self.build_reth_args(&binary_path_str, additional_args, ref_type);
// Format the reth command string for reporting
let reth_command = shlex::try_join(reth_args.iter().map(|s| s.as_str()))
.wrap_err("Failed to format reth command string")?;
// Log additional arguments if any
if !self.additional_reth_args.is_empty() {
info!("Using common additional reth arguments: {:?}", self.additional_reth_args);
}
if !additional_args.is_empty() {
info!("Using reference-specific additional reth arguments: {:?}", additional_args);
}
let mut cmd = if self.enable_profiling {
self.create_profiling_command(ref_type, &reth_args).await?
} else {
self.create_direct_command(&reth_args)
};
// Set process group for better signal handling
#[cfg(unix)]
{
cmd.process_group(0);
}
// Set high queue size to prevent trace dropping during benchmarks
if self.tracing_endpoint.is_some() {
cmd.env("OTEL_BSP_MAX_QUEUE_SIZE", self.otlp_max_queue_size.to_string()); // Traces
cmd.env("OTEL_BLRP_MAX_QUEUE_SIZE", "10000"); // Logs
// Set service name to differentiate baseline vs feature runs in Jaeger
cmd.env("OTEL_SERVICE_NAME", format!("reth-{}", ref_type));
}
debug!("Executing reth command: {cmd:?}");
let mut child = cmd
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true) // Kill on drop so that on Ctrl-C for parent process we stop all child processes
.spawn()
.wrap_err("Failed to start reth node")?;
info!(
"Reth node started with PID: {:?} (binary: {})",
child.id().ok_or_eyre("Reth node is not running")?,
binary_path_str
);
// Prepare log file path
let log_file_path = self.get_log_file_path(ref_type)?;
info!("Reth node logs will be saved to: {:?}", log_file_path);
// Stream stdout and stderr with prefixes at debug level and to log file
if let Some(stdout) = child.stdout.take() {
let log_file = AsyncFile::create(&log_file_path)
.await
.wrap_err(format!("Failed to create log file: {:?}", log_file_path))?;
tokio::spawn(async move {
let reader = AsyncBufReader::new(stdout);
let mut lines = reader.lines();
let mut log_file = log_file;
while let Ok(Some(line)) = lines.next_line().await {
debug!("[RETH] {}", line);
// Write to log file (reth already includes timestamps)
let log_line = format!("{}\n", line);
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
debug!("Failed to write to log file: {}", e);
}
}
});
}
if let Some(stderr) = child.stderr.take() {
let log_file = AsyncFile::options()
.create(true)
.append(true)
.open(&log_file_path)
.await
.wrap_err(format!("Failed to open log file for stderr: {:?}", log_file_path))?;
tokio::spawn(async move {
let reader = AsyncBufReader::new(stderr);
let mut lines = reader.lines();
let mut log_file = log_file;
while let Ok(Some(line)) = lines.next_line().await {
debug!("[RETH] {}", line);
// Write to log file (reth already includes timestamps)
let log_line = format!("{}\n", line);
if let Err(e) = log_file.write_all(log_line.as_bytes()).await {
debug!("Failed to write to log file: {}", e);
}
}
});
}
// Give the node a moment to start up
sleep(Duration::from_secs(5)).await;
Ok((child, reth_command))
}
/// Wait for the node to be ready and return its current tip
pub(crate) async fn wait_for_node_ready_and_get_tip(&self) -> Result<u64> {
info!("Waiting for node to be ready and synced...");
let max_wait = Duration::from_secs(120); // 2 minutes to allow for sync
let check_interval = Duration::from_secs(2);
let rpc_url = "http://localhost:8545";
// Create Alloy provider
let url = rpc_url.parse().map_err(|e| eyre!("Invalid RPC URL '{}': {}", rpc_url, e))?;
let provider = ProviderBuilder::new().connect_http(url);
timeout(max_wait, async {
loop {
// First check if RPC is up and node is not syncing
match provider.syncing().await {
Ok(sync_result) => {
match sync_result {
SyncStatus::Info(sync_info) => {
debug!("Node is still syncing {sync_info:?}, waiting...");
}
_ => {
// Node is not syncing, now get the tip
match provider.get_block_number().await {
Ok(tip) => {
info!("Node is ready and not syncing at block: {}", tip);
return Ok(tip);
}
Err(e) => {
debug!("Failed to get block number: {}", e);
}
}
}
}
}
Err(e) => {
debug!("Node RPC not ready yet or failed to check sync status: {}", e);
}
}
sleep(check_interval).await;
}
})
.await
.wrap_err("Timed out waiting for node to be ready and synced")?
}
/// Stop the reth node gracefully
pub(crate) async fn stop_node(&self, child: &mut tokio::process::Child) -> Result<()> {
let pid = child.id().expect("Child process ID should be available");
// Check if the process has already exited
match child.try_wait() {
Ok(Some(status)) => {
info!("Reth node (PID: {}) has already exited with status: {:?}", pid, status);
return Ok(());
}
Ok(None) => {
// Process is still running, proceed to stop it
info!("Stopping process gracefully with SIGINT (PID: {})...", pid);
}
Err(e) => {
return Err(eyre!("Failed to check process status: {}", e));
}
}
#[cfg(unix)]
{
// Send SIGINT to process group to mimic Ctrl-C behavior
let nix_pgid = Pid::from_raw(pid as i32);
match killpg(nix_pgid, Signal::SIGINT) {
Ok(()) => {}
Err(nix::errno::Errno::ESRCH) => {
info!("Process group {} has already exited", pid);
}
Err(e) => {
return Err(eyre!("Failed to send SIGINT to process group {}: {}", pid, e));
}
}
}
#[cfg(not(unix))]
{
// On non-Unix systems, fall back to using external kill command
let output = Command::new("taskkill")
.args(["/PID", &pid.to_string(), "/F"])
.output()
.await
.wrap_err("Failed to execute taskkill command")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
// Check if the error is because the process doesn't exist
if stderr.contains("not found") || stderr.contains("not exist") {
info!("Process {} has already exited", pid);
} else {
return Err(eyre!("Failed to kill process {}: {}", pid, stderr));
}
}
}
// Wait for the process to exit
match child.wait().await {
Ok(status) => {
info!("Reth node (PID: {}) exited with status: {:?}", pid, status);
}
Err(e) => {
// If we get an error here, it might be because the process already exited
debug!("Error waiting for process exit (may have already exited): {}", e);
}
}
Ok(())
}
/// Unwind the node to a specific block
pub(crate) async fn unwind_to_block(&self, block_number: u64) -> Result<()> {
if self.use_sudo {
info!("Unwinding node to block: {} (with sudo)", block_number);
} else {
info!("Unwinding node to block: {}", block_number);
}
// Use the binary path from the last start_node call, or fallback to default
let binary_path = self
.binary_path
.as_ref()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| "./target/profiling/reth".to_string());
let mut cmd = if self.use_sudo {
let mut sudo_cmd = Command::new("sudo");
sudo_cmd.args([&binary_path, "stage", "unwind"]);
sudo_cmd
} else {
let mut reth_cmd = Command::new(&binary_path);
reth_cmd.args(["stage", "unwind"]);
reth_cmd
};
// Add chain argument (skip for mainnet as it's the default)
let chain_str = self.chain.to_string();
if chain_str != "mainnet" {
cmd.args(["--chain", &chain_str]);
}
// Add datadir if specified
if let Some(ref datadir) = self.datadir {
cmd.args(["--datadir", datadir]);
}
cmd.args(["to-block", &block_number.to_string()]);
// Set environment variable to disable log styling
cmd.env("RUST_LOG_STYLE", "never");
// Debug log the command
debug!("Executing reth unwind command: {:?}", cmd);
let mut child = cmd
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.wrap_err("Failed to start unwind command")?;
// Stream stdout and stderr with prefixes in real-time
if let Some(stdout) = child.stdout.take() {
tokio::spawn(async move {
let reader = AsyncBufReader::new(stdout);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[RETH-UNWIND] {}", line);
}
});
}
if let Some(stderr) = child.stderr.take() {
tokio::spawn(async move {
let reader = AsyncBufReader::new(stderr);
let mut lines = reader.lines();
while let Ok(Some(line)) = lines.next_line().await {
debug!("[RETH-UNWIND] {}", line);
}
});
}
// Wait for the command to complete
let status = child.wait().await.wrap_err("Failed to wait for unwind command")?;
if !status.success() {
return Err(eyre!("Unwind command failed with exit code: {:?}", status.code()));
}
info!("Unwound to block: {}", block_number);
Ok(())
}
}

View File

@@ -35,7 +35,6 @@ alloy-transport-ipc.workspace = true
alloy-transport-ws.workspace = true
alloy-transport.workspace = true
op-alloy-consensus = { workspace = true, features = ["alloy-compat"] }
op-alloy-rpc-types-engine = { workspace = true, features = ["serde"] }
# reqwest
reqwest = { workspace = true, default-features = false, features = ["rustls-tls-native-roots"] }
@@ -65,6 +64,7 @@ humantime.workspace = true
csv.workspace = true
[dev-dependencies]
reth-tracing.workspace = true
[features]
default = ["jemalloc"]
@@ -81,26 +81,11 @@ jemalloc = [
jemalloc-prof = ["reth-cli-util/jemalloc-prof"]
tracy-allocator = ["reth-cli-util/tracy-allocator"]
min-error-logs = [
"tracing/release_max_level_error",
"reth-node-core/min-error-logs",
]
min-warn-logs = [
"tracing/release_max_level_warn",
"reth-node-core/min-warn-logs",
]
min-info-logs = [
"tracing/release_max_level_info",
"reth-node-core/min-info-logs",
]
min-debug-logs = [
"tracing/release_max_level_debug",
"reth-node-core/min-debug-logs",
]
min-trace-logs = [
"tracing/release_max_level_trace",
"reth-node-core/min-trace-logs",
]
min-error-logs = ["tracing/release_max_level_error"]
min-warn-logs = ["tracing/release_max_level_warn"]
min-info-logs = ["tracing/release_max_level_info"]
min-debug-logs = ["tracing/release_max_level_debug"]
min-trace-logs = ["tracing/release_max_level_trace"]
# no-op feature flag for switching between the `optimism` and default functionality in CI matrices
ethereum = []

View File

@@ -49,7 +49,7 @@ reth stage unwind to-block 21000000
The following `reth-bench` command would then start the benchmark at block 21,000,000:
```bash
reth-bench new-payload-fcu --rpc-url <rpc-url> --from 21000000 --to <end_block> --jwt-secret <jwt_file_path>
reth-bench new-payload-fcu --rpc-url <rpc-url> --from 21000000 --to <end_block> --jwtsecret <jwt_file_path>
```
Finally, make sure that reth is built using a build profile suitable for what you are trying to measure.
@@ -84,7 +84,7 @@ samply record -p 3001 target/profiling/reth node --metrics localhost:9001 --auth
```
```bash
reth-bench new-payload-fcu --rpc-url <rpc-url> --from <start_block> --to <end_block> --jwt-secret <jwt_file_path>
reth-bench new-payload-fcu --rpc-url <rpc-url> --from <start_block> --to <end_block> --jwtsecret <jwt_file_path>
```
Replace `<start_block>`, `<end_block>`, and `<jwt_file_path>` with the appropriate values for your testing environment. `<rpc-url>` should be the URL of an RPC endpoint that can provide the blocks that will be used during the execution.
@@ -92,18 +92,6 @@ This should NOT be the node that is being used for the benchmark. The node behin
the benchmark. The node being benchmarked will not have these blocks.
Note that this assumes that the benchmark node's engine API is running on `http://127.0.0.1:8551`, which is set as a default value in `reth-bench`. To configure this value, use the `--engine-rpc-url` flag.
#### Using the `--advance` argument
The `--advance` argument allows you to benchmark a relative number of blocks from the current head, without manually specifying `--from` and `--to`.
```bash
# Benchmark the next 10 blocks from the current head
reth-bench new-payload-fcu --advance 10 --jwt-secret <jwt_file_path> --rpc-url <rpc-url>
# Benchmark the next 50 blocks with a different subcommand
reth-bench new-payload-only --advance 50 --jwt-secret <jwt_file_path> --rpc-url <rpc-url>
```
### Observe Outputs
After running the command, `reth-bench` will output benchmark results, showing processing speeds and gas usage, which are useful metrics for analyzing the node's performance.
@@ -143,5 +131,5 @@ To reproduce the benchmark, first re-set the node to the block that the benchmar
- **RPC Configuration**: The RPC endpoints should be accessible and configured correctly, specifically the RPC endpoint must support `eth_getBlockByNumber` and support fetching full transactions. The benchmark will make one RPC query per block as fast as possible, so ensure the RPC endpoint does not rate limit or block requests after a certain volume.
- **Reproducibility**: Ensure that the node is at the same state before attempting to retry a benchmark. The `new-payload-fcu` command specifically will commit to the database, so the node must be rolled back using `reth stage unwind` to reproducibly retry benchmarks.
- **Profiling tools**: If you are collecting CPU profiles, tools like [`samply`](https://github.com/mstange/samply) and [`perf`](https://perf.wiki.kernel.org/index.php/Main_Page) can be useful for analyzing node performance.
- **Benchmark Data**: `reth-bench` additionally contains a `--output` flag, which will output gas used benchmarks across the benchmark range in CSV format. This may be useful for further data analysis.
- **Benchmark Data**: `reth-bench` additionally contains a `--benchmark.output` flag, which will output gas used benchmarks across the benchmark range in CSV format. This may be useful for further data analysis.
- **Platform Information**: To ensure accurate and reproducible benchmarking, document the platform details, including hardware specifications, OS version, and any other relevant information before publishing any benchmarks.

View File

@@ -1,12 +1,4 @@
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.8"
# dependencies = [
# "pandas",
# "matplotlib",
# "numpy",
# ]
# ///
#!/usr/bin/env python3
# A simple script which plots graphs comparing two combined_latency.csv files
# output by reth-bench. The graphs which are plotted are:
@@ -16,8 +8,6 @@
#
# - A simple line graph plotting the latencies of the two files against each
# other.
#
# - A gas per second (gas/s) chart showing throughput over time.
import argparse
@@ -25,82 +15,15 @@ import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import sys
import os
from matplotlib.ticker import FuncFormatter
def get_output_filename(base_path, suffix=None):
"""Generate output filename with optional suffix."""
if suffix is None:
return base_path
# Split the base path into directory, name, and extension
dir_name = os.path.dirname(base_path)
base_name = os.path.basename(base_path)
name, ext = os.path.splitext(base_name)
# Create new filename with suffix
new_name = f"{name}_{suffix}{ext}"
return os.path.join(dir_name, new_name) if dir_name else new_name
def format_gas_units(value, pos):
"""Format gas values with appropriate units (gas, Kgas, Mgas, Ggas, Tgas)."""
if value == 0:
return '0'
# Define unit thresholds and labels
units = [
(1e12, 'Tgas'), # Teragas
(1e9, 'Ggas'), # Gigagas
(1e6, 'Mgas'), # Megagas
(1e3, 'Kgas'), # Kilogas
(1, 'gas') # gas
]
abs_value = abs(value)
for threshold, unit in units:
if abs_value >= threshold:
scaled_value = value / threshold
# Format with appropriate precision
if scaled_value >= 100:
return f'{scaled_value:.0f}{unit}/s'
elif scaled_value >= 10:
return f'{scaled_value:.1f}{unit}/s'
else:
return f'{scaled_value:.2f}{unit}/s'
return f'{value:.0f}gas/s'
def moving_average(data, window_size):
"""Calculate moving average with given window size."""
if window_size <= 1:
return data
# Use pandas for efficient rolling mean calculation
series = pd.Series(data)
return series.rolling(window=window_size, center=True, min_periods=1).mean().values
def main():
parser = argparse.ArgumentParser(description='Generate histogram of total_latency percent differences between two CSV files')
parser.add_argument('baseline_csv', help='First CSV file, used as the baseline/control')
parser.add_argument('comparison_csv', help='Second CSV file, which is being compared to the baseline')
parser.add_argument('-o', '--output', default='latency.png', help='Output image file (default: latency.png)')
parser.add_argument('--graphs', default='all', help='Comma-separated list of graphs to plot: histogram, line, gas, all (default: all)')
parser.add_argument('--average', type=int, metavar='N', help='Apply moving average over N blocks to smooth line and gas charts')
parser.add_argument('--separate', action='store_true', help='Output each chart as a separate file')
args = parser.parse_args()
# Parse graph selection
if args.graphs.lower() == 'all':
selected_graphs = {'histogram', 'line', 'gas'}
else:
selected_graphs = set(graph.strip().lower() for graph in args.graphs.split(','))
valid_graphs = {'histogram', 'line', 'gas'}
invalid_graphs = selected_graphs - valid_graphs
if invalid_graphs:
print(f"Error: Invalid graph types: {', '.join(invalid_graphs)}. Valid options are: histogram, line, gas, all", file=sys.stderr)
sys.exit(1)
try:
df1 = pd.read_csv(args.baseline_csv)
df2 = pd.read_csv(args.comparison_csv)
@@ -119,24 +42,14 @@ def main():
print(f"Error: 'total_latency' column not found in {args.comparison_csv}", file=sys.stderr)
sys.exit(1)
# Check for gas_used column if gas graph is selected
if 'gas' in selected_graphs:
if 'gas_used' not in df1.columns:
print(f"Error: 'gas_used' column not found in {args.baseline_csv} (required for gas graph)", file=sys.stderr)
sys.exit(1)
if 'gas_used' not in df2.columns:
print(f"Error: 'gas_used' column not found in {args.comparison_csv} (required for gas graph)", file=sys.stderr)
sys.exit(1)
if len(df1) != len(df2):
print("Warning: CSV files have different number of rows. Using minimum length.", file=sys.stderr)
min_len = min(len(df1), len(df2))
df1 = df1.head(min_len)
df2 = df2.head(min_len)
# Convert from microseconds to milliseconds for better readability
latency1 = df1['total_latency'].values / 1000.0
latency2 = df2['total_latency'].values / 1000.0
latency1 = df1['total_latency'].values
latency2 = df2['total_latency'].values
# Handle division by zero
with np.errstate(divide='ignore', invalid='ignore'):
@@ -149,220 +62,54 @@ def main():
print("Error: No valid percent differences could be calculated", file=sys.stderr)
sys.exit(1)
# Calculate statistics once for use in graphs and output
# Create histogram with 1% buckets
min_diff = np.floor(percent_diff.min())
max_diff = np.ceil(percent_diff.max())
bins = np.arange(min_diff, max_diff + 1, 1)
# Create figure with two subplots
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 12))
# Top subplot: Histogram
ax1.hist(percent_diff, bins=bins, edgecolor='black', alpha=0.7)
ax1.set_xlabel('Percent Difference (%)')
ax1.set_ylabel('Number of Blocks')
ax1.set_title(f'Total Latency Percent Difference Histogram\n({args.baseline_csv} vs {args.comparison_csv})')
ax1.grid(True, alpha=0.3)
# Add statistics to the histogram
mean_diff = np.mean(percent_diff)
median_diff = np.median(percent_diff)
ax1.axvline(mean_diff, color='red', linestyle='--', label=f'Mean: {mean_diff:.2f}%')
ax1.axvline(median_diff, color='orange', linestyle='--', label=f'Median: {median_diff:.2f}%')
ax1.legend()
# Determine number of subplots and create figure
num_plots = len(selected_graphs)
if num_plots == 0:
print("Error: No valid graphs selected", file=sys.stderr)
sys.exit(1)
# Store output filenames
output_files = []
if args.separate:
# We'll create individual figures for each graph
pass
# Bottom subplot: Latency vs Block Number
if 'block_number' in df1.columns and 'block_number' in df2.columns:
block_numbers = df1['block_number'].values[:len(percent_diff)]
ax2.plot(block_numbers, latency1[:len(percent_diff)], 'b-', alpha=0.7, label=f'Baseline ({args.baseline_csv})')
ax2.plot(block_numbers, latency2[:len(percent_diff)], 'r-', alpha=0.7, label=f'Comparison ({args.comparison_csv})')
ax2.set_xlabel('Block Number')
ax2.set_ylabel('Total Latency (ms)')
ax2.set_title('Total Latency vs Block Number')
ax2.grid(True, alpha=0.3)
ax2.legend()
else:
# Create combined figure
if num_plots == 1:
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
axes = [ax]
else:
fig, axes = plt.subplots(num_plots, 1, figsize=(12, 6 * num_plots))
# If no block_number column, use index
indices = np.arange(len(percent_diff))
ax2.plot(indices, latency1[:len(percent_diff)], 'b-', alpha=0.7, label=f'Baseline ({args.baseline_csv})')
ax2.plot(indices, latency2[:len(percent_diff)], 'r-', alpha=0.7, label=f'Comparison ({args.comparison_csv})')
ax2.set_xlabel('Block Index')
ax2.set_ylabel('Total Latency (ms)')
ax2.set_title('Total Latency vs Block Index')
ax2.grid(True, alpha=0.3)
ax2.legend()
plot_idx = 0
plt.tight_layout()
plt.savefig(args.output, dpi=300, bbox_inches='tight')
print(f"Histogram and latency graph saved to {args.output}")
# Plot histogram if selected
if 'histogram' in selected_graphs:
if args.separate:
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
else:
ax = axes[plot_idx]
min_diff = np.floor(percent_diff.min())
max_diff = np.ceil(percent_diff.max())
# Create histogram with 1% buckets
bins = np.arange(min_diff, max_diff + 1, 1)
ax.hist(percent_diff, bins=bins, edgecolor='black', alpha=0.7)
ax.set_xlabel('Percent Difference (%)')
ax.set_ylabel('Number of Blocks')
ax.set_title(f'Total Latency Percent Difference Histogram\n({args.baseline_csv} vs {args.comparison_csv})')
ax.grid(True, alpha=0.3)
# Add statistics to the histogram
ax.axvline(mean_diff, color='red', linestyle='--', label=f'Mean: {mean_diff:.2f}%')
ax.axvline(median_diff, color='orange', linestyle='--', label=f'Median: {median_diff:.2f}%')
ax.legend()
if args.separate:
plt.tight_layout()
output_file = get_output_filename(args.output, 'histogram')
plt.savefig(output_file, dpi=300, bbox_inches='tight')
output_files.append(output_file)
plt.close(fig)
else:
plot_idx += 1
# Plot line graph if selected
if 'line' in selected_graphs:
if args.separate:
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
else:
ax = axes[plot_idx]
# Determine comparison color based on median change. The median being
# negative means processing time got faster, so that becomes green.
comparison_color = 'green' if median_diff < 0 else 'red'
# Apply moving average if requested
plot_latency1 = latency1[:len(percent_diff)]
plot_latency2 = latency2[:len(percent_diff)]
if args.average:
plot_latency1 = moving_average(plot_latency1, args.average)
plot_latency2 = moving_average(plot_latency2, args.average)
if 'block_number' in df1.columns and 'block_number' in df2.columns:
block_numbers = df1['block_number'].values[:len(percent_diff)]
ax.plot(block_numbers, plot_latency1, 'orange', alpha=0.7, label=f'Baseline ({args.baseline_csv})')
ax.plot(block_numbers, plot_latency2, comparison_color, alpha=0.7, label=f'Comparison ({args.comparison_csv})')
ax.set_xlabel('Block Number')
ax.set_ylabel('Total Latency (ms)')
title = 'Total Latency vs Block Number'
if args.average:
title += f' ({args.average}-block moving average)'
ax.set_title(title)
ax.grid(True, alpha=0.3)
ax.legend()
else:
# If no block_number column, use index
indices = np.arange(len(percent_diff))
ax.plot(indices, plot_latency1, 'orange', alpha=0.7, label=f'Baseline ({args.baseline_csv})')
ax.plot(indices, plot_latency2, comparison_color, alpha=0.7, label=f'Comparison ({args.comparison_csv})')
ax.set_xlabel('Block Index')
ax.set_ylabel('Total Latency (ms)')
title = 'Total Latency vs Block Index'
if args.average:
title += f' ({args.average}-block moving average)'
ax.set_title(title)
ax.grid(True, alpha=0.3)
ax.legend()
if args.separate:
plt.tight_layout()
output_file = get_output_filename(args.output, 'line')
plt.savefig(output_file, dpi=300, bbox_inches='tight')
output_files.append(output_file)
plt.close(fig)
else:
plot_idx += 1
# Plot gas/s graph if selected
if 'gas' in selected_graphs:
if args.separate:
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
else:
ax = axes[plot_idx]
# Calculate gas per second (gas/s)
# latency is in microseconds, so convert to seconds for gas/s calculation
gas1 = df1['gas_used'].values[:len(percent_diff)]
gas2 = df2['gas_used'].values[:len(percent_diff)]
# Convert latency from microseconds to seconds
latency1_sec = df1['total_latency'].values[:len(percent_diff)] / 1_000_000.0
latency2_sec = df2['total_latency'].values[:len(percent_diff)] / 1_000_000.0
# Calculate gas per second
gas_per_sec1 = gas1 / latency1_sec
gas_per_sec2 = gas2 / latency2_sec
# Store original values for statistics before averaging
original_gas_per_sec1 = gas_per_sec1.copy()
original_gas_per_sec2 = gas_per_sec2.copy()
# Apply moving average if requested
if args.average:
gas_per_sec1 = moving_average(gas_per_sec1, args.average)
gas_per_sec2 = moving_average(gas_per_sec2, args.average)
# Calculate median gas/s for color determination (use original values)
median_gas_per_sec1 = np.median(original_gas_per_sec1)
median_gas_per_sec2 = np.median(original_gas_per_sec2)
comparison_color = 'green' if median_gas_per_sec2 > median_gas_per_sec1 else 'red'
if 'block_number' in df1.columns and 'block_number' in df2.columns:
block_numbers = df1['block_number'].values[:len(percent_diff)]
ax.plot(block_numbers, gas_per_sec1, 'orange', alpha=0.7, label=f'Baseline ({args.baseline_csv})')
ax.plot(block_numbers, gas_per_sec2, comparison_color, alpha=0.7, label=f'Comparison ({args.comparison_csv})')
ax.set_xlabel('Block Number')
ax.set_ylabel('Gas Throughput')
title = 'Gas Throughput vs Block Number'
if args.average:
title += f' ({args.average}-block moving average)'
ax.set_title(title)
ax.grid(True, alpha=0.3)
ax.legend()
# Format Y-axis with gas units
formatter = FuncFormatter(format_gas_units)
ax.yaxis.set_major_formatter(formatter)
else:
# If no block_number column, use index
indices = np.arange(len(percent_diff))
ax.plot(indices, gas_per_sec1, 'orange', alpha=0.7, label=f'Baseline ({args.baseline_csv})')
ax.plot(indices, gas_per_sec2, comparison_color, alpha=0.7, label=f'Comparison ({args.comparison_csv})')
ax.set_xlabel('Block Index')
ax.set_ylabel('Gas Throughput')
title = 'Gas Throughput vs Block Index'
if args.average:
title += f' ({args.average}-block moving average)'
ax.set_title(title)
ax.grid(True, alpha=0.3)
ax.legend()
# Format Y-axis with gas units
formatter = FuncFormatter(format_gas_units)
ax.yaxis.set_major_formatter(formatter)
if args.separate:
plt.tight_layout()
output_file = get_output_filename(args.output, 'gas')
plt.savefig(output_file, dpi=300, bbox_inches='tight')
output_files.append(output_file)
plt.close(fig)
else:
plot_idx += 1
# Save combined figure if not using separate files
if not args.separate:
plt.tight_layout()
plt.savefig(args.output, dpi=300, bbox_inches='tight')
output_files.append(args.output)
# Create graph type description for output message
graph_types = []
if 'histogram' in selected_graphs:
graph_types.append('histogram')
if 'line' in selected_graphs:
graph_types.append('latency graph')
if 'gas' in selected_graphs:
graph_types.append('gas/s graph')
graph_desc = ' and '.join(graph_types)
# Print output file(s) information
if args.separate:
print(f"Saved {len(output_files)} separate files:")
for output_file in output_files:
print(f" - {output_file}")
else:
print(f"{graph_desc.capitalize()} saved to {args.output}")
# Always print statistics
print(f"\nStatistics:")
print(f"Mean percent difference: {mean_diff:.2f}%")
print(f"Median percent difference: {median_diff:.2f}%")
@@ -370,15 +117,6 @@ def main():
print(f"Min: {percent_diff.min():.2f}%")
print(f"Max: {percent_diff.max():.2f}%")
print(f"Total blocks analyzed: {len(percent_diff)}")
# Print gas/s statistics if gas data is available
if 'gas' in selected_graphs:
# Use original values for statistics (not averaged)
print(f"\nGas/s Statistics:")
print(f"Baseline median gas/s: {median_gas_per_sec1:,.0f}")
print(f"Comparison median gas/s: {median_gas_per_sec2:,.0f}")
gas_diff_percent = ((median_gas_per_sec2 - median_gas_per_sec1) / median_gas_per_sec1) * 100
print(f"Gas/s percent change: {gas_diff_percent:+.2f}%")
if __name__ == '__main__':
main()

View File

@@ -3,11 +3,9 @@
use crate::{authenticated_transport::AuthenticatedTransportConnect, bench_mode::BenchMode};
use alloy_eips::BlockNumberOrTag;
use alloy_primitives::address;
use alloy_provider::{network::AnyNetwork, Provider, RootProvider};
use alloy_rpc_client::ClientBuilder;
use alloy_rpc_types_engine::JwtSecret;
use alloy_transport::layers::RetryBackoffLayer;
use reqwest::Url;
use reth_node_core::args::BenchmarkArgs;
use tracing::info;
@@ -27,8 +25,6 @@ pub(crate) struct BenchContext {
pub(crate) benchmark_mode: BenchMode,
/// The next block to fetch.
pub(crate) next_block: u64,
/// Whether the chain is an OP rollup.
pub(crate) is_optimism: bool,
}
impl BenchContext {
@@ -37,35 +33,26 @@ impl BenchContext {
pub(crate) async fn new(bench_args: &BenchmarkArgs, rpc_url: String) -> eyre::Result<Self> {
info!("Running benchmark using data from RPC URL: {}", rpc_url);
// Ensure that output directory exists and is a directory
// Ensure that output directory is a directory
if let Some(output) = &bench_args.output {
if output.is_file() {
return Err(eyre::eyre!("Output path must be a directory"));
}
// Create the directory if it doesn't exist
if !output.exists() {
std::fs::create_dir_all(output)?;
info!("Created output directory: {:?}", output);
}
}
// set up alloy client for blocks
let client = ClientBuilder::default()
.layer(RetryBackoffLayer::new(10, 800, u64::MAX))
.http(rpc_url.parse()?);
let client = ClientBuilder::default().http(rpc_url.parse()?);
let block_provider = RootProvider::<AnyNetwork>::new(client);
// Check if this is an OP chain by checking code at a predeploy address.
let is_optimism = !block_provider
.get_code_at(address!("0x420000000000000000000000000000000000000F"))
.await?
.is_empty();
// If neither `--from` nor `--to` are provided, we will run the benchmark continuously,
// starting at the latest block.
let mut benchmark_mode = BenchMode::new(bench_args.from, bench_args.to)?;
// construct the authenticated provider
let auth_jwt = bench_args
.auth_jwtsecret
.clone()
.ok_or_else(|| eyre::eyre!("--jwt-secret must be provided for authenticated RPC"))?;
.ok_or_else(|| eyre::eyre!("--jwtsecret must be provided for authenticated RPC"))?;
// fetch jwt from file
//
@@ -82,31 +69,6 @@ impl BenchContext {
let client = ClientBuilder::default().connect_with(auth_transport).await?;
let auth_provider = RootProvider::<AnyNetwork>::new(client);
// Computes the block range for the benchmark.
//
// - If `--advance` is provided, fetches the latest block and sets:
// - `from = head + 1`
// - `to = head + advance`
// - Otherwise, uses the values from `--from` and `--to`.
let (from, to) = if let Some(advance) = bench_args.advance {
if advance == 0 {
return Err(eyre::eyre!("--advance must be greater than 0"));
}
let head_block = auth_provider
.get_block_by_number(BlockNumberOrTag::Latest)
.await?
.ok_or_else(|| eyre::eyre!("Failed to fetch latest block for --advance"))?;
let head_number = head_block.header.number;
(Some(head_number), Some(head_number + advance))
} else {
(bench_args.from, bench_args.to)
};
// If neither `--from` nor `--to` are provided, we will run the benchmark continuously,
// starting at the latest block.
let mut benchmark_mode = BenchMode::new(from, to)?;
let first_block = match benchmark_mode {
BenchMode::Continuous => {
// fetch Latest block
@@ -132,6 +94,6 @@ impl BenchContext {
};
let next_block = first_block.header.number + 1;
Ok(Self { auth_provider, block_provider, benchmark_mode, next_block, is_optimism })
Ok(Self { auth_provider, block_provider, benchmark_mode, next_block })
}
}

View File

@@ -38,7 +38,7 @@ pub enum Subcommands {
///
/// One powerful use case is pairing this command with the `cast block` command, for example:
///
/// `cast block latest --full --json | reth-bench send-payload --rpc-url localhost:5000
/// `cast block latest--full --json | reth-bench send-payload --rpc-url localhost:5000
/// --jwt-secret $(cat ~/.local/share/reth/mainnet/jwt.hex)`
SendPayload(send_payload::Command),
}

View File

@@ -9,13 +9,12 @@ use crate::{
GAS_OUTPUT_SUFFIX,
},
},
valid_payload::{block_to_new_payload, call_forkchoice_updated, call_new_payload},
valid_payload::{call_forkchoice_updated, call_new_payload},
};
use alloy_provider::Provider;
use alloy_rpc_types_engine::ForkchoiceState;
use alloy_rpc_types_engine::{ExecutionPayload, ForkchoiceState};
use clap::Parser;
use csv::Writer;
use eyre::{Context, OptionExt};
use humantime::parse_duration;
use reth_cli_runner::CliContext;
use reth_node_core::args::BenchmarkArgs;
@@ -30,18 +29,8 @@ pub struct Command {
rpc_url: String,
/// How long to wait after a forkchoice update before sending the next payload.
#[arg(long, value_name = "WAIT_TIME", value_parser = parse_duration, default_value = "250ms", verbatim_doc_comment)]
wait_time: Duration,
/// The size of the block buffer (channel capacity) for prefetching blocks from the RPC
/// endpoint.
#[arg(
long = "rpc-block-buffer-size",
value_name = "RPC_BLOCK_BUFFER_SIZE",
default_value = "20",
verbatim_doc_comment
)]
rpc_block_buffer_size: usize,
#[arg(long, value_name = "WAIT_TIME", value_parser = parse_duration, verbatim_doc_comment)]
wait_time: Option<Duration>,
#[command(flatten)]
benchmark: BenchmarkArgs,
@@ -50,63 +39,57 @@ pub struct Command {
impl Command {
/// Execute `benchmark new-payload-fcu` command
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
let BenchContext {
benchmark_mode,
block_provider,
auth_provider,
mut next_block,
is_optimism,
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
let buffer_size = self.rpc_block_buffer_size;
// Use a oneshot channel to propagate errors from the spawned task
let (error_sender, mut error_receiver) = tokio::sync::oneshot::channel();
let (sender, mut receiver) = tokio::sync::mpsc::channel(buffer_size);
let BenchContext { benchmark_mode, block_provider, auth_provider, mut next_block } =
BenchContext::new(&self.benchmark, self.rpc_url).await?;
let (sender, mut receiver) = tokio::sync::mpsc::channel(1000);
tokio::task::spawn(async move {
while benchmark_mode.contains(next_block) {
let block_res = block_provider
.get_block_by_number(next_block.into())
.full()
.await
.wrap_err_with(|| format!("Failed to fetch block by number {next_block}"));
let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
Ok(block) => block,
Err(e) => {
tracing::error!("Failed to fetch block {next_block}: {e}");
let _ = error_sender.send(e);
break;
}
};
let block_res = block_provider.get_block_by_number(next_block.into()).full().await;
let block = block_res.unwrap().unwrap();
let head_block_hash = block.header.hash;
let safe_block_hash = block_provider
.get_block_by_number(block.header.number.saturating_sub(32).into());
let block = block
.into_inner()
.map_header(|header| header.map(|h| h.into_header_with_defaults()))
.try_map_transactions(|tx| {
// try to convert unknowns into op type so that we can also support optimism
tx.try_into_either::<op_alloy_consensus::OpTxEnvelope>()
})
.unwrap()
.into_consensus();
let finalized_block_hash = block_provider
.get_block_by_number(block.header.number.saturating_sub(64).into());
let blob_versioned_hashes =
block.body.blob_versioned_hashes_iter().copied().collect::<Vec<_>>();
// Convert to execution payload
let (payload, sidecar) = ExecutionPayload::from_block_slow(&block);
let header = block.header;
let head_block_hash = payload.block_hash();
let safe_block_hash =
block_provider.get_block_by_number(header.number.saturating_sub(32).into());
let finalized_block_hash =
block_provider.get_block_by_number(header.number.saturating_sub(64).into());
let (safe, finalized) = tokio::join!(safe_block_hash, finalized_block_hash,);
let safe_block_hash = match safe {
Ok(Some(block)) => block.header.hash,
Ok(None) | Err(_) => head_block_hash,
};
let finalized_block_hash = match finalized {
Ok(Some(block)) => block.header.hash,
Ok(None) | Err(_) => head_block_hash,
};
let safe_block_hash = safe.unwrap().expect("finalized block exists").header.hash;
let finalized_block_hash =
finalized.unwrap().expect("finalized block exists").header.hash;
next_block += 1;
if let Err(e) = sender
.send((block, head_block_hash, safe_block_hash, finalized_block_hash))
sender
.send((
header,
blob_versioned_hashes,
payload,
sidecar,
head_block_hash,
safe_block_hash,
finalized_block_hash,
))
.await
{
tracing::error!("Failed to send block data: {e}");
break;
}
.unwrap();
}
});
@@ -115,16 +98,15 @@ impl Command {
let total_benchmark_duration = Instant::now();
let mut total_wait_time = Duration::ZERO;
while let Some((block, head, safe, finalized)) = {
while let Some((header, versioned_hashes, payload, sidecar, head, safe, finalized)) = {
let wait_start = Instant::now();
let result = receiver.recv().await;
total_wait_time += wait_start.elapsed();
result
} {
// just put gas used here
let gas_used = block.header.gas_used;
let block_number = block.header.number;
let transaction_count = block.transactions.len() as u64;
let gas_used = header.gas_used;
let block_number = header.number;
debug!(target: "reth-bench", ?block_number, "Sending payload",);
@@ -135,24 +117,26 @@ impl Command {
finalized_block_hash: finalized,
};
let (version, params) = block_to_new_payload(block, is_optimism)?;
let start = Instant::now();
call_new_payload(&auth_provider, version, params).await?;
let message_version = call_new_payload(
&auth_provider,
payload,
sidecar,
header.parent_beacon_block_root,
versioned_hashes,
)
.await?;
let new_payload_result = NewPayloadResult { gas_used, latency: start.elapsed() };
call_forkchoice_updated(&auth_provider, version, forkchoice_state, None).await?;
call_forkchoice_updated(&auth_provider, message_version, forkchoice_state, None)
.await?;
// calculate the total duration and the fcu latency, record
let total_latency = start.elapsed();
let fcu_latency = total_latency - new_payload_result.latency;
let combined_result = CombinedResult {
block_number,
transaction_count,
new_payload_result,
fcu_latency,
total_latency,
};
let combined_result =
CombinedResult { block_number, new_payload_result, fcu_latency, total_latency };
// current duration since the start of the benchmark minus the time
// waiting for blocks
@@ -161,20 +145,16 @@ impl Command {
// convert gas used to gigagas, then compute gigagas per second
info!(%combined_result);
// wait before sending the next payload
tokio::time::sleep(self.wait_time).await;
// wait if we need to
if let Some(wait_time) = self.wait_time {
tokio::time::sleep(wait_time).await;
}
// record the current result
let gas_row =
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
let gas_row = TotalGasRow { block_number, gas_used, time: current_duration };
results.push((gas_row, combined_result));
}
// Check if the spawned task encountered an error
if let Ok(error) = error_receiver.try_recv() {
return Err(error);
}
let (gas_output_results, combined_results): (_, Vec<CombinedResult>) =
results.into_iter().unzip();
@@ -202,7 +182,7 @@ impl Command {
}
// accumulate the results and calculate the overall Ggas/s
let gas_output = TotalGasOutput::new(gas_output_results)?;
let gas_output = TotalGasOutput::new(gas_output_results);
info!(
total_duration=?gas_output.total_duration,
total_gas_used=?gas_output.total_gas_used,

View File

@@ -8,12 +8,12 @@ use crate::{
NEW_PAYLOAD_OUTPUT_SUFFIX,
},
},
valid_payload::{block_to_new_payload, call_new_payload},
valid_payload::call_new_payload,
};
use alloy_provider::Provider;
use alloy_rpc_types_engine::ExecutionPayload;
use clap::Parser;
use csv::Writer;
use eyre::{Context, OptionExt};
use reth_cli_runner::CliContext;
use reth_node_core::args::BenchmarkArgs;
use std::time::{Duration, Instant};
@@ -26,16 +26,6 @@ pub struct Command {
#[arg(long, value_name = "RPC_URL", verbatim_doc_comment)]
rpc_url: String,
/// The size of the block buffer (channel capacity) for prefetching blocks from the RPC
/// endpoint.
#[arg(
long = "rpc-block-buffer-size",
value_name = "RPC_BLOCK_BUFFER_SIZE",
default_value = "20",
verbatim_doc_comment
)]
rpc_block_buffer_size: usize,
#[command(flatten)]
benchmark: BenchmarkArgs,
}
@@ -43,41 +33,29 @@ pub struct Command {
impl Command {
/// Execute `benchmark new-payload-only` command
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
let BenchContext {
benchmark_mode,
block_provider,
auth_provider,
mut next_block,
is_optimism,
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
let buffer_size = self.rpc_block_buffer_size;
// Use a oneshot channel to propagate errors from the spawned task
let (error_sender, mut error_receiver) = tokio::sync::oneshot::channel();
let (sender, mut receiver) = tokio::sync::mpsc::channel(buffer_size);
let BenchContext { benchmark_mode, block_provider, auth_provider, mut next_block } =
BenchContext::new(&self.benchmark, self.rpc_url).await?;
let (sender, mut receiver) = tokio::sync::mpsc::channel(1000);
tokio::task::spawn(async move {
while benchmark_mode.contains(next_block) {
let block_res = block_provider
.get_block_by_number(next_block.into())
.full()
.await
.wrap_err_with(|| format!("Failed to fetch block by number {next_block}"));
let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
Ok(block) => block,
Err(e) => {
tracing::error!("Failed to fetch block {next_block}: {e}");
let _ = error_sender.send(e);
break;
}
};
let block_res = block_provider.get_block_by_number(next_block.into()).full().await;
let block = block_res.unwrap().unwrap();
let block = block
.into_inner()
.map_header(|header| header.map(|h| h.into_header_with_defaults()))
.try_map_transactions(|tx| {
tx.try_into_either::<op_alloy_consensus::OpTxEnvelope>()
})
.unwrap()
.into_consensus();
let blob_versioned_hashes =
block.body.blob_versioned_hashes_iter().copied().collect::<Vec<_>>();
let (payload, sidecar) = ExecutionPayload::from_block_slow(&block);
next_block += 1;
if let Err(e) = sender.send(block).await {
tracing::error!("Failed to send block data: {e}");
break;
}
sender.send((block.header, blob_versioned_hashes, payload, sidecar)).await.unwrap();
}
});
@@ -86,26 +64,32 @@ impl Command {
let total_benchmark_duration = Instant::now();
let mut total_wait_time = Duration::ZERO;
while let Some(block) = {
while let Some((header, versioned_hashes, payload, sidecar)) = {
let wait_start = Instant::now();
let result = receiver.recv().await;
total_wait_time += wait_start.elapsed();
result
} {
let block_number = block.header.number;
let transaction_count = block.transactions.len() as u64;
let gas_used = block.header.gas_used;
// just put gas used here
let gas_used = header.gas_used;
let block_number = payload.block_number();
debug!(
target: "reth-bench",
number=?block.header.number,
number=?header.number,
"Sending payload to engine",
);
let (version, params) = block_to_new_payload(block, is_optimism)?;
let start = Instant::now();
call_new_payload(&auth_provider, version, params).await?;
call_new_payload(
&auth_provider,
payload,
sidecar,
header.parent_beacon_block_root,
versioned_hashes,
)
.await?;
let new_payload_result = NewPayloadResult { gas_used, latency: start.elapsed() };
info!(%new_payload_result);
@@ -115,16 +99,10 @@ impl Command {
let current_duration = total_benchmark_duration.elapsed() - total_wait_time;
// record the current result
let row =
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
let row = TotalGasRow { block_number, gas_used, time: current_duration };
results.push((row, new_payload_result));
}
// Check if the spawned task encountered an error
if let Ok(error) = error_receiver.try_recv() {
return Err(error);
}
let (gas_output_results, new_payload_results): (_, Vec<NewPayloadResult>) =
results.into_iter().unzip();
@@ -152,7 +130,7 @@ impl Command {
}
// accumulate the results and calculate the overall Ggas/s
let gas_output = TotalGasOutput::new(gas_output_results)?;
let gas_output = TotalGasOutput::new(gas_output_results);
info!(
total_duration=?gas_output.total_duration,
total_gas_used=?gas_output.total_gas_used,

View File

@@ -1,7 +1,6 @@
//! Contains various benchmark output formats, either for logging or for
//! serialization to / from files.
use eyre::OptionExt;
use reth_primitives_traits::constants::GIGAGAS;
use serde::{ser::SerializeStruct, Serialize};
use std::time::Duration;
@@ -53,7 +52,7 @@ impl Serialize for NewPayloadResult {
{
// convert the time to microseconds
let time = self.latency.as_micros();
let mut state = serializer.serialize_struct("NewPayloadResult", 2)?;
let mut state = serializer.serialize_struct("NewPayloadResult", 3)?;
state.serialize_field("gas_used", &self.gas_used)?;
state.serialize_field("latency", &time)?;
state.end()
@@ -67,8 +66,6 @@ impl Serialize for NewPayloadResult {
pub(crate) struct CombinedResult {
/// The block number of the block being processed.
pub(crate) block_number: u64,
/// The number of transactions in the block.
pub(crate) transaction_count: u64,
/// The `newPayload` result.
pub(crate) new_payload_result: NewPayloadResult,
/// The latency of the `forkchoiceUpdated` call.
@@ -110,11 +107,10 @@ impl Serialize for CombinedResult {
let fcu_latency = self.fcu_latency.as_micros();
let new_payload_latency = self.new_payload_result.latency.as_micros();
let total_latency = self.total_latency.as_micros();
let mut state = serializer.serialize_struct("CombinedResult", 6)?;
let mut state = serializer.serialize_struct("CombinedResult", 5)?;
// flatten the new payload result because this is meant for CSV writing
state.serialize_field("block_number", &self.block_number)?;
state.serialize_field("transaction_count", &self.transaction_count)?;
state.serialize_field("gas_used", &self.new_payload_result.gas_used)?;
state.serialize_field("new_payload_latency", &new_payload_latency)?;
state.serialize_field("fcu_latency", &fcu_latency)?;
@@ -128,8 +124,6 @@ impl Serialize for CombinedResult {
pub(crate) struct TotalGasRow {
/// The block number of the block being processed.
pub(crate) block_number: u64,
/// The number of transactions in the block.
pub(crate) transaction_count: u64,
/// The total gas used in the block.
pub(crate) gas_used: u64,
/// Time since the start of the benchmark.
@@ -151,14 +145,15 @@ pub(crate) struct TotalGasOutput {
impl TotalGasOutput {
/// Create a new [`TotalGasOutput`] from a list of [`TotalGasRow`].
pub(crate) fn new(rows: Vec<TotalGasRow>) -> eyre::Result<Self> {
pub(crate) fn new(rows: Vec<TotalGasRow>) -> Self {
// the duration is obtained from the last row
let total_duration = rows.last().map(|row| row.time).ok_or_eyre("empty results")?;
let total_duration =
rows.last().map(|row| row.time).expect("the row has at least one element");
let blocks_processed = rows.len() as u64;
let total_gas_used: u64 = rows.into_iter().map(|row| row.gas_used).sum();
let total_gas_per_second = total_gas_used as f64 / total_duration.as_secs_f64();
Ok(Self { total_gas_used, total_duration, total_gas_per_second, blocks_processed })
Self { total_gas_used, total_duration, total_gas_per_second, blocks_processed }
}
/// Return the total gigagas per second.
@@ -177,9 +172,8 @@ impl Serialize for TotalGasRow {
{
// convert the time to microseconds
let time = self.time.as_micros();
let mut state = serializer.serialize_struct("TotalGasRow", 4)?;
let mut state = serializer.serialize_struct("TotalGasRow", 3)?;
state.serialize_field("block_number", &self.block_number)?;
state.serialize_field("transaction_count", &self.transaction_count)?;
state.serialize_field("gas_used", &self.gas_used)?;
state.serialize_field("time", &time)?;
state.end()
@@ -194,12 +188,7 @@ mod tests {
#[test]
fn test_write_total_gas_row_csv() {
let row = TotalGasRow {
block_number: 1,
transaction_count: 10,
gas_used: 1_000,
time: Duration::from_secs(1),
};
let row = TotalGasRow { block_number: 1, gas_used: 1_000, time: Duration::from_secs(1) };
let mut writer = Writer::from_writer(vec![]);
writer.serialize(row).unwrap();
@@ -209,11 +198,11 @@ mod tests {
let mut result = result.as_slice().lines();
// assert header
let expected_first_line = "block_number,transaction_count,gas_used,time";
let expected_first_line = "block_number,gas_used,time";
let first_line = result.next().unwrap().unwrap();
assert_eq!(first_line, expected_first_line);
let expected_second_line = "1,10,1000,1000000";
let expected_second_line = "1,1000,1000000";
let second_line = result.next().unwrap().unwrap();
assert_eq!(second_line, expected_second_line);
}

View File

@@ -9,7 +9,7 @@
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#[global_allocator]
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
@@ -26,9 +26,7 @@ use reth_cli_runner::CliRunner;
fn main() {
// Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided.
if std::env::var_os("RUST_BACKTRACE").is_none() {
unsafe {
std::env::set_var("RUST_BACKTRACE", "1");
}
std::env::set_var("RUST_BACKTRACE", "1");
}
// Run until either exit or sigint or sigterm

View File

@@ -2,20 +2,53 @@
//! response. This is useful for benchmarking, as it allows us to wait for a payload to be valid
//! before sending additional calls.
use alloy_eips::eip7685::Requests;
use alloy_provider::{ext::EngineApi, network::AnyRpcBlock, Network, Provider};
use alloy_eips::eip7685::RequestsOrHash;
use alloy_primitives::B256;
use alloy_provider::{ext::EngineApi, Network, Provider};
use alloy_rpc_types_engine::{
ExecutionPayload, ExecutionPayloadInputV2, ForkchoiceState, ForkchoiceUpdated,
PayloadAttributes, PayloadStatus,
ExecutionPayload, ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1,
ExecutionPayloadV3, ForkchoiceState, ForkchoiceUpdated, PayloadAttributes, PayloadStatus,
};
use alloy_transport::TransportResult;
use op_alloy_rpc_types_engine::OpExecutionPayloadV4;
use reth_node_api::EngineApiMessageVersion;
use tracing::error;
/// An extension trait for providers that implement the engine API, to wait for a VALID response.
#[async_trait::async_trait]
pub trait EngineApiValidWaitExt<N>: Send + Sync {
/// Calls `engine_newPayloadV1` with the given [`ExecutionPayloadV1`], and waits until the
/// response is VALID.
async fn new_payload_v1_wait(
&self,
payload: ExecutionPayloadV1,
) -> TransportResult<PayloadStatus>;
/// Calls `engine_newPayloadV2` with the given [`ExecutionPayloadInputV2`], and waits until the
/// response is VALID.
async fn new_payload_v2_wait(
&self,
payload: ExecutionPayloadInputV2,
) -> TransportResult<PayloadStatus>;
/// Calls `engine_newPayloadV3` with the given [`ExecutionPayloadV3`], parent beacon block root,
/// and versioned hashes, and waits until the response is VALID.
async fn new_payload_v3_wait(
&self,
payload: ExecutionPayloadV3,
versioned_hashes: Vec<B256>,
parent_beacon_block_root: B256,
) -> TransportResult<PayloadStatus>;
/// Calls `engine_newPayloadV4` with the given [`ExecutionPayloadV3`], parent beacon block root,
/// versioned hashes, and requests hash, and waits until the response is VALID.
async fn new_payload_v4_wait(
&self,
payload: ExecutionPayloadV3,
versioned_hashes: Vec<B256>,
parent_beacon_block_root: B256,
requests_hash: B256,
) -> TransportResult<PayloadStatus>;
/// Calls `engine_forkChoiceUpdatedV1` with the given [`ForkchoiceState`] and optional
/// [`PayloadAttributes`], and waits until the response is VALID.
async fn fork_choice_updated_v1_wait(
@@ -47,6 +80,122 @@ where
N: Network,
P: Provider<N> + EngineApi<N>,
{
async fn new_payload_v1_wait(
&self,
payload: ExecutionPayloadV1,
) -> TransportResult<PayloadStatus> {
let mut status = self.new_payload_v1(payload.clone()).await?;
while !status.is_valid() {
if status.is_invalid() {
error!(?status, ?payload, "Invalid newPayloadV1",);
panic!("Invalid newPayloadV1: {status:?}");
}
status = self.new_payload_v1(payload.clone()).await?;
}
Ok(status)
}
async fn new_payload_v2_wait(
&self,
payload: ExecutionPayloadInputV2,
) -> TransportResult<PayloadStatus> {
let mut status = self.new_payload_v2(payload.clone()).await?;
while !status.is_valid() {
if status.is_invalid() {
error!(?status, ?payload, "Invalid newPayloadV2",);
panic!("Invalid newPayloadV2: {status:?}");
}
status = self.new_payload_v2(payload.clone()).await?;
}
Ok(status)
}
async fn new_payload_v3_wait(
&self,
payload: ExecutionPayloadV3,
versioned_hashes: Vec<B256>,
parent_beacon_block_root: B256,
) -> TransportResult<PayloadStatus> {
let mut status = self
.new_payload_v3(payload.clone(), versioned_hashes.clone(), parent_beacon_block_root)
.await?;
while !status.is_valid() {
if status.is_invalid() {
error!(
?status,
?payload,
?versioned_hashes,
?parent_beacon_block_root,
"Invalid newPayloadV3",
);
panic!("Invalid newPayloadV3: {status:?}");
}
if status.is_syncing() {
return Err(alloy_json_rpc::RpcError::UnsupportedFeature(
"invalid range: no canonical state found for parent of requested block",
))
}
status = self
.new_payload_v3(payload.clone(), versioned_hashes.clone(), parent_beacon_block_root)
.await?;
}
Ok(status)
}
async fn new_payload_v4_wait(
&self,
payload: ExecutionPayloadV3,
versioned_hashes: Vec<B256>,
parent_beacon_block_root: B256,
requests_hash: B256,
) -> TransportResult<PayloadStatus> {
// We cannot use `self.new_payload_v4` because it does not support sending
// `RequestsOrHash::Hash`
let mut status: PayloadStatus = self
.client()
.request(
"engine_newPayloadV4",
(
payload.clone(),
versioned_hashes.clone(),
parent_beacon_block_root,
RequestsOrHash::Hash(requests_hash),
),
)
.await?;
while !status.is_valid() {
if status.is_invalid() {
error!(
?status,
?payload,
?versioned_hashes,
?parent_beacon_block_root,
"Invalid newPayloadV4",
);
panic!("Invalid newPayloadV4: {status:?}");
}
if status.is_syncing() {
return Err(alloy_json_rpc::RpcError::UnsupportedFeature(
"invalid range: no canonical state found for parent of requested block",
))
}
status = self
.client()
.request(
"engine_newPayloadV4",
(
payload.clone(),
versioned_hashes.clone(),
parent_beacon_block_root,
RequestsOrHash::Hash(requests_hash),
),
)
.await?;
}
Ok(status)
}
async fn fork_choice_updated_v1_wait(
&self,
fork_choice_state: ForkchoiceState,
@@ -133,60 +282,39 @@ where
}
}
pub(crate) fn block_to_new_payload(
block: AnyRpcBlock,
is_optimism: bool,
) -> eyre::Result<(EngineApiMessageVersion, serde_json::Value)> {
let block = block
.into_inner()
.map_header(|header| header.map(|h| h.into_header_with_defaults()))
.try_map_transactions(|tx| {
// try to convert unknowns into op type so that we can also support optimism
tx.try_into_either::<op_alloy_consensus::OpTxEnvelope>()
})?
.into_consensus();
// Convert to execution payload
let (payload, sidecar) = ExecutionPayload::from_block_slow(&block);
let (version, params) = match payload {
/// Calls the correct `engine_newPayload` method depending on the given [`ExecutionPayload`] and its
/// versioned variant. Returns the [`EngineApiMessageVersion`] depending on the payload's version.
///
/// # Panics
/// If the given payload is a V3 payload, but a parent beacon block root is provided as `None`.
pub(crate) async fn call_new_payload<N, P: EngineApiValidWaitExt<N>>(
provider: P,
payload: ExecutionPayload,
sidecar: ExecutionPayloadSidecar,
parent_beacon_block_root: Option<B256>,
versioned_hashes: Vec<B256>,
) -> TransportResult<EngineApiMessageVersion> {
match payload {
ExecutionPayload::V3(payload) => {
let cancun = sidecar.cancun().unwrap();
// We expect the caller to provide `parent_beacon_block_root` for V3 payloads.
let parent_beacon_block_root = parent_beacon_block_root
.expect("parent_beacon_block_root is required for V3 payloads and higher");
if let Some(prague) = sidecar.prague() {
if is_optimism {
(
EngineApiMessageVersion::V4,
serde_json::to_value((
OpExecutionPayloadV4 {
payload_inner: payload,
withdrawals_root: block.withdrawals_root.unwrap(),
},
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
Requests::default(),
))?,
)
} else {
(
EngineApiMessageVersion::V4,
serde_json::to_value((
payload,
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
prague.requests.requests_hash(),
))?,
)
}
} else {
(
EngineApiMessageVersion::V3,
serde_json::to_value((
if let Some(requests_hash) = sidecar.requests_hash() {
provider
.new_payload_v4_wait(
payload,
cancun.versioned_hashes.clone(),
cancun.parent_beacon_block_root,
))?,
)
versioned_hashes,
parent_beacon_block_root,
requests_hash,
)
.await?;
Ok(EngineApiMessageVersion::V4)
} else {
provider
.new_payload_v3_wait(payload, versioned_hashes, parent_beacon_block_root)
.await?;
Ok(EngineApiMessageVersion::V3)
}
}
ExecutionPayload::V2(payload) => {
@@ -195,43 +323,16 @@ pub(crate) fn block_to_new_payload(
withdrawals: Some(payload.withdrawals),
};
(EngineApiMessageVersion::V2, serde_json::to_value((input,))?)
provider.new_payload_v2_wait(input).await?;
Ok(EngineApiMessageVersion::V2)
}
ExecutionPayload::V1(payload) => {
(EngineApiMessageVersion::V1, serde_json::to_value((payload,))?)
provider.new_payload_v1_wait(payload).await?;
Ok(EngineApiMessageVersion::V1)
}
};
Ok((version, params))
}
/// Calls the correct `engine_newPayload` method depending on the given [`ExecutionPayload`] and its
/// versioned variant. Returns the [`EngineApiMessageVersion`] depending on the payload's version.
///
/// # Panics
/// If the given payload is a V3 payload, but a parent beacon block root is provided as `None`.
pub(crate) async fn call_new_payload<N: Network, P: Provider<N>>(
provider: P,
version: EngineApiMessageVersion,
params: serde_json::Value,
) -> TransportResult<()> {
let method = version.method_name();
let mut status: PayloadStatus = provider.client().request(method, &params).await?;
while !status.is_valid() {
if status.is_invalid() {
error!(?status, ?params, "Invalid {method}",);
panic!("Invalid {method}: {status:?}");
}
if status.is_syncing() {
return Err(alloy_json_rpc::RpcError::UnsupportedFeature(
"invalid range: no canonical state found for parent of requested block",
))
}
status = provider.client().request(method, &params).await?;
}
Ok(())
}
/// Calls the correct `engine_forkchoiceUpdated` method depending on the given

View File

@@ -9,20 +9,6 @@ repository.workspace = true
description = "Reth node implementation"
default-run = "reth"
[package.metadata.deb]
maintainer = "reth team"
depends = "$auto"
section = "network"
priority = "optional"
maintainer-scripts = "../../pkg/reth/debian/"
assets = [
"$auto",
["../../README.md", "usr/share/doc/reth/", "644"],
["../../LICENSE-APACHE", "usr/share/doc/reth/", "644"],
["../../LICENSE-MIT", "usr/share/doc/reth/", "644"],
]
systemd-units = { enable = false, start = false, unit-name = "reth", unit-scripts = "../../pkg/reth/debian" }
[lints]
workspace = true
@@ -54,7 +40,7 @@ reth-node-api.workspace = true
reth-node-core.workspace = true
reth-ethereum-payload-builder.workspace = true
reth-ethereum-primitives.workspace = true
reth-node-ethereum.workspace = true
reth-node-ethereum = { workspace = true, features = ["js-tracer"] }
reth-node-builder.workspace = true
reth-node-metrics.workspace = true
reth-consensus.workspace = true
@@ -78,21 +64,11 @@ eyre.workspace = true
[dev-dependencies]
backon.workspace = true
similar-asserts.workspace = true
tempfile.workspace = true
[features]
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer"]
otlp = [
"reth-ethereum-cli/otlp",
"reth-node-core/otlp",
]
js-tracer = [
"reth-node-builder/js-tracer",
"reth-node-ethereum/js-tracer",
"reth-rpc/js-tracer",
"reth-rpc-eth-types/js-tracer",
]
default = ["jemalloc", "reth-revm/portable"]
dev = ["reth-ethereum-cli/dev"]
@@ -100,7 +76,6 @@ asm-keccak = [
"reth-node-core/asm-keccak",
"reth-primitives/asm-keccak",
"reth-ethereum-cli/asm-keccak",
"reth-node-ethereum/asm-keccak",
]
jemalloc = [
@@ -114,12 +89,6 @@ jemalloc-prof = [
"reth-cli-util/jemalloc-prof",
"reth-ethereum-cli/jemalloc-prof",
]
jemalloc-unprefixed = [
"reth-cli-util/jemalloc-unprefixed",
"reth-node-core/jemalloc",
"reth-node-metrics/jemalloc",
"reth-ethereum-cli/jemalloc",
]
tracy-allocator = [
"reth-cli-util/tracy-allocator",
"reth-ethereum-cli/tracy-allocator",
@@ -140,27 +109,22 @@ snmalloc-native = [
min-error-logs = [
"tracing/release_max_level_error",
"reth-ethereum-cli/min-error-logs",
"reth-node-core/min-error-logs",
]
min-warn-logs = [
"tracing/release_max_level_warn",
"reth-ethereum-cli/min-warn-logs",
"reth-node-core/min-warn-logs",
]
min-info-logs = [
"tracing/release_max_level_info",
"reth-ethereum-cli/min-info-logs",
"reth-node-core/min-info-logs",
]
min-debug-logs = [
"tracing/release_max_level_debug",
"reth-ethereum-cli/min-debug-logs",
"reth-node-core/min-debug-logs",
]
min-trace-logs = [
"tracing/release_max_level_trace",
"reth-ethereum-cli/min-trace-logs",
"reth-node-core/min-trace-logs",
]
[[bin]]

View File

@@ -25,7 +25,7 @@
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
pub mod cli;

View File

@@ -2,7 +2,7 @@ use reth_ethereum_primitives::EthPrimitives;
use reth_evm::ConfigureEvm;
use reth_network::{protocol::IntoRlpxSubProtocol, NetworkProtocols};
use reth_network_api::FullNetwork;
use reth_node_api::ConsensusEngineEvent;
use reth_node_api::BeaconConsensusEngineEvent;
use reth_node_core::args::RessArgs;
use reth_provider::providers::{BlockchainProvider, ProviderNodeTypes};
use reth_ress_protocol::{NodeType, ProtocolState, RessProtocolHandler};
@@ -19,7 +19,7 @@ pub fn install_ress_subprotocol<P, E, N>(
evm_config: E,
network: N,
task_executor: TaskExecutor,
engine_events: EventStream<ConsensusEngineEvent<EthPrimitives>>,
engine_events: EventStream<BeaconConsensusEngineEvent<EthPrimitives>>,
) -> eyre::Result<()>
where
P: ProviderNodeTypes<Primitives = EthPrimitives>,

View File

@@ -5,20 +5,26 @@ edition = "2021"
[dependencies]
clap = { version = "4", features = ["derive"] }
pathdiff = "0.2"
regex = "1"
---
use clap::Parser;
use regex::Regex;
use std::{
borrow::Cow,
fmt, fs, io,
fmt,
fs::{self, File},
io::{self, Write},
iter::once,
path::{Path, PathBuf},
process,
process::{Command, Stdio},
str,
sync::LazyLock,
};
const SECTION_START: &str = "{/* CLI_REFERENCE START */}";
const SECTION_END: &str = "{/* CLI_REFERENCE END */";
const README: &str = r#"import Summary from './SUMMARY.mdx';
# CLI Reference
@@ -38,7 +44,7 @@ macro_rules! regex {
}};
}
/// Generate markdown files from the help output of commands
/// Generate markdown files from help output of commands
#[derive(Parser, Debug)]
#[command(about, long_about = None)]
struct Args {
@@ -62,10 +68,6 @@ struct Args {
#[arg(long)]
root_summary: bool,
/// Whether to generate TypeScript sidebar files
#[arg(long)]
sidebar: bool,
/// Print verbose output
#[arg(short, long)]
verbose: bool,
@@ -120,11 +122,13 @@ fn main() -> io::Result<()> {
}
// Generate SUMMARY.mdx.
let summary: String =
output.iter().map(|(cmd, _)| cmd_summary(cmd, 0)).chain(once("\n".to_string())).collect();
let summary: String = output
.iter()
.map(|(cmd, _)| cmd_summary(None, cmd, 0))
.chain(once("\n".to_string()))
.collect();
println!("Writing SUMMARY.mdx to \"{}\"", out_dir.to_string_lossy());
write_file(&out_dir.join("SUMMARY.mdx"), &summary)?;
write_file(&out_dir.clone().join("SUMMARY.mdx"), &summary)?;
// Generate README.md.
if args.readme {
@@ -137,23 +141,20 @@ fn main() -> io::Result<()> {
// Generate root SUMMARY.mdx.
if args.root_summary {
let root_summary: String =
output.iter().map(|(cmd, _)| cmd_summary(cmd, args.root_indentation)).collect();
let root_summary: String = output
.iter()
.map(|(cmd, _)| {
let root_path = pathdiff::diff_paths(&out_dir, &args.root_dir);
cmd_summary(root_path, cmd, args.root_indentation)
})
.collect();
let path = Path::new(args.root_dir.as_str());
if args.verbose {
println!("Updating root summary in \"{}\"", path.to_string_lossy());
}
update_root_summary(path, &root_summary)?;
}
// Generate TypeScript sidebar files.
if args.sidebar {
let vocs_dir = Path::new(args.root_dir.as_str()).join("vocs");
if args.verbose {
println!("Generating TypeScript sidebar files in \"{}\"", vocs_dir.display());
}
generate_sidebar_files(&vocs_dir, &output, args.verbose)?;
// TODO: This is where we update the cli reference sidebar.ts
// update_root_summary(path, &root_summary)?;
}
Ok(())
@@ -243,200 +244,47 @@ fn parse_description(s: &str) -> (&str, &str) {
}
/// Returns the summary for a command and its subcommands.
fn cmd_summary(cmd: &Cmd, indent: usize) -> String {
fn cmd_summary(md_root: Option<PathBuf>, cmd: &Cmd, indent: usize) -> String {
let cmd_s = cmd.to_string();
let cmd_path = cmd_s.replace(" ", "/");
let indent_string = " ".repeat(indent + (cmd.subcommands.len() * 2));
format!("{}- [`{}`](./{}.mdx)\n", indent_string, cmd_s, cmd_path)
}
/// Overwrites the root SUMMARY.mdx file with the generated content.
fn update_root_summary(root_dir: &Path, root_summary: &str) -> io::Result<()> {
let summary_file = root_dir.join("vocs/docs/pages/cli/SUMMARY.mdx");
println!("Overwriting {}", summary_file.display());
// Simply write the root summary content to the file
write_file(&summary_file, root_summary)
}
/// Generates TypeScript sidebar files for each command.
fn generate_sidebar_files(vocs_dir: &Path, output: &[(Cmd, String)], verbose: bool) -> io::Result<()> {
// Group commands by their root command name (reth or op-reth)
// Also create a map of commands to their help output
let mut commands_by_root: std::collections::HashMap<String, Vec<&Cmd>> = std::collections::HashMap::new();
let mut help_map: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for (cmd, help_output) in output {
let root_name = cmd.command_name().to_string();
commands_by_root.entry(root_name.clone()).or_insert_with(Vec::new).push(cmd);
// Store help output for each command using its string representation as key
help_map.insert(cmd.to_string(), help_output.clone());
}
// Generate sidebar file for each root command
for (root_name, cmds) in commands_by_root {
// Get root help output
let root_help = help_map.get(&root_name).map(|s| s.as_str());
let sidebar_content = generate_sidebar_ts(&root_name, cmds, root_help, &help_map)?;
let file_name = match root_name.as_str() {
"reth" => "sidebar-cli-reth.ts",
"op-reth" => "sidebar-cli-op-reth.ts",
_ => {
if verbose {
println!("Skipping unknown command: {}", root_name);
}
continue;
}
};
let sidebar_file = vocs_dir.join(file_name);
if verbose {
println!("Writing sidebar file: {}", sidebar_file.display());
}
write_file(&sidebar_file, &sidebar_content)?;
}
Ok(())
}
/// Generates TypeScript code for a sidebar file.
fn generate_sidebar_ts(
root_name: &str,
commands: Vec<&Cmd>,
root_help: Option<&str>,
help_map: &std::collections::HashMap<String, String>,
) -> io::Result<String> {
// Find all top-level commands (commands with exactly one subcommand)
let mut top_level_commands: Vec<&Cmd> = commands
.iter()
.copied()
.filter(|cmd| cmd.subcommands.len() == 1)
.collect();
// Remove duplicates using a set
let mut seen = std::collections::HashSet::new();
top_level_commands.retain(|cmd| {
let key = &cmd.subcommands[0];
seen.insert(key.clone())
});
// Sort by the order they appear in help output, not alphabetically
if let Some(help) = root_help {
let help_order = parse_sub_commands(help);
top_level_commands.sort_by(|a, b| {
let a_name = &a.subcommands[0];
let b_name = &b.subcommands[0];
let a_pos = help_order.iter().position(|x| x == a_name).unwrap_or(usize::MAX);
let b_pos = help_order.iter().position(|x| x == b_name).unwrap_or(usize::MAX);
a_pos.cmp(&b_pos)
});
}
// Generate TypeScript code
let var_name = match root_name {
"reth" => "rethCliSidebar",
"op-reth" => "opRethCliSidebar",
_ => "cliSidebar",
let full_cmd_path = match md_root {
None => cmd_path,
Some(md_root) => format!("{}/{}", md_root.to_string_lossy(), cmd_path),
};
let mut ts_code = String::from("import { SidebarItem } from \"vocs\";\n\n");
ts_code.push_str(&format!("export const {}: SidebarItem = {{\n", var_name));
ts_code.push_str(&format!(" text: \"{}\",\n", root_name));
ts_code.push_str(&format!(" link: \"/cli/{}\",\n", root_name));
ts_code.push_str(" collapsed: false,\n");
ts_code.push_str(" items: [\n");
for (idx, cmd) in top_level_commands.iter().enumerate() {
let is_last = idx == top_level_commands.len() - 1;
if let Some(item_str) = build_sidebar_item(root_name, cmd, &commands, 1, help_map, is_last) {
ts_code.push_str(&item_str);
}
}
ts_code.push_str(" ]\n");
ts_code.push_str("};\n\n");
Ok(ts_code)
let indent_string = " ".repeat(indent + (cmd.subcommands.len() * 2));
format!("{}- [`{}`](/cli/{})\n", indent_string, cmd_s, full_cmd_path)
}
/// Builds a sidebar item for a command and its children.
/// Returns TypeScript code string.
fn build_sidebar_item(
root_name: &str,
cmd: &Cmd,
all_commands: &[&Cmd],
depth: usize,
help_map: &std::collections::HashMap<String, String>,
is_last: bool,
) -> Option<String> {
let full_cmd_name = cmd.to_string();
let link_path = format!("/cli/{}", full_cmd_name.replace(" ", "/"));
// Find all direct child commands (commands whose subcommands start with this command's subcommands)
let mut children: Vec<&Cmd> = all_commands
.iter()
.copied()
.filter(|other_cmd| {
other_cmd.subcommands.len() == cmd.subcommands.len() + 1
&& other_cmd.subcommands[..cmd.subcommands.len()] == cmd.subcommands[..]
})
.collect();
// Sort children by the order they appear in help output, not alphabetically
if children.len() > 1 {
// Get help output for this command to determine subcommand order
if let Some(help_output) = help_map.get(&full_cmd_name) {
let help_order = parse_sub_commands(help_output);
children.sort_by(|a, b| {
let a_name = a.subcommands.last().unwrap();
let b_name = b.subcommands.last().unwrap();
let a_pos = help_order.iter().position(|x| x == a_name).unwrap_or(usize::MAX);
let b_pos = help_order.iter().position(|x| x == b_name).unwrap_or(usize::MAX);
a_pos.cmp(&b_pos)
});
} else {
// Fall back to alphabetical if we can't get help
children.sort_by(|a, b| {
a.subcommands.last().unwrap().cmp(b.subcommands.last().unwrap())
});
}
}
let indent = " ".repeat(depth);
let mut item_str = String::new();
item_str.push_str(&format!("{}{{\n", indent));
item_str.push_str(&format!("{} text: \"{}\",\n", indent, full_cmd_name));
item_str.push_str(&format!("{} link: \"{}\"", indent, link_path));
if !children.is_empty() {
item_str.push_str(",\n");
item_str.push_str(&format!("{} collapsed: true,\n", indent));
item_str.push_str(&format!("{} items: [\n", indent));
for (idx, child_cmd) in children.iter().enumerate() {
let child_is_last = idx == children.len() - 1;
if let Some(child_str) = build_sidebar_item(root_name, child_cmd, all_commands, depth + 1, help_map, child_is_last) {
item_str.push_str(&child_str);
}
}
item_str.push_str(&format!("{} ]\n", indent));
if is_last {
item_str.push_str(&format!("{}}}\n", indent));
} else {
item_str.push_str(&format!("{}}},\n", indent));
}
} else {
item_str.push_str("\n");
if is_last {
item_str.push_str(&format!("{}}}\n", indent));
} else {
item_str.push_str(&format!("{}}},\n", indent));
}
/// Replaces the CLI_REFERENCE section in the root SUMMARY.mdx file.
fn update_root_summary(root_dir: &Path, root_summary: &str) -> io::Result<()> {
let summary_file = root_dir.join("SUMMARY.mdx");
let original_summary_content = fs::read_to_string(&summary_file)?;
let section_re = regex!(&format!(r"(?s)\s*{SECTION_START}.*?{SECTION_END}"));
if !section_re.is_match(&original_summary_content) {
eprintln!(
"Could not find CLI_REFERENCE section in {}. Please add the following section to the file:\n{}\n... CLI Reference goes here ...\n\n{}",
summary_file.display(),
SECTION_START,
SECTION_END
);
process::exit(1);
}
Some(item_str)
let section_end_re = regex!(&format!(r".*{SECTION_END}"));
let last_line = section_end_re
.find(&original_summary_content)
.map(|m| m.as_str().to_string())
.expect("Could not extract last line of CLI_REFERENCE section");
let root_summary_s = root_summary.trim_end().replace("\n\n", "\n");
let replace_with = format!(" {}\n{}\n{}", SECTION_START, root_summary_s, last_line);
let new_root_summary =
section_re.replace(&original_summary_content, replace_with.as_str()).to_string();
let mut root_summary_file = File::create(&summary_file)?;
root_summary_file.write_all(new_root_summary.as_bytes())
}
/// Preprocesses the help output of a command.
@@ -461,11 +309,6 @@ fn preprocess_help(s: &str) -> Cow<'_, str> {
r"(rpc.max-tracing-requests <COUNT>\n.*\n.*\n.*\n.*\n.*)\[default: \d+\]",
r"$1[default: <NUM CPU CORES-2>]",
),
// Handle engine.reserved-cpu-cores dynamic default
(
r"(engine\.reserved-cpu-cores.*)\[default: \d+\]",
r"$1[default: <DYNAMIC: min(2, CPU cores)>]",
),
];
patterns
.iter()

21
book/cli/update.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -eo pipefail
BOOK_ROOT="$(dirname "$(dirname "$0")")"
RETH=${1:-"$(dirname "$BOOK_ROOT")/target/debug/reth"}
VOCS_PAGES_ROOT="$BOOK_ROOT/vocs/docs/pages"
echo "Generating CLI documentation for reth at $RETH"
echo "Using book root: $BOOK_ROOT"
echo "Using vocs pages root: $VOCS_PAGES_ROOT"
cmd=(
"$(dirname "$0")/help.rs"
--root-dir "$BOOK_ROOT/"
--root-indentation 2
--root-summary
--verbose
--out-dir "$VOCS_PAGES_ROOT/cli/"
"$RETH"
)
echo "Running: $" "${cmd[*]}"
"${cmd[@]}"

BIN
book/vocs/bun.lockb Executable file

Binary file not shown.

View File

@@ -1,3 +1,5 @@
import React from 'react'
interface SdkProject {
name: string
description: string
@@ -41,16 +43,16 @@ const projects: SdkProject[] = [
export function SdkShowcase() {
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{projects.map((project) => (
{projects.map((project, index) => (
<div
key={project.name}
key={index}
className="relative bg-white/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-xl p-6 hover:bg-black/5 dark:hover:bg-white/10 transition-colors group"
>
{/* LoC Badge */}
<div className="absolute top-4 right-4 bg-black/10 dark:bg-white/10 text-black dark:text-white px-3 py-1 rounded-full text-sm font-medium">
{project.loc} LoC
</div>
{/* Content */}
<div className="space-y-3">
<div>
@@ -61,11 +63,11 @@ export function SdkShowcase() {
{project.company}
</p>
</div>
<p className="text-gray-700 dark:text-gray-300 text-sm leading-relaxed">
{project.description}
</p>
{/* GitHub Link */}
<a
href={project.githubUrl}
@@ -83,4 +85,4 @@ export function SdkShowcase() {
))}
</div>
)
}
}

View File

@@ -1,3 +1,5 @@
import React from 'react'
interface TrustedCompany {
name: string
logoUrl: string
@@ -25,9 +27,9 @@ const companies: TrustedCompany[] = [
export function TrustedBy() {
return (
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{companies.map((company) => (
{companies.map((company, index) => (
<div
key={company.name}
key={index}
className="relative bg-white/5 dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-xl p-8 hover:bg-black/5 dark:hover:bg-white/10 transition-colors flex flex-col items-center justify-center h-40 group"
>
{/* Company Logo */}
@@ -44,4 +46,4 @@ export function TrustedBy() {
))}
</div>
)
}
}

View File

@@ -0,0 +1,47 @@
- [`reth`](/cli/reth)
- [`reth node`](/cli/reth/node)
- [`reth init`](/cli/reth/init)
- [`reth init-state`](/cli/reth/init-state)
- [`reth import`](/cli/reth/import)
- [`reth import-era`](/cli/reth/import-era)
- [`reth dump-genesis`](/cli/reth/dump-genesis)
- [`reth db`](/cli/reth/db)
- [`reth db stats`](/cli/reth/db/stats)
- [`reth db list`](/cli/reth/db/list)
- [`reth db checksum`](/cli/reth/db/checksum)
- [`reth db diff`](/cli/reth/db/diff)
- [`reth db get`](/cli/reth/db/get)
- [`reth db get mdbx`](/cli/reth/db/get/mdbx)
- [`reth db get static-file`](/cli/reth/db/get/static-file)
- [`reth db drop`](/cli/reth/db/drop)
- [`reth db clear`](/cli/reth/db/clear)
- [`reth db clear mdbx`](/cli/reth/db/clear/mdbx)
- [`reth db clear static-file`](/cli/reth/db/clear/static-file)
- [`reth db version`](/cli/reth/db/version)
- [`reth db path`](/cli/reth/db/path)
- [`reth download`](/cli/reth/download)
- [`reth stage`](/cli/reth/stage)
- [`reth stage run`](/cli/reth/stage/run)
- [`reth stage drop`](/cli/reth/stage/drop)
- [`reth stage dump`](/cli/reth/stage/dump)
- [`reth stage dump execution`](/cli/reth/stage/dump/execution)
- [`reth stage dump storage-hashing`](/cli/reth/stage/dump/storage-hashing)
- [`reth stage dump account-hashing`](/cli/reth/stage/dump/account-hashing)
- [`reth stage dump merkle`](/cli/reth/stage/dump/merkle)
- [`reth stage unwind`](/cli/reth/stage/unwind)
- [`reth stage unwind to-block`](/cli/reth/stage/unwind/to-block)
- [`reth stage unwind num-blocks`](/cli/reth/stage/unwind/num-blocks)
- [`reth p2p`](/cli/reth/p2p)
- [`reth p2p header`](/cli/reth/p2p/header)
- [`reth p2p body`](/cli/reth/p2p/body)
- [`reth p2p rlpx`](/cli/reth/p2p/rlpx)
- [`reth p2p rlpx ping`](/cli/reth/p2p/rlpx/ping)
- [`reth config`](/cli/reth/config)
- [`reth debug`](/cli/reth/debug)
- [`reth debug execution`](/cli/reth/debug/execution)
- [`reth debug merkle`](/cli/reth/debug/merkle)
- [`reth debug in-memory-merkle`](/cli/reth/debug/in-memory-merkle)
- [`reth debug build-block`](/cli/reth/debug/build-block)
- [`reth recover`](/cli/reth/recover)
- [`reth recover storage-tries`](/cli/reth/recover/storage-tries)
- [`reth prune`](/cli/reth/prune)

View File

@@ -2,7 +2,7 @@ import Summary from './SUMMARY.mdx';
# CLI Reference
The Reth node is operated via the CLI by running the `reth node` command. To stop it, press `ctrl-c`. You may need to wait a bit as Reth tears down existing p2p connections or performs other cleanup tasks.
The Reth node is operated via the CLI by running the `reth node` command. To stop it, press `ctrl-c`. You may need to wait a bit as Reth tears down existing p2p connections or other cleanup tasks.
However, Reth has more commands:

View File

@@ -12,17 +12,17 @@ Commands:
node Start the node
init Initialize the database from a genesis file
init-state Initialize the database from a state dump file
import This syncs RLP encoded blocks from a file or files
import This syncs RLP encoded blocks from a file
import-era This syncs ERA encoded blocks from a directory
export-era Exports block to era1 files in a specified directory
dump-genesis Dumps genesis block JSON configuration to stdout
db Database debugging utilities
download Download public node snapshots
stage Manipulate individual stages
p2p P2P Debugging utilities
config Write config to stdout
debug Various debug routines
recover Scripts for node recovery
prune Prune according to the configuration without any limits
re-execute Re-execute blocks in parallel to verify historical sync correctness
help Print this message or the help of the given subcommand(s)
Options:
@@ -36,13 +36,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -51,13 +51,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -68,11 +68,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -94,13 +89,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -113,46 +108,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -22,13 +22,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -37,13 +37,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -54,11 +54,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -80,13 +75,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -99,46 +94,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -0,0 +1,158 @@
# reth db
Database debugging utilities
```bash
$ reth db --help
```
```txt
Usage: reth db [OPTIONS] <COMMAND>
Commands:
stats Lists all the tables, their entry count and their size
list Lists the contents of a table
checksum Calculates the content checksum of a table
diff Create a diff between two database tables or two entire databases
get Gets the content of a table for the given key
drop Deletes all database entries
clear Deletes all table entries
version Lists current and local database versions
path Returns the full database path
help Print this message or the help of the given subcommand(s)
Options:
-h, --help
Print help (see a summary with '-h')
Datadir:
--datadir <DATA_DIR>
The path to the data dir for all reth files and subdirectories.
Defaults to the OS-specific data directory:
- Linux: `$XDG_DATA_HOME/reth/` or `$HOME/.local/share/reth/`
- Windows: `{FOLDERID_RoamingAppData}/reth/`
- macOS: `$HOME/Library/Application Support/reth/`
[default: default]
--datadir.static-files <PATH>
The absolute path to store static files in.
--config <FILE>
The path to the configuration file to use
--chain <CHAIN_OR_PATH>
The chain this node is running.
Possible values are either a built-in chain or the path to a chain specification file.
Built-in chains:
mainnet, sepolia, holesky, hoodi, dev
[default: mainnet]
Database:
--db.log-level <LOG_LEVEL>
Database logging level. Levels higher than "notice" require a debug build
Possible values:
- fatal: Enables logging for critical conditions, i.e. assertion failures
- error: Enables logging for error conditions
- warn: Enables logging for warning conditions
- notice: Enables logging for normal but significant condition
- verbose: Enables logging for verbose informational
- debug: Enables logging for debug-level messages
- trace: Enables logging for trace debug-level messages
- extra: Enables logging for extra debug-level messages
--db.exclusive <EXCLUSIVE>
Open environment in exclusive/monopolistic mode. Makes it possible to open a database on an NFS volume
[possible values: true, false]
--db.max-size <MAX_SIZE>
Maximum database size (e.g., 4TB, 8MB)
--db.growth-step <GROWTH_STEP>
Database growth step (e.g., 4GB, 4KB)
--db.read-transaction-timeout <READ_TRANSACTION_TIMEOUT>
Read transaction timeout in seconds, 0 means no timeout
Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
[default: ]
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
--log.file.filter <FILTER>
The filter to use for logs written to the log file
[default: debug]
--log.file.directory <PATH>
The path to put log files in
[default: <CACHE_DIR>/logs]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
[default: 200]
--log.file.max-files <COUNT>
The maximum amount of log files that will be stored. If set to 0, background file logging is disabled
[default: 5]
--log.journald
Write logs to journald
--log.journald.filter <FILTER>
The filter to use for logs written to journald
[default: error]
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Colors on
- never: Colors off
Display:
-v, --verbosity...
Set the minimum log level.
-v Errors
-vv Warnings
-vvv Info
-vvvv Debug
-vvvvv Traces (warning: very verbose!)
-q, --quiet
Silence all log output
```

View File

@@ -39,13 +39,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -54,13 +54,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -71,11 +71,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -97,13 +92,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -116,46 +111,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -31,13 +31,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -46,13 +46,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -63,11 +63,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -89,13 +84,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -108,46 +103,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -30,13 +30,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -45,13 +45,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -62,11 +62,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -88,13 +83,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -107,46 +102,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -0,0 +1,109 @@
# reth db clear static-file
Deletes all static file segment entries
```bash
$ reth db clear static-file --help
```
```txt
Usage: reth db clear static-file [OPTIONS] <SEGMENT>
Arguments:
<SEGMENT>
Possible values:
- headers: Static File segment responsible for the `CanonicalHeaders`, `Headers`, `HeaderTerminalDifficulties` tables
- transactions: Static File segment responsible for the `Transactions` table
- receipts: Static File segment responsible for the `Receipts` table
- block-meta: Static File segment responsible for the `BlockBodyIndices`, `BlockOmmers`, `BlockWithdrawals` tables
Options:
-h, --help
Print help (see a summary with '-h')
Datadir:
--chain <CHAIN_OR_PATH>
The chain this node is running.
Possible values are either a built-in chain or the path to a chain specification file.
Built-in chains:
mainnet, sepolia, holesky, hoodi, dev
[default: mainnet]
Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
[default: ]
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
--log.file.filter <FILTER>
The filter to use for logs written to the log file
[default: debug]
--log.file.directory <PATH>
The path to put log files in
[default: <CACHE_DIR>/logs]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
[default: 200]
--log.file.max-files <COUNT>
The maximum amount of log files that will be stored. If set to 0, background file logging is disabled
[default: 5]
--log.journald
Write logs to journald
--log.journald.filter <FILTER>
The filter to use for logs written to journald
[default: error]
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Colors on
- never: Colors off
Display:
-v, --verbosity...
Set the minimum log level.
-v Errors
-vv Warnings
-vvv Info
-vvvv Debug
-vvvvv Traces (warning: very verbose!)
-q, --quiet
Silence all log output
```

View File

@@ -35,20 +35,7 @@ Database:
[possible values: true, false]
--db.max-size <MAX_SIZE>
Maximum database size (e.g., 4TB, 8TB).
This sets the "map size" of the database. If the database grows beyond this limit, the node will stop with an "environment map size limit reached" error.
The default value is 8TB.
--db.page-size <PAGE_SIZE>
Database page size (e.g., 4KB, 8KB, 16KB).
Specifies the page size used by the MDBX database.
The page size determines the maximum database size. MDBX supports up to 2^31 pages, so with the default 4KB page size, the maximum database size is 8TB. To allow larger databases, increase this value to 8KB or higher.
WARNING: This setting is only configurable at database creation; changing it later requires re-syncing.
Maximum database size (e.g., 4TB, 8MB)
--db.growth-step <GROWTH_STEP>
Database growth step (e.g., 4GB, 4KB)
@@ -56,12 +43,6 @@ Database:
--db.read-transaction-timeout <READ_TRANSACTION_TIMEOUT>
Read transaction timeout in seconds, 0 means no timeout
--db.max-readers <MAX_READERS>
Maximum number of readers allowed to access the database concurrently
--db.sync-mode <SYNC_MODE>
Controls how aggressively the database synchronizes data to disk
--table <TABLE>
The table name to diff. If not specified, all tables are diffed.
@@ -82,13 +63,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -97,13 +78,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -114,11 +95,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -140,13 +116,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -159,46 +135,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -29,13 +29,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -44,13 +44,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -61,11 +61,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -87,13 +82,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -106,46 +101,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -31,13 +31,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -46,13 +46,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -63,11 +63,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -89,13 +84,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -108,46 +103,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -6,7 +6,7 @@ Gets the content of a database table for the given key
$ reth db get mdbx --help
```
```txt
Usage: reth db get mdbx [OPTIONS] <TABLE> <KEY> [SUBKEY] [END_KEY] [END_SUBKEY]
Usage: reth db get mdbx [OPTIONS] <TABLE> <KEY> [SUBKEY]
Arguments:
<TABLE>
@@ -18,12 +18,6 @@ Arguments:
[SUBKEY]
The subkey to get content for
[END_KEY]
Optional end key for range query (exclusive upper bound)
[END_SUBKEY]
Optional end subkey for range query (exclusive upper bound)
Options:
--raw
Output bytes instead of human-readable decoded value
@@ -45,13 +39,13 @@ Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
@@ -60,13 +54,13 @@ Logging:
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
[default: terminal]
--log.file.filter <FILTER>
The filter to use for logs written to the log file
@@ -77,11 +71,6 @@ Logging:
[default: <CACHE_DIR>/logs]
--log.file.name <NAME>
The prefix name of the log files
[default: reth.log]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
@@ -103,13 +92,13 @@ Logging:
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Auto-detect
- auto: Colors on
- never: Colors off
[default: always]
Display:
-v, --verbosity...
Set the minimum log level.
@@ -122,46 +111,4 @@ Display:
-q, --quiet
Silence all log output
Tracing:
--tracing-otlp[=<URL>]
Enable `Opentelemetry` tracing export to an OTLP endpoint.
If no value provided, defaults based on protocol: - HTTP: `http://localhost:4318/v1/traces` - gRPC: `http://localhost:4317`
Example: --tracing-otlp=http://collector:4318/v1/traces
[env: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=]
--tracing-otlp-protocol <PROTOCOL>
OTLP transport protocol to use for exporting traces.
- `http`: expects endpoint path to end with `/v1/traces` - `grpc`: expects endpoint without a path
Defaults to HTTP if not specified.
Possible values:
- http: HTTP/Protobuf transport, port 4318, requires `/v1/traces` path
- grpc: gRPC transport, port 4317
[env: OTEL_EXPORTER_OTLP_PROTOCOL=]
[default: http]
--tracing-otlp.filter <FILTER>
Set a filter directive for the OTLP tracer. This controls the verbosity of spans and events sent to the OTLP endpoint. It follows the same syntax as the `RUST_LOG` environment variable.
Example: --tracing-otlp.filter=info,reth=debug,hyper_util=off
Defaults to TRACE if not specified.
[default: debug]
--tracing-otlp.sample-ratio <RATIO>
Trace sampling ratio to control the percentage of traces to export.
Valid range: 0.0 to 1.0 - 1.0, default: Sample all traces - 0.01: Sample 1% of traces - 0.0: Disable sampling
Example: --tracing-otlp.sample-ratio=0.0.
[env: OTEL_TRACES_SAMPLER_ARG=]
```

View File

@@ -0,0 +1,115 @@
# reth db get static-file
Gets the content of a static file segment for the given key
```bash
$ reth db get static-file --help
```
```txt
Usage: reth db get static-file [OPTIONS] <SEGMENT> <KEY>
Arguments:
<SEGMENT>
Possible values:
- headers: Static File segment responsible for the `CanonicalHeaders`, `Headers`, `HeaderTerminalDifficulties` tables
- transactions: Static File segment responsible for the `Transactions` table
- receipts: Static File segment responsible for the `Receipts` table
- block-meta: Static File segment responsible for the `BlockBodyIndices`, `BlockOmmers`, `BlockWithdrawals` tables
<KEY>
The key to get content for
Options:
--raw
Output bytes instead of human-readable decoded value
-h, --help
Print help (see a summary with '-h')
Datadir:
--chain <CHAIN_OR_PATH>
The chain this node is running.
Possible values are either a built-in chain or the path to a chain specification file.
Built-in chains:
mainnet, sepolia, holesky, hoodi, dev
[default: mainnet]
Logging:
--log.stdout.format <FORMAT>
The format to use for logs written to stdout
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
--log.stdout.filter <FILTER>
The filter to use for logs written to stdout
[default: ]
--log.file.format <FORMAT>
The format to use for logs written to the log file
[default: terminal]
Possible values:
- json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging
- log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications
- terminal: Represents terminal-friendly formatting for logs
--log.file.filter <FILTER>
The filter to use for logs written to the log file
[default: debug]
--log.file.directory <PATH>
The path to put log files in
[default: <CACHE_DIR>/logs]
--log.file.max-size <SIZE>
The maximum size (in MB) of one log file
[default: 200]
--log.file.max-files <COUNT>
The maximum amount of log files that will be stored. If set to 0, background file logging is disabled
[default: 5]
--log.journald
Write logs to journald
--log.journald.filter <FILTER>
The filter to use for logs written to journald
[default: error]
--color <COLOR>
Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting
[default: always]
Possible values:
- always: Colors on
- auto: Colors on
- never: Colors off
Display:
-v, --verbosity...
Set the minimum log level.
-v Errors
-vv Warnings
-vvv Info
-vvvv Debug
-vvvvv Traces (warning: very verbose!)
-q, --quiet
Silence all log output
```

Some files were not shown because too many files have changed in this diff Show More