Compare commits

..

18 Commits

Author SHA1 Message Date
Georgios Konstantopoulos
3c0b81dc71 fix(static-file): use with_len for ChangesetOffsetReader to respect committed length
- truncate_changesets: use with_len instead of new() to respect header's
  committed length, not raw file length (prevents reading uncommitted records)
- heal_changeset_sidecar: use with_len for consistency
- heal_changeset_sidecar: handle edge case where actual_rows=0 but
  committed_len>0 (all offsets invalid, truncate to 0)

Amp-Thread-ID: https://ampcode.com/threads/T-019c2569-ce48-7228-96ac-4f4a8db82a2a
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 22:49:14 +00:00
Georgios Konstantopoulos
04cf37ad4a fix(static-file): validate sidecar file size in ChangesetOffsetReader::with_len
If the header claims more records than the sidecar file contains,
return InvalidData error instead of allowing reads past EOF.

This prevents UnexpectedEof panics when the sidecar is corrupted
or truncated.

Amp-Thread-ID: https://ampcode.com/threads/T-019c2569-ce48-7228-96ac-4f4a8db82a2a
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 22:44:48 +00:00
Georgios Konstantopoulos
9edc72e278 fix(static-file): heal changeset sidecar after NippyJar healing
Move sidecar creation AFTER ensure_end_range_consistency() so that
if NippyJar heals and reduces rows, we validate sidecar offsets against
the new row count.

The new heal_changeset_sidecar() method:
1. Opens sidecar with committed_len from post-heal header
2. Checks if last offset points past actual rows
3. If so, finds the last valid block and truncates sidecar + header
4. Commits the healed state

This fixes a crash scenario where:
1. Sidecar + header committed with offsets pointing to rows 0-100
2. Crash, then NippyJar heals and truncates data to 80 rows
3. On open, sidecar offsets 80-100 now point past EOF
4. Read would fail with out-of-bounds error

Amp-Thread-ID: https://ampcode.com/threads/T-019c252f-6e40-728b-87a1-bbb4f0885215
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 21:27:17 +00:00
Georgios Konstantopoulos
20a182abc3 fix(static-file): add sync after sidecar truncate for crash safety
All set_len() calls on the sidecar file must be followed by sync_all()
for crash safety. Without sync, a crash could resurrect the old file
length.

Fixed in:
- ChangesetOffsetWriter::new() - sync after partial record truncation
- ChangesetOffsetWriter::new() - sync after uncommitted record truncation
- ChangesetOffsetWriter::truncate() - sync after prune truncation

Amp-Thread-ID: https://ampcode.com/threads/T-019c252f-6e40-728b-87a1-bbb4f0885215
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 21:11:14 +00:00
Georgios Konstantopoulos
1f951685cb refactor(static-file): move sidecar healing into ChangesetOffsetWriter::new
Move the truncate-to-committed-length logic from StaticFileProviderRW::new()
into ChangesetOffsetWriter::new() by adding a committed_len parameter.

ChangesetOffsetWriter::new() now:
1. Truncates partial records (crash mid-write)
2. Truncates extra complete records to match committed_len (crash after
   sidecar sync but before header commit)
3. Errors if sidecar has fewer records than committed (data corruption)

This mirrors NippyJar's healing behavior where config/header is the commit
boundary.

Amp-Thread-ID: https://ampcode.com/threads/T-019c252f-6e40-728b-87a1-bbb4f0885215
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 21:01:45 +00:00
Georgios Konstantopoulos
33e695349e fix(static-file): add 'Unlikely' to sidecar-shorter-than-header comment
Amp-Thread-ID: https://ampcode.com/threads/T-019c252f-6e40-728b-87a1-bbb4f0885215
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 20:58:12 +00:00
Georgios Konstantopoulos
5e2b06ba0c fix(static-file): truncate changeset offset sidecar to committed length on open
On writer open, heal the .csoff sidecar file to match the committed
header length. This fixes a crash safety bug where:

1. Sidecar is synced with N records
2. Crash before header commits (header still says len=N-1)
3. Restart: writer sees N records, appends at N+1
4. Result: permanent index shift corrupting future reads

Now the sidecar is truncated to the committed header length on open,
mirroring how NippyJar heals its offset/data files to match config.

Also adds test for this crash recovery scenario.

Amp-Thread-ID: https://ampcode.com/threads/T-019c252f-6e40-728b-87a1-bbb4f0885215
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 20:55:35 +00:00
Georgios Konstantopoulos
f0a9ac0ff6 chore: restore deleted code comment in binary search
The comment explains unexpected behavior when out of range during
binary search for account changeset fetch.

Amp-Thread-ID: https://ampcode.com/threads/T-019c251a-2593-726b-b374-a63fdc188b9f
Co-authored-by: Amp <amp@ampcode.com>
2026-02-03 20:04:19 +00:00
Dan Cline
3ad2425bd2 fix: tracing optional 2026-01-30 15:44:49 -05:00
Dan Cline
c263eac79a fix: feature propagation 2026-01-30 15:44:49 -05:00
Dan Cline
8ffcde8d1a fix: sync_all properly 2026-01-30 15:44:49 -05:00
Dan Cline
262149069a chore: fix tests etc 2026-01-30 15:44:49 -05:00
Georgios Konstantopoulos
4a5d43dc46 chore: fix clippy collapsible_if warning
Amp-Thread-ID: https://ampcode.com/threads/T-019c0be2-7d6c-75cf-b39e-aa4cbc782d51
Co-authored-by: Amp <amp@ampcode.com>
2026-01-30 15:44:49 -05:00
Georgios Konstantopoulos
17d3e80953 fix(static-file): avoid double sync_all in commit() that corrupts offsets
The original code called self.sync_all() before self.writer.commit(), but
commit() internally calls sync_all() again. This caused commit_offsets_inner()
to be called twice, which corrupted the offset file due to BufWriter position
desync when reading via get_ref().read_exact().

Now we only sync the changeset sidecar file in commit(), and let the main
writer be synced by its own commit() call.

Fixes test failures in stages::bodies::tests::{execute_body, full_body_download,
partial_body_download, sync_from_previous_progress}

Amp-Thread-ID: https://ampcode.com/threads/T-019c0be2-7d6c-75cf-b39e-aa4cbc782d51
Co-authored-by: Amp <amp@ampcode.com>
2026-01-30 15:44:49 -05:00
Georgios Konstantopoulos
5282231574 chore: fix trailing newline in Cargo.toml
Amp-Thread-ID: https://ampcode.com/threads/T-019c0bae-30ce-719b-890b-a06ba81aae40
Co-authored-by: Amp <amp@ampcode.com>
2026-01-30 15:44:49 -05:00
Georgios Konstantopoulos
ba9bc425d9 refactor: move sidecar sync to sync_all(), fix trie-sparse clippy
- Move changeset offsets sidecar sync logic from commit() to sync_all()
- commit() now calls sync_all() before writing config
- Fix dead_code warning for PARALLEL_PRUNE_THRESHOLD (cfg-gate to std)
- Fix needless_return in is_prune_parallelism_enabled

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c0b47-4de3-76b9-b0bd-54b8696cb9e2
2026-01-30 15:44:49 -05:00
Georgios Konstantopoulos
4e2fa3db44 chore: trigger CI
Amp-Thread-ID: https://ampcode.com/threads/T-019c0b47-4de3-76b9-b0bd-54b8696cb9e2
Co-authored-by: Amp <amp@ampcode.com>
2026-01-30 15:36:31 -05:00
Georgios Konstantopoulos
34811b908c feat(static-file): incremental changeset offset storage
Replace inline Vec<ChangesetOffset> in SegmentHeader with a separate
.csoff sidecar file for incremental append/prune operations.

## Problem

Previously, changeset offsets were stored as Vec<ChangesetOffset> in
SegmentHeader and fully rewritten on every commit. For segments with
500k+ blocks, this meant ~8MB rewritten per commit even when appending
a single block.

## Solution

- Store offsets in a separate .csoff sidecar file (16-byte fixed records)
- SegmentHeader now stores only changeset_offsets_len: u64 (count)
- Append: O(total_blocks) -> O(1) (16 bytes per block)
- Prune: O(remaining_blocks) -> O(1) (truncate sidecar + update len)
- Commit overhead: ~8MB for 500k blocks -> ~100 bytes (header only)

## Breaking Change

This changes the static file format for changeset segments. Existing
changeset static files are not backwards compatible and must be
regenerated.

Amp-Thread-ID: https://ampcode.com/threads/T-019c0b47-4de3-76b9-b0bd-54b8696cb9e2
Co-authored-by: Amp <amp@ampcode.com>
2026-01-30 15:36:31 -05:00
777 changed files with 59019 additions and 19442 deletions

View File

@@ -1,4 +0,0 @@
---
---
Added site-level meta description for SEO.

View File

@@ -1,5 +0,0 @@
---
reth-transaction-pool: patch
---
Renamed and documented validation methods for clarity. `validate_one_no_state` and `validate_one_against_state` are now public methods `validate_stateless` and `validate_stateful` with improved documentation explaining their respective validation phases.

View File

@@ -1,20 +0,0 @@
# Changelogs configuration for reth
# https://github.com/wevm/changelogs
# How to bump packages that depend on changed packages
dependent_bump = "patch"
[changelog]
# Generate per-crate changelogs (vs single root changelog)
format = "per-crate"
# Fixed groups: all always share the same version
# reth binaries share version
[[fixed]]
members = ["reth"]
# Packages to ignore (internal/test-only crates)
ignore = [
"reth-testing-utils",
"reth-bench",
]

View File

@@ -1,17 +0,0 @@
---
reth: minor
reth-cli-commands: minor
reth-e2e-test-utils: minor
reth-ethereum-cli: minor
reth-node-core: minor
reth-optimism-bin: minor
reth-optimism-cli: minor
reth-prune: patch
reth-stages: patch
reth-storage-api: minor
reth-storage-db-api: minor
reth-storage-db-common: patch
reth-storage-provider: patch
---
Introduced `--storage.v2` flag to control storage mode defaults, replacing the `edge` feature flag with `rocksdb` feature. The new flag enables v2 storage settings (static files + RocksDB routing) while individual `--static-files.*` and `--rocksdb.*` flags can still override defaults. Updated feature gates from `edge` to `rocksdb` across all affected crates.

View File

@@ -1,5 +0,0 @@
---
reth: patch
---
Removed Windows platform support from the codebase, including the Windows cross-compilation Dockerfile, build targets in Cross.toml and Makefile, and Windows-specific options in the bug report template.

View File

@@ -1,5 +0,0 @@
---
reth-network: minor
---
Added reason label to backed_off_peers metric. The metric now tracks backed off peers by reason (too_many_peers, graceful_close, connection_error) to improve observability.

View File

@@ -1,5 +0,0 @@
---
reth-trie-sparse-parallel: patch
---
Fixed parallel sparse trie to skip revealing disconnected leaves by checking parent branch reachability before inserting leaf nodes.

View File

@@ -1,5 +0,0 @@
---
ef-tests: patch
---
Removed reth-stateless crate and stateless validation from ef-tests.

View File

@@ -1,6 +0,0 @@
---
reth-engine-tree: patch
reth-trie-sparse-parallel: patch
---
Added tracing spans and debug logs to sparse trie operations for better observability during parallel state root computation.

View File

@@ -1,6 +0,0 @@
---
reth-exex: patch
reth-exex-types: patch
---
Added configurable backfill thresholds to ExEx notifications stream and added regression tests for state provider parity between pipeline and backfill execution paths.

View File

@@ -1,4 +0,0 @@
---
---
Added WebSocket subscription integration tests for eth_subscribe.

View File

@@ -1,4 +0,0 @@
---
---
Improved nightly Docker build failure Slack notification with more detailed formatting and context.

View File

@@ -1,5 +0,0 @@
---
reth-engine-tree: patch
---
Reordered cache size calculations in `ExecutionCache::new` to group related operations together.

View File

@@ -1,7 +0,0 @@
---
reth: patch
reth-cli-commands: patch
reth-node-core: patch
---
Removed experimental ress protocol support for stateless Ethereum nodes.

View File

@@ -1,5 +0,0 @@
---
reth-node-builder: patch
---
Removed biased select in engine service loop to allow fair scheduling of shutdown requests alongside event processing.

View File

@@ -1,5 +0,0 @@
---
reth-transaction-pool: patch
---
Fixed swapped arguments in `blob_tx_priority` function calls, correcting the parameter order to match the function signature.

View File

@@ -1,4 +0,0 @@
---
---
Improved documentation overview page with better structure and clarity.

View File

@@ -1,5 +0,0 @@
---
reth: patch
---
Re-enabled changelog workflow to run automatically on pull requests.

View File

@@ -1,5 +0,0 @@
---
reth-node-events: patch
---
Updated consensus engine log message to be more accurate about received updates.

View File

@@ -1,6 +0,0 @@
---
reth-chainspec: minor
reth-network-peers: minor
---
Removed OP stack bootnodes from default chain configurations and network peers module.

View File

@@ -1,9 +0,0 @@
---
reth-network-api: minor
reth-network-types: minor
reth-network: minor
reth-node-core: minor
reth: minor
---
Added optional ENR fork ID enforcement to filter out peers from incompatible networks during peer discovery, controlled by the `--enforce-enr-fork-id` CLI flag.

View File

@@ -1,5 +0,0 @@
---
reth-primitives: patch
---
Moved feature-referenced dependencies from dev-dependencies to optional dependencies to ensure they are available when their corresponding features are enabled.

View File

@@ -1,5 +0,0 @@
---
reth-transaction-pool: minor
---
Added `IntoIter: Send` bounds to `validate_transactions` and `validate_transactions_with_origin` in the `TransactionValidator` trait, avoiding unnecessary `Vec` collects. Simplified default `validate_transactions_with_origin` to delegate to `validate_transactions`.

View File

@@ -1,6 +0,0 @@
---
reth-static-file-types: patch
reth-provider: patch
---
Move changeset offsets from segment header to external `.csoff` sidecar file for incremental writes and crash recovery.

View File

@@ -1,5 +0,0 @@
---
reth-provider: patch
---
Removed unused staging types from ProviderFactoryBuilder.

View File

@@ -1,5 +0,0 @@
---
reth: patch
---
Added automated changelog generation infrastructure using wevm/changelogs-rs with Claude Code integration. Configured per-crate changelog format with fixed version groups for reth binaries and exclusions for internal test utilities.

View File

@@ -1,5 +0,0 @@
---
reth-trie-sparse: minor
---
Removed `SerialSparseTrie` from the workspace, consolidating on `ParallelSparseTrie` as the single sparse trie implementation in `reth-trie-sparse`.

View File

@@ -1,5 +0,0 @@
---
reth-trie-sparse: patch
---
Fixed a bug where trie nodes could appear in both `updated_nodes` and `removed_nodes` simultaneously by removing entries from `removed_nodes` when a node is inserted as updated.

View File

@@ -1,5 +0,0 @@
---
reth: patch
---
Updated Alloy dependencies from 1.5.2 to 1.6.1.

View File

@@ -1,4 +0,0 @@
---
---
Expanded CLI integration tests with subcommand help coverage, config TOML validation, genesis JSON validation, and send transaction round-trip test for dev mode.

View File

@@ -1,5 +0,0 @@
---
reth-network: minor
---
Added direction labels to `closed_sessions` and `pending_session_failures` metrics. Operators can now distinguish session closures and failures by direction (`active`, `incoming_pending`, `outgoing_pending` for closed sessions; `inbound`, `outbound` for pending session failures).

View File

@@ -1,4 +0,0 @@
---
---
Moved Kurtosis CI failure notifications to the hive Slack channel.

View File

@@ -1,7 +0,0 @@
---
reth-rpc-api: minor
reth-rpc-builder: patch
reth-rpc: minor
---
Added `subscribeFinalizedChainNotifications` RPC endpoint that buffers committed chain notifications and emits them once a new finalized block is received.

View File

@@ -1,6 +0,0 @@
---
reth-trie: minor
reth-trie-parallel: minor
---
Added `root_node` and `storage_root_node` methods to proof calculators for efficient root-only calculations. These methods directly return the root node without requiring dummy targets, replacing the previous workaround of passing fake targets to proof generation.

View File

@@ -1,5 +0,0 @@
---
reth-trie: patch
---
Fixed a potential panic in `ProofCalculator` by clearing internal computation state (`branch_stack`, `child_stack`, `branch_path`, etc.) after errors, preventing stale state from causing `usize` underflow panics when the calculator is reused. Added a test verifying correct behavior after simulated mid-computation errors.

3
.github/CODEOWNERS vendored
View File

@@ -19,6 +19,7 @@ crates/metrics/ @mattsse @Rjected
crates/net/ @mattsse @Rjected
crates/net/downloaders/ @Rjected
crates/node/ @mattsse @Rjected @klkvr
crates/optimism/ @mattsse @Rjected
crates/payload/ @mattsse @Rjected
crates/primitives-traits/ @Rjected @mattsse @klkvr
crates/primitives/ @Rjected @mattsse @klkvr
@@ -38,7 +39,7 @@ crates/storage/libmdbx-rs/ @shekhirin
crates/storage/nippy-jar/ @joshieDo @shekhirin
crates/storage/provider/ @joshieDo @shekhirin @yongkangc
crates/storage/storage-api/ @joshieDo
crates/tasks/ @mattsse @DaniPopes
crates/tasks/ @mattsse
crates/tokio-util/ @mattsse
crates/tracing/ @mattsse @shekhirin
crates/tracing-otlp/ @mattsse @Rjected

View File

@@ -43,6 +43,7 @@ body:
- `~/.cache/reth/logs` on Linux
- `~/Library/Caches/reth/logs` on macOS
- `%localAppData%/reth/logs` on Windows
render: text
validations:
required: false
@@ -57,6 +58,8 @@ body:
- Linux (ARM)
- Mac (Intel)
- Mac (Apple Silicon)
- Windows (x86)
- Windows (ARM)
- type: dropdown
id: container_type
attributes:

View File

@@ -0,0 +1,36 @@
ethereum_package:
participants:
- el_type: reth
el_extra_params:
- "--rpc.eth-proof-window=100"
cl_type: teku
network_params:
preset: minimal
genesis_delay: 5
additional_preloaded_contracts: '
{
"0x4e59b44847b379578588920cA78FbF26c0B4956C": {
"balance": "0ETH",
"code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3",
"storage": {},
"nonce": "1"
}
}'
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
network_params:
holocene_time_offset: 0
isthmus_time_offset: 0

View File

@@ -4,17 +4,3 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
labels:
- "A-dependencies"
commit-message:
prefix: "chore(deps)"
open-pull-requests-limit: 1
groups:
cargo-weekly:
applies-to: "version-updates"
patterns: ["*"]
update-types: ["minor", "patch"]

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env bash
set -uo pipefail
set +e # Disable immediate exit on error
# Array of crates to check
crates_to_check=(
reth-codecs-derive
reth-primitives
@@ -27,22 +28,61 @@ crates_to_check=(
reth-ethereum-forks
reth-ethereum-primitives
reth-ethereum-consensus
reth-stateless
## optimism
reth-optimism-chainspec
reth-optimism-forks
reth-optimism-consensus
reth-optimism-primitives
reth-optimism-evm
)
# Array to hold the results
results=()
# Flag to track if any command fails
any_failed=0
tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t reth-check)
trap 'rm -rf -- "$tmpdir"' EXIT INT TERM
for crate in "${crates_to_check[@]}"; do
outfile="$tmpdir/$crate.log"
if cargo +stable build -p "$crate" --target riscv32imac-unknown-none-elf --no-default-features --color never >"$outfile" 2>&1; then
echo "$crate"
cmd="cargo +stable build -p $crate --target riscv32imac-unknown-none-elf --no-default-features"
if [ -n "$CI" ]; then
echo "::group::$cmd"
else
echo "$crate"
sed 's/^/ /' "$outfile"
echo ""
printf "\n%s:\n %s\n" "$crate" "$cmd"
fi
set +e # Disable immediate exit on error
# Run the command and capture the return code
$cmd
ret_code=$?
set -e # Re-enable immediate exit on error
# Store the result in the dictionary
if [ $ret_code -eq 0 ]; then
results+=("1:✅:$crate")
else
results+=("2:❌:$crate")
any_failed=1
fi
if [ -n "$CI" ]; then
echo "::endgroup::"
fi
done
# Sort the results by status and then by crate name
IFS=$'\n' sorted_results=($(sort <<<"${results[*]}"))
unset IFS
# Print summary
echo -e "\nSummary of build results:"
for result in "${sorted_results[@]}"; do
status="${result#*:}"
status="${status%%:*}"
crate="${result##*:}"
echo "$status $crate"
done
# Exit with a non-zero status if any command fails
exit $any_failed

View File

@@ -1,10 +1,11 @@
#!/usr/bin/env bash
set -uo pipefail
set +e # Disable immediate exit on error
readarray -t crates < <(
cargo metadata --format-version=1 --no-deps | jq -r '.packages[].name' | grep '^reth' | sort
)
# Array of crates to compile
crates=($(cargo metadata --format-version=1 --no-deps | jq -r '.packages[].name' | grep '^reth' | sort))
# Array of crates to exclude
# Used with the `contains` function.
# shellcheck disable=SC2034
exclude_crates=(
# The following require investigation if they can be fixed
@@ -39,6 +40,12 @@ exclude_crates=(
reth-node-ethereum
reth-node-events
reth-node-metrics
reth-optimism-cli
reth-optimism-flashblocks
reth-optimism-node
reth-optimism-payload-builder
reth-optimism-rpc
reth-optimism-storage
reth-rpc
reth-rpc-api
reth-rpc-api-testing-util
@@ -63,7 +70,6 @@ exclude_crates=(
reth-provider # tokio
reth-prune # tokio
reth-prune-static-files # reth-provider
reth-tasks # tokio rt-multi-thread
reth-stages-api # reth-provider, reth-prune
reth-static-file # tokio
reth-transaction-pool # c-kzg
@@ -71,41 +77,77 @@ exclude_crates=(
reth-trie-parallel # tokio
reth-trie-sparse-parallel # rayon
reth-testing-utils
reth-optimism-txpool # reth-transaction-pool
reth-era-downloader # tokio
reth-era-utils # tokio
reth-tracing-otlp
reth-node-ethstats
)
# Array to hold the results
results=()
# Flag to track if any command fails
any_failed=0
tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t reth-check)
trap 'rm -rf -- "$tmpdir"' EXIT INT TERM
# Function to check if a value exists in an array
contains() {
local array="$1[@]"
local seeking="$2"
local element
local seeking=$2
local in=1
for element in "${!array}"; do
[[ "$element" == "$seeking" ]] && return 0
if [[ "$element" == "$seeking" ]]; then
in=0
break
fi
done
return 1
return $in
}
for crate in "${crates[@]}"; do
if contains exclude_crates "$crate"; then
echo "⏭️ $crate"
results+=("3:⏭️:$crate")
continue
fi
outfile="$tmpdir/$crate.log"
if cargo +stable build -p "$crate" --target wasm32-wasip1 --no-default-features --color never >"$outfile" 2>&1; then
echo "$crate"
cmd="cargo +stable build -p $crate --target wasm32-wasip1 --no-default-features"
if [ -n "$CI" ]; then
echo "::group::$cmd"
else
echo "$crate"
sed 's/^/ /' "$outfile"
echo ""
printf "\n%s:\n %s\n" "$crate" "$cmd"
fi
set +e # Disable immediate exit on error
# Run the command and capture the return code
$cmd
ret_code=$?
set -e # Re-enable immediate exit on error
# Store the result in the dictionary
if [ $ret_code -eq 0 ]; then
results+=("1:✅:$crate")
else
results+=("2:❌:$crate")
any_failed=1
fi
if [ -n "$CI" ]; then
echo "::endgroup::"
fi
done
# Sort the results by status and then by crate name
IFS=$'\n' sorted_results=($(sort <<<"${results[*]}"))
unset IFS
# Print summary
echo -e "\nSummary of build results:"
for result in "${sorted_results[@]}"; do
status="${result#*:}"
status="${status%%:*}"
crate="${result##*:}"
echo "$status $crate"
done
# Exit with a non-zero status if any command fails
exit $any_failed

View File

@@ -1,53 +0,0 @@
#!/usr/bin/env bash
# Verifies that Docker images have the expected architectures.
#
# Usage:
# ./verify_image_arch.sh <targets> <registry> <ethereum_tags>
#
# Environment:
# DRY_RUN=true - Skip actual verification, just print what would be checked.
set -euo pipefail
TARGETS="${1:-}"
REGISTRY="${2:-}"
ETHEREUM_TAGS="${3:-}"
DRY_RUN="${DRY_RUN:-false}"
verify_image() {
local image="$1"
shift
local expected_archs=("$@")
echo "Checking $image..."
if [[ "$DRY_RUN" == "true" ]]; then
echo " [dry-run] Would verify architectures: ${expected_archs[*]}"
return 0
fi
manifest=$(docker manifest inspect "$image" 2>/dev/null) || {
echo "::error::Failed to inspect manifest for $image"
return 1
}
for arch in "${expected_archs[@]}"; do
if ! echo "$manifest" | jq -e ".manifests[] | select(.platform.architecture == \"$arch\" and .platform.os == \"linux\")" > /dev/null; then
echo "::error::Missing architecture $arch for $image"
return 1
fi
echo " ✓ linux/$arch"
done
}
if [[ "$TARGETS" == *"nightly"* ]]; then
verify_image "${REGISTRY}/reth:nightly" amd64 arm64
verify_image "${REGISTRY}/reth:nightly-profiling" amd64
verify_image "${REGISTRY}/reth:nightly-edge-profiling" amd64
else
for tag in $(echo "$ETHEREUM_TAGS" | tr ',' ' '); do
verify_image "$tag" amd64 arm64
done
fi
echo "All image architectures verified successfully"

View File

@@ -1,25 +0,0 @@
name: Changelog
on:
pull_request:
types: [opened, synchronize]
jobs:
changelog:
# Skip for fork PRs since they can't access secrets
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install -g @anthropic-ai/claude-code
- uses: wevm/changelogs/check@master
with:
ai: 'claude -p'
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}

View File

@@ -23,6 +23,7 @@ jobs:
matrix:
bin:
- cargo run --bin reth --features "dev"
- cargo run --bin op-reth --features "dev" --manifest-path crates/optimism/bin/Cargo.toml
steps:
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable

View File

@@ -15,7 +15,6 @@ permissions:
jobs:
update:
if: github.repository == 'paradigmxyz/reth'
uses: tempoxyz/ci/.github/workflows/cargo-update-pr.yml@main
secrets:
token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -14,6 +14,12 @@ on:
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 }}
@@ -41,3 +47,27 @@ jobs:
- 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

@@ -1,53 +0,0 @@
name: Build test Docker image
on:
workflow_call:
inputs:
hive_target:
required: true
type: string
description: "Docker bake target to build (e.g. hive-stable, hive-edge)"
artifact_name:
required: false
type: string
default: "artifacts"
description: "Name for the uploaded artifact"
jobs:
build:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v6
- run: mkdir -p artifacts
- name: Get git info
id: git
run: |
echo "sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo "describe=$(git describe --always --tags)" >> "$GITHUB_OUTPUT"
- name: Set up Depot CLI
uses: depot/setup-action@v1
- name: Build reth image
uses: depot/bake-action@v1
env:
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
VERGEN_GIT_DESCRIBE: ${{ steps.git.outputs.describe }}
with:
project: ${{ vars.DEPOT_PROJECT_ID }}
files: docker-bake.hcl
targets: ${{ inputs.hive_target }}
push: false
- name: Upload reth image
uses: actions/upload-artifact@v6
with:
name: ${{ inputs.artifact_name }}
path: ./artifacts

View File

@@ -31,7 +31,6 @@ on:
jobs:
build:
if: github.repository == 'paradigmxyz/reth'
name: Build Docker images
runs-on: ubuntu-24.04
permissions:
@@ -65,32 +64,27 @@ jobs:
if [[ "${{ github.event_name }}" == "push" ]]; then
VERSION="${GITHUB_REF#refs/tags/}"
echo "targets=ethereum" >> "$GITHUB_OUTPUT"
echo "targets=ethereum optimism" >> "$GITHUB_OUTPUT"
# Add 'latest' tag for non-RC releases
if [[ ! "$VERSION" =~ -rc ]]; then
echo "ethereum_tags=${REGISTRY}/reth:${VERSION},${REGISTRY}/reth:latest" >> "$GITHUB_OUTPUT"
{
echo "ethereum_set<<EOF"
echo "ethereum.tags=${REGISTRY}/reth:${VERSION}"
echo "ethereum.tags=${REGISTRY}/reth:latest"
echo "EOF"
} >> "$GITHUB_OUTPUT"
echo "optimism_tags=${REGISTRY}/op-reth:${VERSION},${REGISTRY}/op-reth:latest" >> "$GITHUB_OUTPUT"
else
echo "ethereum_tags=${REGISTRY}/reth:${VERSION}" >> "$GITHUB_OUTPUT"
echo "ethereum_set=ethereum.tags=${REGISTRY}/reth:${VERSION}" >> "$GITHUB_OUTPUT"
echo "optimism_tags=${REGISTRY}/op-reth:${VERSION}" >> "$GITHUB_OUTPUT"
fi
elif [[ "${{ github.event_name }}" == "schedule" ]] || [[ "${{ inputs.build_type }}" == "nightly" ]]; then
echo "targets=nightly" >> "$GITHUB_OUTPUT"
echo "ethereum_tags=${REGISTRY}/reth:nightly" >> "$GITHUB_OUTPUT"
echo "ethereum_set=ethereum.tags=${REGISTRY}/reth:nightly" >> "$GITHUB_OUTPUT"
echo "optimism_tags=${REGISTRY}/op-reth:nightly" >> "$GITHUB_OUTPUT"
else
# git-sha build
echo "targets=ethereum" >> "$GITHUB_OUTPUT"
echo "targets=ethereum optimism" >> "$GITHUB_OUTPUT"
echo "ethereum_tags=${REGISTRY}/reth:${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo "ethereum_set=ethereum.tags=${REGISTRY}/reth:${{ github.sha }}" >> "$GITHUB_OUTPUT"
echo "optimism_tags=${REGISTRY}/op-reth:${{ github.sha }}" >> "$GITHUB_OUTPUT"
fi
- name: Build and push images
@@ -106,38 +100,5 @@ jobs:
targets: ${{ steps.params.outputs.targets }}
push: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}
set: |
${{ steps.params.outputs.ethereum_set }}
- name: Verify image architectures
env:
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
run: |
./.github/scripts/verify_image_arch.sh \
"${{ steps.params.outputs.targets }}" \
"ghcr.io/${{ github.repository_owner }}" \
"${{ steps.params.outputs.ethereum_tags }}"
notify:
name: Notify on failure
runs-on: ubuntu-latest
needs: build
if: failure() && github.event_name == 'schedule'
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: danger
SLACK_ICON_EMOJI: ":rotating_light:"
SLACK_USERNAME: "GitHub Actions"
SLACK_TITLE: ":rotating_light: Nightly Docker Build Failed"
SLACK_MESSAGE: |
The scheduled nightly Docker build failed.
*Commit:* `${{ github.sha }}`
*Branch:* `${{ github.ref_name }}`
*Run:* <https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}|View logs>
*Action required:* Re-run the workflow or investigate the build failure.
SLACK_FOOTER: "paradigmxyz/reth · docker.yml"
MSG_MINIMAL: true
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
ethereum.tags=${{ steps.params.outputs.ethereum_tags }}
optimism.tags=${{ steps.params.outputs.optimism_tags }}

View File

@@ -35,13 +35,13 @@ jobs:
- name: Run e2e tests
run: |
cargo nextest run \
--no-fail-fast \
--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)'
@@ -62,7 +62,6 @@ jobs:
- name: Run RocksDB e2e tests
run: |
cargo nextest run \
--no-fail-fast \
--locked --features "edge" \
-p reth-e2e-test-utils \
-E 'binary(rocksdb)'

View File

@@ -5,7 +5,7 @@ name: hive
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
- cron: "0 */6 * * *"
env:
CARGO_TERM_COLOR: always
@@ -15,24 +15,27 @@ concurrency:
cancel-in-progress: true
jobs:
build-reth-stable:
uses: ./.github/workflows/docker-test.yml
prepare-reth-stable:
uses: ./.github/workflows/prepare-reth.yml
with:
hive_target: hive-stable
image_tag: ghcr.io/paradigmxyz/reth:latest
binary_name: reth
cargo_features: "asm-keccak"
artifact_name: "reth-stable"
secrets: inherit
build-reth-edge:
uses: ./.github/workflows/docker-test.yml
prepare-reth-edge:
uses: ./.github/workflows/prepare-reth.yml
with:
hive_target: hive-edge
image_tag: ghcr.io/paradigmxyz/reth:latest
binary_name: reth
cargo_features: "asm-keccak edge"
artifact_name: "reth-edge"
secrets: inherit
prepare-hive:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: depot-ubuntu-latest-4
runs-on:
group: Reth
steps:
- uses: actions/checkout@v6
- name: Checkout hive tests
@@ -184,11 +187,12 @@ jobs:
- sim: ethereum/eels/consume-rlp
limit: .*tests/paris.*
needs:
- build-reth-stable
- build-reth-edge
- prepare-reth-stable
- prepare-reth-edge
- prepare-hive
name: ${{ matrix.storage }} / ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
runs-on: depot-ubuntu-latest-4
runs-on:
group: Reth
permissions:
issues: write
steps:
@@ -262,4 +266,4 @@ jobs:
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}"
SLACK_WEBHOOK: ${{ secrets.SLACK_HIVE_WEBHOOK_URL }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}

View File

@@ -29,8 +29,11 @@ jobs:
RUST_BACKTRACE: 1
strategy:
matrix:
network: ["ethereum"]
network: ["ethereum", "optimism"]
storage: ["stable", "edge"]
exclude:
- network: optimism
storage: edge
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
@@ -43,13 +46,18 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- name: Run tests
- if: matrix.network == 'ethereum'
name: Run tests
run: |
cargo nextest run \
--no-fail-fast \
--locked --features "asm-keccak ${{ matrix.network }} ${{ matrix.storage == 'edge' && 'edge' || '' }}" \
--workspace --exclude ef-tests \
-E "kind(test) and not binary(e2e_testsuite)"
- if: matrix.network == 'optimism'
name: Run tests
run: |
cargo nextest run \
--locked -p reth-optimism-node
integration-success:
name: integration success
@@ -65,7 +73,7 @@ jobs:
era-files:
name: era1 file integration tests once a day
if: github.event_name == 'schedule' && github.repository == 'paradigmxyz/reth'
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
@@ -77,4 +85,4 @@ jobs:
with:
cache-on-failure: true
- name: run era1 files integration tests
run: cargo nextest run --no-fail-fast --release --package reth-era --test it -- --ignored
run: cargo nextest run --release --package reth-era --test it -- --ignored

95
.github/workflows/kurtosis-op.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
# Runs simple OP stack setup in Kurtosis
name: kurtosis-op
on:
workflow_dispatch:
schedule:
- cron: "0 */6 * * *"
push:
tags:
- "*"
env:
CARGO_TERM_COLOR: always
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
prepare-reth:
uses: ./.github/workflows/prepare-reth.yml
with:
image_tag: ghcr.io/paradigmxyz/op-reth:kurtosis-ci
binary_name: op-reth
cargo_features: asm-keccak
cargo_package: crates/optimism/bin/Cargo.toml
test:
timeout-minutes: 60
strategy:
fail-fast: false
name: run kurtosis
runs-on: depot-ubuntu-latest
needs:
- prepare-reth
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download reth image
uses: actions/download-artifact@v7
with:
name: artifacts
path: /tmp
- name: Load Docker image
run: |
docker load -i /tmp/reth_image.tar &
wait
docker image ls -a
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Run kurtosis
run: |
echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list
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
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')
echo "GETH_RPC=http://127.0.0.1:$GETH_PORT" >> $GITHUB_ENV
echo "RETH_RPC=http://127.0.0.1:$RETH_PORT" >> $GITHUB_ENV
- name: Assert that clients advance
run: |
for i in {1..100}; do
sleep 5
BLOCK_GETH=$(cast bn --rpc-url $GETH_RPC)
BLOCK_RETH=$(cast bn --rpc-url $RETH_RPC)
if [ $BLOCK_GETH -ge 100 ] && [ $BLOCK_RETH -ge 100 ] ; then exit 0; fi
echo "Waiting for clients to advance..., Reth: $BLOCK_RETH Geth: $BLOCK_GETH"
done
kurtosis service logs -a op-devnet op-el-2151908-2-op-reth-op-node-op-kurtosis
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
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}"
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}

View File

@@ -5,7 +5,7 @@ name: kurtosis
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
- cron: "0 */6 * * *"
push:
tags:
@@ -19,12 +19,11 @@ concurrency:
cancel-in-progress: true
jobs:
build-reth:
if: github.repository == 'paradigmxyz/reth'
uses: ./.github/workflows/docker-test.yml
prepare-reth:
uses: ./.github/workflows/prepare-reth.yml
with:
hive_target: kurtosis
secrets: inherit
image_tag: ghcr.io/paradigmxyz/reth:kurtosis-ci
binary_name: reth
test:
timeout-minutes: 60
@@ -33,7 +32,7 @@ jobs:
name: run kurtosis
runs-on: depot-ubuntu-latest
needs:
- build-reth
- prepare-reth
steps:
- uses: actions/checkout@v6
with:
@@ -66,4 +65,4 @@ jobs:
env:
SLACK_COLOR: ${{ job.status }}
SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}"
SLACK_WEBHOOK: ${{ secrets.SLACK_HIVE_WEBHOOK_URL }}
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}

View File

@@ -119,6 +119,11 @@ jobs:
name: MSRV
runs-on: depot-ubuntu-latest
timeout-minutes: 30
strategy:
matrix:
include:
- binary: reth
- binary: op-reth
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
@@ -129,7 +134,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo build --bin reth --workspace
- run: cargo build --bin "${{ matrix.binary }}" --workspace
env:
RUSTFLAGS: -D warnings
@@ -193,9 +198,10 @@ jobs:
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
- run: ./docs/cli/update.sh target/debug/reth target/debug/op-reth
- name: Check docs changes
run: git diff --exit-code

61
.github/workflows/prepare-reth.yml vendored Normal file
View File

@@ -0,0 +1,61 @@
name: Prepare Reth Image
on:
workflow_call:
inputs:
image_tag:
required: true
type: string
description: "Docker image tag to use"
binary_name:
required: false
type: string
default: "reth"
description: "Binary name to build (reth or op-reth)"
cargo_features:
required: false
type: string
default: "asm-keccak"
description: "Cargo features to enable"
cargo_package:
required: false
type: string
description: "Optional cargo package path"
artifact_name:
required: false
type: string
default: "artifacts"
description: "Name for the uploaded artifact"
jobs:
prepare-reth:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: depot-ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: mkdir artifacts
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and export reth image
uses: docker/build-push-action@v6
with:
context: .
file: .github/scripts/hive/Dockerfile
tags: ${{ inputs.image_tag }}
outputs: type=docker,dest=./artifacts/reth_image.tar
build-args: |
CARGO_BIN=${{ inputs.binary_name }}
MANIFEST_PATH=${{ inputs.cargo_package }}
FEATURES=${{ inputs.cargo_features }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Upload reth image
id: upload
uses: actions/upload-artifact@v6
with:
name: ${{ inputs.artifact_name }}
path: ./artifacts

View File

@@ -17,9 +17,11 @@ on:
env:
REPO_NAME: ${{ github.repository_owner }}/reth
IMAGE_NAME: ${{ github.repository_owner }}/reth
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"
jobs:
@@ -85,6 +87,10 @@ jobs:
os: macos-14
profile: maxperf
allow_fail: false
- target: x86_64-pc-windows-gnu
os: ubuntu-24.04
profile: maxperf
allow_fail: false
- target: riscv64gc-unknown-linux-gnu
os: ubuntu-24.04
profile: maxperf
@@ -92,6 +98,8 @@ jobs:
build:
- command: build
binary: reth
- command: op-build
binary: op-reth
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1
@@ -118,7 +126,8 @@ jobs:
- name: Move binary
run: |
mkdir artifacts
mv "target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/${{ matrix.build.binary }}" ./artifacts
[[ "${{ matrix.configs.target }}" == *windows* ]] && ext=".exe"
mv "target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/${{ matrix.build.binary }}${ext}" ./artifacts
- name: Configure GPG and create artifacts
env:
@@ -235,9 +244,21 @@ jobs:
|:---:|:---:|:---:|:---|
| <img src="https://www.svgrepo.com/download/473700/linux.svg" width="50"/> | x86_64 | [reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/473700/linux.svg" width="50"/> | aarch64 | [reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/513083/windows-174.svg" width="50"/> | x86_64 | [reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/511330/apple-173.svg" width="50"/> | x86_64 | [reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/511330/apple-173.svg" width="50"/> | aarch64 | [reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/473589/docker.svg" width="50"/> | Docker | [${{ env.IMAGE_NAME }}](${{ env.DOCKER_IMAGE_NAME_URL }}) | - |
### OP-Reth
| System | Architecture | Binary | PGP Signature |
|:---:|:---:|:---:|:---|
| <img src="https://www.svgrepo.com/download/473700/linux.svg" width="50"/> | x86_64 | [op-reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/473700/linux.svg" width="50"/> | aarch64 | [op-reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/513083/windows-174.svg" width="50"/> | x86_64 | [op-reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/511330/apple-173.svg" width="50"/> | x86_64 | [op-reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/511330/apple-173.svg" width="50"/> | aarch64 | [op-reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz.asc) |
| <img src="https://www.svgrepo.com/download/473589/docker.svg" width="50"/> | Docker | [${{ env.OP_IMAGE_NAME }}](${{ env.DOCKER_OP_IMAGE_NAME_URL }}) | - |
ENDBODY
)
assets=()

View File

@@ -7,7 +7,6 @@ on:
jobs:
build:
if: github.repository == 'paradigmxyz/reth'
name: build reproducible binaries
runs-on: ${{ matrix.runner }}
strategy:

View File

@@ -38,7 +38,7 @@ jobs:
cache-on-failure: true
- name: Build reth
run: |
cargo install --path bin/reth
cargo install --features asm-keccak,jemalloc --path bin/reth
- name: Run headers stage
run: |
reth stage run headers --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints

View File

@@ -9,7 +9,6 @@ on:
jobs:
close-issues:
if: github.repository == 'paradigmxyz/reth'
runs-on: ubuntu-latest
permissions:
issues: write

View File

@@ -17,7 +17,6 @@ concurrency:
jobs:
sync:
if: github.repository == 'paradigmxyz/reth'
name: sync (${{ matrix.chain.bin }})
runs-on: depot-ubuntu-latest
env:
@@ -33,6 +32,12 @@ jobs:
tip: "0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4"
block: 100000
unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a"
- build: install-op
bin: op-reth
chain: base
tip: "0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7"
block: 10000
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1

View File

@@ -17,7 +17,6 @@ concurrency:
jobs:
sync:
if: github.repository == 'paradigmxyz/reth'
name: sync (${{ matrix.chain.bin }})
runs-on: depot-ubuntu-latest
env:
@@ -33,6 +32,12 @@ jobs:
tip: "0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4"
block: 100000
unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a"
- build: install-op
bin: op-reth
chain: base
tip: "0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7"
block: 10000
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
steps:
- uses: actions/checkout@v6
- uses: rui314/setup-mold@v1

View File

@@ -26,12 +26,15 @@ jobs:
EDGE_FEATURES: ${{ matrix.storage == 'edge' && 'edge' || '' }}
strategy:
matrix:
type: [ethereum]
type: [ethereum, optimism]
storage: [stable, edge]
include:
- type: ethereum
features: asm-keccak ethereum
exclude_args: ""
- type: optimism
features: asm-keccak
exclude_args: --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum"
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
@@ -49,7 +52,6 @@ jobs:
- name: Run tests
run: |
cargo nextest run \
--no-fail-fast \
--features "${{ matrix.features }} $EDGE_FEATURES" --locked \
${{ matrix.exclude_args }} --workspace \
--exclude ef-tests --no-tests=warn \
@@ -88,7 +90,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo nextest run --no-fail-fast --cargo-profile hivetests -p ef-tests --features "asm-keccak ef-tests"
- run: cargo nextest run --release -p ef-tests --features "asm-keccak ef-tests"
doc:
name: doc tests

36
.github/workflows/update-superchain.yml vendored Normal file
View File

@@ -0,0 +1,36 @@
name: Update Superchain Config
on:
schedule:
- cron: '0 3 * * 0'
workflow_dispatch:
permissions:
contents: write
jobs:
update-superchain:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Install required tools
run: |
sudo apt-get update
sudo apt-get install -y jq zstd qpdf yq
- name: Run fetch_superchain_config.sh
run: |
chmod +x crates/optimism/chainspec/res/fetch_superchain_config.sh
cd crates/optimism/chainspec/res
./fetch_superchain_config.sh
- name: Create Pull Request
uses: peter-evans/create-pull-request@v8
with:
commit-message: "chore: update superchain config"
title: "chore: update superchain config"
body: "This PR updates the superchain configs via scheduled workflow."
branch: "ci/update-superchain-config"
delete-branch: true

54
.github/workflows/windows.yml vendored Normal file
View File

@@ -0,0 +1,54 @@
# Windows build
name: windows
on:
push:
branches: [main]
pull_request:
branches: [main]
merge_group:
env:
RUSTC_WRAPPER: "sccache"
jobs:
check-reth:
runs-on: depot-ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- 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
- name: mingw-w64
run: sudo apt-get install -y mingw-w64
- name: Check Reth
run: cargo check --target x86_64-pc-windows-gnu
check-op-reth:
runs-on: depot-ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- 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
- name: mingw-w64
run: sudo apt-get install -y mingw-w64
- name: Check OP-Reth
run: cargo check -p op-reth --target x86_64-pc-windows-gnu

View File

@@ -24,7 +24,7 @@ Reth is a high-performance Ethereum execution client written in Rust, focusing o
- **Modularity**: Each crate can be used as a standalone library
- **Performance**: Extensive use of parallelism, memory-mapped I/O, and optimized data structures
- **Extensibility**: Traits and generic types allow for different chain implementations
- **Extensibility**: Traits and generic types allow for different implementations (Ethereum, Optimism, etc.)
- **Type Safety**: Strong typing throughout with minimal use of dynamic dispatch
## Development Workflow
@@ -38,7 +38,7 @@ Reth is a high-performance Ethereum execution client written in Rust, focusing o
2. **Linting**: Run clippy with all features
```bash
cargo +nightly clippy --workspace --lib --examples --tests --benches --all-features
RUSTFLAGS="-D warnings" cargo +nightly clippy --workspace --lib --examples --tests --benches --all-features --locked
```
3. **Testing**: Use nextest for faster test execution
@@ -169,16 +169,18 @@ Based on PR patterns, avoid:
Before submitting changes, ensure:
1. **Format Check**: `cargo +nightly fmt --all --check`
2. **Clippy**: No warnings
2. **Clippy**: No warnings with `RUSTFLAGS="-D warnings"`
3. **Tests Pass**: All unit and integration tests
4. **Documentation**: Update relevant docs and add doc comments with `cargo docs --document-private-items`
5. **Commit Messages**: Follow conventional format (feat:, fix:, chore:, etc.)
### Opening PRs against <https://github.com/paradigmxyz/reth>
Label PRs appropriately, first check the available labels and then apply the relevant ones:
* when changes are RPC related, add A-rpc label
* 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`
@@ -232,7 +234,7 @@ Tests often need expansion for:
Common refactoring pattern:
- Replace concrete types with generics
- Add trait bounds for flexibility
- Enable reuse across different chain types
- Enable reuse across different chain types (Ethereum, Optimism)
#### When to Comment
@@ -347,11 +349,11 @@ Let's say you want to fix a bug where external IP resolution fails on startup:
}
```
5. **Run checks** (IMPORTANT!):
5. **Run checks**:
```bash
cargo +nightly fmt --all
cargo clippy --workspace --all-features # Make sure WHOLE WORKSPACE compiles!
cargo nextest run -p reth-discv4
cargo clippy --all-features
cargo test -p reth-discv4
```
6. **Commit with clear message**:
@@ -372,7 +374,7 @@ Let's say you want to fix a bug where external IP resolution fails on startup:
cargo +nightly fmt --all
# Run lints
cargo +nightly clippy --workspace --all-features
RUSTFLAGS="-D warnings" cargo +nightly clippy --workspace --all-features --locked
# Run tests
cargo nextest run --workspace
@@ -381,7 +383,7 @@ cargo nextest run --workspace
cargo bench --bench bench_name
# Build optimized binary
cargo build --release
cargo build --release --features "jemalloc asm-keccak"
# Check compilation for all features
cargo check --workspace --all-features

2161
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[workspace.package]
version = "1.11.3"
version = "1.10.2"
edition = "2024"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
@@ -72,6 +72,24 @@ members = [
"crates/node/events/",
"crates/node/metrics",
"crates/node/types",
"crates/optimism/bin",
"crates/optimism/chainspec",
"crates/optimism/cli",
"crates/optimism/consensus",
"crates/optimism/evm/",
"crates/optimism/examples/custom-node",
"crates/optimism/examples/engine-api-access",
"crates/optimism/examples/exex-hello-world",
"crates/optimism/examples/op-db-access",
"crates/optimism/flashblocks/",
"crates/optimism/hardforks/",
"crates/optimism/node/",
"crates/optimism/payload/",
"crates/optimism/primitives/",
"crates/optimism/reth/",
"crates/optimism/rpc/",
"crates/optimism/storage",
"crates/optimism/txpool/",
"crates/payload/basic/",
"crates/payload/builder/",
"crates/payload/builder-primitives/",
@@ -83,6 +101,8 @@ members = [
"crates/prune/db",
"crates/prune/prune",
"crates/prune/types",
"crates/ress/protocol",
"crates/ress/provider",
"crates/revm/",
"crates/rpc/ipc/",
"crates/rpc/rpc-api/",
@@ -99,6 +119,7 @@ members = [
"crates/stages/api/",
"crates/stages/stages/",
"crates/stages/types/",
"crates/stateless",
"crates/static-file/static-file",
"crates/static-file/types/",
"crates/storage/codecs/",
@@ -122,6 +143,7 @@ members = [
"crates/trie/db",
"crates/trie/parallel/",
"crates/trie/sparse",
"crates/trie/sparse-parallel/",
"crates/trie/trie",
"examples/beacon-api-sidecar-fetcher/",
"examples/beacon-api-sse/",
@@ -306,11 +328,6 @@ inherits = "release"
lto = "fat"
codegen-units = 1
[profile.maxperf-symbols]
inherits = "maxperf"
debug = "full"
strip = "none"
[profile.reproducible]
inherits = "release"
panic = "abort"
@@ -319,6 +336,7 @@ 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-basic-payload-builder = { path = "crates/payload/basic" }
@@ -367,6 +385,7 @@ reth-ethereum = { path = "crates/ethereum/reth" }
reth-etl = { path = "crates/etl" }
reth-evm = { path = "crates/evm/evm", default-features = false }
reth-evm-ethereum = { path = "crates/ethereum/evm", default-features = false }
reth-optimism-evm = { path = "crates/optimism/evm", default-features = false }
reth-execution-errors = { path = "crates/evm/execution-errors", default-features = false }
reth-execution-types = { path = "crates/evm/execution-types", default-features = false }
reth-exex = { path = "crates/exex/exex" }
@@ -393,7 +412,18 @@ 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-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" }
reth-optimism-primitives = { path = "crates/optimism/primitives", default-features = false }
reth-optimism-rpc = { path = "crates/optimism/rpc" }
reth-optimism-storage = { path = "crates/optimism/storage" }
reth-optimism-txpool = { path = "crates/optimism/txpool" }
reth-payload-builder = { path = "crates/payload/builder" }
reth-payload-builder-primitives = { path = "crates/payload/builder-primitives" }
reth-payload-primitives = { path = "crates/payload/primitives" }
@@ -414,11 +444,13 @@ 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-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 }
@@ -434,7 +466,10 @@ reth-trie-common = { path = "crates/trie/common", default-features = false }
reth-trie-db = { path = "crates/trie/db" }
reth-trie-parallel = { path = "crates/trie/parallel" }
reth-trie-sparse = { path = "crates/trie/sparse", default-features = false }
reth-trie-sparse-parallel = { path = "crates/trie/sparse-parallel" }
reth-zstd-compressors = { path = "crates/storage/zstd-compressors", default-features = false }
reth-ress-protocol = { path = "crates/ress/protocol" }
reth-ress-provider = { path = "crates/ress/provider" }
# revm
revm = { version = "34.0.0", default-features = false }
@@ -445,63 +480,52 @@ revm-primitives = { version = "22.0.0", default-features = false }
revm-interpreter = { version = "32.0.0", default-features = false }
revm-database-interface = { version = "9.0.0", default-features = false }
op-revm = { version = "15.0.0", default-features = false }
revm-inspectors = "0.34.2"
revm-inspectors = "0.34.1"
# eth
alloy-dyn-abi = "1.5.6"
alloy-primitives = { version = "1.5.6", default-features = false, features = [
"map-foldhash",
] }
alloy-sol-types = { version = "1.5.6", default-features = false }
alloy-dyn-abi = "1.5.4"
alloy-primitives = { version = "1.5.4", default-features = false, features = ["map-foldhash"] }
alloy-sol-types = { version = "1.5.4", default-features = false }
alloy-chains = { version = "0.2.5", default-features = false }
alloy-eip2124 = { version = "0.2.0", default-features = false }
alloy-eip7928 = { version = "0.3.0", default-features = false }
alloy-evm = { version = "0.27.2", default-features = false }
alloy-rlp = { version = "0.3.13", default-features = false, features = [
"core-net",
] }
alloy-trie = { version = "0.9.4", default-features = false }
alloy-evm = { version = "0.27.0", default-features = false }
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
alloy-trie = { version = "0.9.1", default-features = false }
alloy-hardforks = "0.4.5"
alloy-consensus = { version = "1.6.3", default-features = false }
alloy-contract = { version = "1.6.3", default-features = false }
alloy-eips = { version = "1.6.3", default-features = false }
alloy-genesis = { version = "1.6.3", default-features = false }
alloy-json-rpc = { version = "1.6.3", default-features = false }
alloy-network = { version = "1.6.3", default-features = false }
alloy-network-primitives = { version = "1.6.3", default-features = false }
alloy-provider = { version = "1.6.3", features = [
"reqwest",
"debug-api",
], default-features = false }
alloy-pubsub = { version = "1.6.3", default-features = false }
alloy-rpc-client = { version = "1.6.3", default-features = false }
alloy-rpc-types = { version = "1.6.3", features = [
"eth",
], default-features = false }
alloy-rpc-types-admin = { version = "1.6.3", default-features = false }
alloy-rpc-types-anvil = { version = "1.6.3", default-features = false }
alloy-rpc-types-beacon = { version = "1.6.3", default-features = false }
alloy-rpc-types-debug = { version = "1.6.3", default-features = false }
alloy-rpc-types-engine = { version = "1.6.3", default-features = false }
alloy-rpc-types-eth = { version = "1.6.3", default-features = false }
alloy-rpc-types-mev = { version = "1.6.3", default-features = false }
alloy-rpc-types-trace = { version = "1.6.3", default-features = false }
alloy-rpc-types-txpool = { version = "1.6.3", default-features = false }
alloy-serde = { version = "1.6.3", default-features = false }
alloy-signer = { version = "1.6.3", default-features = false }
alloy-signer-local = { version = "1.6.3", default-features = false }
alloy-transport = { version = "1.6.3" }
alloy-transport-http = { version = "1.6.3", features = [
"reqwest-rustls-tls",
], default-features = false }
alloy-transport-ipc = { version = "1.6.3", default-features = false }
alloy-transport-ws = { version = "1.6.3", default-features = false }
alloy-consensus = { version = "1.5.2", default-features = false }
alloy-contract = { version = "1.5.2", default-features = false }
alloy-eips = { version = "1.5.2", default-features = false }
alloy-genesis = { version = "1.5.2", default-features = false }
alloy-json-rpc = { version = "1.5.2", default-features = false }
alloy-network = { version = "1.5.2", default-features = false }
alloy-network-primitives = { version = "1.5.2", default-features = false }
alloy-provider = { version = "1.5.2", features = ["reqwest", "debug-api"], default-features = false }
alloy-pubsub = { version = "1.5.2", default-features = false }
alloy-rpc-client = { version = "1.5.2", default-features = false }
alloy-rpc-types = { version = "1.5.2", features = ["eth"], default-features = false }
alloy-rpc-types-admin = { version = "1.5.2", default-features = false }
alloy-rpc-types-anvil = { version = "1.5.2", default-features = false }
alloy-rpc-types-beacon = { version = "1.5.2", default-features = false }
alloy-rpc-types-debug = { version = "1.5.2", default-features = false }
alloy-rpc-types-engine = { version = "1.5.2", default-features = false }
alloy-rpc-types-eth = { version = "1.5.2", default-features = false }
alloy-rpc-types-mev = { version = "1.5.2", default-features = false }
alloy-rpc-types-trace = { version = "1.5.2", default-features = false }
alloy-rpc-types-txpool = { version = "1.5.2", default-features = false }
alloy-serde = { version = "1.5.2", default-features = false }
alloy-signer = { version = "1.5.2", default-features = false }
alloy-signer-local = { version = "1.5.2", default-features = false }
alloy-transport = { version = "1.5.2" }
alloy-transport-http = { version = "1.5.2", features = ["reqwest-rustls-tls"], default-features = false }
alloy-transport-ipc = { version = "1.5.2", default-features = false }
alloy-transport-ws = { version = "1.5.2", default-features = false }
# op
alloy-op-evm = { version = "0.27.2", default-features = false }
alloy-op-evm = { version = "0.27.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 }
@@ -515,14 +539,11 @@ 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",
] }
backon = { version = "1.2", default-features = false, features = ["std-blocking-sleep", "tokio-sleep"] }
bincode = "1.3"
bitflags = "2.4"
boyer-moore-magiclen = "0.2.16"
bytes = { version = "1.11.1", default-features = false }
bytes = { version = "1.5", default-features = false }
brotli = "8"
cfg-if = "1.0"
clap = "4"
@@ -539,14 +560,10 @@ humantime-serde = "1.1"
itertools = { version = "0.14", default-features = false }
linked_hash_set = "0.1"
lz4 = "1.28.1"
modular-bitfield = "0.13.1"
notify = { version = "8.0.0", default-features = false, features = [
"macos_fsevent",
] }
nybbles = { version = "0.4.8", default-features = false }
once_cell = { version = "1.19", default-features = false, features = [
"critical-section",
] }
modular-bitfield = "0.11.2"
notify = { version = "8.0.0", default-features = false, features = ["macos_fsevent"] }
nybbles = { version = "0.4.2", default-features = false }
once_cell = { version = "1.19", default-features = false, features = ["critical-section"] }
parking_lot = "0.12"
paste = "1.0"
rand = "0.9"
@@ -565,22 +582,20 @@ strum_macros = "0.27"
syn = "2.0"
thiserror = { version = "2.0.0", default-features = false }
tar = "0.4.44"
tracing = { version = "0.1.0", default-features = false, features = [
"attributes",
] }
tracing = { version = "0.1.0", default-features = false }
tracing-appender = "0.2"
url = { version = "2.3", default-features = false }
zstd = "0.13"
byteorder = "1"
fixed-cache = { version = "0.1.7", features = ["stats"] }
moka = "0.12"
tar-no-std = { version = "0.4.2", default-features = false }
miniz_oxide = { version = "0.9.0", default-features = false }
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"
metrics-derive = "0.1.1"
metrics-derive = "0.1"
metrics-exporter-prometheus = { version = "0.18.0", default-features = false }
metrics-process = "2.1.0"
metrics-util = { default-features = false, version = "0.20.0" }
@@ -592,7 +607,7 @@ quote = "1.0"
# tokio
tokio = { version = "1.44.2", default-features = false }
tokio-stream = "0.1.11"
tokio-tungstenite = "0.28.0"
tokio-tungstenite = "0.26.2"
tokio-util = { version = "0.7.4", features = ["codec"] }
# async
@@ -605,11 +620,7 @@ futures-util = { version = "0.3", default-features = false }
hyper = "1.3"
hyper-util = "0.1.5"
pin-project = "1.0.12"
reqwest = { version = "0.12", default-features = false, features = [
"rustls-tls",
"rustls-tls-native-roots",
"stream",
] }
reqwest = { version = "0.12", default-features = false }
tracing-futures = "0.2"
tower = "0.5"
tower-http = "0.6"
@@ -629,15 +640,13 @@ jsonrpsee-types = "0.26.0"
http = "1.0"
http-body = "1.0"
http-body-util = "0.1.2"
jsonwebtoken = "9"
proptest-arbitrary-interop = "0.1.0"
# crypto
enr = { version = "0.13", default-features = false }
k256 = { version = "0.13", default-features = false, features = ["ecdsa"] }
secp256k1 = { version = "0.30", default-features = false, features = [
"global-context",
"recovery",
] }
secp256k1 = { version = "0.30", default-features = false, features = ["global-context", "recovery"] }
# rand 8 for secp256k1
rand_08 = { package = "rand", version = "0.8" }
@@ -645,7 +654,7 @@ rand_08 = { package = "rand", version = "0.8" }
c-kzg = "2.1.5"
# config
toml = "0.9"
toml = "0.8"
# rocksdb
rocksdb = { version = "0.24" }
@@ -664,16 +673,16 @@ assert_matches = "1.5.0"
criterion = { package = "codspeed-criterion-compat", version = "4.3" }
insta = "1.41"
proptest = "1.7"
proptest-derive = "0.7"
proptest-derive = "0.5"
similar-asserts = { version = "1.5.0", features = ["serde"] }
tempfile = "3.20"
test-fuzz = "7"
rstest = "0.26.1"
rstest = "0.24.0"
test-case = "3"
# ssz encoding
ethereum_ssz = "0.10.1"
ethereum_ssz_derive = "0.10.1"
ethereum_ssz = "0.9.0"
ethereum_ssz_derive = "0.9.0"
# allocators
jemalloc_pprof = { version = "0.8", default-features = false }
@@ -685,14 +694,14 @@ snmalloc-rs = { version = "0.3.7", features = ["build_cc"] }
aes = "0.8.1"
ahash = "0.8"
anyhow = "1.0"
bindgen = { version = "0.72", default-features = false }
block-padding = "0.3"
bindgen = { version = "0.71", 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"
crossbeam-channel = "0.5.13"
crossterm = "0.29.0"
crossterm = "0.28.0"
csv = "1.3.0"
ctrlc = "3.4"
ctr = "0.9.2"
@@ -705,7 +714,7 @@ hmac = "0.12.1"
human_bytes = "0.4.1"
indexmap = "2"
interprocess = "2.2.0"
lz4_flex = { version = "0.12", default-features = false }
lz4_flex = { version = "0.11", default-features = false }
memmap2 = "0.9.4"
mev-share-sse = { version = "0.5.0", default-features = false }
num-traits = "0.2.15"
@@ -713,15 +722,15 @@ page_size = "0.6.0"
parity-scale-codec = "3.2.1"
plain_hasher = "0.2"
pretty_assertions = "1.4"
ratatui = { version = "0.30", default-features = false }
ringbuffer = "0.16.0"
ratatui = { version = "0.29", default-features = false }
ringbuffer = "0.15.0"
rmp-serde = "1.3"
roaring = "0.11.3"
roaring = "0.10.2"
rolling-file = "0.2.0"
sha3 = "0.10.5"
snap = "1.1.1"
socket2 = { version = "0.6", default-features = false }
sysinfo = { version = "0.38", default-features = false }
socket2 = { version = "0.5", default-features = false }
sysinfo = { version = "0.33", default-features = false }
tracing-journald = "0.3"
tracing-logfmt = "=0.3.5"
tracing-samply = "0.1"

View File

@@ -15,6 +15,15 @@ pre-build = [
"apt-get update && apt-get install --assume-yes --no-install-recommends llvm-dev libclang-dev clang",
]
[target.x86_64-pc-windows-gnu]
# Why do we need a custom Dockerfile on Windows:
# 1. `reth-libmdbx` stopped working with MinGW 9.3 that cross image comes with.
# 2. To be able to update the version of MinGW, we need to also update the Ubuntu that the image is based on.
#
# Also see https://github.com/cross-rs/cross/issues/1667
# Inspired by https://github.com/cross-rs/cross/blob/9e2298e17170655342d3248a9c8ac37ef92ba38f/docker/Dockerfile.x86_64-pc-windows-gnu#L51
dockerfile = "./Dockerfile.x86_64-pc-windows-gnu"
[target.riscv64gc-unknown-linux-gnu]
image = "ubuntu:24.04"
pre-build = [

View File

@@ -1,8 +1,9 @@
# syntax=docker/dockerfile:1
# Dockerfile for reth, optimized for Depot builds
# Unified Dockerfile for reth and op-reth, optimized for Depot builds
# Usage:
# reth: --build-arg BINARY=reth
# reth: --build-arg BINARY=reth
# op-reth: --build-arg BINARY=op-reth --build-arg MANIFEST_PATH=crates/optimism/bin
FROM rust:1 AS builder
WORKDIR /app
@@ -18,7 +19,7 @@ ENV RUSTC_WRAPPER=sccache
ENV SCCACHE_DIR=/sccache
ENV SCCACHE_WEBDAV_ENDPOINT=https://cache.depot.dev
# Binary to build
# Binary to build (reth or op-reth)
ARG BINARY=reth
# Manifest path for the binary
@@ -28,8 +29,9 @@ ARG MANIFEST_PATH=bin/reth
ARG BUILD_PROFILE=release
ENV BUILD_PROFILE=$BUILD_PROFILE
# Extra Cargo flags (can be overridden, otherwise set per-platform below)
# Extra Cargo flags
ARG RUSTFLAGS=""
ENV RUSTFLAGS="$RUSTFLAGS"
# Extra Cargo features
ARG FEATURES=""
@@ -44,22 +46,14 @@ ENV VERGEN_GIT_DESCRIBE=$VERGEN_GIT_DESCRIBE
ENV VERGEN_GIT_DIRTY=$VERGEN_GIT_DIRTY
# Build application
# Platform-specific RUSTFLAGS: amd64 uses x86-64-v3 (Haswell+) with pclmulqdq for rocksdb
ARG TARGETPLATFORM
COPY --exclude=.git . .
RUN --mount=type=secret,id=DEPOT_TOKEN,env=SCCACHE_WEBDAV_TOKEN \
--mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
--mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
--mount=type=cache,target=$SCCACHE_DIR,sharing=shared \
export RUSTC_WRAPPER=sccache SCCACHE_WEBDAV_ENDPOINT=https://cache.depot.dev SCCACHE_DIR=/sccache && \
sccache --start-server && \
if [ -n "$RUSTFLAGS" ]; then \
export RUSTFLAGS="$RUSTFLAGS"; \
elif [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
export RUSTFLAGS="-C target-cpu=x86-64-v3 -C target-feature=+pclmulqdq"; \
fi && \
cargo build --profile $BUILD_PROFILE --features "$FEATURES" --locked --bin $BINARY --manifest-path $MANIFEST_PATH/Cargo.toml && \
sccache --show-stats
cargo build --profile $BUILD_PROFILE --features "$FEATURES" --locked --bin $BINARY --manifest-path $MANIFEST_PATH/Cargo.toml
RUN sccache --show-stats || true
# Copy binary to a known location (ARG not resolved in COPY)
# Note: Custom profiles like maxperf/profiling output to target/<profile>/, not target/release/

View File

@@ -0,0 +1,79 @@
FROM ubuntu:24.04 AS cross-base
ENV DEBIAN_FRONTEND=noninteractive
# Use HTTPS for package sources
RUN apt-get update && apt-get install --assume-yes --no-install-recommends ca-certificates
RUN find /etc/apt/ -type f \( -name '*.list' -o -name '*.sources' \) -exec sed -i 's|http://|https://|g' {} +
# Configure APT retries and timeouts to handle network issues
RUN echo 'Acquire::Retries \"3\";' > /etc/apt/apt.conf.d/80-retries && \
echo 'Acquire::http::Timeout \"60\";' >> /etc/apt/apt.conf.d/80-retries && \
echo 'Acquire::ftp::Timeout \"60\";' >> /etc/apt/apt.conf.d/80-retries
# configure fallback mirrors
RUN sed -i 's|URIs: https://archive.ubuntu.com/ubuntu/|URIs: https://mirror.cov.ukservers.com/ubuntu/ https://archive.ubuntu.com/ubuntu/ https://mirror.ox.ac.uk/sites/archive.ubuntu.com/ubuntu/|g' /etc/apt/sources.list.d/ubuntu.sources
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 cp common.sh lib.sh / && /common.sh
RUN cp cmake.sh / && /cmake.sh
RUN cp xargo.sh / && /xargo.sh
FROM cross-base AS build
RUN apt-get install --assume-yes --no-install-recommends libz-mingw-w64-dev g++-mingw-w64-x86-64 gfortran-mingw-w64-x86-64
# Install Wine using OpenSUSE repository because official one is often lagging behind
RUN dpkg --add-architecture i386 && \
apt-get install --assume-yes --no-install-recommends wget gpg && \
mkdir -pm755 /etc/apt/keyrings && curl -fsSL \
https://download.opensuse.org/repositories/Emulators:/Wine:/Debian/xUbuntu_24.04/Release.key \
| tee /etc/apt/keyrings/obs-winehq.key >/dev/null && \
echo "deb [arch=amd64,i386 signed-by=/etc/apt/keyrings/obs-winehq.key] \
https://download.opensuse.org/repositories/Emulators:/Wine:/Debian/xUbuntu_24.04/ ./" \
| tee /etc/apt/sources.list.d/obs-winehq.list && \
apt-get update && apt-get install --assume-yes --install-recommends winehq-stable
# run-detectors are responsible for calling the correct interpreter for exe
# files. For some reason it does not work inside a docker container (it works
# fine in the host). So we replace the usual paths of run-detectors to run wine
# directly. This only affects the guest, we are not messing up with the host.
#
# See /usr/share/doc/binfmt-support/detectors
RUN mkdir -p /usr/lib/binfmt-support/ && \
rm -f /usr/lib/binfmt-support/run-detectors /usr/bin/run-detectors && \
ln -s /usr/bin/wine /usr/lib/binfmt-support/run-detectors && \
ln -s /usr/bin/wine /usr/bin/run-detectors
RUN cp windows-entry.sh /
ENTRYPOINT ["/windows-entry.sh"]
RUN cp toolchain.cmake /opt/toolchain.cmake
# for why we always link with pthread support, see:
# https://github.com/cross-rs/cross/pull/1123#issuecomment-1312287148
ENV CROSS_TOOLCHAIN_PREFIX=x86_64-w64-mingw32-
ENV CROSS_TOOLCHAIN_SUFFIX=-posix
ENV CROSS_SYSROOT=/usr/x86_64-w64-mingw32
ENV CROSS_TARGET_RUNNER="env -u CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUNNER wine"
ENV CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER="$CROSS_TOOLCHAIN_PREFIX"gcc"$CROSS_TOOLCHAIN_SUFFIX" \
CARGO_TARGET_X86_64_PC_WINDOWS_GNU_RUNNER="$CROSS_TARGET_RUNNER" \
AR_x86_64_pc_windows_gnu="$CROSS_TOOLCHAIN_PREFIX"ar \
CC_x86_64_pc_windows_gnu="$CROSS_TOOLCHAIN_PREFIX"gcc"$CROSS_TOOLCHAIN_SUFFIX" \
CXX_x86_64_pc_windows_gnu="$CROSS_TOOLCHAIN_PREFIX"g++"$CROSS_TOOLCHAIN_SUFFIX" \
CMAKE_TOOLCHAIN_FILE_x86_64_pc_windows_gnu=/opt/toolchain.cmake \
BINDGEN_EXTRA_CLANG_ARGS_x86_64_pc_windows_gnu="--sysroot=$CROSS_SYSROOT -idirafter/usr/include" \
CROSS_CMAKE_SYSTEM_NAME=Windows \
CROSS_CMAKE_SYSTEM_PROCESSOR=AMD64 \
CROSS_CMAKE_CRT=gnu \
CROSS_CMAKE_OBJECT_FLAGS="-ffunction-sections -fdata-sections -m64"

46
DockerfileOp Normal file
View File

@@ -0,0 +1,46 @@
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
WORKDIR /app
LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth
LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0"
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
ARG BUILD_PROFILE=maxperf
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
COPY . .
RUN cargo build --profile $BUILD_PROFILE --features "$FEATURES" --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
RUN apt-get update && \
apt-get install -y ca-certificates libssl-dev pkg-config strace && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/op-reth /usr/local/bin/
RUN chmod +x /usr/local/bin/op-reth
COPY LICENSE-* ./
EXPOSE 30303 30303/udp 9001 8545 8546 7545 8551
ENTRYPOINT ["/usr/local/bin/op-reth"]

View File

@@ -24,3 +24,18 @@
parameters.
- Update version specific validation checks in the `EngineValidator` trait.
## Op-Reth changes
### Updates to the engine API
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`.
Adding a new versioned endpoint requires the same changes as for L1 just for the dedicated OP types.
### Hardforks
Opstack has dedicated hardforks (e.g. Isthmus), that can be entirely opstack specific (e.g. Holocene) or can be an L1
equivalent hardfork. Since opstack sticks to the L1 header primitive, a new L1 equivalent hardfork also requires new
equivalent consensus checks. For this reason these `OpHardfork` must be mapped to L1 `EthereumHardfork`, for example:
`OpHardfork::Isthmus` corresponds to `EthereumHardfork::Prague`. These mappings must be defined in the `ChainSpec`.

View File

@@ -12,7 +12,12 @@ FULL_DB_TOOLS_DIR := $(shell pwd)/$(DB_TOOLS_DIR)/
CARGO_TARGET_DIR ?= target
# List of features to use when building. Can be overridden via the environment.
FEATURES ?=
# No jemalloc on Windows
ifeq ($(OS),Windows_NT)
FEATURES ?= asm-keccak min-debug-logs
else
FEATURES ?= jemalloc asm-keccak min-debug-logs
endif
# Cargo profile for builds. Default is for local builds, CI uses an override.
PROFILE ?= release
@@ -45,6 +50,13 @@ install: ## Build and install the reth binary under `$(CARGO_HOME)/bin`.
--profile "$(PROFILE)" \
$(CARGO_INSTALL_EXTRA_FLAGS)
.PHONY: install-op
install-op: ## Build and install the op-reth binary under `$(CARGO_HOME)/bin`.
cargo install --path crates/optimism/bin --bin op-reth --force --locked \
--features "$(FEATURES)" \
--profile "$(PROFILE)" \
$(CARGO_INSTALL_EXTRA_FLAGS)
.PHONY: build
build: ## Build the reth binary into `target` directory.
cargo build --bin reth --features "$(FEATURES)" --profile "$(PROFILE)"
@@ -72,10 +84,21 @@ build-%-reproducible:
.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.
cargo build --bin op-reth --features "$(FEATURES)" --profile "$(PROFILE)" --manifest-path crates/optimism/bin/Cargo.toml
# Builds the reth binary natively.
build-native-%:
cargo build --bin reth --target $* --features "$(FEATURES)" --profile "$(PROFILE)"
op-build-native-%:
cargo build --bin op-reth --target $* --features "$(FEATURES)" --profile "$(PROFILE)" --manifest-path crates/optimism/bin/Cargo.toml
# The following commands use `cross` to build a cross-compile.
#
# These commands require that:
@@ -92,6 +115,11 @@ build-native-%:
# on other systems. JEMALLOC_SYS_WITH_LG_PAGE=16 tells jemalloc to use 64-KiB
# pages. See: https://github.com/paradigmxyz/reth/issues/6742
build-aarch64-unknown-linux-gnu: export JEMALLOC_SYS_WITH_LG_PAGE=16
op-build-aarch64-unknown-linux-gnu: export JEMALLOC_SYS_WITH_LG_PAGE=16
# No jemalloc on Windows
build-x86_64-pc-windows-gnu: FEATURES := $(filter-out jemalloc jemalloc-prof,$(FEATURES))
op-build-x86_64-pc-windows-gnu: FEATURES := $(filter-out jemalloc jemalloc-prof,$(FEATURES))
# Note: The additional rustc compiler flags are for intrinsics needed by MDBX.
# See: https://github.com/cross-rs/cross/wiki/FAQ#undefined-reference-with-build-std
@@ -99,6 +127,10 @@ build-%:
RUSTFLAGS="-C link-arg=-lgcc -Clink-arg=-static-libgcc" \
cross build --bin reth --target $* --features "$(FEATURES)" --profile "$(PROFILE)"
op-build-%:
RUSTFLAGS="-C link-arg=-lgcc -Clink-arg=-static-libgcc" \
cross build --bin op-reth --target $* --features "$(FEATURES)" --profile "$(PROFILE)" --manifest-path crates/optimism/bin/Cargo.toml
# Unfortunately we can't easily use cross to build for Darwin because of licensing issues.
# If we wanted to, we would need to build a custom Docker image with the SDK available.
#
@@ -109,6 +141,11 @@ build-x86_64-apple-darwin:
$(MAKE) build-native-x86_64-apple-darwin
build-aarch64-apple-darwin:
$(MAKE) build-native-aarch64-apple-darwin
op-build-x86_64-apple-darwin:
$(MAKE) op-build-native-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) \
@@ -144,6 +181,8 @@ build-release-tarballs: ## Create a series of `.tar.gz` files in the BIN_DIR dir
$(call tarball_release_binary,"x86_64-unknown-linux-gnu","reth","")
$(MAKE) build-aarch64-unknown-linux-gnu
$(call tarball_release_binary,"aarch64-unknown-linux-gnu","reth","")
$(MAKE) build-x86_64-pc-windows-gnu
$(call tarball_release_binary,"x86_64-pc-windows-gnu","reth.exe","")
##@ Test
@@ -153,7 +192,7 @@ COV_FILE := lcov.info
.PHONY: test-unit
test-unit: ## Run unit tests.
cargo install cargo-nextest --locked
cargo nextest run --no-fail-fast $(UNIT_TEST_ARGS)
cargo nextest run $(UNIT_TEST_ARGS)
.PHONY: cov-unit
@@ -186,7 +225,7 @@ $(EEST_TESTS_DIR):
.PHONY: ef-tests
ef-tests: $(EF_TESTS_DIR) $(EEST_TESTS_DIR) ## Runs Legacy and EEST tests.
cargo nextest run --no-fail-fast -p ef-tests --release --features ef-tests
cargo nextest run -p ef-tests --release --features ef-tests
##@ reth-bench
@@ -227,21 +266,30 @@ 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 ## Update book cli documentation.
update-book-cli: build-debug build-debug-op## Update book cli documentation.
@echo "Updating book cli doc..."
@./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth
@./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth $(CARGO_TARGET_DIR)/debug/op-reth
.PHONY: profiling
profiling: ## Builds `reth` with optimisations, but also symbols.
RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling
RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --features jemalloc,asm-keccak
.PHONY: profiling-op
profiling-op: ## Builds `op-reth` with optimisations, but also symbols.
RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --features jemalloc,asm-keccak --bin op-reth --manifest-path crates/optimism/bin/Cargo.toml
.PHONY: maxperf
maxperf: ## Builds `reth` with the most aggressive optimisations.
RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf
RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf --features jemalloc,asm-keccak
.PHONY: maxperf-op
maxperf-op: ## Builds `op-reth` with the most aggressive optimisations.
RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf --features jemalloc,asm-keccak --bin op-reth --manifest-path crates/optimism/bin/Cargo.toml
.PHONY: maxperf-no-asm
maxperf-no-asm: ## Builds `reth` with the most aggressive optimisations, minus the "asm-keccak" feature.
RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf --no-default-features --features jemalloc,min-debug-logs,otlp,otlp-logs,reth-revm/portable,js-tracer,keccak-cache-global,rocksdb
RUSTFLAGS="-C target-cpu=native" cargo build --profile maxperf --features jemalloc
fmt:
cargo +nightly fmt
@@ -256,6 +304,17 @@ clippy:
--all-features \
-- -D warnings
clippy-op-dev:
cargo +nightly clippy \
--bin op-reth \
--workspace \
--lib \
--examples \
--tests \
--benches \
--locked \
--all-features
lint-typos: ensure-typos
typos
@@ -320,6 +379,7 @@ rustdocs: ## Runs `cargo docs` to generate the Rust documents in the `target/doc
cargo-test:
cargo test \
--workspace \
--bin "op-reth" \
--lib --examples \
--tests \
--benches \

View File

@@ -18,11 +18,6 @@
[gh-lint]: https://github.com/paradigmxyz/reth/actions/workflows/lint.yml
[tg-badge]: https://img.shields.io/endpoint?color=neon&logo=telegram&label=chat&url=https%3A%2F%2Ftg.sumanjay.workers.dev%2Fparadigm%5Freth
> **Note: OP-Reth has moved**
>
> The Optimism (op-reth) crates have been moved to [ethereum-optimism/optimism](https://github.com/ethereum-optimism/optimism).
> Git contribution history has been preserved. If you are looking for op-reth, please see the new repository.
## 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.
@@ -37,7 +32,7 @@ More concretely, our goals are:
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/).
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. Note: OP-Reth has moved to [ethereum-optimism/optimism](https://github.com/ethereum-optimism/optimism).
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.
6. **Configurability**: We want to solve for node operators that care about fast historical queries, but also for hobbyists who cannot operate on large hardware. We also want to support teams and individuals who want both sync from genesis and via "fast sync". We envision that Reth will be configurable enough and provide configurable "profiles" for the tradeoffs that each team faces.
## Status
@@ -46,13 +41,13 @@ Reth is production ready, and suitable for usage in mission-critical environment
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://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.
### Database compatibility
@@ -82,8 +77,8 @@ 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
@@ -128,9 +123,9 @@ If you have any questions, first see if the answer to your question can be found
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
@@ -142,9 +137,9 @@ 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

View File

@@ -56,7 +56,7 @@ ctrlc.workspace = true
shlex.workspace = true
[target.'cfg(unix)'.dependencies]
nix = { version = "0.31", features = ["signal", "process"] }
nix = { version = "0.29", features = ["signal", "process"] }
[features]
default = ["jemalloc"]
@@ -98,5 +98,5 @@ min-trace-logs = [
"reth-node-core/min-trace-logs",
]
# no-op feature flag for CI matrices
# no-op feature flag for switching between the `optimism` and default functionality in CI matrices
ethereum = []

View File

@@ -30,7 +30,7 @@ reth-bench-compare \
| `--draw` | Generate charts (needs Python/uv) | `false` | No |
| `--profile` | Enable CPU profiling (needs samply) | `false` | No |
| `-vvvv` | Debug logging | Info | No |
| `--features <FEATURES>` | Extra Rust features for both builds | - | No |
| `--features <FEATURES>` | Rust features for both builds | `jemalloc,asm-keccak` | No |
| `--rustflags <FLAGS>` | RUSTFLAGS for both builds | `-C target-cpu=native` | No |
| `--baseline-features <FEATURES>` | Features for baseline only | Inherits `--features` | No |
| `--feature-features <FEATURES>` | Features for feature only | Inherits `--features` | No |

View File

@@ -186,12 +186,10 @@ impl BenchmarkRunner {
&output_dir.to_string_lossy(),
]);
// Configure wait mode: both can be used together
// When both are set: wait at least wait_time, and also wait for persistence if needed
// Configure wait mode: wait-time takes precedence over persistence-based flow
if let Some(ref wait_time) = self.wait_time {
cmd.args(["--wait-time", wait_time]);
}
if self.wait_for_persistence {
} else if self.wait_for_persistence {
cmd.arg("--wait-for-persistence");
// Add persistence threshold if specified

View File

@@ -116,9 +116,9 @@ pub(crate) struct Args {
/// Optional fixed delay between engine API calls (passed to reth-bench).
///
/// Can be combined with `--wait-for-persistence`: when both are set,
/// waits at least this duration, and also waits for persistence if needed.
#[arg(long, value_name = "DURATION")]
/// When set, reth-bench uses wait-time mode and disables persistence-based flow.
/// This flag remains for compatibility with older scripts.
#[arg(long, value_name = "DURATION", hide = true)]
pub wait_time: Option<String>,
/// Wait for blocks to be persisted before sending the next batch (passed to reth-bench).
@@ -126,9 +126,6 @@ pub(crate) struct Args {
/// When enabled, waits for every Nth block to be persisted using the
/// `reth_subscribePersistedBlock` subscription. This ensures the benchmark
/// doesn't outpace persistence.
///
/// Can be combined with `--wait-time`: when both are set, waits at least
/// wait-time, and also waits for persistence if the block hasn't been persisted yet.
#[arg(long)]
pub wait_for_persistence: bool,
@@ -191,9 +188,10 @@ pub(crate) struct Args {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub reth_args: Vec<String>,
/// Comma-separated list of extra features to enable during reth compilation (applied to both
/// builds)
#[arg(long, value_name = "FEATURES", default_value = "")]
/// Comma-separated list of features to enable during reth compilation (applied to both builds)
///
/// Example: `jemalloc,asm-keccak`
#[arg(long, value_name = "FEATURES", default_value = "jemalloc,asm-keccak")]
pub features: String,
/// Comma-separated list of features to enable only for baseline build (overrides --features)
@@ -204,7 +202,7 @@ pub(crate) struct Args {
/// Comma-separated list of features to enable only for feature build (overrides --features)
///
/// Example: `--feature-features jemalloc-prof`
/// Example: `--feature-features jemalloc,asm-keccak`
#[arg(long, value_name = "FEATURES")]
pub feature_features: Option<String>,
@@ -276,8 +274,10 @@ impl Args {
/// Get the default RPC URL for a given chain
const fn get_default_rpc_url(chain: &Chain) -> &'static str {
match chain.id() {
27082 => "https://rpc.hoodi.ethpandaops.io", // hoodi
_ => "https://ethereum.reth.rs/rpc", // mainnet and fallback
8453 => "https://base.reth.rs/rpc", // base
84532 => "https://base-sepolia.rpc.ithaca.xyz", // base-sepolia
27082 => "https://rpc.hoodi.ethpandaops.io", // hoodi
_ => "https://ethereum.reth.rs/rpc", // mainnet and fallback
}
}
@@ -471,6 +471,7 @@ async fn run_compilation_phase(
git_manager: &GitManager,
compilation_manager: &CompilationManager,
args: &Args,
is_optimism: bool,
) -> Result<(String, String)> {
info!("=== Running compilation phase ===");
@@ -523,7 +524,7 @@ async fn run_compilation_phase(
git_manager.switch_ref(git_ref)?;
// Compile reth (with caching)
compilation_manager.compile_reth(commit, features, rustflags)?;
compilation_manager.compile_reth(commit, is_optimism, features, rustflags)?;
info!("Completed compilation for {} reference", ref_type);
}
@@ -543,6 +544,7 @@ async fn run_warmup_phase(
node_manager: &mut NodeManager,
benchmark_runner: &BenchmarkRunner,
args: &Args,
is_optimism: bool,
baseline_commit: &str,
starting_tip: u64,
) -> Result<()> {
@@ -560,7 +562,8 @@ async fn run_warmup_phase(
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);
let binary_path =
compilation_manager.get_cached_binary_path_for_commit(baseline_commit, is_optimism);
// Verify the cached binary exists
if !binary_path.exists() {
@@ -613,13 +616,18 @@ async fn run_benchmark_workflow(
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).await?;
run_compilation_phase(git_manager, compilation_manager, args, is_optimism).await?;
// Switch to baseline reference and get the starting tip
git_manager.switch_ref(&args.baseline_ref)?;
let binary_path = compilation_manager.get_cached_binary_path_for_commit(&baseline_commit);
let binary_path =
compilation_manager.get_cached_binary_path_for_commit(&baseline_commit, is_optimism);
if !binary_path.exists() {
return Err(eyre!(
"Cached baseline binary not found at {:?}. Compilation phase should have created it.",
@@ -649,6 +657,7 @@ async fn run_benchmark_workflow(
node_manager,
benchmark_runner,
args,
is_optimism,
&baseline_commit,
starting_tip,
)
@@ -674,7 +683,8 @@ async fn run_benchmark_workflow(
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);
let binary_path =
compilation_manager.get_cached_binary_path_for_commit(commit, is_optimism);
// Verify the cached binary exists
if !binary_path.exists() {

View File

@@ -1,6 +1,8 @@
//! 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};
@@ -23,14 +25,54 @@ impl CompilationManager {
Ok(Self { repo_root, output_dir, git_manager })
}
/// 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) -> PathBuf {
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
self.output_dir.join("bin").join(format!("reth_{identifier}"))
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, features: &str, rustflags: &str) -> Result<()> {
pub(crate) fn compile_reth(
&self,
commit: &str,
is_optimism: bool,
features: &str,
rustflags: &str,
) -> Result<()> {
// Validate that current git commit matches the expected commit
let current_commit = self.git_manager.get_current_commit()?;
if current_commit != commit {
@@ -41,7 +83,7 @@ impl CompilationManager {
));
}
let cached_path = self.get_cached_binary_path_for_commit(commit);
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() {
@@ -51,7 +93,7 @@ impl CompilationManager {
info!("No cached binary found, compiling (commit: {})...", &commit[..8]);
let binary_name = "reth";
let binary_name = if is_optimism { "op-reth" } else { "reth" };
info!(
"Compiling {} with profiling configuration (commit: {})...",
@@ -65,6 +107,14 @@ impl CompilationManager {
cmd.arg("--features").arg(features);
info!("Using features: {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

View File

@@ -14,8 +14,6 @@
#[global_allocator]
static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::new_allocator();
use alloy_primitives as _;
mod benchmark;
mod cli;
mod comparison;

View File

@@ -45,7 +45,7 @@ op-alloy-consensus = { workspace = true, features = ["alloy-compat"] }
op-alloy-rpc-types-engine = { workspace = true, features = ["serde"] }
# reqwest
reqwest.workspace = true
reqwest = { workspace = true, default-features = false, features = ["rustls-tls-native-roots"] }
# tower
tower.workspace = true
@@ -117,7 +117,7 @@ min-trace-logs = [
"reth-node-core/min-trace-logs",
]
# no-op feature flag for CI matrices
# no-op feature flag for switching between the `optimism` and default functionality in CI matrices
ethereum = []
[[bin]]

View File

@@ -32,7 +32,7 @@ Otherwise, running `make maxperf` at the root of the repo should be sufficient f
The `reth-bench new-payload-fcu` command is the most representative of ethereum mainnet live sync, alternating between sending `engine_newPayload` calls and `engine_forkchoiceUpdated` calls.
The `new-payload-fcu` command supports two optional waiting modes that can be used together or independently:
- `--wait-time <duration>`: Fixed sleep interval between blocks (e.g., `--wait-time 100ms` or `--wait-time 400` for 400ms)
- `--wait-time <duration>`: Fixed sleep interval between blocks (e.g., `--wait-time 100ms`)
- `--wait-for-persistence`: Waits for blocks to be persisted using the `reth_subscribePersistedBlock` subscription
When using `--wait-for-persistence`, the benchmark waits after every `(threshold + 1)` blocks, where the threshold defaults to the engine's persistence threshold (2). This can be customized with `--persistence-threshold <N>`.
@@ -73,7 +73,7 @@ make profiling
If the purpose of the benchmark is to obtain `jemalloc` memory profiles that can then be analyzed by `jeprof`, it should be compiled with the `profiling` profile and the `jemalloc-prof` feature:
```bash
RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --features "jemalloc-prof"
RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --features "jemalloc-prof,asm-keccak"
```
> [!NOTE]
@@ -82,7 +82,7 @@ RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --features "jem
Finally, if the purpose of the benchmark is to profile the node when `snmalloc` is configured as the default allocator, it would be built with the following
command:
```bash
RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --no-default-features --features "snmalloc-native,asm-keccak,min-debug-logs"
RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --no-default-features --features "snmalloc-native,asm-keccak"
```
### Run the Benchmark:

View File

@@ -35,7 +35,7 @@ impl BenchContext {
/// This is the initialization code for most benchmarks, taking in a [`BenchmarkArgs`] and
/// returning the providers needed to run a benchmark.
pub(crate) async fn new(bench_args: &BenchmarkArgs, rpc_url: String) -> eyre::Result<Self> {
info!(target: "reth-bench", "Running benchmark using data from RPC URL: {}", rpc_url);
info!("Running benchmark using data from RPC URL: {}", rpc_url);
// Ensure that output directory exists and is a directory
if let Some(output) = &bench_args.output {
@@ -45,7 +45,7 @@ impl BenchContext {
// Create the directory if it doesn't exist
if !output.exists() {
std::fs::create_dir_all(output)?;
info!(target: "reth-bench", "Created output directory: {:?}", output);
info!("Created output directory: {:?}", output);
}
}
@@ -77,7 +77,7 @@ impl BenchContext {
let auth_url = Url::parse(&bench_args.engine_rpc_url)?;
// construct the authed transport
info!(target: "reth-bench", "Connecting to Engine RPC at {} for replay", auth_url);
info!("Connecting to Engine RPC at {} for replay", auth_url);
let auth_transport = AuthenticatedTransportConnect::new(auth_url, jwt);
let client = ClientBuilder::default().connect_with(auth_transport).await?;
let auth_provider = RootProvider::<AnyNetwork>::new(client);

View File

@@ -87,7 +87,7 @@ impl Command {
}
if !self.output.exists() {
std::fs::create_dir_all(&self.output)?;
info!(target: "reth-bench", "Created output directory: {:?}", self.output);
info!("Created output directory: {:?}", self.output);
}
// Set up authenticated provider (used for both Engine API and eth_ methods)
@@ -95,7 +95,7 @@ impl Command {
let jwt = JwtSecret::from_hex(jwt)?;
let auth_url = Url::parse(&self.engine_rpc_url)?;
info!(target: "reth-bench", "Connecting to Engine RPC at {}", auth_url);
info!("Connecting to Engine RPC at {}", auth_url);
let auth_transport = AuthenticatedTransportConnect::new(auth_url, jwt);
let client = ClientBuilder::default().connect_with(auth_transport).await?;
let provider = RootProvider::<AnyNetwork>::new(client);
@@ -120,7 +120,6 @@ impl Command {
match mode {
RampMode::Blocks(blocks) => {
info!(
target: "reth-bench",
canonical_parent,
start_block,
end_block = start_block + blocks - 1,
@@ -129,7 +128,6 @@ impl Command {
}
RampMode::TargetGasLimit(target) => {
info!(
target: "reth-bench",
canonical_parent,
start_block,
current_gas_limit = parent_header.gas_limit,
@@ -178,7 +176,7 @@ impl Command {
GasRampPayloadFile { version: version as u8, block_hash, params: params.clone() };
let payload_json = serde_json::to_string_pretty(&file)?;
std::fs::write(&payload_path, &payload_json)?;
info!(target: "reth-bench", block_number = block.header.number, path = %payload_path.display(), "Saved payload");
info!(block_number = block.header.number, path = %payload_path.display(), "Saved payload");
call_new_payload(&provider, version, params).await?;
@@ -192,20 +190,10 @@ impl Command {
parent_header = block.header;
parent_hash = block_hash;
blocks_processed += 1;
let progress = match mode {
RampMode::Blocks(total) => format!("{blocks_processed}/{total}"),
RampMode::TargetGasLimit(target) => {
let pct = (parent_header.gas_limit as f64 / target as f64 * 100.0).min(100.0);
format!("{pct:.1}%")
}
};
info!(target: "reth-bench", progress, block_number = parent_header.number, gas_limit = parent_header.gas_limit, "Block processed");
}
let final_gas_limit = parent_header.gas_limit;
info!(
target: "reth-bench",
total_duration=?total_benchmark_duration.elapsed(),
blocks_processed,
final_gas_limit,

View File

@@ -152,7 +152,7 @@ impl<S: TransactionSource> TransactionCollector<S> {
while total_gas < gas_target {
let Some((block_txs, _)) = self.source.fetch_block_transactions(current_block).await?
else {
warn!(target: "reth-bench", block = current_block, "Block not found, stopping");
warn!(block = current_block, "Block not found, stopping");
break;
};
@@ -182,7 +182,6 @@ impl<S: TransactionSource> TransactionCollector<S> {
}
info!(
target: "reth-bench",
total_txs = transactions.len(),
gas_sent = total_gas,
next_block = current_block,
@@ -300,10 +299,10 @@ async fn fetch_batch_with_retry<S: TransactionSource>(
Ok(result) => return Some(result),
Err(e) => {
if attempt == MAX_FETCH_RETRIES {
warn!(target: "reth-bench", attempt, error = %e, "Failed to fetch transactions after max retries");
warn!(attempt, error = %e, "Failed to fetch transactions after max retries");
return None;
}
warn!(target: "reth-bench", attempt, error = %e, "Failed to fetch transactions, retrying...");
warn!(attempt, error = %e, "Failed to fetch transactions, retrying...");
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
@@ -343,7 +342,7 @@ impl TxBuffer {
impl Command {
/// Execute the `generate-big-block` command
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
info!(target: "reth-bench", target_gas = self.target_gas, count = self.count, "Generating big block(s)");
info!(target_gas = self.target_gas, count = self.count, "Generating big block(s)");
// Set up authenticated engine provider
let jwt =
@@ -351,20 +350,20 @@ impl Command {
let jwt = JwtSecret::from_hex(jwt.trim())?;
let auth_url = Url::parse(&self.engine_rpc_url)?;
info!(target: "reth-bench", "Connecting to Engine RPC at {}", auth_url);
info!("Connecting to Engine RPC at {}", auth_url);
let auth_transport = AuthenticatedTransportConnect::new(auth_url.clone(), jwt);
let auth_client = ClientBuilder::default().connect_with(auth_transport).await?;
let auth_provider = RootProvider::<AnyNetwork>::new(auth_client);
// Set up testing RPC provider (for testing_buildBlockV1)
info!(target: "reth-bench", "Connecting to Testing RPC at {}", self.testing_rpc_url);
info!("Connecting to Testing RPC at {}", self.testing_rpc_url);
let testing_client = ClientBuilder::default()
.layer(RetryBackoffLayer::new(10, 800, u64::MAX))
.http(self.testing_rpc_url.parse()?);
let testing_provider = RootProvider::<AnyNetwork>::new(testing_client);
// Get the parent block (latest canonical block)
info!(target: "reth-bench", endpoint = "engine", method = "eth_getBlockByNumber", block = "latest", "RPC call");
info!(endpoint = "engine", method = "eth_getBlockByNumber", block = "latest", "RPC call");
let parent_block = auth_provider
.get_block_by_number(BlockNumberOrTag::Latest)
.await?
@@ -375,7 +374,6 @@ impl Command {
let parent_timestamp = parent_block.header.timestamp;
info!(
target: "reth-bench",
parent_hash = %parent_hash,
parent_number = parent_number,
"Using initial parent block"
@@ -419,7 +417,7 @@ impl Command {
.await?;
}
info!(target: "reth-bench", count = self.count, output_dir = %self.output_dir.display(), "All payloads generated");
info!(count = self.count, output_dir = %self.output_dir.display(), "All payloads generated");
Ok(())
}
@@ -450,9 +448,9 @@ impl Command {
self.save_payload(&built)?;
if self.execute || self.count > 1 {
info!(target: "reth-bench", payload = i + 1, block_hash = %built.block_hash, gas_used = built.gas_used, "Executing payload (newPayload + FCU)");
info!(payload = i + 1, block_hash = %built.block_hash, gas_used = built.gas_used, "Executing payload (newPayload + FCU)");
self.execute_payload_v4(auth_provider, built.envelope, parent_hash).await?;
info!(target: "reth-bench", payload = i + 1, "Payload executed successfully");
info!(payload = i + 1, "Payload executed successfully");
}
parent_hash = built.block_hash;
@@ -479,7 +477,6 @@ impl Command {
let gas_sent = result.gas_sent;
info!(
target: "reth-bench",
payload = index + 1,
attempt,
tx_count = tx_bytes.len(),
@@ -509,7 +506,7 @@ impl Command {
}
}
warn!(target: "reth-bench", payload = index + 1, "Retry loop exited without returning a payload");
warn!(payload = index + 1, "Retry loop exited without returning a payload");
Err(eyre::eyre!("build_with_retry exhausted retries without result"))
}
@@ -537,7 +534,7 @@ impl Command {
let tx_source = match RpcTransactionSource::from_url(&rpc_url) {
Ok(source) => source,
Err(e) => {
warn!(target: "reth-bench", error = %e, "Failed to create transaction source");
warn!(error = %e, "Failed to create transaction source");
return None;
}
};
@@ -547,12 +544,11 @@ impl Command {
while let Some(batch) = fetch_batch_with_retry(&collector, current_block).await {
if batch.transactions.is_empty() {
info!(target: "reth-bench", block = current_block, "Reached chain tip, stopping fetcher");
info!(block = current_block, "Reached chain tip, stopping fetcher");
break;
}
info!(
target: "reth-bench",
tx_count = batch.transactions.len(),
gas_sent = batch.gas_sent,
blocks = format!("{}..{}", current_block, batch.next_block),
@@ -576,24 +572,13 @@ impl Command {
for i in 0..self.count {
// Get initial batch of transactions for this payload
let Some(mut result) = tx_buffer.take_batch().await else {
info!(
target: "reth-bench",
payloads_built = i,
payloads_requested = self.count,
"Transaction source exhausted, stopping"
);
break;
};
let mut result = tx_buffer
.take_batch()
.await
.ok_or_else(|| eyre::eyre!("Transaction fetcher stopped unexpectedly"))?;
if result.transactions.is_empty() {
info!(
target: "reth-bench",
payloads_built = i,
payloads_requested = self.count,
"No more transactions available, stopping"
);
break;
return Err(eyre::eyre!("No transactions collected for payload {}", i + 1));
}
// Build with retry - may need to request more transactions
@@ -614,9 +599,9 @@ impl Command {
let current_timestamp = built.timestamp;
// Execute payload
info!(target: "reth-bench", payload = i + 1, block_hash = %current_block_hash, gas_used = built.gas_used, "Executing payload (newPayload + FCU)");
info!(payload = i + 1, block_hash = %current_block_hash, gas_used = built.gas_used, "Executing payload (newPayload + FCU)");
self.execute_payload_v4(auth_provider, built.envelope, parent_hash).await?;
info!(target: "reth-bench", payload = i + 1, "Payload executed successfully");
info!(payload = i + 1, "Payload executed successfully");
parent_hash = current_block_hash;
parent_timestamp = current_timestamp;
@@ -644,7 +629,6 @@ impl Command {
let gas_sent = result.gas_sent;
info!(
target: "reth-bench",
payload = index + 1,
attempt,
tx_count = tx_bytes.len(),
@@ -673,7 +657,7 @@ impl Command {
result.gas_sent = result.gas_sent.saturating_add(batch.gas_sent);
result.next_block = batch.next_block;
} else {
warn!(target: "reth-bench", "Transaction fetcher exhausted, proceeding with available transactions");
warn!("Transaction fetcher exhausted, proceeding with available transactions");
break;
}
}
@@ -681,7 +665,7 @@ impl Command {
}
}
warn!(target: "reth-bench", payload = index + 1, "Retry loop exited without returning a payload");
warn!(payload = index + 1, "Retry loop exited without returning a payload");
Err(eyre::eyre!("build_with_retry_buffered exhausted retries without result"))
}
@@ -697,7 +681,6 @@ impl Command {
if gas_used + MIN_TARGET_SLACK >= self.target_gas {
info!(
target: "reth-bench",
payload = index + 1,
gas_used,
target_gas = self.target_gas,
@@ -709,7 +692,6 @@ impl Command {
if attempt == MAX_BUILD_RETRIES {
warn!(
target: "reth-bench",
payload = index + 1,
gas_used,
target_gas = self.target_gas,
@@ -721,7 +703,6 @@ impl Command {
if gas_used == 0 {
warn!(
target: "reth-bench",
payload = index + 1,
"Zero gas used in payload, requesting fixed chunk of additional transactions"
);
@@ -735,7 +716,6 @@ impl Command {
if additional == 0 {
info!(
target: "reth-bench",
payload = index + 1,
gas_used,
target_gas = self.target_gas,
@@ -746,7 +726,6 @@ impl Command {
let ratio = gas_used as f64 / gas_sent as f64;
info!(
target: "reth-bench",
payload = index + 1,
gas_used,
gas_sent,
@@ -780,7 +759,6 @@ impl Command {
let total_tx_bytes: usize = transactions.iter().map(|tx| tx.len()).sum();
info!(
target: "reth-bench",
payload = index + 1,
tx_count = transactions.len(),
total_tx_bytes = total_tx_bytes,
@@ -808,7 +786,7 @@ impl Command {
let json = serde_json::to_string_pretty(&payload.envelope)?;
std::fs::write(&filepath, &json)
.wrap_err_with(|| format!("Failed to write payload to {:?}", filepath))?;
info!(target: "reth-bench", block_number = payload.block_number, block_hash = %payload.block_hash, path = %filepath.display(), "Payload saved");
info!(block_number = payload.block_number, block_hash = %payload.block_hash, path = %filepath.display(), "Payload saved");
Ok(())
}

View File

@@ -2,10 +2,7 @@
use crate::valid_payload::call_forkchoice_updated;
use eyre::Result;
use std::{
io::{BufReader, Read},
time::Duration,
};
use std::io::{BufReader, Read};
/// Read input from either a file path or stdin.
pub(crate) fn read_input(path: Option<&str>) -> Result<String> {
@@ -54,29 +51,13 @@ pub(crate) fn parse_gas_limit(s: &str) -> eyre::Result<u64> {
let base: u64 = num_str.trim().parse()?;
base.checked_mul(multiplier).ok_or_else(|| eyre::eyre!("value overflow"))
}
/// Parses a duration string, treating bare integers as milliseconds.
///
/// Accepts either a `humantime` duration string (e.g. `"100ms"`, `"2s"`) or a plain
/// integer which is interpreted as milliseconds (e.g. `"400"` → 400ms).
pub(crate) fn parse_duration(s: &str) -> eyre::Result<Duration> {
match humantime::parse_duration(s) {
Ok(d) => Ok(d),
Err(_) => {
let millis: u64 =
s.trim().parse().map_err(|_| eyre::eyre!("invalid duration: {s:?}"))?;
Ok(Duration::from_millis(millis))
}
}
}
use alloy_consensus::Header;
use alloy_eips::eip4844::kzg_to_versioned_hash;
use alloy_primitives::{Address, B256};
use alloy_provider::{ext::EngineApi, network::AnyNetwork, RootProvider};
use alloy_rpc_types_engine::{
CancunPayloadFields, ExecutionPayload, ExecutionPayloadSidecar, ForkchoiceState,
PayloadAttributes, PayloadId,
PayloadAttributes, PayloadId, PraguePayloadFields,
};
use eyre::OptionExt;
use reth_chainspec::{ChainSpec, EthereumHardforks};
@@ -199,7 +180,7 @@ pub(crate) async fn get_payload_with_sidecar(
payload_id: PayloadId,
parent_beacon_block_root: Option<B256>,
) -> eyre::Result<(ExecutionPayload, ExecutionPayloadSidecar)> {
debug!(target: "reth-bench", get_payload_version = ?version, ?payload_id, "Sending getPayload");
debug!(get_payload_version = ?version, ?payload_id, "Sending getPayload");
match version {
1 => {
@@ -230,14 +211,34 @@ pub(crate) async fn get_payload_with_sidecar(
}
4 => {
let envelope = provider.get_payload_v4(payload_id).await?;
Ok(envelope.into_payload_and_sidecar(
parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V4")?,
let versioned_hashes = versioned_hashes_from_commitments(
&envelope.envelope_inner.blobs_bundle.commitments,
);
let cancun_fields = CancunPayloadFields {
parent_beacon_block_root: parent_beacon_block_root
.ok_or_eyre("parent_beacon_block_root required for V4")?,
versioned_hashes,
};
let prague_fields = PraguePayloadFields::new(envelope.execution_requests);
Ok((
ExecutionPayload::V3(envelope.envelope_inner.execution_payload),
ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
))
}
5 => {
// V5 (Osaka) - use raw request since alloy doesn't have get_payload_v5 yet
let envelope = provider.get_payload_v5(payload_id).await?;
Ok(envelope.into_payload_and_sidecar(
parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V5")?,
let versioned_hashes =
versioned_hashes_from_commitments(&envelope.blobs_bundle.commitments);
let cancun_fields = CancunPayloadFields {
parent_beacon_block_root: parent_beacon_block_root
.ok_or_eyre("parent_beacon_block_root required for V5")?,
versioned_hashes,
};
let prague_fields = PraguePayloadFields::new(envelope.execution_requests);
Ok((
ExecutionPayload::V3(envelope.execution_payload),
ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
))
}
_ => panic!("This tool does not support getPayload versions past v5"),
@@ -289,24 +290,4 @@ mod tests {
assert!(parse_gas_limit("G").is_err());
assert!(parse_gas_limit("-1G").is_err());
}
#[test]
fn test_parse_duration_with_unit() {
assert_eq!(parse_duration("100ms").unwrap(), Duration::from_millis(100));
assert_eq!(parse_duration("2s").unwrap(), Duration::from_secs(2));
assert_eq!(parse_duration("1m").unwrap(), Duration::from_secs(60));
}
#[test]
fn test_parse_duration_bare_millis() {
assert_eq!(parse_duration("400").unwrap(), Duration::from_millis(400));
assert_eq!(parse_duration("0").unwrap(), Duration::from_millis(0));
assert_eq!(parse_duration("1000").unwrap(), Duration::from_millis(1000));
}
#[test]
fn test_parse_duration_errors() {
assert!(parse_duration("abc").is_err());
assert!(parse_duration("").is_err());
}
}

View File

@@ -111,18 +111,7 @@ impl BenchmarkCommand {
///
/// If file logging is enabled, this function returns a guard that must be kept alive to ensure
/// that all logs are flushed to disk.
///
/// Always enables log target display (`RUST_LOG_TARGET=1`) so that the `reth-bench` target
/// is visible in output, making it easy to distinguish reth-bench logs from reth logs when
/// both are streamed to the same console or file.
pub fn init_tracing(&self) -> eyre::Result<Option<FileWorkerGuard>> {
// Always show the log target so "reth-bench" is visible in the output.
if std::env::var_os("RUST_LOG_TARGET").is_none() {
// SAFETY: This is called early during single-threaded initialization, before any
// threads are spawned and before the tracing subscriber is set up.
unsafe { std::env::set_var("RUST_LOG_TARGET", "1") };
}
let guard = self.logs.init_tracing()?;
Ok(guard)
}

View File

@@ -12,12 +12,12 @@
use crate::{
bench::{
context::BenchContext,
helpers::parse_duration,
output::{
write_benchmark_results, CombinedResult, NewPayloadResult, TotalGasOutput, TotalGasRow,
},
persistence_waiter::{
derive_ws_rpc_url, setup_persistence_subscription, PersistenceWaiter,
PERSISTENCE_CHECKPOINT_TIMEOUT,
},
},
valid_payload::{block_to_new_payload, call_forkchoice_updated, call_new_payload},
@@ -26,6 +26,7 @@ use alloy_provider::Provider;
use alloy_rpc_types_engine::ForkchoiceState;
use clap::Parser;
use eyre::{Context, OptionExt};
use humantime::parse_duration;
use reth_cli_runner::CliContext;
use reth_engine_primitives::config::DEFAULT_PERSISTENCE_THRESHOLD;
use reth_node_core::args::BenchmarkArgs;
@@ -40,9 +41,6 @@ pub struct Command {
rpc_url: String,
/// How long to wait after a forkchoice update before sending the next payload.
///
/// Accepts a duration string (e.g. `100ms`, `2s`) or a bare integer treated as
/// milliseconds (e.g. `400`).
#[arg(long, value_name = "WAIT_TIME", value_parser = parse_duration, verbatim_doc_comment)]
wait_time: Option<Duration>,
@@ -69,19 +67,6 @@ pub struct Command {
)]
persistence_threshold: u64,
/// Timeout for waiting on persistence at each checkpoint.
///
/// Must be long enough to account for the persistence thread being blocked
/// by pruning after the previous save.
#[arg(
long = "persistence-timeout",
value_name = "PERSISTENCE_TIMEOUT",
value_parser = parse_duration,
default_value = "120s",
verbatim_doc_comment
)]
persistence_timeout: Duration,
/// The size of the block buffer (channel capacity) for prefetching blocks from the RPC
/// endpoint.
#[arg(
@@ -101,44 +86,29 @@ impl Command {
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
// Log mode configuration
if let Some(duration) = self.wait_time {
info!(target: "reth-bench", "Using wait-time mode with {}ms delay between blocks", duration.as_millis());
info!("Using wait-time mode with {}ms delay between blocks", duration.as_millis());
}
if self.wait_for_persistence {
info!(
target: "reth-bench",
"Persistence waiting enabled (waits after every {} blocks to match engine gap > {} behavior)",
self.persistence_threshold + 1,
self.persistence_threshold
);
}
// Set up waiter based on configured options
// When both are set: wait at least wait_time, and also wait for persistence if needed
// Set up waiter based on configured options (duration takes precedence)
let mut waiter = match (self.wait_time, self.wait_for_persistence) {
(Some(duration), true) => {
let ws_url = derive_ws_rpc_url(
self.benchmark.ws_rpc_url.as_deref(),
&self.benchmark.engine_rpc_url,
)?;
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
Some(PersistenceWaiter::with_duration_and_subscription(
duration,
sub,
self.persistence_threshold,
self.persistence_timeout,
))
}
(Some(duration), false) => Some(PersistenceWaiter::with_duration(duration)),
(Some(duration), _) => Some(PersistenceWaiter::with_duration(duration)),
(None, true) => {
let ws_url = derive_ws_rpc_url(
self.benchmark.ws_rpc_url.as_deref(),
&self.benchmark.engine_rpc_url,
)?;
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
let sub = setup_persistence_subscription(ws_url).await?;
Some(PersistenceWaiter::with_subscription(
sub,
self.persistence_threshold,
self.persistence_timeout,
PERSISTENCE_CHECKPOINT_TIMEOUT,
))
}
(None, false) => None,
@@ -153,7 +123,6 @@ impl Command {
..
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
let total_blocks = benchmark_mode.total_blocks();
let buffer_size = self.rpc_block_buffer_size;
// Use a oneshot channel to propagate errors from the spawned task
@@ -170,7 +139,7 @@ impl Command {
let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
Ok(block) => block,
Err(e) => {
tracing::error!(target: "reth-bench", "Failed to fetch block {next_block}: {e}");
tracing::error!("Failed to fetch block {next_block}: {e}");
let _ = error_sender.send(e);
break;
}
@@ -200,14 +169,13 @@ impl Command {
.send((block, head_block_hash, safe_block_hash, finalized_block_hash))
.await
{
tracing::error!(target: "reth-bench", "Failed to send block data: {e}");
tracing::error!("Failed to send block data: {e}");
break;
}
}
});
let mut results = Vec::new();
let mut blocks_processed = 0u64;
let total_benchmark_duration = Instant::now();
let mut total_wait_time = Duration::ZERO;
@@ -251,13 +219,8 @@ impl Command {
// Exclude time spent waiting on the block prefetch channel from the benchmark duration.
// We want to measure engine throughput, not RPC fetch latency.
blocks_processed += 1;
let current_duration = total_benchmark_duration.elapsed() - total_wait_time;
let progress = match total_blocks {
Some(total) => format!("{blocks_processed}/{total}"),
None => format!("{blocks_processed}"),
};
info!(target: "reth-bench", progress, %combined_result);
info!(%combined_result);
if let Some(w) = &mut waiter {
w.on_block(block_number).await?;
@@ -288,7 +251,6 @@ impl Command {
TotalGasOutput::with_combined_results(gas_output_results, &combined_results)?;
info!(
target: "reth-bench",
total_gas_used = gas_output.total_gas_used,
total_duration = ?gas_output.total_duration,
execution_duration = ?gas_output.execution_duration,

View File

@@ -52,7 +52,6 @@ impl Command {
..
} = BenchContext::new(&self.benchmark, self.rpc_url).await?;
let total_blocks = benchmark_mode.total_blocks();
let buffer_size = self.rpc_block_buffer_size;
// Use a oneshot channel to propagate errors from the spawned task
@@ -69,7 +68,7 @@ impl Command {
let block = match block_res.and_then(|opt| opt.ok_or_eyre("Block not found")) {
Ok(block) => block,
Err(e) => {
tracing::error!(target: "reth-bench", "Failed to fetch block {next_block}: {e}");
tracing::error!("Failed to fetch block {next_block}: {e}");
let _ = error_sender.send(e);
break;
}
@@ -77,14 +76,14 @@ impl Command {
next_block += 1;
if let Err(e) = sender.send(block).await {
tracing::error!(target: "reth-bench", "Failed to send block data: {e}");
tracing::error!("Failed to send block data: {e}");
break;
}
}
});
// put results in a summary vec so they can be printed at the end
let mut results = Vec::new();
let mut blocks_processed = 0u64;
let total_benchmark_duration = Instant::now();
let mut total_wait_time = Duration::ZERO;
@@ -98,7 +97,7 @@ impl Command {
let transaction_count = block.transactions.len() as u64;
let gas_used = block.header.gas_used;
debug!(target: "reth-bench", number=?block.header.number, "Sending payload to engine");
debug!(number=?block.header.number, "Sending payload to engine");
let (version, params) = block_to_new_payload(block, is_optimism)?;
@@ -106,12 +105,7 @@ impl Command {
call_new_payload(&auth_provider, version, params).await?;
let new_payload_result = NewPayloadResult { gas_used, latency: start.elapsed() };
blocks_processed += 1;
let progress = match total_blocks {
Some(total) => format!("{blocks_processed}/{total}"),
None => format!("{blocks_processed}"),
};
info!(target: "reth-bench", progress, %new_payload_result);
info!(%new_payload_result);
// current duration since the start of the benchmark minus the time
// waiting for blocks
@@ -135,7 +129,7 @@ impl Command {
if let Some(path) = self.benchmark.output {
// first write the new payload results to a file
let output_path = path.join(NEW_PAYLOAD_OUTPUT_SUFFIX);
info!(target: "reth-bench", "Writing newPayload call latency output to file: {:?}", output_path);
info!("Writing newPayload call latency output to file: {:?}", output_path);
let mut writer = Writer::from_path(output_path)?;
for result in new_payload_results {
writer.serialize(result)?;
@@ -144,20 +138,19 @@ impl Command {
// now write the gas output to a file
let output_path = path.join(GAS_OUTPUT_SUFFIX);
info!(target: "reth-bench", "Writing total gas output to file: {:?}", output_path);
info!("Writing total gas output to file: {:?}", output_path);
let mut writer = Writer::from_path(output_path)?;
for row in &gas_output_results {
writer.serialize(row)?;
}
writer.flush()?;
info!(target: "reth-bench", "Finished writing benchmark output files to {:?}.", path);
info!("Finished writing benchmark output files to {:?}.", path);
}
// accumulate the results and calculate the overall Ggas/s
let gas_output = TotalGasOutput::new(gas_output_results)?;
info!(
target: "reth-bench",
total_duration=?gas_output.total_duration,
total_gas_used=?gas_output.total_gas_used,
blocks_processed=?gas_output.blocks_processed,

View File

@@ -226,7 +226,7 @@ pub(crate) fn write_benchmark_results(
fs::create_dir_all(output_dir)?;
let output_path = output_dir.join(COMBINED_OUTPUT_SUFFIX);
info!(target: "reth-bench", "Writing engine api call latency output to file: {:?}", output_path);
info!("Writing engine api call latency output to file: {:?}", output_path);
let mut writer = Writer::from_path(&output_path)?;
for result in combined_results {
writer.serialize(result)?;
@@ -234,14 +234,14 @@ pub(crate) fn write_benchmark_results(
writer.flush()?;
let output_path = output_dir.join(GAS_OUTPUT_SUFFIX);
info!(target: "reth-bench", "Writing total gas output to file: {:?}", output_path);
info!("Writing total gas output to file: {:?}", output_path);
let mut writer = Writer::from_path(&output_path)?;
for row in gas_results {
writer.serialize(row)?;
}
writer.flush()?;
info!(target: "reth-bench", "Finished writing benchmark output files to {:?}.", output_dir);
info!("Finished writing benchmark output files to {:?}.", output_dir);
Ok(())
}

View File

@@ -4,8 +4,6 @@
//! - **Fixed duration waits**: Sleep for a fixed time between blocks
//! - **Persistence-based waits**: Wait for blocks to be persisted using
//! `reth_subscribePersistedBlock` subscription
//! - **Combined mode**: Wait at least the fixed duration, and also wait for persistence if the
//! block hasn't been persisted yet (whichever takes longer)
use alloy_eips::BlockNumHash;
use alloy_network::Ethereum;
@@ -22,6 +20,9 @@ use tracing::{debug, info};
const DEFAULT_WS_RPC_PORT: u16 = 8546;
use url::Url;
/// Default timeout for waiting on persistence.
pub(crate) const PERSISTENCE_CHECKPOINT_TIMEOUT: Duration = Duration::from_secs(60);
/// Returns the websocket RPC URL used for the persistence subscription.
///
/// Preference:
@@ -154,18 +155,12 @@ impl PersistenceSubscription {
}
/// Establishes a websocket connection and subscribes to `reth_subscribePersistedBlock`.
///
/// The `keepalive_interval` is set to match `persistence_timeout` so that the `WebSocket`
/// connection is not dropped during long MDBX commits that block the server from responding
/// to pings.
pub(crate) async fn setup_persistence_subscription(
ws_url: Url,
persistence_timeout: Duration,
) -> eyre::Result<PersistenceSubscription> {
info!(target: "reth-bench", "Connecting to WebSocket at {} for persistence subscription", ws_url);
info!("Connecting to WebSocket at {} for persistence subscription", ws_url);
let ws_connect =
WsConnect::new(ws_url.to_string()).with_keepalive_interval(persistence_timeout);
let ws_connect = WsConnect::new(ws_url.to_string());
let client = RpcClient::connect_pubsub(ws_connect)
.await
.wrap_err("Failed to connect to WebSocket RPC endpoint")?;
@@ -176,7 +171,7 @@ pub(crate) async fn setup_persistence_subscription(
.await
.wrap_err("Failed to subscribe to persistence notifications")?;
info!(target: "reth-bench", "Subscribed to persistence notifications");
info!("Subscribed to persistence notifications");
Ok(PersistenceSubscription::new(provider, subscription.into_stream()))
}
@@ -224,39 +219,14 @@ impl PersistenceWaiter {
}
}
/// Creates a waiter that combines both duration and persistence waiting.
///
/// Waits at least `wait_time` between blocks, and also waits for persistence
/// if the block hasn't been persisted yet (whichever takes longer).
pub(crate) const fn with_duration_and_subscription(
wait_time: Duration,
subscription: PersistenceSubscription,
threshold: u64,
timeout: Duration,
) -> Self {
Self {
wait_time: Some(wait_time),
subscription: Some(subscription),
blocks_sent: 0,
last_persisted: 0,
threshold,
timeout,
}
}
/// Called once per block. Waits based on the configured mode.
///
/// When both `wait_time` and `subscription` are set (combined mode):
/// - Always waits at least `wait_time`
/// - Additionally waits for persistence if we're at a persistence checkpoint
#[allow(clippy::manual_is_multiple_of)]
pub(crate) async fn on_block(&mut self, block_number: u64) -> eyre::Result<()> {
// Always wait for the fixed duration if configured
if let Some(wait_time) = self.wait_time {
tokio::time::sleep(wait_time).await;
return Ok(());
}
// Check persistence if subscription is configured
let Some(ref mut subscription) = self.subscription else {
return Ok(());
};

View File

@@ -14,13 +14,13 @@
use crate::{
authenticated_transport::AuthenticatedTransportConnect,
bench::{
helpers::parse_duration,
output::{
write_benchmark_results, CombinedResult, GasRampPayloadFile, NewPayloadResult,
TotalGasOutput, TotalGasRow,
},
persistence_waiter::{
derive_ws_rpc_url, setup_persistence_subscription, PersistenceWaiter,
PERSISTENCE_CHECKPOINT_TIMEOUT,
},
},
valid_payload::{call_forkchoice_updated, call_new_payload},
@@ -31,6 +31,7 @@ use alloy_rpc_client::ClientBuilder;
use alloy_rpc_types_engine::{ExecutionPayloadEnvelopeV4, ForkchoiceState, JwtSecret};
use clap::Parser;
use eyre::Context;
use humantime::parse_duration;
use reth_cli_runner::CliContext;
use reth_engine_primitives::config::DEFAULT_PERSISTENCE_THRESHOLD;
use reth_node_api::EngineApiMessageVersion;
@@ -78,9 +79,6 @@ pub struct Command {
output: Option<PathBuf>,
/// How long to wait after a forkchoice update before sending the next payload.
///
/// Accepts a duration string (e.g. `100ms`, `2s`) or a bare integer treated as
/// milliseconds (e.g. `400`).
#[arg(long, value_name = "WAIT_TIME", value_parser = parse_duration, verbatim_doc_comment)]
wait_time: Option<Duration>,
@@ -107,19 +105,6 @@ pub struct Command {
)]
persistence_threshold: u64,
/// Timeout for waiting on persistence at each checkpoint.
///
/// Must be long enough to account for the persistence thread being blocked
/// by pruning after the previous save.
#[arg(
long = "persistence-timeout",
value_name = "PERSISTENCE_TIMEOUT",
value_parser = parse_duration,
default_value = "120s",
verbatim_doc_comment
)]
persistence_timeout: Duration,
/// Optional `WebSocket` RPC URL for persistence subscription.
/// If not provided, derives from engine RPC URL by changing scheme to ws and port to 8546.
#[arg(long, value_name = "WS_RPC_URL", verbatim_doc_comment)]
@@ -149,42 +134,30 @@ struct GasRampPayload {
impl Command {
/// Execute the `replay-payloads` command.
pub async fn execute(self, _ctx: CliContext) -> eyre::Result<()> {
info!(target: "reth-bench", payload_dir = %self.payload_dir.display(), "Replaying payloads");
info!(payload_dir = %self.payload_dir.display(), "Replaying payloads");
// Log mode configuration
if let Some(duration) = self.wait_time {
info!(target: "reth-bench", "Using wait-time mode with {}ms delay between blocks", duration.as_millis());
info!("Using wait-time mode with {}ms delay between blocks", duration.as_millis());
}
if self.wait_for_persistence {
info!(
target: "reth-bench",
"Persistence waiting enabled (waits after every {} blocks to match engine gap > {} behavior)",
self.persistence_threshold + 1,
self.persistence_threshold
);
}
// Set up waiter based on configured options
// When both are set: wait at least wait_time, and also wait for persistence if needed
// Set up waiter based on configured options (duration takes precedence)
let mut waiter = match (self.wait_time, self.wait_for_persistence) {
(Some(duration), true) => {
let ws_url = derive_ws_rpc_url(self.ws_rpc_url.as_deref(), &self.engine_rpc_url)?;
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
Some(PersistenceWaiter::with_duration_and_subscription(
duration,
sub,
self.persistence_threshold,
self.persistence_timeout,
))
}
(Some(duration), false) => Some(PersistenceWaiter::with_duration(duration)),
(Some(duration), _) => Some(PersistenceWaiter::with_duration(duration)),
(None, true) => {
let ws_url = derive_ws_rpc_url(self.ws_rpc_url.as_deref(), &self.engine_rpc_url)?;
let sub = setup_persistence_subscription(ws_url, self.persistence_timeout).await?;
let sub = setup_persistence_subscription(ws_url).await?;
Some(PersistenceWaiter::with_subscription(
sub,
self.persistence_threshold,
self.persistence_timeout,
PERSISTENCE_CHECKPOINT_TIMEOUT,
))
}
(None, false) => None,
@@ -196,7 +169,7 @@ impl Command {
let jwt = JwtSecret::from_hex(jwt.trim())?;
let auth_url = Url::parse(&self.engine_rpc_url)?;
info!(target: "reth-bench", "Connecting to Engine RPC at {}", auth_url);
info!("Connecting to Engine RPC at {}", auth_url);
let auth_transport = AuthenticatedTransportConnect::new(auth_url.clone(), jwt);
let auth_client = ClientBuilder::default().connect_with(auth_transport).await?;
let auth_provider = RootProvider::<AnyNetwork>::new(auth_client);
@@ -211,7 +184,6 @@ impl Command {
let initial_parent_number = parent_block.header.number;
info!(
target: "reth-bench",
parent_hash = %initial_parent_hash,
parent_number = initial_parent_number,
"Using initial parent block"
@@ -223,7 +195,7 @@ impl Command {
if payloads.is_empty() {
return Err(eyre::eyre!("No gas ramp payload files found in {:?}", gas_ramp_dir));
}
info!(target: "reth-bench", count = payloads.len(), "Loaded gas ramp payloads from disk");
info!(count = payloads.len(), "Loaded gas ramp payloads from disk");
payloads
} else {
Vec::new()
@@ -233,14 +205,13 @@ impl Command {
if payloads.is_empty() {
return Err(eyre::eyre!("No payload files found in {:?}", self.payload_dir));
}
info!(target: "reth-bench", count = payloads.len(), "Loaded main payloads from disk");
info!(count = payloads.len(), "Loaded main payloads from disk");
let mut parent_hash = initial_parent_hash;
// Replay gas ramp payloads first
for (i, payload) in gas_ramp_payloads.iter().enumerate() {
info!(
target: "reth-bench",
gas_ramp_payload = i + 1,
total = gas_ramp_payloads.len(),
block_number = payload.block_number,
@@ -257,7 +228,7 @@ impl Command {
};
call_forkchoice_updated(&auth_provider, payload.version, fcu_state, None).await?;
info!(target: "reth-bench", gas_ramp_payload = i + 1, "Gas ramp payload executed successfully");
info!(gas_ramp_payload = i + 1, "Gas ramp payload executed successfully");
if let Some(w) = &mut waiter {
w.on_block(payload.block_number).await?;
@@ -267,7 +238,7 @@ impl Command {
}
if !gas_ramp_payloads.is_empty() {
info!(target: "reth-bench", count = gas_ramp_payloads.len(), "All gas ramp payloads replayed");
info!(count = gas_ramp_payloads.len(), "All gas ramp payloads replayed");
}
let mut results = Vec::new();
@@ -286,7 +257,6 @@ impl Command {
execution_payload.payload_inner.payload_inner.transactions.len() as u64;
debug!(
target: "reth-bench",
payload = i + 1,
total = payloads.len(),
index = payload.index,
@@ -297,7 +267,6 @@ impl Command {
let start = Instant::now();
debug!(
target: "reth-bench",
method = "engine_newPayloadV4",
block_hash = %block_hash,
"Sending newPayload"
@@ -324,7 +293,7 @@ impl Command {
finalized_block_hash: parent_hash,
};
debug!(target: "reth-bench", method = "engine_forkchoiceUpdatedV3", ?fcu_state, "Sending forkchoiceUpdated");
debug!(method = "engine_forkchoiceUpdatedV3", ?fcu_state, "Sending forkchoiceUpdated");
let fcu_result = auth_provider.fork_choice_updated_v3(fcu_state, None).await?;
@@ -341,8 +310,7 @@ impl Command {
};
let current_duration = total_benchmark_duration.elapsed();
let progress = format!("{}/{}", i + 1, payloads.len());
info!(target: "reth-bench", progress, %combined_result);
info!(%combined_result);
if let Some(w) = &mut waiter {
w.on_block(block_number).await?;
@@ -352,7 +320,7 @@ impl Command {
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
results.push((gas_row, combined_result));
debug!(target: "reth-bench", ?status, ?fcu_result, "Payload executed successfully");
debug!(?status, ?fcu_result, "Payload executed successfully");
parent_hash = block_hash;
}
@@ -370,7 +338,6 @@ impl Command {
let gas_output =
TotalGasOutput::with_combined_results(gas_output_results, &combined_results)?;
info!(
target: "reth-bench",
total_gas_used = gas_output.total_gas_used,
total_duration = ?gas_output.total_duration,
execution_duration = ?gas_output.execution_duration,
@@ -429,8 +396,7 @@ impl Command {
let block_hash =
envelope.envelope_inner.execution_payload.payload_inner.payload_inner.block_hash;
debug!(
target: "reth-bench",
info!(
index = index,
block_hash = %block_hash,
path = %path.display(),

View File

@@ -20,19 +20,6 @@ impl BenchMode {
}
}
/// Returns the total number of blocks in the benchmark, if known.
///
/// For [`BenchMode::Range`] this is the length of the range.
/// For [`BenchMode::Continuous`] the total is unbounded, so `None` is returned.
pub const fn total_blocks(&self) -> Option<u64> {
match self {
Self::Continuous(_) => None,
Self::Range(range) => {
Some(range.end().saturating_sub(*range.start()).saturating_add(1))
}
}
}
/// Create a [`BenchMode`] from optional `from` and `to` fields.
pub fn new(from: Option<u64>, to: Option<u64>, latest_block: u64) -> Result<Self, eyre::Error> {
// If neither `--from` nor `--to` are provided, we will run the benchmark continuously,

View File

@@ -54,7 +54,6 @@ where
payload_attributes: Option<PayloadAttributes>,
) -> TransportResult<ForkchoiceUpdated> {
debug!(
target: "reth-bench",
method = "engine_forkchoiceUpdatedV1",
?fork_choice_state,
?payload_attributes,
@@ -67,7 +66,6 @@ where
while !status.is_valid() {
if status.is_invalid() {
error!(
target: "reth-bench",
?status,
?fork_choice_state,
?payload_attributes,
@@ -93,7 +91,6 @@ where
payload_attributes: Option<PayloadAttributes>,
) -> TransportResult<ForkchoiceUpdated> {
debug!(
target: "reth-bench",
method = "engine_forkchoiceUpdatedV2",
?fork_choice_state,
?payload_attributes,
@@ -106,7 +103,6 @@ where
while !status.is_valid() {
if status.is_invalid() {
error!(
target: "reth-bench",
?status,
?fork_choice_state,
?payload_attributes,
@@ -132,7 +128,6 @@ where
payload_attributes: Option<PayloadAttributes>,
) -> TransportResult<ForkchoiceUpdated> {
debug!(
target: "reth-bench",
method = "engine_forkchoiceUpdatedV3",
?fork_choice_state,
?payload_attributes,
@@ -145,7 +140,6 @@ where
while !status.is_valid() {
if status.is_invalid() {
error!(
target: "reth-bench",
?status,
?fork_choice_state,
?payload_attributes,
@@ -259,13 +253,13 @@ pub(crate) async fn call_new_payload<N: Network, P: Provider<N>>(
) -> TransportResult<()> {
let method = version.method_name();
debug!(target: "reth-bench", method, "Sending newPayload");
debug!(method, "Sending newPayload");
let mut status: PayloadStatus = provider.client().request(method, &params).await?;
while !status.is_valid() {
if status.is_invalid() {
error!(target: "reth-bench", ?status, ?params, "Invalid {method}",);
error!(?status, ?params, "Invalid {method}",);
return Err(alloy_json_rpc::RpcError::LocalUsageError(Box::new(std::io::Error::other(
format!("Invalid {method}: {status:?}"),
))))

View File

@@ -33,6 +33,7 @@ reth-chainspec.workspace = true
reth-primitives.workspace = true
reth-db = { workspace = true, features = ["mdbx"] }
reth-provider.workspace = true
reth-evm.workspace = true
reth-revm.workspace = true
reth-transaction-pool.workspace = true
reth-cli-runner.workspace = true
@@ -52,44 +53,35 @@ reth-payload-primitives.workspace = true
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-builder.workspace = true
reth-node-metrics.workspace = true
reth-consensus.workspace = true
reth-tokio-util.workspace = true
reth-ress-protocol.workspace = true
reth-ress-provider.workspace = true
# alloy
alloy-primitives.workspace = true
alloy-rpc-types = { workspace = true, features = ["engine"] }
# tracing
tracing.workspace = true
# async
tokio = { workspace = true, features = ["sync", "macros", "time", "rt-multi-thread"] }
# misc
aquamarine.workspace = true
clap = { workspace = true, features = ["derive", "env"] }
eyre.workspace = true
[dev-dependencies]
alloy-node-bindings = "1.6.3"
alloy-provider = { workspace = true, features = ["reqwest"] }
alloy-rpc-types-eth.workspace = true
backon.workspace = true
serde_json.workspace = true
tempfile.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
toml.workspace = true
[features]
default = [
"jemalloc",
"otlp",
"otlp-logs",
"reth-revm/portable",
"js-tracer",
"keccak-cache-global",
"asm-keccak",
"min-debug-logs",
"rocksdb",
]
default = ["jemalloc", "otlp", "otlp-logs", "reth-revm/portable", "js-tracer", "keccak-cache-global", "asm-keccak"]
otlp = [
"reth-ethereum-cli/otlp",
@@ -113,12 +105,10 @@ asm-keccak = [
"reth-primitives/asm-keccak",
"reth-ethereum-cli/asm-keccak",
"reth-node-ethereum/asm-keccak",
"alloy-primitives/asm-keccak",
]
keccak-cache-global = [
"reth-node-core/keccak-cache-global",
"reth-node-ethereum/keccak-cache-global",
"alloy-primitives/keccak-cache-global",
]
jemalloc = [
"reth-cli-util/jemalloc",
@@ -190,8 +180,7 @@ min-trace-logs = [
"reth-node-core/min-trace-logs",
]
rocksdb = ["reth-ethereum-cli/rocksdb", "reth-node-core/rocksdb"]
edge = ["rocksdb"]
edge = ["reth-ethereum-cli/edge", "reth-node-core/edge"]
[[bin]]
name = "reth"

View File

@@ -51,9 +51,6 @@
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
#![cfg_attr(docsrs, feature(doc_cfg))]
// Used in feature flags only (`asm-keccak`, `keccak-cache-global`)
use alloy_primitives as _;
pub mod cli;
/// Re-exported utils.
@@ -208,9 +205,12 @@ pub mod rpc {
}
}
/// Ress subprotocol installation.
pub mod ress;
// re-export for convenience
#[doc(inline)]
pub use reth_cli_runner::{CliContext, CliRunner};
pub use reth_cli_runner::{tokio_runtime, CliContext, CliRunner};
// for rendering diagrams
use aquamarine as _;
@@ -218,4 +218,3 @@ use aquamarine as _;
// used in main
use clap as _;
use reth_cli_util as _;
use tracing as _;

View File

@@ -8,8 +8,9 @@ static ALLOC: reth_cli_util::allocator::Allocator = reth_cli_util::allocator::ne
static MALLOC_CONF: &[u8] = b"prof:true,prof_active:true,lg_prof_sample:19\0";
use clap::Parser;
use reth::cli::Cli;
use reth::{args::RessArgs, cli::Cli, ress::install_ress_subprotocol};
use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
use reth_node_builder::NodeHandle;
use reth_node_ethereum::EthereumNode;
use tracing::info;
@@ -21,12 +22,27 @@ fn main() {
unsafe { std::env::set_var("RUST_BACKTRACE", "1") };
}
if let Err(err) = Cli::<EthereumChainSpecParser>::parse().run(async move |builder, _| {
info!(target: "reth::cli", "Launching node");
let handle = builder.node(EthereumNode::default()).launch_with_debug_capabilities().await?;
if let Err(err) =
Cli::<EthereumChainSpecParser, RessArgs>::parse().run(async move |builder, ress_args| {
info!(target: "reth::cli", "Launching node");
let NodeHandle { node, node_exit_future } =
builder.node(EthereumNode::default()).launch_with_debug_capabilities().await?;
handle.wait_for_node_exit().await
}) {
// Install ress subprotocol.
if ress_args.enabled {
install_ress_subprotocol(
ress_args,
node.provider,
node.evm_config,
node.network,
node.task_executor,
node.add_ons_handle.engine_events.new_listener(),
)?;
}
node_exit_future.await
})
{
eprintln!("Error: {err:?}");
std::process::exit(1);
}

67
bin/reth/src/ress.rs Normal file
View File

@@ -0,0 +1,67 @@
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_core::args::RessArgs;
use reth_provider::providers::{BlockchainProvider, ProviderNodeTypes};
use reth_ress_protocol::{NodeType, ProtocolState, RessProtocolHandler};
use reth_ress_provider::{maintain_pending_state, PendingState, RethRessProtocolProvider};
use reth_tasks::TaskExecutor;
use reth_tokio_util::EventStream;
use tokio::sync::mpsc;
use tracing::*;
/// Install `ress` subprotocol if it's enabled.
pub fn install_ress_subprotocol<P, E, N>(
args: RessArgs,
provider: BlockchainProvider<P>,
evm_config: E,
network: N,
task_executor: TaskExecutor,
engine_events: EventStream<ConsensusEngineEvent<EthPrimitives>>,
) -> eyre::Result<()>
where
P: ProviderNodeTypes<Primitives = EthPrimitives>,
E: ConfigureEvm<Primitives = EthPrimitives> + Clone + 'static,
N: FullNetwork + NetworkProtocols,
{
info!(target: "reth::cli", "Installing ress subprotocol");
let pending_state = PendingState::default();
// Spawn maintenance task for pending state.
task_executor.spawn(maintain_pending_state(
engine_events,
provider.clone(),
pending_state.clone(),
));
let (tx, mut rx) = mpsc::unbounded_channel();
let provider = RethRessProtocolProvider::new(
provider,
evm_config,
Box::new(task_executor.clone()),
args.max_witness_window,
args.witness_max_parallel,
args.witness_cache_size,
pending_state,
)?;
network.add_rlpx_sub_protocol(
RessProtocolHandler {
provider,
node_type: NodeType::Stateful,
peers_handle: network.peers_handle().clone(),
max_active_connections: args.max_active_connections,
state: ProtocolState::new(tx),
}
.into_rlpx_sub_protocol(),
);
info!(target: "reth::cli", "Ress subprotocol support enabled");
task_executor.spawn(async move {
while let Some(event) = rx.recv().await {
trace!(target: "reth::ress", ?event, "Received ress event");
}
});
Ok(())
}

View File

@@ -1,255 +0,0 @@
#![allow(missing_docs)]
use std::process::Command;
const RETH: &str = env!("CARGO_BIN_EXE_reth");
// ── Helpers ──────────────────────────────────────────────────────────────────
/// Runs `reth <args>` and returns stdout, asserting exit code 0.
///
/// Tracing is suppressed via `RUST_LOG=off` so that log lines emitted during
/// binary startup don't pollute stdout-based assertions.
#[track_caller]
fn reth_ok(args: &[&str]) -> String {
let output = Command::new(RETH).env("RUST_LOG", "off").args(args).output().unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(output.status.success(), "args {args:?} failed.\nstdout: {stdout}\nstderr: {stderr}");
stdout.into_owned()
}
/// Spawns an isolated dev-mode reth node.
///
/// Discovery is disabled and peer limits are zeroed so the node is fully
/// isolated. Each call gets a unique temporary data directory so that
/// concurrent test runs never collide on the default `reth/dev/` path.
fn spawn_dev() -> (alloy_node_bindings::RethInstance, tempfile::TempDir) {
use alloy_node_bindings::Reth;
let datadir = tempfile::tempdir().expect("failed to create temp dir");
let instance = Reth::at(RETH)
.dev()
.disable_discovery()
.data_dir(datadir.path())
.args(["--max-outbound-peers", "0", "--max-inbound-peers", "0"])
.spawn();
// Return the TempDir alongside the instance so it lives as long as the node.
(instance, datadir)
}
// ── Original tests (from PR #22069) ──────────────────────────────────────────
#[test]
fn help() {
let stdout = reth_ok(&["--help"]);
assert!(stdout.contains("Usage"), "stdout: {stdout}");
assert!(stdout.contains("node"), "stdout: {stdout}");
}
#[test]
fn version() {
let stdout = reth_ok(&["--version"]);
assert!(stdout.to_lowercase().contains("reth"), "stdout: {stdout}");
}
#[test]
fn node_help() {
let stdout = reth_ok(&["node", "--help"]);
assert!(stdout.contains("--dev"), "stdout: {stdout}");
assert!(stdout.contains("--http"), "stdout: {stdout}");
}
#[test]
fn unknown_subcommand() {
let output = Command::new(RETH).arg("definitely-not-a-cmd").output().unwrap();
assert!(!output.status.success());
}
#[test]
fn unknown_flag() {
let output = Command::new(RETH).args(["node", "--no-such-flag"]).output().unwrap();
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(!output.status.success());
assert!(stderr.contains("--no-such-flag"), "stderr: {stderr}");
}
#[tokio::test]
async fn dev_node_eth_syncing() {
use alloy_provider::{Provider, ProviderBuilder};
let (reth, _datadir) = spawn_dev();
let provider = ProviderBuilder::new().connect_http(reth.endpoint().parse().unwrap());
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let _syncing = provider.syncing().await.expect("eth_syncing failed");
}
// ── Subcommand --help coverage ───────────────────────────────────────────────
//
// Every registered subcommand must produce valid --help output. This catches
// clap wiring regressions (e.g. a missing field, a conflicting arg name, or a
// broken `help_message()` call) that would otherwise only surface when a user
// runs the command.
#[test]
fn init_help() {
let stdout = reth_ok(&["init", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn init_state_help() {
let stdout = reth_ok(&["init-state", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn import_help() {
let stdout = reth_ok(&["import", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn import_era_help() {
let stdout = reth_ok(&["import-era", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn export_era_help() {
let stdout = reth_ok(&["export-era", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn dump_genesis_help() {
let stdout = reth_ok(&["dump-genesis", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn db_help() {
let stdout = reth_ok(&["db", "--help"]);
assert!(stdout.contains("stats"), "stdout: {stdout}");
}
#[test]
fn stage_help() {
let stdout = reth_ok(&["stage", "--help"]);
assert!(stdout.contains("run"), "stdout: {stdout}");
}
#[test]
fn p2p_help() {
let stdout = reth_ok(&["p2p", "--help"]);
assert!(stdout.contains("header"), "stdout: {stdout}");
}
#[test]
fn config_help() {
let stdout = reth_ok(&["config", "--help"]);
assert!(stdout.contains("--default"), "stdout: {stdout}");
}
#[test]
fn prune_help() {
let stdout = reth_ok(&["prune", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn download_help() {
let stdout = reth_ok(&["download", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
#[test]
fn re_execute_help() {
let stdout = reth_ok(&["re-execute", "--help"]);
assert!(stdout.contains("--chain"), "stdout: {stdout}");
}
// ── `config --default` outputs valid TOML ────────────────────────────────────
#[test]
fn config_default_valid_toml() {
let stdout = reth_ok(&["config", "--default"]);
let parsed: toml::Value =
toml::from_str(&stdout).expect("config --default did not produce valid TOML");
// The default config must contain the [stages] table — this is the heart of
// the pipeline configuration and its absence would indicate a serialization
// regression.
assert!(parsed.get("stages").is_some(), "missing [stages] in config output");
}
// ── `dump-genesis` outputs valid JSON ────────────────────────────────────────
#[test]
fn dump_genesis_mainnet_valid_json() {
let stdout = reth_ok(&["dump-genesis"]);
let genesis: serde_json::Value =
serde_json::from_str(&stdout).expect("dump-genesis did not produce valid JSON");
assert!(genesis.get("nonce").is_some(), "missing nonce in genesis JSON");
assert!(genesis.get("alloc").is_some(), "missing alloc in genesis JSON");
}
#[test]
fn dump_genesis_sepolia_valid_json() {
let stdout = reth_ok(&["dump-genesis", "--chain", "sepolia"]);
let genesis: serde_json::Value = serde_json::from_str(&stdout)
.expect("dump-genesis --chain sepolia did not produce valid JSON");
assert!(genesis.get("alloc").is_some(), "missing alloc in sepolia genesis JSON");
}
// ── Dev node: send transaction round-trip ────────────────────────────────────
//
// Exercises the full pipeline: RPC submission → mempool → sealing → execution →
// receipt retrieval. Uses the pre-funded dev account so no genesis customization
// is required.
#[tokio::test]
async fn dev_node_send_tx_and_mine() {
use alloy_primitives::{Address, U256};
use alloy_provider::{Provider, ProviderBuilder};
use alloy_rpc_types_eth::TransactionRequest;
let (reth, _datadir) = spawn_dev();
let provider = ProviderBuilder::new().connect_http(reth.endpoint().parse().unwrap());
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
// Dev mode pre-funds the first dev account.
let accounts = provider.get_accounts().await.expect("eth_accounts failed");
assert!(!accounts.is_empty(), "dev node should expose at least one account");
let sender = accounts[0];
let recipient = Address::with_last_byte(0x42);
let tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1_000_000));
let tx_hash = provider.send_transaction(tx).await.expect("eth_sendTransaction failed");
// In dev/instant-mine mode the node seals a block for each transaction, so
// the receipt becomes available almost immediately.
let receipt = tx_hash.get_receipt().await.expect("failed to get receipt");
assert!(receipt.status(), "transaction should have succeeded");
assert_eq!(receipt.to, Some(recipient));
assert!(receipt.block_number.unwrap() > 0, "receipt should be in a mined block");
// Verify the transfer actually mutated state.
let balance = provider.get_balance(recipient).await.expect("eth_getBalance failed");
assert_eq!(balance, U256::from(1_000_000));
}
const fn main() {}

View File

@@ -312,11 +312,6 @@ impl DeferredTrieData {
/// Given that invariant, circular wait dependencies are impossible.
#[instrument(level = "debug", target = "engine::tree::deferred_trie", skip_all)]
pub fn wait_cloned(&self) -> ComputedTrieData {
#[cfg(feature = "rayon")]
debug_assert!(
rayon::current_thread_index().is_none(),
"wait_cloned must not be called from a rayon worker thread"
);
let mut state = self.state.lock();
match &mut *state {
// If the deferred trie data is ready, return the cached result.

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