Compare commits

..

6 Commits

Author SHA1 Message Date
Alexey Shekhirin
74351d98e9 ci: use macos-14 runner (#19658) 2025-11-11 17:37:49 +00:00
Alexey Shekhirin
a672700b4f revert: "refactor(prune): remove receipts log filter segment (#19184)" (#19646) 2025-11-11 12:24:09 +00:00
rakita
465d7479a7 chore: bump op-revm v12.0.2 patch (#19629) 2025-11-11 12:23:55 +00:00
Alexey Shekhirin
5b3cb2d101 chore: bump version to 1.9.2 (#19647) 2025-11-11 12:23:50 +00:00
Matthias Seitz
3afe69a573 chore: bump version 2025-11-07 08:54:31 +01:00
rakita
35ac40a70b chore: bump revm v31.0.1 (#19567) 2025-11-07 08:53:33 +01:00
655 changed files with 9839 additions and 39228 deletions

View File

@@ -15,12 +15,3 @@ slow-timeout = { period = "2m", terminate-after = 10 }
[[profile.default.overrides]]
filter = "binary(e2e_testsuite)"
slow-timeout = { period = "2m", terminate-after = 3 }
[[profile.default.overrides]]
filter = "package(reth-era) and binary(it)"
slow-timeout = { period = "2m", terminate-after = 10 }
# Allow slower ethereum node e2e tests (p2p + blobs) to run up to 5 minutes.
[[profile.default.overrides]]
filter = "package(reth-node-ethereum) and binary(e2e)"
slow-timeout = { period = "1m", terminate-after = 5 }

View File

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

View File

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

1
.github/CODEOWNERS vendored
View File

@@ -40,6 +40,5 @@ crates/tasks/ @mattsse
crates/tokio-util/ @fgimenez
crates/transaction-pool/ @mattsse @yongkangc
crates/trie/ @Rjected @shekhirin @mediocregopher
bin/reth-bench-compare/ @mediocregopher @shekhirin @yongkangc
etc/ @Rjected @shekhirin
.github/ @gakonst @DaniPopes

View File

@@ -15,9 +15,10 @@ env:
name: bench
jobs:
codspeed:
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
submodules: true
- uses: rui314/setup-mold@v1

View File

@@ -16,7 +16,7 @@ jobs:
timeout-minutes: 90
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Install bun
uses: oven-sh/setup-bun@v2

View File

@@ -17,7 +17,8 @@ env:
name: compact-codec
jobs:
compact-codec:
runs-on: ubuntu-latest
runs-on:
group: Reth
strategy:
matrix:
bin:
@@ -30,7 +31,7 @@ jobs:
with:
cache-on-failure: true
- name: Checkout base
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
ref: ${{ github.base_ref || 'main' }}
# On `main` branch, generates test vectors and serializes them to disk using `Compact`.
@@ -38,7 +39,7 @@ jobs:
run: |
${{ matrix.bin }} -- test-vectors compact --write
- name: Checkout PR
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
clean: false
# On incoming merge try to read and decode previously generated vectors with `Compact`

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,12 +19,13 @@ concurrency:
jobs:
test:
name: e2e-testsuite
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_BACKTRACE: 1
timeout-minutes: 90
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
@@ -42,3 +43,4 @@ jobs:
--exclude 'op-reth' \
--exclude 'reth' \
-E 'binary(e2e_testsuite)'

View File

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

View File

@@ -24,11 +24,12 @@ jobs:
prepare-hive:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Checkout hive tests
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
repository: ethereum/hive
path: hivetests
@@ -178,11 +179,12 @@ jobs:
- prepare-reth
- prepare-hive
name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
runs-on: ubuntu-latest
runs-on:
group: Reth
permissions:
issues: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -207,7 +209,7 @@ jobs:
chmod +x /usr/local/bin/hive
- name: Checkout hive tests
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
repository: ethereum/hive
ref: master
@@ -245,7 +247,8 @@ jobs:
notify-on-error:
needs: test
if: failure()
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

@@ -23,7 +23,8 @@ jobs:
test:
name: test / ${{ matrix.network }}
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_BACKTRACE: 1
strategy:
@@ -31,7 +32,7 @@ jobs:
network: ["ethereum", "optimism"]
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- name: Install Geth
@@ -70,7 +71,7 @@ jobs:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
@@ -78,4 +79,4 @@ jobs:
with:
cache-on-failure: true
- name: run era1 files integration tests
run: cargo nextest run --release --package reth-era --test it -- --ignored
run: cargo nextest run --package reth-era --test it -- --ignored

View File

@@ -9,7 +9,7 @@ on:
push:
tags:
- "*"
- '*'
env:
CARGO_TERM_COLOR: always
@@ -32,11 +32,12 @@ jobs:
strategy:
fail-fast: false
name: run kurtosis
runs-on: ubuntu-latest
runs-on:
group: Reth
needs:
- prepare-reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
fetch-depth: 0
@@ -82,10 +83,12 @@ jobs:
kurtosis service logs -a op-devnet op-cl-2151908-2-op-node-op-reth-op-kurtosis
exit 1
notify-on-error:
needs: test
if: failure()
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- name: Slack Webhook Action
uses: rtCamp/action-slack-notify@v2

View File

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

View File

@@ -11,7 +11,7 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
fetch-depth: 0

View File

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

View File

@@ -21,7 +21,7 @@ jobs:
args: --workspace --lib --examples --tests --benches --locked
features: "ethereum asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@clippy
with:
@@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
with:
@@ -59,7 +59,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
@@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
@@ -92,22 +92,17 @@ jobs:
run: .github/assets/check_rv32imac.sh
crate-checks:
name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }})
runs-on: ubuntu-latest
strategy:
matrix:
partition: [1, 2]
total_partitions: [2]
timeout-minutes: 60
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@cargo-hack
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo hack check --workspace --partition ${{ matrix.partition }}/${{ matrix.total_partitions }}
- run: cargo hack check --workspace
msrv:
name: MSRV
@@ -119,7 +114,7 @@ jobs:
- binary: reth
- binary: op-reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@master
with:
@@ -136,7 +131,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
@@ -153,7 +148,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
with:
@@ -166,7 +161,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
@@ -180,17 +175,16 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
with:
cache-on-failure: true
- run: cargo build --bin reth --workspace
- run: cargo build --bin op-reth --workspace
- run: cargo build --bin reth --workspace --features ethereum
env:
RUSTFLAGS: -D warnings
- run: ./docs/cli/update.sh target/debug/reth target/debug/op-reth
- run: ./docs/cli/update.sh target/debug/reth
- name: Check docs changes
run: git diff --exit-code
@@ -198,7 +192,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: crate-ci/typos@v1
check-toml:
@@ -206,7 +200,7 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Run dprint
uses: dprint/check@v2.3
with:
@@ -216,7 +210,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Check dashboard JSON with jq
uses: sergeysova/jq-action@v2
with:
@@ -226,7 +220,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- name: Ensure no arbitrary or proptest dependency on default build
@@ -238,7 +232,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@clippy
- uses: Swatinem/rust-cache@v2
@@ -255,7 +249,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- uses: rui314/setup-mold@v1
- uses: taiki-e/cache-cargo-install-action@v2

View File

@@ -26,9 +26,10 @@ jobs:
prepare-reth:
if: github.repository == 'paradigmxyz/reth'
timeout-minutes: 45
runs-on: ubuntu-latest
runs-on:
group: Reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- run: mkdir artifacts
- name: Set up Docker Buildx

View File

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

View File

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

View File

@@ -49,7 +49,7 @@ jobs:
needs: extract-version
if: ${{ github.event.inputs.dry_run != 'true' }}
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: dtolnay/rust-toolchain@stable
- name: Verify crate version matches tag
# Check that the Cargo version starts with the tag,
@@ -99,7 +99,7 @@ jobs:
- command: op-build
binary: op-reth
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
@@ -166,7 +166,7 @@ jobs:
steps:
# This is necessary for generating the changelog.
# It has to come before "Download Artifacts" or else it deletes the artifacts.
- uses: actions/checkout@v6
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Download artifacts

View File

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

View File

@@ -22,13 +22,14 @@ jobs:
name: stage-run-test
# Only run stage commands test in merge groups
if: github.event_name == 'merge_group'
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2

View File

@@ -17,7 +17,8 @@ concurrency:
jobs:
sync:
name: sync (${{ matrix.chain.bin }})
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
@@ -38,7 +39,7 @@ jobs:
block: 10000
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
@@ -63,4 +64,4 @@ jobs:
${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }}
- name: Run stage unwind to block hash
run: |
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}

View File

@@ -17,7 +17,8 @@ concurrency:
jobs:
sync:
name: sync (${{ matrix.chain.bin }})
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
@@ -38,7 +39,7 @@ jobs:
block: 10000
unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de"
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
@@ -62,4 +63,4 @@ jobs:
${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }}
- name: Run stage unwind to block hash
run: |
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}
${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }}

View File

@@ -19,7 +19,8 @@ concurrency:
jobs:
test:
name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }})
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_BACKTRACE: 1
strategy:
@@ -43,7 +44,7 @@ jobs:
total_partitions: 2
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
@@ -64,15 +65,16 @@ jobs:
state:
name: Ethereum state tests
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_LOG: info,sync=error
RUST_BACKTRACE: 1
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- name: Checkout ethereum/tests
uses: actions/checkout@v6
uses: actions/checkout@v5
with:
repository: ethereum/tests
ref: 81862e4848585a438d64f911a19b3825f0f4cd95
@@ -98,12 +100,13 @@ jobs:
doc:
name: doc tests
runs-on: ubuntu-latest
runs-on:
group: Reth
env:
RUST_BACKTRACE: 1
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2

View File

@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v5
- name: Install required tools
run: |

View File

@@ -15,7 +15,7 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:
@@ -34,7 +34,7 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v5
- uses: rui314/setup-mold@v1
- uses: dtolnay/rust-toolchain@stable
with:

1140
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
[workspace.package]
version = "1.9.3"
version = "1.9.2"
edition = "2024"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
@@ -328,12 +328,6 @@ inherits = "release"
lto = "fat"
codegen-units = 1
[profile.reproducible]
inherits = "release"
panic = "abort"
codegen-units = 1
incremental = false
[workspace.dependencies]
# reth
op-reth = { path = "crates/optimism/bin" }
@@ -472,65 +466,68 @@ reth-ress-protocol = { path = "crates/ress/protocol" }
reth-ress-provider = { path = "crates/ress/provider" }
# revm
revm = { version = "33.1.0", default-features = false }
revm = { version = "31.0.2", default-features = false }
revm-bytecode = { version = "7.1.1", default-features = false }
revm-database = { version = "9.0.5", default-features = false }
revm-state = { version = "8.1.1", default-features = false }
revm-primitives = { version = "21.0.2", default-features = false }
revm-interpreter = { version = "31.1.0", default-features = false }
revm-interpreter = { version = "29.0.1", default-features = false }
revm-inspector = { version = "12.0.2", default-features = false }
revm-context = { version = "11.0.2", default-features = false }
revm-context-interface = { version = "12.0.1", default-features = false }
revm-database-interface = { version = "8.0.5", default-features = false }
op-revm = { version = "14.1.0", default-features = false }
revm-inspectors = "0.33.1"
op-revm = { version = "12.0.2", default-features = false }
revm-inspectors = "0.32.0"
# eth
alloy-chains = { version = "0.2.5", default-features = false }
alloy-dyn-abi = "1.4.1"
alloy-eip2124 = { version = "0.2.0", default-features = false }
alloy-evm = { version = "0.24.1", default-features = false }
alloy-evm = { version = "0.23.0", default-features = false }
alloy-primitives = { version = "1.4.1", default-features = false, features = ["map-foldhash"] }
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
alloy-sol-macro = "1.4.1"
alloy-sol-types = { version = "1.4.1", default-features = false }
alloy-trie = { version = "0.9.1", default-features = false }
alloy-hardforks = "0.4.5"
alloy-hardforks = "0.4.4"
alloy-consensus = { version = "1.1.3", default-features = false }
alloy-contract = { version = "1.1.3", default-features = false }
alloy-eips = { version = "1.1.3", default-features = false }
alloy-genesis = { version = "1.1.3", default-features = false }
alloy-json-rpc = { version = "1.1.3", default-features = false }
alloy-network = { version = "1.1.3", default-features = false }
alloy-network-primitives = { version = "1.1.3", default-features = false }
alloy-provider = { version = "1.1.3", features = ["reqwest", "debug-api"], default-features = false }
alloy-pubsub = { version = "1.1.3", default-features = false }
alloy-rpc-client = { version = "1.1.3", default-features = false }
alloy-rpc-types = { version = "1.1.3", features = ["eth"], default-features = false }
alloy-rpc-types-admin = { version = "1.1.3", default-features = false }
alloy-rpc-types-anvil = { version = "1.1.3", default-features = false }
alloy-rpc-types-beacon = { version = "1.1.3", default-features = false }
alloy-rpc-types-debug = { version = "1.1.3", default-features = false }
alloy-rpc-types-engine = { version = "1.1.3", default-features = false }
alloy-rpc-types-eth = { version = "1.1.3", default-features = false }
alloy-rpc-types-mev = { version = "1.1.3", default-features = false }
alloy-rpc-types-trace = { version = "1.1.3", default-features = false }
alloy-rpc-types-txpool = { version = "1.1.3", default-features = false }
alloy-serde = { version = "1.1.3", default-features = false }
alloy-signer = { version = "1.1.3", default-features = false }
alloy-signer-local = { version = "1.1.3", default-features = false }
alloy-transport = { version = "1.1.3" }
alloy-transport-http = { version = "1.1.3", features = ["reqwest-rustls-tls"], default-features = false }
alloy-transport-ipc = { version = "1.1.3", default-features = false }
alloy-transport-ws = { version = "1.1.3", default-features = false }
alloy-consensus = { version = "1.0.41", default-features = false }
alloy-contract = { version = "1.0.41", default-features = false }
alloy-eips = { version = "1.0.41", default-features = false }
alloy-genesis = { version = "1.0.41", default-features = false }
alloy-json-rpc = { version = "1.0.41", default-features = false }
alloy-network = { version = "1.0.41", default-features = false }
alloy-network-primitives = { version = "1.0.41", default-features = false }
alloy-provider = { version = "1.0.41", features = ["reqwest"], default-features = false }
alloy-pubsub = { version = "1.0.41", default-features = false }
alloy-rpc-client = { version = "1.0.41", default-features = false }
alloy-rpc-types = { version = "1.0.41", features = ["eth"], default-features = false }
alloy-rpc-types-admin = { version = "1.0.41", default-features = false }
alloy-rpc-types-anvil = { version = "1.0.41", default-features = false }
alloy-rpc-types-beacon = { version = "1.0.41", default-features = false }
alloy-rpc-types-debug = { version = "1.0.41", default-features = false }
alloy-rpc-types-engine = { version = "1.0.41", default-features = false }
alloy-rpc-types-eth = { version = "1.0.41", default-features = false }
alloy-rpc-types-mev = { version = "1.0.41", default-features = false }
alloy-rpc-types-trace = { version = "1.0.41", default-features = false }
alloy-rpc-types-txpool = { version = "1.0.41", default-features = false }
alloy-serde = { version = "1.0.41", default-features = false }
alloy-signer = { version = "1.0.41", default-features = false }
alloy-signer-local = { version = "1.0.41", default-features = false }
alloy-transport = { version = "1.0.41" }
alloy-transport-http = { version = "1.0.41", features = ["reqwest-rustls-tls"], default-features = false }
alloy-transport-ipc = { version = "1.0.41", default-features = false }
alloy-transport-ws = { version = "1.0.41", default-features = false }
# op
alloy-op-evm = { version = "0.24.1", default-features = false }
alloy-op-evm = { version = "0.23.0", default-features = false }
alloy-op-hardforks = "0.4.4"
op-alloy-rpc-types = { version = "0.22.4", default-features = false }
op-alloy-rpc-types-engine = { version = "0.22.4", default-features = false }
op-alloy-network = { version = "0.22.4", default-features = false }
op-alloy-consensus = { version = "0.22.4", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.22.4", default-features = false }
op-alloy-rpc-types = { version = "0.22.0", default-features = false }
op-alloy-rpc-types-engine = { version = "0.22.0", default-features = false }
op-alloy-network = { version = "0.22.0", default-features = false }
op-alloy-consensus = { version = "0.22.0", default-features = false }
op-alloy-rpc-jsonrpsee = { version = "0.22.0", default-features = false }
op-alloy-flz = { version = "0.13.1", default-features = false }
# misc
@@ -552,6 +549,8 @@ dirs-next = "2.0.0"
dyn-clone = "1.0.17"
eyre = "0.6"
fdlimit = "0.3.0"
# pinned until downstream crypto libs migrate to 1.0 because 0.14.8 marks all types as deprecated
generic-array = "=0.14.7"
humantime = "2.1"
humantime-serde = "1.1"
itertools = { version = "0.14", default-features = false }
@@ -652,9 +651,6 @@ c-kzg = "2.1.5"
# config
toml = "0.8"
# rocksdb
rocksdb = { version = "0.24" }
# otlp obs
opentelemetry_sdk = "0.31"
opentelemetry = "0.31"
@@ -666,7 +662,6 @@ tracing-opentelemetry = "0.32"
arbitrary = "1.3"
assert_matches = "1.5.0"
criterion = { package = "codspeed-criterion-compat", version = "2.7" }
insta = "1.41"
proptest = "1.7"
proptest-derive = "0.5"
similar-asserts = { version = "1.5.0", features = ["serde"] }
@@ -735,9 +730,6 @@ visibility = "0.1.1"
walkdir = "2.3.3"
vergen-git2 = "1.0.5"
# networking
ipnet = "2.11"
# [patch.crates-io]
# alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }
# alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" }

View File

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

View File

@@ -65,31 +65,37 @@ build: ## Build the reth binary into `target` directory.
cargo build --bin reth --features "$(FEATURES)" --profile "$(PROFILE)"
# Environment variables for reproducible builds
# Initialize RUSTFLAGS
RUST_BUILD_FLAGS =
# Enable static linking to ensure reproducibility across builds
RUST_BUILD_FLAGS += --C target-feature=+crt-static
# Set the linker to use static libgcc to ensure reproducibility across builds
RUST_BUILD_FLAGS += -C link-arg=-static-libgcc
# Remove build ID from the binary to ensure reproducibility across builds
RUST_BUILD_FLAGS += -C link-arg=-Wl,--build-id=none
# Remove metadata hash from symbol names to ensure reproducible builds
RUST_BUILD_FLAGS += -C metadata=''
# Set timestamp from last git commit for reproducible builds
SOURCE_DATE ?= $(shell git log -1 --pretty=%ct)
# Disable incremental compilation to avoid non-deterministic artifacts
CARGO_INCREMENTAL_VAL = 0
# Set C locale for consistent string handling and sorting
LOCALE_VAL = C
# Set UTC timezone for consistent time handling across builds
TZ_VAL = UTC
# Extra RUSTFLAGS for reproducible builds. Can be overridden via the environment.
RUSTFLAGS_REPRODUCIBLE_EXTRA ?=
# `reproducible` only supports reth on x86_64-unknown-linux-gnu
build-%-reproducible:
@if [ "$*" != "reth" ]; then \
echo "Error: Reproducible builds are only supported for reth, not $*"; \
exit 1; \
fi
.PHONY: build-reproducible
build-reproducible: ## Build the reth binary into `target` directory with reproducible builds. Only works for x86_64-unknown-linux-gnu currently
SOURCE_DATE_EPOCH=$(SOURCE_DATE) \
RUSTFLAGS="-C symbol-mangling-version=v0 -C strip=none -C link-arg=-Wl,--build-id=none -C metadata='' --remap-path-prefix $$(pwd)=. $(RUSTFLAGS_REPRODUCIBLE_EXTRA)" \
LC_ALL=C \
TZ=UTC \
JEMALLOC_OVERRIDE=/usr/lib/x86_64-linux-gnu/libjemalloc.a \
cargo build --bin reth --features "$(FEATURES) jemalloc-unprefixed" --profile "reproducible" --locked --target x86_64-unknown-linux-gnu
RUSTFLAGS="${RUST_BUILD_FLAGS} --remap-path-prefix $$(pwd)=." \
CARGO_INCREMENTAL=${CARGO_INCREMENTAL_VAL} \
LC_ALL=${LOCALE_VAL} \
TZ=${TZ_VAL} \
cargo build --bin reth --features "$(FEATURES)" --profile "release" --locked --target x86_64-unknown-linux-gnu
.PHONY: build-debug
build-debug: ## Build the reth binary into `target/debug` directory.
cargo build --bin reth --features "$(FEATURES)"
.PHONY: build-debug-op
build-debug-op: ## Build the op-reth binary into `target/debug` directory.
cargo build --bin op-reth --features "$(FEATURES)" --manifest-path crates/optimism/bin/Cargo.toml
.PHONY: build-op
build-op: ## Build the op-reth binary into `target` directory.
@@ -149,22 +155,6 @@ op-build-x86_64-apple-darwin:
op-build-aarch64-apple-darwin:
$(MAKE) op-build-native-aarch64-apple-darwin
build-deb-%:
@case "$*" in \
x86_64-unknown-linux-gnu|aarch64-unknown-linux-gnu|riscv64gc-unknown-linux-gnu) \
echo "Building debian package for $*"; \
;; \
*) \
echo "Error: Debian packages are only supported for x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, and riscv64gc-unknown-linux-gnu, not $*"; \
exit 1; \
;; \
esac
cargo install cargo-deb@3.6.0 --locked
cargo deb --profile $(PROFILE) --no-build --no-dbgsym --no-strip \
--target $* \
$(if $(VERSION),--deb-version "1~$(VERSION)") \
$(if $(VERSION),--output "target/$*/$(PROFILE)/reth-$(VERSION)-$*-$(PROFILE).deb")
# Create a `.tar.gz` containing a binary for a specific target.
define tarball_release_binary
cp $(CARGO_TARGET_DIR)/$(1)/$(PROFILE)/$(2) $(BIN_DIR)/$(2)
@@ -390,9 +380,9 @@ db-tools: ## Compile MDBX debugging tools.
@echo "Run \"$(DB_TOOLS_DIR)/mdbx_chk\" for the MDBX db file integrity check."
.PHONY: update-book-cli
update-book-cli: build-debug build-debug-op## Update book cli documentation.
update-book-cli: build-debug ## Update book cli documentation.
@echo "Updating book cli doc..."
@./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth $(CARGO_TARGET_DIR)/debug/op-reth
@./docs/cli/update.sh $(CARGO_TARGET_DIR)/debug/reth
.PHONY: profiling
profiling: ## Builds `reth` with optimisations, but also symbols.

View File

@@ -103,8 +103,7 @@ impl BenchmarkRunner {
cmd.args(["--wait-time", wait_time]);
}
cmd.env("RUST_LOG_STYLE", "never")
.stdout(std::process::Stdio::piped())
cmd.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);
@@ -191,8 +190,7 @@ impl BenchmarkRunner {
cmd.args(["--wait-time", wait_time]);
}
cmd.env("RUST_LOG_STYLE", "never")
.stdout(std::process::Stdio::piped())
cmd.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.kill_on_drop(true);

View File

@@ -5,7 +5,7 @@ use clap::Parser;
use eyre::{eyre, Result, WrapErr};
use reth_chainspec::Chain;
use reth_cli_runner::CliContext;
use reth_node_core::args::{DatadirArgs, LogArgs, TraceArgs};
use reth_node_core::args::{DatadirArgs, LogArgs};
use reth_tracing::FileWorkerGuard;
use std::{net::TcpListener, path::PathBuf, str::FromStr};
use tokio::process::Command;
@@ -131,19 +131,6 @@ pub(crate) struct Args {
#[command(flatten)]
pub logs: LogArgs,
#[command(flatten)]
pub traces: TraceArgs,
/// Maximum queue size for OTLP Batch Span Processor (traces).
/// Higher values prevent trace drops when benchmarking many blocks.
#[arg(
long,
value_name = "OTLP_BUFFER_SIZE",
default_value = "32768",
help_heading = "Tracing"
)]
pub otlp_max_queue_size: usize,
/// Additional arguments to pass to baseline reth node command
///
/// Example: `--baseline-args "--debug.tip 0xabc..."`

View File

@@ -6,7 +6,6 @@ use csv::Reader;
use eyre::{eyre, Result, WrapErr};
use serde::{Deserialize, Serialize};
use std::{
cmp::Ordering,
collections::HashMap,
fs,
path::{Path, PathBuf},
@@ -37,7 +36,6 @@ pub(crate) struct BenchmarkResults {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct CombinedLatencyRow {
pub block_number: u64,
pub transaction_count: u64,
pub gas_used: u64,
pub new_payload_latency: u128,
}
@@ -46,32 +44,19 @@ pub(crate) struct CombinedLatencyRow {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub(crate) struct TotalGasRow {
pub block_number: u64,
pub transaction_count: u64,
pub gas_used: u64,
pub time: u128,
}
/// Summary statistics for a benchmark run.
///
/// Latencies are derived from per-block `engine_newPayload` timings (converted from µs to ms):
/// - `mean_new_payload_latency_ms`: arithmetic mean latency across blocks.
/// - `median_new_payload_latency_ms`: p50 latency across blocks.
/// - `p90_new_payload_latency_ms` / `p99_new_payload_latency_ms`: tail latencies across blocks.
/// - `std_dev_new_payload_latency_ms`: standard deviation of latency across blocks.
/// Summary statistics for a benchmark run
#[derive(Debug, Clone, Serialize)]
pub(crate) struct BenchmarkSummary {
pub total_blocks: u64,
pub total_gas_used: u64,
pub total_duration_ms: u128,
pub mean_new_payload_latency_ms: f64,
pub median_new_payload_latency_ms: f64,
pub p90_new_payload_latency_ms: f64,
pub p99_new_payload_latency_ms: f64,
pub std_dev_new_payload_latency_ms: f64,
pub avg_new_payload_latency_ms: f64,
pub gas_per_second: f64,
pub blocks_per_second: f64,
pub min_block_number: u64,
pub max_block_number: u64,
}
/// Comparison report between two benchmark runs
@@ -93,31 +78,10 @@ pub(crate) struct RefInfo {
pub end_timestamp: Option<DateTime<Utc>>,
}
/// Summary of the comparison between references.
///
/// Percent deltas are `(feature - baseline) / baseline * 100`:
/// - `new_payload_latency_p50_change_percent` / p90 / p99: percent changes of the respective
/// per-block percentiles.
/// - `std_dev_change_percent`: percent change in standard deviation of newPayload latency.
/// - `per_block_latency_change_mean_percent` / `per_block_latency_change_median_percent` are the
/// mean and median of per-block percent deltas (feature vs baseline), capturing block-level
/// drift.
/// - `per_block_latency_change_std_dev_percent`: standard deviation of per-block percent changes,
/// measuring consistency of performance changes across blocks.
/// - `new_payload_total_latency_change_percent` is the percent change of the total newPayload time
/// across the run.
///
/// Positive means slower/higher; negative means faster/lower.
/// Summary of the comparison between references
#[derive(Debug, Serialize)]
pub(crate) struct ComparisonSummary {
pub per_block_latency_change_mean_percent: f64,
pub per_block_latency_change_median_percent: f64,
pub per_block_latency_change_std_dev_percent: f64,
pub new_payload_total_latency_change_percent: f64,
pub new_payload_latency_p50_change_percent: f64,
pub new_payload_latency_p90_change_percent: f64,
pub new_payload_latency_p99_change_percent: f64,
pub std_dev_change_percent: f64,
pub new_payload_latency_change_percent: f64,
pub gas_per_second_change_percent: f64,
pub blocks_per_second_change_percent: f64,
}
@@ -126,8 +90,6 @@ pub(crate) struct ComparisonSummary {
#[derive(Debug, Serialize)]
pub(crate) struct BlockComparison {
pub block_number: u64,
pub transaction_count: u64,
pub gas_used: u64,
pub baseline_new_payload_latency: u128,
pub feature_new_payload_latency: u128,
pub new_payload_latency_change_percent: f64,
@@ -220,12 +182,10 @@ impl ComparisonGenerator {
let feature =
self.feature_results.as_ref().ok_or_else(|| eyre!("Feature results not loaded"))?;
// Generate comparison
let comparison_summary =
self.calculate_comparison_summary(&baseline.summary, &feature.summary)?;
let per_block_comparisons = self.calculate_per_block_comparisons(baseline, feature)?;
let comparison_summary = self.calculate_comparison_summary(
&baseline.summary,
&feature.summary,
&per_block_comparisons,
)?;
let report = ComparisonReport {
timestamp: self.timestamp.clone(),
@@ -315,11 +275,7 @@ impl ComparisonGenerator {
Ok(rows)
}
/// Calculate summary statistics for a benchmark run.
///
/// Computes latency statistics from per-block `new_payload_latency` values in `combined_data`
/// (converting from µs to ms), and throughput metrics using the total run duration from
/// `total_gas_data`. Percentiles (p50/p90/p99) use linear interpolation on sorted latencies.
/// Calculate summary statistics for a benchmark run
fn calculate_summary(
&self,
combined_data: &[CombinedLatencyRow],
@@ -334,19 +290,9 @@ impl ComparisonGenerator {
let total_duration_ms = total_gas_data.last().unwrap().time / 1000; // Convert microseconds to milliseconds
let latencies_ms: Vec<f64> =
combined_data.iter().map(|r| r.new_payload_latency as f64 / 1000.0).collect();
let mean_new_payload_latency_ms: f64 =
latencies_ms.iter().sum::<f64>() / total_blocks as f64;
let std_dev_new_payload_latency_ms =
calculate_std_dev(&latencies_ms, mean_new_payload_latency_ms);
let mut sorted_latencies_ms = latencies_ms;
sorted_latencies_ms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
let median_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.5);
let p90_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.9);
let p99_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.99);
let avg_new_payload_latency_ms: f64 =
combined_data.iter().map(|r| r.new_payload_latency as f64 / 1000.0).sum::<f64>() /
total_blocks as f64;
let total_duration_seconds = total_duration_ms as f64 / 1000.0;
let gas_per_second = if total_duration_seconds > f64::EPSILON {
@@ -361,22 +307,13 @@ impl ComparisonGenerator {
0.0
};
let min_block_number = combined_data.first().unwrap().block_number;
let max_block_number = combined_data.last().unwrap().block_number;
Ok(BenchmarkSummary {
total_blocks,
total_gas_used,
total_duration_ms,
mean_new_payload_latency_ms,
median_new_payload_latency_ms,
p90_new_payload_latency_ms,
p99_new_payload_latency_ms,
std_dev_new_payload_latency_ms,
avg_new_payload_latency_ms,
gas_per_second,
blocks_per_second,
min_block_number,
max_block_number,
})
}
@@ -385,7 +322,6 @@ impl ComparisonGenerator {
&self,
baseline: &BenchmarkSummary,
feature: &BenchmarkSummary,
per_block_comparisons: &[BlockComparison],
) -> Result<ComparisonSummary> {
let calc_percent_change = |baseline: f64, feature: f64| -> f64 {
if baseline.abs() > f64::EPSILON {
@@ -395,54 +331,10 @@ impl ComparisonGenerator {
}
};
// Calculate per-block statistics. "Per-block" means: for each block, compute the percent
// change (feature - baseline) / baseline * 100, then calculate statistics across those
// per-block percent changes. This captures how consistently the feature performs relative
// to baseline across all blocks.
let per_block_percent_changes: Vec<f64> =
per_block_comparisons.iter().map(|c| c.new_payload_latency_change_percent).collect();
let per_block_latency_change_mean_percent = if per_block_percent_changes.is_empty() {
0.0
} else {
per_block_percent_changes.iter().sum::<f64>() / per_block_percent_changes.len() as f64
};
let per_block_latency_change_median_percent = if per_block_percent_changes.is_empty() {
0.0
} else {
let mut sorted = per_block_percent_changes.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
percentile(&sorted, 0.5)
};
let per_block_latency_change_std_dev_percent =
calculate_std_dev(&per_block_percent_changes, per_block_latency_change_mean_percent);
let baseline_total_latency_ms =
baseline.mean_new_payload_latency_ms * baseline.total_blocks as f64;
let feature_total_latency_ms =
feature.mean_new_payload_latency_ms * feature.total_blocks as f64;
let new_payload_total_latency_change_percent =
calc_percent_change(baseline_total_latency_ms, feature_total_latency_ms);
Ok(ComparisonSummary {
per_block_latency_change_mean_percent,
per_block_latency_change_median_percent,
per_block_latency_change_std_dev_percent,
new_payload_total_latency_change_percent,
new_payload_latency_p50_change_percent: calc_percent_change(
baseline.median_new_payload_latency_ms,
feature.median_new_payload_latency_ms,
),
new_payload_latency_p90_change_percent: calc_percent_change(
baseline.p90_new_payload_latency_ms,
feature.p90_new_payload_latency_ms,
),
new_payload_latency_p99_change_percent: calc_percent_change(
baseline.p99_new_payload_latency_ms,
feature.p99_new_payload_latency_ms,
),
std_dev_change_percent: calc_percent_change(
baseline.std_dev_new_payload_latency_ms,
feature.std_dev_new_payload_latency_ms,
new_payload_latency_change_percent: calc_percent_change(
baseline.avg_new_payload_latency_ms,
feature.avg_new_payload_latency_ms,
),
gas_per_second_change_percent: calc_percent_change(
baseline.gas_per_second,
@@ -479,8 +371,6 @@ impl ComparisonGenerator {
let comparison = BlockComparison {
block_number: feature_row.block_number,
transaction_count: feature_row.transaction_count,
gas_used: feature_row.gas_used,
baseline_new_payload_latency: baseline_row.new_payload_latency,
feature_new_payload_latency: feature_row.new_payload_latency,
new_payload_latency_change_percent: calc_percent_change(
@@ -546,64 +436,20 @@ impl ComparisonGenerator {
let summary = &report.comparison_summary;
println!("Performance Changes:");
println!(
" NewPayload Latency per-block mean change: {:+.2}%",
summary.per_block_latency_change_mean_percent
);
println!(
" NewPayload Latency per-block median change: {:+.2}%",
summary.per_block_latency_change_median_percent
);
println!(
" NewPayload Latency per-block std dev: {:.2}%",
summary.per_block_latency_change_std_dev_percent
);
println!(
" Total newPayload time change: {:+.2}%",
summary.new_payload_total_latency_change_percent
);
println!(
" NewPayload Latency p50: {:+.2}%",
summary.new_payload_latency_p50_change_percent
);
println!(
" NewPayload Latency p90: {:+.2}%",
summary.new_payload_latency_p90_change_percent
);
println!(
" NewPayload Latency p99: {:+.2}%",
summary.new_payload_latency_p99_change_percent
);
println!(" NewPayload Latency std dev: {:+.2}%", summary.std_dev_change_percent);
println!(
" Gas/Second: {:+.2}%",
summary.gas_per_second_change_percent
);
println!(
" Blocks/Second: {:+.2}%",
summary.blocks_per_second_change_percent
);
println!(" NewPayload Latency: {:+.2}%", summary.new_payload_latency_change_percent);
println!(" Gas/Second: {:+.2}%", summary.gas_per_second_change_percent);
println!(" Blocks/Second: {:+.2}%", summary.blocks_per_second_change_percent);
println!();
println!("Baseline Summary:");
let baseline = &report.baseline.summary;
println!(
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
" Blocks: {}, Gas: {}, Duration: {:.2}s",
baseline.total_blocks,
baseline.min_block_number,
baseline.max_block_number,
baseline.total_gas_used,
baseline.total_duration_ms as f64 / 1000.0
);
println!(" NewPayload latency (ms):");
println!(
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}, std dev: {:.2}",
baseline.mean_new_payload_latency_ms,
baseline.median_new_payload_latency_ms,
baseline.p90_new_payload_latency_ms,
baseline.p99_new_payload_latency_ms,
baseline.std_dev_new_payload_latency_ms
);
println!(" Avg NewPayload: {:.2}ms", baseline.avg_new_payload_latency_ms);
if let (Some(start), Some(end)) =
(&report.baseline.start_timestamp, &report.baseline.end_timestamp)
{
@@ -618,22 +464,12 @@ impl ComparisonGenerator {
println!("Feature Summary:");
let feature = &report.feature.summary;
println!(
" Blocks: {} (blocks {} to {}), Gas: {}, Duration: {:.2}s",
" Blocks: {}, Gas: {}, Duration: {:.2}s",
feature.total_blocks,
feature.min_block_number,
feature.max_block_number,
feature.total_gas_used,
feature.total_duration_ms as f64 / 1000.0
);
println!(" NewPayload latency (ms):");
println!(
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}, std dev: {:.2}",
feature.mean_new_payload_latency_ms,
feature.median_new_payload_latency_ms,
feature.p90_new_payload_latency_ms,
feature.p99_new_payload_latency_ms,
feature.std_dev_new_payload_latency_ms
);
println!(" Avg NewPayload: {:.2}ms", feature.avg_new_payload_latency_ms);
if let (Some(start), Some(end)) =
(&report.feature.start_timestamp, &report.feature.end_timestamp)
{
@@ -646,52 +482,3 @@ impl ComparisonGenerator {
println!();
}
}
/// Calculate standard deviation from a set of values and their mean.
///
/// Computes the population standard deviation using the formula:
/// `sqrt(sum((x - mean)²) / n)`
///
/// Returns 0.0 for empty input.
fn calculate_std_dev(values: &[f64], mean: f64) -> f64 {
if values.is_empty() {
return 0.0;
}
let variance = values
.iter()
.map(|x| {
let diff = x - mean;
diff * diff
})
.sum::<f64>() /
values.len() as f64;
variance.sqrt()
}
/// Calculate percentile using linear interpolation on a sorted slice.
///
/// Computes `rank = percentile × (n - 1)` where n is the array length. If the rank falls
/// between two indices, linearly interpolates between those values. For example, with 100 values,
/// p90 computes rank = 0.9 × 99 = 89.1, then returns `values[89] × 0.9 + values[90] × 0.1`.
///
/// Returns 0.0 for empty input.
fn percentile(sorted_values: &[f64], percentile: f64) -> f64 {
if sorted_values.is_empty() {
return 0.0;
}
let clamped = percentile.clamp(0.0, 1.0);
let max_index = sorted_values.len() - 1;
let rank = clamped * max_index as f64;
let lower = rank.floor() as usize;
let upper = rank.ceil() as usize;
if lower == upper {
sorted_values[lower]
} else {
let weight = rank - lower as f64;
sorted_values[lower].mul_add(1.0 - weight, sorted_values[upper] * weight)
}
}

View File

@@ -181,43 +181,45 @@ impl GitManager {
/// Validate that the specified git references exist (branches, tags, or commits)
pub(crate) fn validate_refs(&self, refs: &[&str]) -> Result<()> {
for &git_ref in refs {
// Try to resolve the ref similar to `git checkout` by peeling to a commit.
// First try the ref as-is with ^{commit}, then fall back to origin/{ref}^{commit}.
let as_is = format!("{git_ref}^{{commit}}");
let ref_check = Command::new("git")
.args(["rev-parse", "--verify", &as_is])
// Try branch first, then tag, then commit
let branch_check = Command::new("git")
.args(["rev-parse", "--verify", &format!("refs/heads/{git_ref}")])
.current_dir(&self.repo_root)
.output();
let found = if let Ok(output) = ref_check &&
let tag_check = Command::new("git")
.args(["rev-parse", "--verify", &format!("refs/tags/{git_ref}")])
.current_dir(&self.repo_root)
.output();
let commit_check = Command::new("git")
.args(["rev-parse", "--verify", &format!("{git_ref}^{{commit}}")])
.current_dir(&self.repo_root)
.output();
let found = if let Ok(output) = branch_check &&
output.status.success()
{
info!("Validated reference exists: {}", git_ref);
info!("Validated branch exists: {}", git_ref);
true
} else if let Ok(output) = tag_check &&
output.status.success()
{
info!("Validated tag exists: {}", git_ref);
true
} else if let Ok(output) = commit_check &&
output.status.success()
{
info!("Validated commit exists: {}", git_ref);
true
} else {
// Try remote-only branches via origin/{ref}
let origin_ref = format!("origin/{git_ref}^{{commit}}");
let origin_check = Command::new("git")
.args(["rev-parse", "--verify", &origin_ref])
.current_dir(&self.repo_root)
.output();
if let Ok(output) = origin_check &&
output.status.success()
{
info!("Validated remote reference exists: origin/{}", git_ref);
true
} else {
false
}
false
};
if !found {
return Err(eyre!(
"Git reference '{}' does not exist as branch, tag, or commit (tried '{}' and 'origin/{}^{{commit}}')",
git_ref,
format!("{git_ref}^{{commit}}"),
git_ref,
"Git reference '{}' does not exist as branch, tag, or commit",
git_ref
));
}
}

View File

@@ -29,8 +29,6 @@ pub(crate) struct NodeManager {
output_dir: PathBuf,
additional_reth_args: Vec<String>,
comparison_dir: Option<PathBuf>,
tracing_endpoint: Option<String>,
otlp_max_queue_size: usize,
}
impl NodeManager {
@@ -44,16 +42,8 @@ impl NodeManager {
binary_path: None,
enable_profiling: args.profile,
output_dir: args.output_dir_path(),
// Filter out empty strings to prevent invalid arguments being passed to reth node
additional_reth_args: args
.reth_args
.iter()
.filter(|s| !s.is_empty())
.cloned()
.collect(),
additional_reth_args: args.reth_args.clone(),
comparison_dir: None,
tracing_endpoint: args.traces.otlp.as_ref().map(|u| u.to_string()),
otlp_max_queue_size: args.otlp_max_queue_size,
}
}
@@ -129,7 +119,6 @@ impl NodeManager {
&self,
binary_path_str: &str,
additional_args: &[String],
ref_type: &str,
) -> (Vec<String>, String) {
let mut reth_args = vec![binary_path_str.to_string(), "node".to_string()];
@@ -157,13 +146,6 @@ impl NodeManager {
"--trusted-only".to_string(),
]);
// Add tracing arguments if OTLP endpoint is configured
if let Some(ref endpoint) = self.tracing_endpoint {
info!("Enabling OTLP tracing export to: {} (service: reth-{})", endpoint, ref_type);
// Endpoint requires equals per clap settings in reth
reth_args.push(format!("--tracing-otlp={}", endpoint));
}
// Add any additional arguments passed via command line (common to both baseline and
// feature)
reth_args.extend_from_slice(&self.additional_reth_args);
@@ -211,9 +193,6 @@ impl NodeManager {
cmd.arg("--");
cmd.args(reth_args);
// Set environment variable to disable log styling
cmd.env("RUST_LOG_STYLE", "never");
Ok(cmd)
}
@@ -221,22 +200,17 @@ impl NodeManager {
fn create_direct_command(&self, reth_args: &[String]) -> Command {
let binary_path = &reth_args[0];
let mut cmd = if self.use_sudo {
if self.use_sudo {
info!("Starting reth node with sudo...");
let mut sudo_cmd = Command::new("sudo");
sudo_cmd.args(reth_args);
sudo_cmd
let mut cmd = Command::new("sudo");
cmd.args(reth_args);
cmd
} else {
info!("Starting reth node...");
let mut reth_cmd = Command::new(binary_path);
reth_cmd.args(&reth_args[1..]); // Skip the binary path since it's the command
reth_cmd
};
// Set environment variable to disable log styling
cmd.env("RUST_LOG_STYLE", "never");
cmd
let mut cmd = Command::new(binary_path);
cmd.args(&reth_args[1..]); // Skip the binary path since it's the command
cmd
}
}
/// Start a reth node using the specified binary path and return the process handle
@@ -251,7 +225,7 @@ impl NodeManager {
self.binary_path = Some(binary_path.to_path_buf());
let binary_path_str = binary_path.to_string_lossy();
let (reth_args, _) = self.build_reth_args(&binary_path_str, additional_args, ref_type);
let (reth_args, _) = self.build_reth_args(&binary_path_str, additional_args);
// Log additional arguments if any
if !self.additional_reth_args.is_empty() {
@@ -273,15 +247,6 @@ impl NodeManager {
cmd.process_group(0);
}
// Set high queue size to prevent trace dropping during benchmarks
if self.tracing_endpoint.is_some() {
cmd.env("OTEL_BSP_MAX_QUEUE_SIZE", self.otlp_max_queue_size.to_string()); // Traces
cmd.env("OTEL_BLRP_MAX_QUEUE_SIZE", "10000"); // Logs
// Set service name to differentiate baseline vs feature runs in Jaeger
cmd.env("OTEL_SERVICE_NAME", format!("reth-{}", ref_type));
}
debug!("Executing reth command: {cmd:?}");
let mut child = cmd
@@ -503,9 +468,6 @@ impl NodeManager {
cmd.args(["to-block", &block_number.to_string()]);
// Set environment variable to disable log styling
cmd.env("RUST_LOG_STYLE", "never");
// Debug log the command
debug!("Executing reth unwind command: {:?}", cmd);

View File

@@ -79,13 +79,22 @@ impl Command {
break;
}
};
let header = block.header.clone();
let head_block_hash = block.header.hash;
let safe_block_hash = block_provider
.get_block_by_number(block.header.number.saturating_sub(32).into());
let (version, params) = match block_to_new_payload(block, is_optimism) {
Ok(result) => result,
Err(e) => {
tracing::error!("Failed to convert block to new payload: {e}");
let _ = error_sender.send(e);
break;
}
};
let head_block_hash = header.hash;
let safe_block_hash =
block_provider.get_block_by_number(header.number.saturating_sub(32).into());
let finalized_block_hash = block_provider
.get_block_by_number(block.header.number.saturating_sub(64).into());
let finalized_block_hash =
block_provider.get_block_by_number(header.number.saturating_sub(64).into());
let (safe, finalized) = tokio::join!(safe_block_hash, finalized_block_hash,);
@@ -101,7 +110,14 @@ impl Command {
next_block += 1;
if let Err(e) = sender
.send((block, head_block_hash, safe_block_hash, finalized_block_hash))
.send((
header,
version,
params,
head_block_hash,
safe_block_hash,
finalized_block_hash,
))
.await
{
tracing::error!("Failed to send block data: {e}");
@@ -115,16 +131,15 @@ impl Command {
let total_benchmark_duration = Instant::now();
let mut total_wait_time = Duration::ZERO;
while let Some((block, head, safe, finalized)) = {
while let Some((header, version, params, head, safe, finalized)) = {
let wait_start = Instant::now();
let result = receiver.recv().await;
total_wait_time += wait_start.elapsed();
result
} {
// just put gas used here
let gas_used = block.header.gas_used;
let block_number = block.header.number;
let transaction_count = block.transactions.len() as u64;
let gas_used = header.gas_used;
let block_number = header.number;
debug!(target: "reth-bench", ?block_number, "Sending payload",);
@@ -135,7 +150,6 @@ impl Command {
finalized_block_hash: finalized,
};
let (version, params) = block_to_new_payload(block, is_optimism)?;
let start = Instant::now();
call_new_payload(&auth_provider, version, params).await?;
@@ -146,13 +160,8 @@ impl Command {
// calculate the total duration and the fcu latency, record
let total_latency = start.elapsed();
let fcu_latency = total_latency - new_payload_result.latency;
let combined_result = CombinedResult {
block_number,
transaction_count,
new_payload_result,
fcu_latency,
total_latency,
};
let combined_result =
CombinedResult { block_number, new_payload_result, fcu_latency, total_latency };
// current duration since the start of the benchmark minus the time
// waiting for blocks
@@ -165,8 +174,7 @@ impl Command {
tokio::time::sleep(self.wait_time).await;
// record the current result
let gas_row =
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
let gas_row = TotalGasRow { block_number, gas_used, time: current_duration };
results.push((gas_row, combined_result));
}

View File

@@ -72,9 +72,19 @@ impl Command {
break;
}
};
let header = block.header.clone();
let (version, params) = match block_to_new_payload(block, is_optimism) {
Ok(result) => result,
Err(e) => {
tracing::error!("Failed to convert block to new payload: {e}");
let _ = error_sender.send(e);
break;
}
};
next_block += 1;
if let Err(e) = sender.send(block).await {
if let Err(e) = sender.send((header, version, params)).await {
tracing::error!("Failed to send block data: {e}");
break;
}
@@ -86,24 +96,23 @@ impl Command {
let total_benchmark_duration = Instant::now();
let mut total_wait_time = Duration::ZERO;
while let Some(block) = {
while let Some((header, version, params)) = {
let wait_start = Instant::now();
let result = receiver.recv().await;
total_wait_time += wait_start.elapsed();
result
} {
let block_number = block.header.number;
let transaction_count = block.transactions.len() as u64;
let gas_used = block.header.gas_used;
// just put gas used here
let gas_used = header.gas_used;
let block_number = header.number;
debug!(
target: "reth-bench",
number=?block.header.number,
number=?header.number,
"Sending payload to engine",
);
let (version, params) = block_to_new_payload(block, is_optimism)?;
let start = Instant::now();
call_new_payload(&auth_provider, version, params).await?;
@@ -115,8 +124,7 @@ impl Command {
let current_duration = total_benchmark_duration.elapsed() - total_wait_time;
// record the current result
let row =
TotalGasRow { block_number, transaction_count, gas_used, time: current_duration };
let row = TotalGasRow { block_number, gas_used, time: current_duration };
results.push((row, new_payload_result));
}

View File

@@ -67,8 +67,6 @@ impl Serialize for NewPayloadResult {
pub(crate) struct CombinedResult {
/// The block number of the block being processed.
pub(crate) block_number: u64,
/// The number of transactions in the block.
pub(crate) transaction_count: u64,
/// The `newPayload` result.
pub(crate) new_payload_result: NewPayloadResult,
/// The latency of the `forkchoiceUpdated` call.
@@ -110,11 +108,10 @@ impl Serialize for CombinedResult {
let fcu_latency = self.fcu_latency.as_micros();
let new_payload_latency = self.new_payload_result.latency.as_micros();
let total_latency = self.total_latency.as_micros();
let mut state = serializer.serialize_struct("CombinedResult", 6)?;
let mut state = serializer.serialize_struct("CombinedResult", 5)?;
// flatten the new payload result because this is meant for CSV writing
state.serialize_field("block_number", &self.block_number)?;
state.serialize_field("transaction_count", &self.transaction_count)?;
state.serialize_field("gas_used", &self.new_payload_result.gas_used)?;
state.serialize_field("new_payload_latency", &new_payload_latency)?;
state.serialize_field("fcu_latency", &fcu_latency)?;
@@ -128,8 +125,6 @@ impl Serialize for CombinedResult {
pub(crate) struct TotalGasRow {
/// The block number of the block being processed.
pub(crate) block_number: u64,
/// The number of transactions in the block.
pub(crate) transaction_count: u64,
/// The total gas used in the block.
pub(crate) gas_used: u64,
/// Time since the start of the benchmark.
@@ -177,9 +172,8 @@ impl Serialize for TotalGasRow {
{
// convert the time to microseconds
let time = self.time.as_micros();
let mut state = serializer.serialize_struct("TotalGasRow", 4)?;
let mut state = serializer.serialize_struct("TotalGasRow", 3)?;
state.serialize_field("block_number", &self.block_number)?;
state.serialize_field("transaction_count", &self.transaction_count)?;
state.serialize_field("gas_used", &self.gas_used)?;
state.serialize_field("time", &time)?;
state.end()
@@ -194,12 +188,7 @@ mod tests {
#[test]
fn test_write_total_gas_row_csv() {
let row = TotalGasRow {
block_number: 1,
transaction_count: 10,
gas_used: 1_000,
time: Duration::from_secs(1),
};
let row = TotalGasRow { block_number: 1, gas_used: 1_000, time: Duration::from_secs(1) };
let mut writer = Writer::from_writer(vec![]);
writer.serialize(row).unwrap();
@@ -209,11 +198,11 @@ mod tests {
let mut result = result.as_slice().lines();
// assert header
let expected_first_line = "block_number,transaction_count,gas_used,time";
let expected_first_line = "block_number,gas_used,time";
let first_line = result.next().unwrap().unwrap();
assert_eq!(first_line, expected_first_line);
let expected_second_line = "1,10,1000,1000000";
let expected_second_line = "1,1000,1000000";
let second_line = result.next().unwrap().unwrap();
assert_eq!(second_line, expected_second_line);
}

View File

@@ -9,20 +9,6 @@ repository.workspace = true
description = "Reth node implementation"
default-run = "reth"
[package.metadata.deb]
maintainer = "reth team"
depends = "$auto"
section = "network"
priority = "optional"
maintainer-scripts = "../../pkg/reth/debian/"
assets = [
"$auto",
["../../README.md", "usr/share/doc/reth/", "644"],
["../../LICENSE-APACHE", "usr/share/doc/reth/", "644"],
["../../LICENSE-MIT", "usr/share/doc/reth/", "644"],
]
systemd-units = { enable = false, start = false, unit-name = "reth", unit-scripts = "../../pkg/reth/debian" }
[lints]
workspace = true
@@ -114,12 +100,6 @@ jemalloc-prof = [
"reth-cli-util/jemalloc-prof",
"reth-ethereum-cli/jemalloc-prof",
]
jemalloc-unprefixed = [
"reth-cli-util/jemalloc-unprefixed",
"reth-node-core/jemalloc",
"reth-node-metrics/jemalloc",
"reth-ethereum-cli/jemalloc",
]
tracy-allocator = [
"reth-cli-util/tracy-allocator",
"reth-ethereum-cli/tracy-allocator",

View File

@@ -1,444 +0,0 @@
use alloy_primitives::B256;
use parking_lot::Mutex;
use reth_metrics::{metrics::Counter, Metrics};
use reth_trie::{
updates::{TrieUpdates, TrieUpdatesSorted},
HashedPostState, HashedPostStateSorted, TrieInputSorted,
};
use std::{
fmt,
sync::{Arc, LazyLock},
};
use tracing::instrument;
/// Shared handle to asynchronously populated trie data.
///
/// Uses a try-lock + fallback computation approach for deadlock-free access.
/// If the deferred task hasn't completed, computes trie data synchronously
/// from stored unsorted inputs rather than blocking.
#[derive(Clone)]
pub struct DeferredTrieData {
/// Shared deferred state holding either raw inputs (pending) or computed result (ready).
state: Arc<Mutex<DeferredState>>,
}
/// Sorted trie data computed for an executed block.
/// These represent the complete set of sorted trie data required to persist
/// block state for, and generate proofs on top of, a block.
#[derive(Clone, Debug, Default)]
pub struct ComputedTrieData {
/// Sorted hashed post-state produced by execution.
pub hashed_state: Arc<HashedPostStateSorted>,
/// Sorted trie updates produced by state root computation.
pub trie_updates: Arc<TrieUpdatesSorted>,
/// Trie input bundled with its anchor hash, if available.
pub anchored_trie_input: Option<AnchoredTrieInput>,
}
/// Trie input bundled with its anchor hash.
///
/// This is used to store the trie input and anchor hash for a block together.
#[derive(Clone, Debug)]
pub struct AnchoredTrieInput {
/// The persisted ancestor hash this trie input is anchored to.
pub anchor_hash: B256,
/// Trie input constructed from in-memory overlays.
pub trie_input: Arc<TrieInputSorted>,
}
/// Metrics for deferred trie computation.
#[derive(Metrics)]
#[metrics(scope = "sync.block_validation")]
struct DeferredTrieMetrics {
/// Number of times deferred trie data was ready (async task completed first).
deferred_trie_async_ready: Counter,
/// Number of times deferred trie data required synchronous computation (fallback path).
deferred_trie_sync_fallback: Counter,
}
static DEFERRED_TRIE_METRICS: LazyLock<DeferredTrieMetrics> =
LazyLock::new(DeferredTrieMetrics::default);
/// Internal state for deferred trie data.
enum DeferredState {
/// Data is not yet available; raw inputs stored for fallback computation.
Pending(PendingInputs),
/// Data has been computed and is ready.
Ready(ComputedTrieData),
}
/// Inputs kept while a deferred trie computation is pending.
#[derive(Clone, Debug)]
struct PendingInputs {
/// Unsorted hashed post-state from execution.
hashed_state: Arc<HashedPostState>,
/// Unsorted trie updates from state root computation.
trie_updates: Arc<TrieUpdates>,
/// The persisted ancestor hash this trie input is anchored to.
anchor_hash: B256,
/// Deferred trie data from ancestor blocks for merging.
ancestors: Vec<DeferredTrieData>,
}
impl fmt::Debug for DeferredTrieData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.state.lock();
match &*state {
DeferredState::Pending(_) => {
f.debug_struct("DeferredTrieData").field("state", &"pending").finish()
}
DeferredState::Ready(_) => {
f.debug_struct("DeferredTrieData").field("state", &"ready").finish()
}
}
}
}
impl DeferredTrieData {
/// Create a new pending handle with fallback inputs for synchronous computation.
///
/// If the async task hasn't completed when `wait_cloned` is called, the trie data
/// will be computed synchronously from these inputs. This eliminates deadlock risk.
///
/// # Arguments
/// * `hashed_state` - Unsorted hashed post-state from execution
/// * `trie_updates` - Unsorted trie updates from state root computation
/// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
/// * `ancestors` - Deferred trie data from ancestor blocks for merging
pub fn pending(
hashed_state: Arc<HashedPostState>,
trie_updates: Arc<TrieUpdates>,
anchor_hash: B256,
ancestors: Vec<Self>,
) -> Self {
Self {
state: Arc::new(Mutex::new(DeferredState::Pending(PendingInputs {
hashed_state,
trie_updates,
anchor_hash,
ancestors,
}))),
}
}
/// Create a handle that is already populated with the given [`ComputedTrieData`].
///
/// Useful when trie data is available immediately.
/// [`Self::wait_cloned`] will return without any computation.
pub fn ready(bundle: ComputedTrieData) -> Self {
Self { state: Arc::new(Mutex::new(DeferredState::Ready(bundle))) }
}
/// Sort block execution outputs and build a [`TrieInputSorted`] overlay.
///
/// The trie input overlay accumulates sorted hashed state (account/storage changes) and
/// trie node updates from all in-memory ancestor blocks. This overlay is required for:
/// - Computing state roots on top of in-memory blocks
/// - Generating storage/account proofs for unpersisted state
///
/// # Process
/// 1. Sort the current block's hashed state and trie updates
/// 2. Merge ancestor overlays (oldest -> newest, so later state takes precedence)
/// 3. Extend the merged overlay with this block's sorted data
///
/// Used by both the async background task and the synchronous fallback path.
///
/// # Arguments
/// * `hashed_state` - Unsorted hashed post-state (account/storage changes) from execution
/// * `trie_updates` - Unsorted trie node updates from state root computation
/// * `anchor_hash` - The persisted ancestor hash this trie input is anchored to
/// * `ancestors` - Deferred trie data from ancestor blocks for merging
pub fn sort_and_build_trie_input(
hashed_state: &HashedPostState,
trie_updates: &TrieUpdates,
anchor_hash: B256,
ancestors: &[Self],
) -> ComputedTrieData {
// Sort the current block's hashed state and trie updates
let sorted_hashed_state = Arc::new(hashed_state.clone_into_sorted());
let sorted_trie_updates = Arc::new(trie_updates.clone().into_sorted());
// Merge trie data from ancestors (oldest -> newest so later state takes precedence)
let mut overlay = TrieInputSorted::default();
for ancestor in ancestors {
let ancestor_data = ancestor.wait_cloned();
{
let state_mut = Arc::make_mut(&mut overlay.state);
state_mut.extend_ref(ancestor_data.hashed_state.as_ref());
}
{
let nodes_mut = Arc::make_mut(&mut overlay.nodes);
nodes_mut.extend_ref(ancestor_data.trie_updates.as_ref());
}
}
// Extend overlay with current block's sorted data
{
let state_mut = Arc::make_mut(&mut overlay.state);
state_mut.extend_ref(sorted_hashed_state.as_ref());
}
{
let nodes_mut = Arc::make_mut(&mut overlay.nodes);
nodes_mut.extend_ref(sorted_trie_updates.as_ref());
}
ComputedTrieData::with_trie_input(
sorted_hashed_state,
sorted_trie_updates,
anchor_hash,
Arc::new(overlay),
)
}
/// Returns trie data, computing synchronously if the async task hasn't completed.
///
/// - If the async task has completed (`Ready`), returns the cached result.
/// - If pending, computes synchronously from stored inputs.
///
/// Deadlock is avoided as long as the provided ancestors form a true ancestor chain (a DAG):
/// - Each block only waits on its ancestors (blocks on the path to the persisted root)
/// - Sibling blocks (forks) are never in each other's ancestor lists
/// - A block never waits on its descendants
///
/// Given that invariant, circular wait dependencies are impossible.
#[instrument(level = "debug", target = "engine::tree::deferred_trie", skip_all)]
pub fn wait_cloned(&self) -> ComputedTrieData {
let mut state = self.state.lock();
match &*state {
// If the deferred trie data is ready, return the cached result.
DeferredState::Ready(bundle) => {
DEFERRED_TRIE_METRICS.deferred_trie_async_ready.increment(1);
bundle.clone()
}
// If the deferred trie data is pending, compute the trie data synchronously and return
// the result. This is the fallback path if the async task hasn't completed.
DeferredState::Pending(inputs) => {
DEFERRED_TRIE_METRICS.deferred_trie_sync_fallback.increment(1);
let computed = Self::sort_and_build_trie_input(
&inputs.hashed_state,
&inputs.trie_updates,
inputs.anchor_hash,
&inputs.ancestors,
);
*state = DeferredState::Ready(computed.clone());
computed
}
}
}
}
impl ComputedTrieData {
/// Construct a bundle that includes trie input anchored to a persisted ancestor.
pub const fn with_trie_input(
hashed_state: Arc<HashedPostStateSorted>,
trie_updates: Arc<TrieUpdatesSorted>,
anchor_hash: B256,
trie_input: Arc<TrieInputSorted>,
) -> Self {
Self {
hashed_state,
trie_updates,
anchored_trie_input: Some(AnchoredTrieInput { anchor_hash, trie_input }),
}
}
/// Construct a bundle without trie input or anchor information.
///
/// Unlike [`Self::with_trie_input`], this constructor omits the accumulated trie input overlay
/// and its anchor hash. Use this when the trie input is not needed, such as in block builders
/// or sequencers that don't require proof generation on top of in-memory state.
///
/// The trie input anchor identifies the persisted block hash from which the in-memory overlay
/// was built. Without it, consumers cannot determine which on-disk state to combine with.
pub const fn without_trie_input(
hashed_state: Arc<HashedPostStateSorted>,
trie_updates: Arc<TrieUpdatesSorted>,
) -> Self {
Self { hashed_state, trie_updates, anchored_trie_input: None }
}
/// Returns the anchor hash, if present.
pub fn anchor_hash(&self) -> Option<B256> {
self.anchored_trie_input.as_ref().map(|anchored| anchored.anchor_hash)
}
/// Returns the trie input, if present.
pub fn trie_input(&self) -> Option<&Arc<TrieInputSorted>> {
self.anchored_trie_input.as_ref().map(|anchored| &anchored.trie_input)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{map::B256Map, U256};
use reth_primitives_traits::Account;
use reth_trie::updates::TrieUpdates;
use std::{
sync::Arc,
thread,
time::{Duration, Instant},
};
fn empty_bundle() -> ComputedTrieData {
ComputedTrieData {
hashed_state: Arc::default(),
trie_updates: Arc::default(),
anchored_trie_input: None,
}
}
fn empty_pending() -> DeferredTrieData {
empty_pending_with_anchor(B256::ZERO)
}
fn empty_pending_with_anchor(anchor: B256) -> DeferredTrieData {
DeferredTrieData::pending(
Arc::new(HashedPostState::default()),
Arc::new(TrieUpdates::default()),
anchor,
Vec::new(),
)
}
/// Verifies that a ready handle returns immediately without computation.
#[test]
fn ready_returns_immediately() {
let bundle = empty_bundle();
let deferred = DeferredTrieData::ready(bundle.clone());
let start = Instant::now();
let result = deferred.wait_cloned();
let elapsed = start.elapsed();
assert_eq!(result.hashed_state, bundle.hashed_state);
assert_eq!(result.trie_updates, bundle.trie_updates);
assert_eq!(result.anchor_hash(), bundle.anchor_hash());
assert!(elapsed < Duration::from_millis(20));
}
/// Verifies that a pending handle computes trie data synchronously via fallback.
#[test]
fn pending_computes_fallback() {
let deferred = empty_pending();
// wait_cloned should compute from inputs without blocking
let start = Instant::now();
let result = deferred.wait_cloned();
let elapsed = start.elapsed();
// Should return quickly (fallback computation)
assert!(elapsed < Duration::from_millis(100));
assert!(result.hashed_state.is_empty());
}
/// Verifies that fallback computation result is cached for subsequent calls.
#[test]
fn fallback_result_is_cached() {
let deferred = empty_pending();
// First call computes and should stash the result
let first = deferred.wait_cloned();
// Second call should reuse the cached result (same Arc pointer)
let second = deferred.wait_cloned();
assert!(Arc::ptr_eq(&first.hashed_state, &second.hashed_state));
assert!(Arc::ptr_eq(&first.trie_updates, &second.trie_updates));
assert_eq!(first.anchor_hash(), second.anchor_hash());
}
/// Verifies that concurrent `wait_cloned` calls result in only one computation,
/// with all callers receiving the same cached result.
#[test]
fn concurrent_wait_cloned_computes_once() {
let deferred = empty_pending();
// Spawn multiple threads that all call wait_cloned concurrently
let handles: Vec<_> = (0..10)
.map(|_| {
let d = deferred.clone();
thread::spawn(move || d.wait_cloned())
})
.collect();
// Collect all results
let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
// All results should share the same Arc pointers (same computed result)
let first = &results[0];
for result in &results[1..] {
assert!(Arc::ptr_eq(&first.hashed_state, &result.hashed_state));
assert!(Arc::ptr_eq(&first.trie_updates, &result.trie_updates));
}
}
/// Tests that ancestor trie data is merged during fallback computation and that the
/// resulting `ComputedTrieData` uses the current block's anchor hash, not the ancestor's.
#[test]
fn ancestors_are_merged() {
// Create ancestor with some data
let ancestor_bundle = ComputedTrieData {
hashed_state: Arc::default(),
trie_updates: Arc::default(),
anchored_trie_input: Some(AnchoredTrieInput {
anchor_hash: B256::with_last_byte(1),
trie_input: Arc::new(TrieInputSorted::default()),
}),
};
let ancestor = DeferredTrieData::ready(ancestor_bundle);
// Create pending with ancestor
let deferred = DeferredTrieData::pending(
Arc::new(HashedPostState::default()),
Arc::new(TrieUpdates::default()),
B256::with_last_byte(2),
vec![ancestor],
);
let result = deferred.wait_cloned();
// Should have the current block's anchor, not the ancestor's
assert_eq!(result.anchor_hash(), Some(B256::with_last_byte(2)));
}
/// Ensures ancestor overlays are merged oldest -> newest so latest state wins (no overwrite by
/// older ancestors).
#[test]
fn ancestors_merge_in_chronological_order() {
let key = B256::with_last_byte(1);
// Oldest ancestor sets nonce to 1
let oldest_state = HashedPostStateSorted::new(
vec![(key, Some(Account { nonce: 1, balance: U256::ZERO, bytecode_hash: None }))],
B256Map::default(),
);
// Newest ancestor overwrites nonce to 2
let newest_state = HashedPostStateSorted::new(
vec![(key, Some(Account { nonce: 2, balance: U256::ZERO, bytecode_hash: None }))],
B256Map::default(),
);
let oldest = ComputedTrieData {
hashed_state: Arc::new(oldest_state),
trie_updates: Arc::default(),
anchored_trie_input: None,
};
let newest = ComputedTrieData {
hashed_state: Arc::new(newest_state),
trie_updates: Arc::default(),
anchored_trie_input: None,
};
// Pass ancestors oldest -> newest; newest should take precedence
let deferred = DeferredTrieData::pending(
Arc::new(HashedPostState::default()),
Arc::new(TrieUpdates::default()),
B256::ZERO,
vec![DeferredTrieData::ready(oldest), DeferredTrieData::ready(newest)],
);
let result = deferred.wait_cloned();
let overlay_state = &result.anchored_trie_input.as_ref().unwrap().trie_input.state.accounts;
assert_eq!(overlay_state.len(), 1);
let (_, account) = &overlay_state[0];
assert_eq!(account.unwrap().nonce, 2);
}
}

View File

@@ -2,7 +2,7 @@
use crate::{
CanonStateNotification, CanonStateNotificationSender, CanonStateNotifications,
ChainInfoTracker, ComputedTrieData, DeferredTrieData, MemoryOverlayStateProvider,
ChainInfoTracker, MemoryOverlayStateProvider,
};
use alloy_consensus::{transaction::TransactionMeta, BlockHeader};
use alloy_eips::{BlockHashOrNumber, BlockNumHash};
@@ -17,7 +17,7 @@ use reth_primitives_traits::{
SignedTransaction,
};
use reth_storage_api::StateProviderBox;
use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
use reth_trie::{updates::TrieUpdates, HashedPostState};
use std::{collections::BTreeMap, sync::Arc, time::Instant};
use tokio::sync::{broadcast, watch};
@@ -565,7 +565,7 @@ impl<N: NodePrimitives> CanonicalInMemoryState<N> {
/// State after applying the given block, this block is part of the canonical chain that partially
/// stored in memory and can be traced back to a canonical block on disk.
#[derive(Debug, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct BlockState<N: NodePrimitives = EthPrimitives> {
/// The executed block that determines the state after this block has been executed.
block: ExecutedBlock<N>,
@@ -573,12 +573,6 @@ pub struct BlockState<N: NodePrimitives = EthPrimitives> {
parent: Option<Arc<Self>>,
}
impl<N: NodePrimitives> PartialEq for BlockState<N> {
fn eq(&self, other: &Self) -> bool {
self.block == other.block && self.parent == other.parent
}
}
impl<N: NodePrimitives> BlockState<N> {
/// [`BlockState`] constructor.
pub const fn new(block: ExecutedBlock<N>) -> Self {
@@ -725,17 +719,16 @@ impl<N: NodePrimitives> BlockState<N> {
}
/// Represents an executed block stored in-memory.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ExecutedBlock<N: NodePrimitives = EthPrimitives> {
/// Recovered Block
pub recovered_block: Arc<RecoveredBlock<N::Block>>,
/// Block's execution outcome.
pub execution_output: Arc<ExecutionOutcome<N::Receipt>>,
/// Deferred trie data produced by execution.
///
/// This allows deferring the computation of the trie data which can be expensive.
/// The data can be populated asynchronously after the block was validated.
pub trie_data: DeferredTrieData,
/// Block's hashed state.
pub hashed_state: Arc<HashedPostState>,
/// Trie updates that result from calculating the state root for the block.
pub trie_updates: Arc<TrieUpdates>,
}
impl<N: NodePrimitives> Default for ExecutedBlock<N> {
@@ -743,54 +736,13 @@ impl<N: NodePrimitives> Default for ExecutedBlock<N> {
Self {
recovered_block: Default::default(),
execution_output: Default::default(),
trie_data: DeferredTrieData::ready(ComputedTrieData::default()),
hashed_state: Default::default(),
trie_updates: Default::default(),
}
}
}
impl<N: NodePrimitives> PartialEq for ExecutedBlock<N> {
fn eq(&self, other: &Self) -> bool {
// Trie data is computed asynchronously and doesn't define block identity.
self.recovered_block == other.recovered_block &&
self.execution_output == other.execution_output
}
}
impl<N: NodePrimitives> ExecutedBlock<N> {
/// Create a new [`ExecutedBlock`] with already-computed trie data.
///
/// Use this constructor when trie data is available immediately (e.g., sequencers,
/// payload builders). This is the safe default path.
pub fn new(
recovered_block: Arc<RecoveredBlock<N::Block>>,
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
trie_data: ComputedTrieData,
) -> Self {
Self { recovered_block, execution_output, trie_data: DeferredTrieData::ready(trie_data) }
}
/// Create a new [`ExecutedBlock`] with deferred trie data.
///
/// This is useful if the trie data is populated somewhere else, e.g. asynchronously
/// after the block was validated.
///
/// The [`DeferredTrieData`] handle allows expensive trie operations (sorting hashed state,
/// sorting trie updates, and building the accumulated trie input overlay) to be performed
/// outside the critical validation path. This can improve latency for time-sensitive
/// operations like block validation.
///
/// If the data hasn't been populated when [`Self::trie_data()`] is called, computation
/// occurs synchronously from stored inputs, so there is no blocking or deadlock risk.
///
/// Use [`Self::new()`] instead when trie data is already computed and available immediately.
pub const fn with_deferred_trie_data(
recovered_block: Arc<RecoveredBlock<N::Block>>,
execution_output: Arc<ExecutionOutcome<N::Receipt>>,
trie_data: DeferredTrieData,
) -> Self {
Self { recovered_block, execution_output, trie_data }
}
/// Returns a reference to an inner [`SealedBlock`]
#[inline]
pub fn sealed_block(&self) -> &SealedBlock<N::Block> {
@@ -809,55 +761,16 @@ impl<N: NodePrimitives> ExecutedBlock<N> {
&self.execution_output
}
/// Returns the trie data, computing it synchronously if not already cached.
///
/// Uses `OnceLock::get_or_init` internally:
/// - If already computed: returns cached result immediately
/// - If not computed: first caller computes, others wait for that result
/// Returns a reference to the hashed state result of the execution outcome
#[inline]
#[tracing::instrument(level = "debug", target = "engine::tree", name = "trie_data", skip_all)]
pub fn trie_data(&self) -> ComputedTrieData {
self.trie_data.wait_cloned()
pub fn hashed_state(&self) -> &HashedPostState {
&self.hashed_state
}
/// Returns a clone of the deferred trie data handle.
///
/// A handle is a lightweight reference that can be passed to descendants without
/// forcing trie data to be computed immediately. The actual work runs when
/// `wait_cloned()` is called by a consumer (e.g. when merging overlays).
/// Returns a reference to the trie updates resulting from the execution outcome
#[inline]
pub fn trie_data_handle(&self) -> DeferredTrieData {
self.trie_data.clone()
}
/// Returns the hashed state result of the execution outcome.
///
/// May compute trie data synchronously if the deferred task hasn't completed.
#[inline]
pub fn hashed_state(&self) -> Arc<HashedPostStateSorted> {
self.trie_data().hashed_state
}
/// Returns the trie updates resulting from the execution outcome.
///
/// May compute trie data synchronously if the deferred task hasn't completed.
#[inline]
pub fn trie_updates(&self) -> Arc<TrieUpdatesSorted> {
self.trie_data().trie_updates
}
/// Returns the trie input anchored to the persisted ancestor.
///
/// May compute trie data synchronously if the deferred task hasn't completed.
#[inline]
pub fn trie_input(&self) -> Option<Arc<TrieInputSorted>> {
self.trie_data().trie_input().cloned()
}
/// Returns the anchor hash of the trie input, if present.
#[inline]
pub fn anchor_hash(&self) -> Option<B256> {
self.trie_data().anchor_hash()
pub fn trie_updates(&self) -> &TrieUpdates {
&self.trie_updates
}
/// Returns a [`BlockNumber`] of the block.
@@ -962,8 +875,8 @@ mod tests {
StateProofProvider, StateProvider, StateRootProvider, StorageRootProvider,
};
use reth_trie::{
updates::TrieUpdates, AccountProof, HashedPostState, HashedStorage, MultiProof,
MultiProofTargets, StorageMultiProof, StorageProof, TrieInput,
AccountProof, HashedStorage, MultiProof, MultiProofTargets, StorageMultiProof,
StorageProof, TrieInput,
};
fn create_mock_state(

View File

@@ -11,9 +11,6 @@
mod in_memory;
pub use in_memory::*;
mod deferred_trie;
pub use deferred_trie::*;
mod noop;
mod chain_info;

View File

@@ -53,10 +53,11 @@ impl<'a, N: NodePrimitives> MemoryOverlayStateProviderRef<'a, N> {
/// Return lazy-loaded trie state aggregated from in-memory blocks.
fn trie_input(&self) -> &TrieInput {
self.trie_input.get_or_init(|| {
let bundles: Vec<_> =
self.in_memory.iter().rev().map(|block| block.trie_data()).collect();
TrieInput::from_blocks_sorted(
bundles.iter().map(|data| (data.hashed_state.as_ref(), data.trie_updates.as_ref())),
TrieInput::from_blocks(
self.in_memory
.iter()
.rev()
.map(|block| (block.hashed_state.as_ref(), block.trie_updates.as_ref())),
)
})
}

View File

@@ -1,6 +1,6 @@
use crate::{
in_memory::ExecutedBlock, CanonStateNotification, CanonStateNotifications,
CanonStateSubscriptions, ComputedTrieData,
CanonStateSubscriptions,
};
use alloy_consensus::{Header, SignableTransaction, TxEip1559, TxReceipt, EMPTY_ROOT_HASH};
use alloy_eips::{
@@ -23,7 +23,7 @@ use reth_primitives_traits::{
SignedTransaction,
};
use reth_storage_api::NodePrimitivesProvider;
use reth_trie::root::state_root_unhashed;
use reth_trie::{root::state_root_unhashed, updates::TrieUpdates, HashedPostState};
use revm_database::BundleState;
use revm_state::AccountInfo;
use std::{
@@ -92,7 +92,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
&mut self,
number: BlockNumber,
parent_hash: B256,
) -> SealedBlock<reth_ethereum_primitives::Block> {
) -> RecoveredBlock<reth_ethereum_primitives::Block> {
let mut rng = rand::rng();
let mock_tx = |nonce: u64| -> Recovered<_> {
@@ -167,14 +167,17 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
..Default::default()
};
SealedBlock::from_sealed_parts(
let block = SealedBlock::from_sealed_parts(
SealedHeader::seal_slow(header),
BlockBody {
transactions: transactions.into_iter().map(|tx| tx.into_inner()).collect(),
ommers: Vec::new(),
withdrawals: Some(vec![].into()),
},
)
);
RecoveredBlock::try_recover_sealed_with_senders(block, vec![self.signer; num_txs as usize])
.unwrap()
}
/// Creates a fork chain with the given base block.
@@ -188,9 +191,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
for _ in 0..length {
let block = self.generate_random_block(parent.number + 1, parent.hash());
parent = block.clone();
let senders = vec![self.signer; block.body().transactions.len()];
let block = block.with_senders(senders);
parent = block.clone_sealed_block();
fork.push(block);
}
@@ -204,19 +205,20 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
receipts: Vec<Vec<Receipt>>,
parent_hash: B256,
) -> ExecutedBlock {
let block = self.generate_random_block(block_number, parent_hash);
let senders = vec![self.signer; block.body().transactions.len()];
let trie_data = ComputedTrieData::default();
ExecutedBlock::new(
Arc::new(RecoveredBlock::new_sealed(block, senders)),
Arc::new(ExecutionOutcome::new(
let block_with_senders = self.generate_random_block(block_number, parent_hash);
let (block, senders) = block_with_senders.split_sealed();
ExecutedBlock {
recovered_block: Arc::new(RecoveredBlock::new_sealed(block, senders)),
execution_output: Arc::new(ExecutionOutcome::new(
BundleState::default(),
receipts,
block_number,
vec![Requests::default()],
)),
trie_data,
)
hashed_state: Arc::new(HashedPostState::default()),
trie_updates: Arc::new(TrieUpdates::default()),
}
}
/// Generates an [`ExecutedBlock`] that includes the given receipts.

View File

@@ -30,9 +30,8 @@ pub use info::ChainInfo;
#[cfg(any(test, feature = "test-utils"))]
pub use spec::test_fork_ids;
pub use spec::{
blob_params_to_schedule, create_chain_config, mainnet_chain_config, make_genesis_header,
BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder, ChainSpecProvider,
DepositContract, ForkBaseFeeParams, DEV, HOLESKY, HOODI, MAINNET, SEPOLIA,
make_genesis_header, BaseFeeParams, BaseFeeParamsKind, ChainSpec, ChainSpecBuilder,
ChainSpecProvider, DepositContract, ForkBaseFeeParams, DEV, HOLESKY, HOODI, MAINNET, SEPOLIA,
};
use reth_primitives_traits::sync::OnceLock;

View File

@@ -10,14 +10,7 @@ use crate::{
sepolia::SEPOLIA_PARIS_BLOCK,
EthChainSpec,
};
use alloc::{
boxed::Box,
collections::BTreeMap,
format,
string::{String, ToString},
sync::Arc,
vec::Vec,
};
use alloc::{boxed::Box, format, sync::Arc, vec::Vec};
use alloy_chains::{Chain, NamedChain};
use alloy_consensus::{
constants::{
@@ -30,7 +23,7 @@ use alloy_eips::{
eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams,
eip7892::BlobScheduleBlobParams,
};
use alloy_genesis::{ChainConfig, Genesis};
use alloy_genesis::Genesis;
use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256};
use alloy_trie::root::state_root_ref_unhashed;
use core::fmt::Debug;
@@ -247,111 +240,6 @@ pub static DEV: LazyLock<Arc<ChainSpec>> = LazyLock::new(|| {
.into()
});
/// Creates a [`ChainConfig`] from the given chain, hardforks, deposit contract address, and blob
/// schedule.
pub fn create_chain_config(
chain: Option<Chain>,
hardforks: &ChainHardforks,
deposit_contract_address: Option<Address>,
blob_schedule: BTreeMap<String, BlobParams>,
) -> ChainConfig {
// Helper to extract block number from a hardfork condition
let block_num = |fork: EthereumHardfork| hardforks.fork(fork).block_number();
// Helper to extract timestamp from a hardfork condition
let timestamp = |fork: EthereumHardfork| -> Option<u64> {
match hardforks.fork(fork) {
ForkCondition::Timestamp(t) => Some(t),
_ => None,
}
};
// Extract TTD from Paris fork
let (terminal_total_difficulty, terminal_total_difficulty_passed) =
match hardforks.fork(EthereumHardfork::Paris) {
ForkCondition::TTD { total_difficulty, .. } => (Some(total_difficulty), true),
_ => (None, false),
};
// Check if DAO fork is supported (it has an activation block)
let dao_fork_support = hardforks.fork(EthereumHardfork::Dao) != ForkCondition::Never;
ChainConfig {
chain_id: chain.map(|c| c.id()).unwrap_or(0),
homestead_block: block_num(EthereumHardfork::Homestead),
dao_fork_block: block_num(EthereumHardfork::Dao),
dao_fork_support,
eip150_block: block_num(EthereumHardfork::Tangerine),
eip155_block: block_num(EthereumHardfork::SpuriousDragon),
eip158_block: block_num(EthereumHardfork::SpuriousDragon),
byzantium_block: block_num(EthereumHardfork::Byzantium),
constantinople_block: block_num(EthereumHardfork::Constantinople),
petersburg_block: block_num(EthereumHardfork::Petersburg),
istanbul_block: block_num(EthereumHardfork::Istanbul),
muir_glacier_block: block_num(EthereumHardfork::MuirGlacier),
berlin_block: block_num(EthereumHardfork::Berlin),
london_block: block_num(EthereumHardfork::London),
arrow_glacier_block: block_num(EthereumHardfork::ArrowGlacier),
gray_glacier_block: block_num(EthereumHardfork::GrayGlacier),
merge_netsplit_block: None,
shanghai_time: timestamp(EthereumHardfork::Shanghai),
cancun_time: timestamp(EthereumHardfork::Cancun),
prague_time: timestamp(EthereumHardfork::Prague),
osaka_time: timestamp(EthereumHardfork::Osaka),
bpo1_time: timestamp(EthereumHardfork::Bpo1),
bpo2_time: timestamp(EthereumHardfork::Bpo2),
bpo3_time: timestamp(EthereumHardfork::Bpo3),
bpo4_time: timestamp(EthereumHardfork::Bpo4),
bpo5_time: timestamp(EthereumHardfork::Bpo5),
terminal_total_difficulty,
terminal_total_difficulty_passed,
ethash: None,
clique: None,
parlia: None,
extra_fields: Default::default(),
deposit_contract_address,
blob_schedule,
}
}
/// Returns a [`ChainConfig`] for the current Ethereum mainnet chain.
pub fn mainnet_chain_config() -> ChainConfig {
let hardforks: ChainHardforks = EthereumHardfork::mainnet().into();
let blob_schedule = blob_params_to_schedule(&MAINNET.blob_params, &hardforks);
create_chain_config(
Some(Chain::mainnet()),
&hardforks,
Some(MAINNET_DEPOSIT_CONTRACT.address),
blob_schedule,
)
}
/// Converts the given [`BlobScheduleBlobParams`] into blobs schedule.
pub fn blob_params_to_schedule(
params: &BlobScheduleBlobParams,
hardforks: &ChainHardforks,
) -> BTreeMap<String, BlobParams> {
let mut schedule = BTreeMap::new();
schedule.insert("cancun".to_string(), params.cancun);
schedule.insert("prague".to_string(), params.prague);
schedule.insert("osaka".to_string(), params.osaka);
// Map scheduled entries back to bpo fork names by matching timestamps
let bpo_forks = EthereumHardfork::bpo_variants();
for (timestamp, blob_params) in &params.scheduled {
for bpo_fork in bpo_forks {
if let ForkCondition::Timestamp(fork_ts) = hardforks.fork(bpo_fork) &&
fork_ts == *timestamp
{
schedule.insert(bpo_fork.name().to_lowercase(), *blob_params);
break;
}
}
}
schedule
}
/// A wrapper around [`BaseFeeParams`] that allows for specifying constant or dynamic EIP-1559
/// parameters based on the active [Hardfork].
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -626,15 +514,8 @@ impl<H: BlockHeader> ChainSpec<H> {
/// Compute the [`ForkId`] for the given [`Head`] following eip-6122 spec.
///
/// The fork hash is computed by starting from the genesis hash and iteratively adding
/// block numbers (for block-based forks) or timestamps (for timestamp-based forks) of
/// active forks. The `next` field indicates the next fork activation point, or `0` if
/// all forks are active.
///
/// Block-based forks are processed first, then timestamp-based forks. Multiple hardforks
/// activated at the same block or timestamp: only the first one is applied.
///
/// See: <https://eips.ethereum.org/EIPS/eip-6122>
/// Note: In case there are multiple hardforks activated at the same block or timestamp, only
/// the first gets applied.
pub fn fork_id(&self, head: &Head) -> ForkId {
let mut forkhash = ForkHash::from(self.genesis_hash());
@@ -691,10 +572,6 @@ impl<H: BlockHeader> ChainSpec<H> {
}
/// An internal helper function that returns a head block that satisfies a given Fork condition.
///
/// Creates a [`Head`] representation for a fork activation point, used by [`Self::fork_id`] to
/// compute fork IDs. For timestamp-based forks, includes the last block-based fork number
/// before the merge (if any).
pub(crate) fn satisfy(&self, cond: ForkCondition) -> Head {
match cond {
ForkCondition::Block(number) => Head { number, ..Default::default() },
@@ -1048,16 +925,9 @@ impl ChainSpecBuilder {
self
}
/// Enable Dao at genesis.
pub fn dao_activated(mut self) -> Self {
self = self.frontier_activated();
self.hardforks.insert(EthereumHardfork::Dao, ForkCondition::Block(0));
self
}
/// Enable Homestead at genesis.
pub fn homestead_activated(mut self) -> Self {
self = self.dao_activated();
self = self.frontier_activated();
self.hardforks.insert(EthereumHardfork::Homestead, ForkCondition::Block(0));
self
}
@@ -1104,16 +974,9 @@ impl ChainSpecBuilder {
self
}
/// Enable Muir Glacier at genesis.
pub fn muirglacier_activated(mut self) -> Self {
self = self.istanbul_activated();
self.hardforks.insert(EthereumHardfork::MuirGlacier, ForkCondition::Block(0));
self
}
/// Enable Berlin at genesis.
pub fn berlin_activated(mut self) -> Self {
self = self.muirglacier_activated();
self = self.istanbul_activated();
self.hardforks.insert(EthereumHardfork::Berlin, ForkCondition::Block(0));
self
}
@@ -1125,23 +988,9 @@ impl ChainSpecBuilder {
self
}
/// Enable Arrow Glacier at genesis.
pub fn arrowglacier_activated(mut self) -> Self {
self = self.london_activated();
self.hardforks.insert(EthereumHardfork::ArrowGlacier, ForkCondition::Block(0));
self
}
/// Enable Gray Glacier at genesis.
pub fn grayglacier_activated(mut self) -> Self {
self = self.arrowglacier_activated();
self.hardforks.insert(EthereumHardfork::GrayGlacier, ForkCondition::Block(0));
self
}
/// Enable Paris at genesis.
pub fn paris_activated(mut self) -> Self {
self = self.grayglacier_activated();
self = self.london_activated();
self.hardforks.insert(
EthereumHardfork::Paris,
ForkCondition::TTD {
@@ -1514,72 +1363,72 @@ Post-merge hard forks (timestamp based):
&[
(
EthereumHardfork::Frontier,
ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
),
(
EthereumHardfork::Homestead,
ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
),
(
EthereumHardfork::Dao,
ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
),
(
EthereumHardfork::Tangerine,
ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
),
(
EthereumHardfork::SpuriousDragon,
ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
),
(
EthereumHardfork::Byzantium,
ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
),
(
EthereumHardfork::Constantinople,
ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
),
(
EthereumHardfork::Petersburg,
ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
),
(
EthereumHardfork::Istanbul,
ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
),
(
EthereumHardfork::MuirGlacier,
ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
),
(
EthereumHardfork::Berlin,
ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
),
(
EthereumHardfork::London,
ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
),
(
EthereumHardfork::ArrowGlacier,
ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
),
(
EthereumHardfork::GrayGlacier,
ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
),
(
EthereumHardfork::Shanghai,
ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
),
(
EthereumHardfork::Cancun,
ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 1746612311 },
),
(
EthereumHardfork::Prague,
ForkId {
hash: ForkHash(hex!("0xc376cf8b")),
hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]),
next: mainnet::MAINNET_OSAKA_TIMESTAMP,
},
),
@@ -1594,60 +1443,60 @@ Post-merge hard forks (timestamp based):
&[
(
EthereumHardfork::Frontier,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Homestead,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Tangerine,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::SpuriousDragon,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Byzantium,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Constantinople,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Petersburg,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Istanbul,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Berlin,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::London,
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
EthereumHardfork::Paris,
ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
ForkId { hash: ForkHash([0xb9, 0x6c, 0xbd, 0x13]), next: 1677557088 },
),
(
EthereumHardfork::Shanghai,
ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
ForkId { hash: ForkHash([0xf7, 0xf9, 0xbc, 0x08]), next: 1706655072 },
),
(
EthereumHardfork::Cancun,
ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
ForkId { hash: ForkHash([0x88, 0xcf, 0x81, 0xd9]), next: 1741159776 },
),
(
EthereumHardfork::Prague,
ForkId {
hash: ForkHash(hex!("0xed88b5fd")),
hash: ForkHash([0xed, 0x88, 0xb5, 0xfd]),
next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
},
),
@@ -1662,71 +1511,71 @@ Post-merge hard forks (timestamp based):
&[
(
Head { number: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
),
(
Head { number: 1150000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
),
(
Head { number: 1920000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
),
(
Head { number: 2463000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
),
(
Head { number: 2675000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
),
(
Head { number: 4370000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
),
(
Head { number: 7280000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
),
(
Head { number: 9069000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
),
(
Head { number: 9200000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
),
(
Head { number: 12244000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
),
(
Head { number: 12965000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
),
(
Head { number: 13773000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
),
(
Head { number: 15050000, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
),
// First Shanghai block
(
Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
),
// First Cancun block
(
Head { number: 20000001, timestamp: 1710338135, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 1746612311 },
),
// First Prague block
(
Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
ForkId {
hash: ForkHash(hex!("0xc376cf8b")),
hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]),
next: mainnet::MAINNET_OSAKA_TIMESTAMP,
},
),
@@ -1753,13 +1602,13 @@ Post-merge hard forks (timestamp based):
&[
(
Head { number: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xbef71d30")), next: 1742999832 },
ForkId { hash: ForkHash([0xbe, 0xf7, 0x1d, 0x30]), next: 1742999832 },
),
// First Prague block
(
Head { number: 0, timestamp: 1742999833, ..Default::default() },
ForkId {
hash: ForkHash(hex!("0x0929e24e")),
hash: ForkHash([0x09, 0x29, 0xe2, 0x4e]),
next: hoodi::HOODI_OSAKA_TIMESTAMP,
},
),
@@ -1786,43 +1635,43 @@ Post-merge hard forks (timestamp based):
&[
(
Head { number: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
ForkId { hash: ForkHash([0xc6, 0x1a, 0x60, 0x98]), next: 1696000704 },
),
// First MergeNetsplit block
(
Head { number: 123, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
ForkId { hash: ForkHash([0xc6, 0x1a, 0x60, 0x98]), next: 1696000704 },
),
// Last MergeNetsplit block
(
Head { number: 123, timestamp: 1696000703, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xc61a6098")), next: 1696000704 },
ForkId { hash: ForkHash([0xc6, 0x1a, 0x60, 0x98]), next: 1696000704 },
),
// First Shanghai block
(
Head { number: 123, timestamp: 1696000704, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
ForkId { hash: ForkHash([0xfd, 0x4f, 0x01, 0x6b]), next: 1707305664 },
),
// Last Shanghai block
(
Head { number: 123, timestamp: 1707305663, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xfd4f016b")), next: 1707305664 },
ForkId { hash: ForkHash([0xfd, 0x4f, 0x01, 0x6b]), next: 1707305664 },
),
// First Cancun block
(
Head { number: 123, timestamp: 1707305664, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
ForkId { hash: ForkHash([0x9b, 0x19, 0x2a, 0xd0]), next: 1740434112 },
),
// Last Cancun block
(
Head { number: 123, timestamp: 1740434111, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x9b192ad0")), next: 1740434112 },
ForkId { hash: ForkHash([0x9b, 0x19, 0x2a, 0xd0]), next: 1740434112 },
),
// First Prague block
(
Head { number: 123, timestamp: 1740434112, ..Default::default() },
ForkId {
hash: ForkHash(hex!("0xdfbd9bed")),
hash: ForkHash([0xdf, 0xbd, 0x9b, 0xed]),
next: holesky::HOLESKY_OSAKA_TIMESTAMP,
},
),
@@ -1849,45 +1698,45 @@ Post-merge hard forks (timestamp based):
&[
(
Head { number: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
Head { number: 1735370, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xfe3366e7")), next: 1735371 },
ForkId { hash: ForkHash([0xfe, 0x33, 0x66, 0xe7]), next: 1735371 },
),
(
Head { number: 1735371, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
ForkId { hash: ForkHash([0xb9, 0x6c, 0xbd, 0x13]), next: 1677557088 },
),
(
Head { number: 1735372, timestamp: 1677557087, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xb96cbd13")), next: 1677557088 },
ForkId { hash: ForkHash([0xb9, 0x6c, 0xbd, 0x13]), next: 1677557088 },
),
// First Shanghai block
(
Head { number: 1735373, timestamp: 1677557088, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
ForkId { hash: ForkHash([0xf7, 0xf9, 0xbc, 0x08]), next: 1706655072 },
),
// Last Shanghai block
(
Head { number: 1735374, timestamp: 1706655071, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xf7f9bc08")), next: 1706655072 },
ForkId { hash: ForkHash([0xf7, 0xf9, 0xbc, 0x08]), next: 1706655072 },
),
// First Cancun block
(
Head { number: 1735375, timestamp: 1706655072, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
ForkId { hash: ForkHash([0x88, 0xcf, 0x81, 0xd9]), next: 1741159776 },
),
// Last Cancun block
(
Head { number: 1735376, timestamp: 1741159775, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x88cf81d9")), next: 1741159776 },
ForkId { hash: ForkHash([0x88, 0xcf, 0x81, 0xd9]), next: 1741159776 },
),
// First Prague block
(
Head { number: 1735377, timestamp: 1741159776, ..Default::default() },
ForkId {
hash: ForkHash(hex!("0xed88b5fd")),
hash: ForkHash([0xed, 0x88, 0xb5, 0xfd]),
next: sepolia::SEPOLIA_OSAKA_TIMESTAMP,
},
),
@@ -1913,7 +1762,7 @@ Post-merge hard forks (timestamp based):
&DEV,
&[(
Head { number: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x0b1a4ef7")), next: 0 },
ForkId { hash: ForkHash([0x0b, 0x1a, 0x4e, 0xf7]), next: 0 },
)],
)
}
@@ -1929,128 +1778,128 @@ Post-merge hard forks (timestamp based):
&[
(
Head { number: 0, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
), // Unsynced
(
Head { number: 1149999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xfc64ec04")), next: 1150000 },
ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 },
), // Last Frontier block
(
Head { number: 1150000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
), // First Homestead block
(
Head { number: 1919999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x97c2c34c")), next: 1920000 },
ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 },
), // Last Homestead block
(
Head { number: 1920000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
), // First DAO block
(
Head { number: 2462999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x91d1f948")), next: 2463000 },
ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 },
), // Last DAO block
(
Head { number: 2463000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
), // First Tangerine block
(
Head { number: 2674999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x7a64da13")), next: 2675000 },
ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 },
), // Last Tangerine block
(
Head { number: 2675000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
), // First Spurious block
(
Head { number: 4369999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x3edd5b10")), next: 4370000 },
ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 },
), // Last Spurious block
(
Head { number: 4370000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
), // First Byzantium block
(
Head { number: 7279999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xa00bc324")), next: 7280000 },
ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 },
), // Last Byzantium block
(
Head { number: 7280000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
), // First and last Constantinople, first Petersburg block
(
Head { number: 9068999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x668db0af")), next: 9069000 },
ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 },
), // Last Petersburg block
(
Head { number: 9069000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
), // First Istanbul and first Muir Glacier block
(
Head { number: 9199999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x879d6e30")), next: 9200000 },
ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 },
), // Last Istanbul and first Muir Glacier block
(
Head { number: 9200000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
), // First Muir Glacier block
(
Head { number: 12243999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xe029e991")), next: 12244000 },
ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 },
), // Last Muir Glacier block
(
Head { number: 12244000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
), // First Berlin block
(
Head { number: 12964999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x0eb440f6")), next: 12965000 },
ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 },
), // Last Berlin block
(
Head { number: 12965000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
), // First London block
(
Head { number: 13772999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xb715077d")), next: 13773000 },
ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 },
), // Last London block
(
Head { number: 13773000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
), // First Arrow Glacier block
(
Head { number: 15049999, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x20c327fc")), next: 15050000 },
ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 },
), // Last Arrow Glacier block
(
Head { number: 15050000, timestamp: 0, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
), // First Gray Glacier block
(
Head { number: 19999999, timestamp: 1667999999, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xf0afd0e3")), next: 1681338455 },
ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 1681338455 },
), // Last Gray Glacier block
(
Head { number: 20000000, timestamp: 1681338455, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
), // Last Shanghai block
(
Head { number: 20000001, timestamp: 1710338134, ..Default::default() },
ForkId { hash: ForkHash(hex!("0xdce96c2d")), next: 1710338135 },
ForkId { hash: ForkHash([0xdc, 0xe9, 0x6c, 0x2d]), next: 1710338135 },
), // First Cancun block
(
Head { number: 20000002, timestamp: 1710338135, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 1746612311 },
), // Last Cancun block
(
Head { number: 20000003, timestamp: 1746612310, ..Default::default() },
ForkId { hash: ForkHash(hex!("0x9f3d2254")), next: 1746612311 },
ForkId { hash: ForkHash([0x9f, 0x3d, 0x22, 0x54]), next: 1746612311 },
), // First Prague block
(
Head { number: 20000004, timestamp: 1746612311, ..Default::default() },
ForkId {
hash: ForkHash(hex!("0xc376cf8b")),
hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]),
next: mainnet::MAINNET_OSAKA_TIMESTAMP,
},
),
@@ -2520,7 +2369,7 @@ Post-merge hard forks (timestamp based):
let chainspec = ChainSpec::from(genesis);
// make sure we are at ForkHash("bc0c2605") with Head post-cancun
let expected_forkid = ForkId { hash: ForkHash(hex!("0xbc0c2605")), next: 0 };
let expected_forkid = ForkId { hash: ForkHash([0xbc, 0x0c, 0x26, 0x05]), next: 0 };
let got_forkid =
chainspec.fork_id(&Head { number: 73, timestamp: 840, ..Default::default() });
@@ -2630,7 +2479,7 @@ Post-merge hard forks (timestamp based):
assert_eq!(genesis_hash, expected_hash);
// check that the forkhash is correct
let expected_forkhash = ForkHash(hex!("0x8062457a"));
let expected_forkhash = ForkHash(hex!("8062457a"));
assert_eq!(ForkHash::from(genesis_hash), expected_forkhash);
}

View File

@@ -49,7 +49,6 @@ reth-stages.workspace = true
reth-stages-types = { workspace = true, optional = true }
reth-static-file-types = { workspace = true, features = ["clap"] }
reth-static-file.workspace = true
reth-tasks.workspace = true
reth-trie = { workspace = true, features = ["metrics"] }
reth-trie-db = { workspace = true, features = ["metrics"] }
reth-trie-common.workspace = true
@@ -83,7 +82,6 @@ backon.workspace = true
secp256k1 = { workspace = true, features = ["global-context", "std", "recovery"] }
tokio-stream.workspace = true
reqwest.workspace = true
metrics.workspace = true
# io
fdlimit.workspace = true

View File

@@ -1,7 +1,5 @@
//! Contains common `reth` arguments
pub use reth_primitives_traits::header::HeaderMut;
use alloy_primitives::B256;
use clap::Parser;
use reth_chainspec::EthChainSpec;
@@ -9,7 +7,7 @@ use reth_cli::chainspec::ChainSpecParser;
use reth_config::{config::EtlConfig, Config};
use reth_consensus::noop::NoopConsensus;
use reth_db::{init_db, open_db_read_only, DatabaseEnv};
use reth_db_common::init::init_genesis_with_settings;
use reth_db_common::init::init_genesis;
use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader};
use reth_eth_wire::NetPrimitivesFor;
use reth_evm::{noop::NoopEvmConfig, ConfigureEvm};
@@ -19,7 +17,7 @@ use reth_node_builder::{
Node, NodeComponents, NodeComponentsBuilder, NodeTypes, NodeTypesWithDBAdapter,
};
use reth_node_core::{
args::{DatabaseArgs, DatadirArgs, StaticFilesArgs},
args::{DatabaseArgs, DatadirArgs},
dirs::{ChainPath, DataDirPath},
};
use reth_provider::{
@@ -59,10 +57,6 @@ pub struct EnvironmentArgs<C: ChainSpecParser> {
/// All database related arguments
#[command(flatten)]
pub db: DatabaseArgs,
/// All static files related arguments
#[command(flatten)]
pub static_files: StaticFilesArgs,
}
impl<C: ChainSpecParser> EnvironmentArgs<C> {
@@ -103,16 +97,16 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
Arc::new(init_db(db_path, self.db.database_args())?),
StaticFileProvider::read_write(sf_path)?,
),
AccessRights::RO | AccessRights::RoInconsistent => (
AccessRights::RO => (
Arc::new(open_db_read_only(&db_path, self.db.database_args())?),
StaticFileProvider::read_only(sf_path, false)?,
),
};
let provider_factory = self.create_provider_factory(&config, db, sfp, access)?;
let provider_factory = self.create_provider_factory(&config, db, sfp)?;
if access.is_read_write() {
debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
init_genesis_with_settings(&provider_factory, self.static_files.to_settings())?;
init_genesis(&provider_factory)?;
}
Ok(Environment { config, provider_factory, data_dir })
@@ -128,23 +122,23 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
config: &Config,
db: Arc<DatabaseEnv>,
static_file_provider: StaticFileProvider<N::Primitives>,
access: AccessRights,
) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
where
C: ChainSpecParser<ChainSpec = N::ChainSpec>,
{
let has_receipt_pruning = config.prune.has_receipts_pruning();
let prune_modes = config.prune.segments.clone();
let factory = ProviderFactory::<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>::new(
db,
self.chain.clone(),
static_file_provider,
)?
)
.with_prune_modes(prune_modes.clone());
// Check for consistency between database and static files.
if !access.is_read_only_inconsistent() &&
let Some(unwind_target) =
factory.static_file_provider().check_consistency(&factory.provider()?)?
if let Some(unwind_target) = factory
.static_file_provider()
.check_consistency(&factory.provider()?, has_receipt_pruning)?
{
if factory.db_ref().is_read_only()? {
warn!(target: "reth::cli", ?unwind_target, "Inconsistent storage. Restart node to heal.");
@@ -205,8 +199,6 @@ pub enum AccessRights {
RW,
/// Read-only access
RO,
/// Read-only access with possibly inconsistent data
RoInconsistent,
}
impl AccessRights {
@@ -214,12 +206,6 @@ impl AccessRights {
pub const fn is_read_write(&self) -> bool {
matches!(self, Self::RW)
}
/// Returns `true` if it requires read-only access to the environment with possibly inconsistent
/// data.
pub const fn is_read_only_inconsistent(&self) -> bool {
matches!(self, Self::RoInconsistent)
}
}
/// Helper alias to satisfy `FullNodeTypes` bound on [`Node`] trait generic.
@@ -229,6 +215,17 @@ type FullTypesAdapter<T> = FullNodeTypesAdapter<
BlockchainProvider<NodeTypesWithDBAdapter<T, Arc<DatabaseEnv>>>,
>;
/// Trait for block headers that can be modified through CLI operations.
pub trait CliHeader {
fn set_number(&mut self, number: u64);
}
impl CliHeader for alloy_consensus::Header {
fn set_number(&mut self, number: u64) {
self.number = number;
}
}
/// Helper trait with a common set of requirements for the
/// [`NodeTypes`] in CLI.
pub trait CliNodeTypes: Node<FullTypesAdapter<Self>> + NodeTypesForProvider {

View File

@@ -22,14 +22,13 @@ impl Command {
let config = if self.default {
Config::default()
} else {
let path = match self.config.as_ref() {
Some(path) => path,
None => bail!("No config file provided. Use --config <FILE> or pass --default"),
};
let path = self.config.clone().unwrap_or_default();
// Check if the file exists
if !path.exists() {
bail!("Config file does not exist: {}", path.display());
}
Config::from_path(path)
// Read the configuration file
Config::from_path(&path)
.wrap_err_with(|| format!("Could not load config file: {}", path.display()))?
};
println!("{}", toml::to_string_pretty(&config)?);

View File

@@ -1,92 +0,0 @@
use alloy_primitives::{keccak256, Address};
use clap::Parser;
use human_bytes::human_bytes;
use reth_codecs::Compact;
use reth_db_api::{cursor::DbDupCursorRO, database::Database, tables, transaction::DbTx};
use reth_db_common::DbTool;
use reth_node_builder::NodeTypesWithDB;
use std::time::{Duration, Instant};
use tracing::info;
/// Log progress every 5 seconds
const LOG_INTERVAL: Duration = Duration::from_secs(5);
/// The arguments for the `reth db account-storage` command
#[derive(Parser, Debug)]
pub struct Command {
/// The account address to check storage for
address: Address,
}
impl Command {
/// Execute `db account-storage` command
pub fn execute<N: NodeTypesWithDB>(self, tool: &DbTool<N>) -> eyre::Result<()> {
let address = self.address;
let (slot_count, plain_size) = tool.provider_factory.db_ref().view(|tx| {
let mut cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
let mut count = 0usize;
let mut total_value_bytes = 0usize;
let mut last_log = Instant::now();
// Walk all storage entries for this address
let walker = cursor.walk_dup(Some(address), None)?;
for entry in walker {
let (_, storage_entry) = entry?;
count += 1;
// StorageEntry encodes as: 32 bytes (key/subkey uncompressed) + compressed U256
let mut buf = Vec::new();
let entry_len = storage_entry.to_compact(&mut buf);
total_value_bytes += entry_len;
if last_log.elapsed() >= LOG_INTERVAL {
info!(
target: "reth::cli",
address = %address,
slots = count,
key = %storage_entry.key,
"Processing storage slots"
);
last_log = Instant::now();
}
}
// Add 20 bytes for the Address key (stored once per account in dupsort)
let total_size = if count > 0 { 20 + total_value_bytes } else { 0 };
Ok::<_, eyre::Report>((count, total_size))
})??;
// Estimate hashed storage size: 32-byte B256 key instead of 20-byte Address
let hashed_size_estimate = if slot_count > 0 { plain_size + 12 } else { 0 };
let total_estimate = plain_size + hashed_size_estimate;
let hashed_address = keccak256(address);
println!("Account: {address}");
println!("Hashed address: {hashed_address}");
println!("Storage slots: {slot_count}");
println!("Plain storage size: {} (estimated)", human_bytes(plain_size as f64));
println!("Hashed storage size: {} (estimated)", human_bytes(hashed_size_estimate as f64));
println!("Total estimated size: {}", human_bytes(total_estimate as f64));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_address_arg() {
let cmd = Command::try_parse_from([
"account-storage",
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
])
.unwrap();
assert_eq!(
cmd.address,
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045".parse::<Address>().unwrap()
);
}
}

View File

@@ -6,9 +6,8 @@ use reth_db_api::{
transaction::{DbTx, DbTxMut},
TableViewer, Tables,
};
use reth_db_common::DbTool;
use reth_node_builder::NodeTypesWithDB;
use reth_provider::StaticFileProviderFactory;
use reth_provider::{ProviderFactory, StaticFileProviderFactory};
use reth_static_file_types::StaticFileSegment;
/// The arguments for the `reth db clear` command
@@ -20,13 +19,16 @@ pub struct Command {
impl Command {
/// Execute `db clear` command
pub fn execute<N: NodeTypesWithDB>(self, tool: &DbTool<N>) -> eyre::Result<()> {
pub fn execute<N: NodeTypesWithDB>(
self,
provider_factory: ProviderFactory<N>,
) -> eyre::Result<()> {
match self.subcommand {
Subcommands::Mdbx { table } => {
table.view(&ClearViewer { db: tool.provider_factory.db_ref() })?
table.view(&ClearViewer { db: provider_factory.db_ref() })?
}
Subcommands::StaticFile { segment } => {
let static_file_provider = tool.provider_factory.static_file_provider();
let static_file_provider = provider_factory.static_file_provider();
let static_files = iter_static_files(static_file_provider.directory())?;
if let Some(segment_static_files) = static_files.get(&segment) {

View File

@@ -3,7 +3,6 @@ use clap::Parser;
use reth_db::{
static_file::{
ColumnSelectorOne, ColumnSelectorTwo, HeaderWithHashMask, ReceiptMask, TransactionMask,
TransactionSenderMask,
},
RawDupSort,
};
@@ -76,21 +75,19 @@ impl Command {
StaticFileSegment::Receipts => {
(table_key::<tables::Receipts>(&key)?, <ReceiptMask<ReceiptTy<N>>>::MASK)
}
StaticFileSegment::TransactionSenders => (
table_key::<tables::TransactionSenders>(&key)?,
<TransactionSenderMask>::MASK,
),
};
let content = tool
.provider_factory
.static_file_provider()
.get_segment_provider(segment, key)?
.cursor()?
.get(key.into(), mask)
.map(|result| {
result.map(|vec| vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>())
})?;
let content = tool.provider_factory.static_file_provider().find_static_file(
segment,
|provider| {
let mut cursor = provider.cursor()?;
cursor.get(key.into(), mask).map(|result| {
result.map(|vec| {
vec.iter().map(|slice| slice.to_vec()).collect::<Vec<_>>()
})
})
},
)?;
match content {
Some(content) => {
@@ -119,13 +116,6 @@ impl Command {
)?;
println!("{}", serde_json::to_string_pretty(&receipt)?);
}
StaticFileSegment::TransactionSenders => {
let sender =
<<tables::TransactionSenders as Table>::Value>::decompress(
content[0].as_slice(),
)?;
println!("{}", serde_json::to_string_pretty(&sender)?);
}
}
}
}

View File

@@ -3,7 +3,7 @@ use alloy_primitives::hex;
use clap::Parser;
use eyre::WrapErr;
use reth_chainspec::EthereumHardforks;
use reth_db::{transaction::DbTx, DatabaseEnv};
use reth_db::DatabaseEnv;
use reth_db_api::{database::Database, table::Table, RawValue, TableViewer, Tables};
use reth_db_common::{DbTool, ListFilter};
use reth_node_builder::{NodeTypes, NodeTypesWithDBAdapter};
@@ -96,9 +96,6 @@ impl<N: NodeTypes> TableViewer<()> for ListTableViewer<'_, N> {
fn view<T: Table>(&self) -> Result<(), Self::Error> {
self.tool.provider_factory.db_ref().view(|tx| {
// We may be using the tui for a long time
tx.disable_long_read_transaction_safety();
let table_db = tx.inner.open_db(Some(self.args.table.name())).wrap_err("Could not open db.")?;
let stats = tx.inner.db_stat(&table_db).wrap_err(format!("Could not find table: {}", self.args.table.name()))?;
let total_entries = stats.entries();

View File

@@ -8,15 +8,12 @@ use std::{
io::{self, Write},
sync::Arc,
};
mod account_storage;
mod checksum;
mod clear;
mod diff;
mod get;
mod list;
mod repair_trie;
mod settings;
mod static_file_header;
mod stats;
/// DB List TUI
mod tui;
@@ -54,23 +51,16 @@ pub enum Subcommands {
Clear(clear::Command),
/// Verifies trie consistency and outputs any inconsistencies
RepairTrie(repair_trie::Command),
/// Reads and displays the static file segment header
StaticFileHeader(static_file_header::Command),
/// Lists current and local database versions
Version,
/// Returns the full database path
Path,
/// Manage storage settings
Settings(settings::Command),
/// Gets storage size information for an account
AccountStorage(account_storage::Command),
}
/// Initializes a provider factory with specified access rights, and then execute with the provided
/// command
macro_rules! db_exec {
($env:expr, $tool:ident, $N:ident, $access_rights:expr, $command:block) => {
let Environment { provider_factory, .. } = $env.init::<$N>($access_rights)?;
/// `db_ro_exec` opens a database in read-only mode, and then execute with the provided command
macro_rules! db_ro_exec {
($env:expr, $tool:ident, $N:ident, $command:block) => {
let Environment { provider_factory, .. } = $env.init::<$N>(AccessRights::RO)?;
let $tool = DbTool::new(provider_factory)?;
$command;
@@ -98,32 +88,27 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
match self.command {
// TODO: We'll need to add this on the DB trait.
Subcommands::Stats(command) => {
let access_rights = if command.skip_consistency_checks {
AccessRights::RoInconsistent
} else {
AccessRights::RO
};
db_exec!(self.env, tool, N, access_rights, {
db_ro_exec!(self.env, tool, N, {
command.execute(data_dir, &tool)?;
});
}
Subcommands::List(command) => {
db_exec!(self.env, tool, N, AccessRights::RO, {
db_ro_exec!(self.env, tool, N, {
command.execute(&tool)?;
});
}
Subcommands::Checksum(command) => {
db_exec!(self.env, tool, N, AccessRights::RO, {
db_ro_exec!(self.env, tool, N, {
command.execute(&tool)?;
});
}
Subcommands::Diff(command) => {
db_exec!(self.env, tool, N, AccessRights::RO, {
db_ro_exec!(self.env, tool, N, {
command.execute(&tool)?;
});
}
Subcommands::Get(command) => {
db_exec!(self.env, tool, N, AccessRights::RO, {
db_ro_exec!(self.env, tool, N, {
command.execute(&tool)?;
});
}
@@ -145,26 +130,19 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
}
}
db_exec!(self.env, tool, N, AccessRights::RW, {
tool.drop(db_path, static_files_path, exex_wal_path)?;
});
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
let tool = DbTool::new(provider_factory)?;
tool.drop(db_path, static_files_path, exex_wal_path)?;
}
Subcommands::Clear(command) => {
db_exec!(self.env, tool, N, AccessRights::RW, {
command.execute(&tool)?;
});
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RW)?;
command.execute(provider_factory)?;
}
Subcommands::RepairTrie(command) => {
let access_rights =
if command.dry_run { AccessRights::RO } else { AccessRights::RW };
db_exec!(self.env, tool, N, access_rights, {
command.execute(&tool)?;
});
}
Subcommands::StaticFileHeader(command) => {
db_exec!(self.env, tool, N, AccessRights::RoInconsistent, {
command.execute(&tool)?;
});
let Environment { provider_factory, .. } = self.env.init::<N>(access_rights)?;
command.execute(provider_factory)?;
}
Subcommands::Version => {
let local_db_version = match get_db_version(&db_path) {
@@ -184,16 +162,6 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
Subcommands::Path => {
println!("{}", db_path.display());
}
Subcommands::Settings(command) => {
db_exec!(self.env, tool, N, command.access_rights(), {
command.execute(&tool)?;
});
}
Subcommands::AccountStorage(command) => {
db_exec!(self.env, tool, N, AccessRights::RO, {
command.execute(&tool)?;
});
}
}
Ok(())

View File

@@ -1,22 +1,12 @@
use clap::Parser;
use metrics::{self, Counter};
use reth_chainspec::EthChainSpec;
use reth_cli_util::parse_socket_address;
use reth_db_api::{
cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO},
database::Database,
tables,
transaction::{DbTx, DbTxMut},
};
use reth_db_common::DbTool;
use reth_node_core::version::version_metadata;
use reth_node_metrics::{
chain::ChainSpecInfo,
hooks::Hooks,
server::{MetricServer, MetricServerConfig},
version::VersionInfo,
};
use reth_provider::{providers::ProviderNodeTypes, ChainSpecProvider, StageCheckpointReader};
use reth_node_builder::NodeTypesWithDB;
use reth_provider::{providers::ProviderNodeTypes, ProviderFactory, StageCheckpointReader};
use reth_stages::StageId;
use reth_trie::{
verify::{Output, Verifier},
@@ -24,10 +14,7 @@ use reth_trie::{
};
use reth_trie_common::{StorageTrieEntry, StoredNibbles, StoredNibblesSubKey};
use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory};
use std::{
net::SocketAddr,
time::{Duration, Instant},
};
use std::time::{Duration, Instant};
use tracing::{info, warn};
const PROGRESS_PERIOD: Duration = Duration::from_secs(5);
@@ -38,89 +25,27 @@ pub struct Command {
/// Only show inconsistencies without making any repairs
#[arg(long)]
pub(crate) dry_run: bool,
/// Enable Prometheus metrics.
///
/// The metrics will be served at the given interface and port.
#[arg(long = "metrics", value_name = "ADDR:PORT", value_parser = parse_socket_address)]
pub(crate) metrics: Option<SocketAddr>,
}
impl Command {
/// Execute `db repair-trie` command
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
// Set up metrics server if requested
let _metrics_handle = if let Some(listen_addr) = self.metrics {
// Spawn an OS thread with a single-threaded tokio runtime for the metrics server
let chain_name = tool.provider_factory.chain_spec().chain().to_string();
let handle = std::thread::Builder::new().name("metrics-server".to_string()).spawn(
move || {
// Create a single-threaded tokio runtime
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed to create tokio runtime for metrics server");
let handle = runtime.handle().clone();
runtime.block_on(async move {
let task_manager = reth_tasks::TaskManager::new(handle.clone());
let task_executor = task_manager.executor();
let config = MetricServerConfig::new(
listen_addr,
VersionInfo {
version: version_metadata().cargo_pkg_version.as_ref(),
build_timestamp: version_metadata().vergen_build_timestamp.as_ref(),
cargo_features: version_metadata().vergen_cargo_features.as_ref(),
git_sha: version_metadata().vergen_git_sha.as_ref(),
target_triple: version_metadata()
.vergen_cargo_target_triple
.as_ref(),
build_profile: version_metadata().build_profile_name.as_ref(),
},
ChainSpecInfo { name: chain_name },
task_executor,
Hooks::builder().build(),
);
// Spawn the metrics server
if let Err(e) = MetricServer::new(config).serve().await {
tracing::error!("Metrics server error: {}", e);
}
// Block forever to keep the runtime alive
std::future::pending::<()>().await
});
},
)?;
Some(handle)
} else {
None
};
pub fn execute<N: ProviderNodeTypes>(
self,
provider_factory: ProviderFactory<N>,
) -> eyre::Result<()> {
if self.dry_run {
verify_only(tool)?
verify_only(provider_factory)?
} else {
verify_and_repair(tool)?
verify_and_repair(provider_factory)?
}
Ok(())
}
}
fn verify_only<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()> {
// Log the database block tip from Finish stage checkpoint
let finish_checkpoint = tool
.provider_factory
.provider()?
.get_stage_checkpoint(StageId::Finish)?
.unwrap_or_default();
info!("Database block tip: {}", finish_checkpoint.block_number);
fn verify_only<N: NodeTypesWithDB>(provider_factory: ProviderFactory<N>) -> eyre::Result<()> {
// Get a database transaction directly from the database
let db = tool.provider_factory.db_ref();
let db = provider_factory.db_ref();
let mut tx = db.tx()?;
tx.disable_long_read_transaction_safety();
@@ -129,8 +54,6 @@ fn verify_only<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()> {
let trie_cursor_factory = DatabaseTrieCursorFactory::new(&tx);
let verifier = Verifier::new(&trie_cursor_factory, hashed_cursor_factory)?;
let metrics = RepairTrieMetrics::new();
let mut inconsistent_nodes = 0;
let start_time = Instant::now();
let mut last_progress_time = Instant::now();
@@ -147,21 +70,6 @@ fn verify_only<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()> {
} else {
warn!("Inconsistency found: {output:?}");
inconsistent_nodes += 1;
// Record metrics based on output type
match output {
Output::AccountExtra(_, _) |
Output::AccountWrong { .. } |
Output::AccountMissing(_, _) => {
metrics.account_inconsistencies.increment(1);
}
Output::StorageExtra(_, _, _) |
Output::StorageWrong { .. } |
Output::StorageMissing(_, _, _) => {
metrics.storage_inconsistencies.increment(1);
}
Output::Progress(_) => unreachable!(),
}
}
}
@@ -206,13 +114,11 @@ fn verify_checkpoints(provider: impl StageCheckpointReader) -> eyre::Result<()>
Ok(())
}
fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()> {
fn verify_and_repair<N: ProviderNodeTypes>(
provider_factory: ProviderFactory<N>,
) -> eyre::Result<()> {
// Get a read-write database provider
let mut provider_rw = tool.provider_factory.provider_rw()?;
// Log the database block tip from Finish stage checkpoint
let finish_checkpoint = provider_rw.get_stage_checkpoint(StageId::Finish)?.unwrap_or_default();
info!("Database block tip: {}", finish_checkpoint.block_number);
let mut provider_rw = provider_factory.provider_rw()?;
// Check that a pipeline sync isn't in progress.
verify_checkpoints(provider_rw.as_ref())?;
@@ -232,8 +138,6 @@ fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()>
// Create the verifier
let verifier = Verifier::new(&trie_cursor_factory, hashed_cursor_factory)?;
let metrics = RepairTrieMetrics::new();
let mut inconsistent_nodes = 0;
let start_time = Instant::now();
let mut last_progress_time = Instant::now();
@@ -245,21 +149,6 @@ fn verify_and_repair<N: ProviderNodeTypes>(tool: &DbTool<N>) -> eyre::Result<()>
if !matches!(output, Output::Progress(_)) {
warn!("Inconsistency found, will repair: {output:?}");
inconsistent_nodes += 1;
// Record metrics based on output type
match &output {
Output::AccountExtra(_, _) |
Output::AccountWrong { .. } |
Output::AccountMissing(_, _) => {
metrics.account_inconsistencies.increment(1);
}
Output::StorageExtra(_, _, _) |
Output::StorageWrong { .. } |
Output::StorageMissing(_, _, _) => {
metrics.storage_inconsistencies.increment(1);
}
Output::Progress(_) => {}
}
}
match output {
@@ -358,25 +247,3 @@ fn output_progress(last_account: Nibbles, start_time: Instant, inconsistent_node
"Repairing trie tables",
);
}
/// Metrics for tracking trie repair inconsistencies
#[derive(Debug)]
struct RepairTrieMetrics {
account_inconsistencies: Counter,
storage_inconsistencies: Counter,
}
impl RepairTrieMetrics {
fn new() -> Self {
Self {
account_inconsistencies: metrics::counter!(
"db.repair_trie.inconsistencies_found",
"type" => "account"
),
storage_inconsistencies: metrics::counter!(
"db.repair_trie.inconsistencies_found",
"type" => "storage"
),
}
}
}

View File

@@ -1,125 +0,0 @@
//! `reth db settings` command for managing storage settings
use clap::{ArgAction, Parser, Subcommand};
use reth_db_common::DbTool;
use reth_provider::{
providers::ProviderNodeTypes, DBProvider, DatabaseProviderFactory, MetadataProvider,
MetadataWriter, StorageSettings,
};
use crate::common::AccessRights;
/// `reth db settings` subcommand
#[derive(Debug, Parser)]
pub struct Command {
#[command(subcommand)]
command: Subcommands,
}
impl Command {
/// Returns database access rights required for the command.
pub fn access_rights(&self) -> AccessRights {
match self.command {
Subcommands::Get => AccessRights::RO,
Subcommands::Set(_) => AccessRights::RW,
}
}
}
#[derive(Debug, Clone, Copy, Subcommand)]
enum Subcommands {
/// Get current storage settings from database
Get,
/// Set storage settings in database
#[clap(subcommand)]
Set(SetCommand),
}
/// Set storage settings
#[derive(Debug, Clone, Copy, Subcommand)]
#[clap(rename_all = "snake_case")]
pub enum SetCommand {
/// Store receipts in static files instead of the database
ReceiptsInStaticFiles {
#[clap(action(ArgAction::Set))]
value: bool,
},
/// Store transaction senders in static files instead of the database
TransactionSendersInStaticFiles {
#[clap(action(ArgAction::Set))]
value: bool,
},
}
impl Command {
/// Execute the command
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
match self.command {
Subcommands::Get => self.get(tool),
Subcommands::Set(cmd) => self.set(cmd, tool),
}
}
fn get<N: ProviderNodeTypes>(&self, tool: &DbTool<N>) -> eyre::Result<()> {
// Read storage settings
let provider = tool.provider_factory.provider()?;
let storage_settings = provider.storage_settings()?;
// Display settings
match storage_settings {
Some(settings) => {
println!("Current storage settings:");
println!("{settings:#?}");
}
None => {
println!("No storage settings found.");
}
}
Ok(())
}
fn set<N: ProviderNodeTypes>(&self, cmd: SetCommand, tool: &DbTool<N>) -> eyre::Result<()> {
// Read storage settings
let provider_rw = tool.provider_factory.database_provider_rw()?;
// Destruct settings struct to not miss adding support for new fields
let settings = provider_rw.storage_settings()?;
if settings.is_none() {
println!("No storage settings found, creating new settings.");
}
let mut settings @ StorageSettings {
receipts_in_static_files: _,
transaction_senders_in_static_files: _,
storages_history_in_rocksdb: _,
} = settings.unwrap_or_else(StorageSettings::legacy);
// Update the setting based on the key
match cmd {
SetCommand::ReceiptsInStaticFiles { value } => {
if settings.receipts_in_static_files == value {
println!("receipts_in_static_files is already set to {}", value);
return Ok(());
}
settings.receipts_in_static_files = value;
println!("Set receipts_in_static_files = {}", value);
}
SetCommand::TransactionSendersInStaticFiles { value } => {
if settings.transaction_senders_in_static_files == value {
println!("transaction_senders_in_static_files is already set to {}", value);
return Ok(());
}
settings.transaction_senders_in_static_files = value;
println!("Set transaction_senders_in_static_files = {}", value);
}
}
// Write updated settings
provider_rw.write_storage_settings(settings)?;
provider_rw.commit()?;
println!("Storage settings updated successfully.");
Ok(())
}
}

View File

@@ -1,63 +0,0 @@
use clap::{Parser, Subcommand};
use reth_db_common::DbTool;
use reth_provider::{providers::ProviderNodeTypes, StaticFileProviderFactory};
use reth_static_file_types::StaticFileSegment;
use std::path::PathBuf;
use tracing::warn;
/// The arguments for the `reth db static-file-header` command
#[derive(Parser, Debug)]
pub struct Command {
#[command(subcommand)]
source: Source,
}
/// Source for locating the static file
#[derive(Subcommand, Debug)]
enum Source {
/// Query by segment and block number
Block {
/// Static file segment
#[arg(value_enum)]
segment: StaticFileSegment,
/// Block number to query
block: u64,
},
/// Query by path to static file
Path {
/// Path to the static file
path: PathBuf,
},
}
impl Command {
/// Execute `db static-file-header` command
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
let static_file_provider = tool.provider_factory.static_file_provider();
if let Err(err) = static_file_provider.check_consistency(&tool.provider_factory.provider()?)
{
warn!("Error checking consistency of static files: {err}");
}
// Get the provider based on the source
let provider = match self.source {
Source::Path { path } => {
static_file_provider.get_segment_provider_for_path(&path)?.ok_or_else(|| {
eyre::eyre!("Could not find static file segment for path: {}", path.display())
})?
}
Source::Block { segment, block } => {
static_file_provider.get_segment_provider(segment, block)?
}
};
let header = provider.user_header();
println!("Segment: {}", header.segment());
println!("Expected Block Range: {}", header.expected_block_range());
println!("Block Range: {:?}", header.block_range());
println!("Transaction Range: {:?}", header.tx_range());
Ok(())
}
}

View File

@@ -18,10 +18,6 @@ use std::{sync::Arc, time::Duration};
#[derive(Parser, Debug)]
/// The arguments for the `reth db stats` command
pub struct Command {
/// Skip consistency checks for static files.
#[arg(long, default_value_t = false)]
pub(crate) skip_consistency_checks: bool,
/// Show only the total size for static files.
#[arg(long, default_value_t = false)]
detailed_sizes: bool,
@@ -195,11 +191,10 @@ impl Command {
mut segment_config_size,
) = (0, 0, 0, 0, 0, 0);
for (block_range, header) in &ranges {
let fixed_block_range =
static_file_provider.find_fixed_range(segment, block_range.start());
for (block_range, tx_range) in &ranges {
let fixed_block_range = static_file_provider.find_fixed_range(block_range.start());
let jar_provider = static_file_provider
.get_segment_provider_for_range(segment, || Some(fixed_block_range), None)?
.get_segment_provider(segment, || Some(fixed_block_range), None)?
.ok_or_else(|| {
eyre::eyre!("Failed to get segment provider for segment: {}", segment)
})?;
@@ -225,7 +220,7 @@ impl Command {
row.add_cell(Cell::new(segment))
.add_cell(Cell::new(format!("{block_range}")))
.add_cell(Cell::new(
header.tx_range().map_or("N/A".to_string(), |range| format!("{range}")),
tx_range.map_or("N/A".to_string(), |tx_range| format!("{tx_range}")),
))
.add_cell(Cell::new(format!("{columns} x {rows}")));
if self.detailed_sizes {
@@ -275,12 +270,10 @@ impl Command {
let tx_range = {
let start = ranges
.iter()
.find_map(|(_, header)| header.tx_range().map(|range| range.start()))
.find_map(|(_, tx_range)| tx_range.map(|r| r.start()))
.unwrap_or_default();
let end = ranges
.iter()
.rev()
.find_map(|(_, header)| header.tx_range().map(|range| range.end()));
let end =
ranges.iter().rev().find_map(|(_, tx_range)| tx_range.map(|r| r.end()));
end.map(|end| SegmentRangeInclusive::new(start, end))
};

View File

@@ -4,7 +4,7 @@ use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
use clap::{Args, Parser};
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_cli::chainspec::ChainSpecParser;
use reth_era::era1::types::execution::MAX_BLOCKS_PER_ERA1;
use reth_era::execution_types::MAX_BLOCKS_PER_ERA1;
use reth_era_utils as era1;
use reth_provider::DatabaseProviderFactory;
use std::{path::PathBuf, sync::Arc};

View File

@@ -1,6 +1,6 @@
//! Command that initializes the node from a genesis file.
use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
use crate::common::{AccessRights, CliHeader, CliNodeTypes, Environment, EnvironmentArgs};
use alloy_consensus::BlockHeader as AlloyBlockHeader;
use alloy_primitives::{Sealable, B256};
use clap::Parser;
@@ -8,7 +8,7 @@ use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_cli::chainspec::ChainSpecParser;
use reth_db_common::init::init_from_state_dump;
use reth_node_api::NodePrimitives;
use reth_primitives_traits::{header::HeaderMut, SealedHeader};
use reth_primitives_traits::{BlockHeader, SealedHeader};
use reth_provider::{
BlockNumReader, DBProvider, DatabaseProviderFactory, StaticFileProviderFactory,
StaticFileWriter,
@@ -69,7 +69,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitStateC
where
N: CliNodeTypes<
ChainSpec = C::ChainSpec,
Primitives: NodePrimitives<BlockHeader: HeaderMut>,
Primitives: NodePrimitives<BlockHeader: BlockHeader + CliHeader>,
>,
{
info!(target: "reth::cli", "Reth init-state starting");

View File

@@ -10,7 +10,7 @@ use reth_node_builder::NodeBuilder;
use reth_node_core::{
args::{
DatabaseArgs, DatadirArgs, DebugArgs, DevArgs, EngineArgs, EraArgs, MetricArgs,
NetworkArgs, PayloadBuilderArgs, PruningArgs, RpcServerArgs, StaticFilesArgs, TxPoolArgs,
NetworkArgs, PayloadBuilderArgs, PruningArgs, RpcServerArgs, TxPoolArgs,
},
node_config::NodeConfig,
version,
@@ -110,10 +110,6 @@ pub struct NodeCommand<C: ChainSpecParser, Ext: clap::Args + fmt::Debug = NoArgs
#[command(flatten, next_help_heading = "ERA")]
pub era: EraArgs,
/// All static files related arguments
#[command(flatten, next_help_heading = "Static Files")]
pub static_files: StaticFilesArgs,
/// Additional cli arguments
#[command(flatten, next_help_heading = "Extension")]
pub ext: Ext,
@@ -149,7 +145,7 @@ where
where
L: Launcher<C, Ext>,
{
tracing::info!(target: "reth::cli", version = ?version::version_metadata().short_version, "Starting {}", version::version_metadata().name_client);
tracing::info!(target: "reth::cli", version = ?version::version_metadata().short_version, "Starting reth");
let Self {
datadir,
@@ -166,10 +162,9 @@ where
db,
dev,
pruning,
ext,
engine,
era,
static_files,
ext,
} = self;
// set up node config
@@ -189,7 +184,6 @@ where
pruning,
engine,
era,
static_files,
};
let data_dir = node_config.datadir();

View File

@@ -60,7 +60,7 @@ impl Command {
if self.v5 {
info!("Starting discv5");
let config = Config::builder(self.addr).build();
let (_discv5, updates) = Discv5::start(&sk, config).await?;
let (_discv5, updates, _local_enr_discv5) = Discv5::start(&sk, config).await?;
discv5_updates = Some(updates);
};

View File

@@ -8,7 +8,7 @@ use backon::{ConstantBuilder, Retryable};
use clap::{Parser, Subcommand};
use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
use reth_cli::chainspec::ChainSpecParser;
use reth_cli_util::hash_or_num_value_parser;
use reth_cli_util::{get_secret_key, hash_or_num_value_parser};
use reth_config::Config;
use reth_network::{BlockDownloaderProvider, NetworkConfigBuilder};
use reth_network_p2p::bodies::client::BodiesClient;
@@ -183,7 +183,9 @@ impl<C: ChainSpecParser> DownloadArgs<C> {
config.peers.trusted_nodes_only = self.network.trusted_only;
let default_secret_key_path = data_dir.p2p_secret();
let p2p_secret_key = self.network.secret_key(default_secret_key_path)?;
let secret_key_path =
self.network.p2p_secret_key.clone().unwrap_or(default_secret_key_path);
let p2p_secret_key = get_secret_key(&secret_key_path)?;
let rlpx_socket = (self.network.addr, self.network.port).into();
let boot_nodes = self.chain.bootnodes().unwrap_or_default();

View File

@@ -9,7 +9,6 @@ use clap::Parser;
use eyre::WrapErr;
use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks};
use reth_cli::chainspec::ChainSpecParser;
use reth_cli_util::cancellation::CancellationToken;
use reth_consensus::FullConsensus;
use reth_evm::{execute::Executor, ConfigureEvm};
use reth_primitives_traits::{format_gas_throughput, BlockBody, GotExpected};
@@ -45,10 +44,6 @@ pub struct Command<C: ChainSpecParser> {
/// Number of tasks to run in parallel
#[arg(long, default_value = "10")]
num_tasks: u64,
/// Continues with execution when an invalid block is encountered and collects these blocks.
#[arg(long)]
skip_invalid_blocks: bool,
}
impl<C: ChainSpecParser> Command<C> {
@@ -66,23 +61,11 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
{
let Environment { provider_factory, .. } = self.env.init::<N>(AccessRights::RO)?;
let provider = provider_factory.database_provider_ro()?;
let components = components(provider_factory.chain_spec());
let min_block = self.from;
let best_block = DatabaseProviderFactory::database_provider_ro(&provider_factory)?
.best_block_number()?;
let mut max_block = best_block;
if let Some(to) = self.to {
if to > best_block {
warn!(
requested = to,
best_block,
"Requested --to is beyond available chain head; clamping to best block"
);
} else {
max_block = to;
}
};
let max_block = self.to.unwrap_or(provider.best_block_number()?);
let total_blocks = max_block - min_block;
let total_gas = calculate_gas_used_from_headers(
@@ -100,11 +83,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
}
};
let skip_invalid_blocks = self.skip_invalid_blocks;
let (stats_tx, mut stats_rx) = mpsc::unbounded_channel();
let (info_tx, mut info_rx) = mpsc::unbounded_channel();
let cancellation = CancellationToken::new();
let _guard = cancellation.drop_guard();
let mut tasks = JoinSet::new();
for i in 0..self.num_tasks {
@@ -118,40 +97,17 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
let consensus = components.consensus().clone();
let db_at = db_at.clone();
let stats_tx = stats_tx.clone();
let info_tx = info_tx.clone();
let cancellation = cancellation.clone();
tasks.spawn_blocking(move || {
let mut executor = evm_config.batch_executor(db_at(start_block - 1));
let mut executor_created = Instant::now();
let executor_lifetime = Duration::from_secs(120);
'blocks: for block in start_block..end_block {
if cancellation.is_cancelled() {
// exit if the program is being terminated
break
}
for block in start_block..end_block {
let block = provider_factory
.recovered_block(block.into(), TransactionVariant::NoHash)?
.unwrap();
let result = match executor.execute_one(&block) {
Ok(result) => result,
Err(err) => {
if skip_invalid_blocks {
executor = evm_config.batch_executor(db_at(block.number()));
let _ = info_tx.send((block, eyre::Report::new(err)));
continue
}
return Err(err.into())
}
};
let result = executor.execute_one(&block)?;
if let Err(err) = consensus
.validate_block_post_execution(&block, &result)
.wrap_err_with(|| {
format!("Failed to validate block {} {}", block.number(), block.hash())
})
.wrap_err_with(|| format!("Failed to validate block {}", block.number()))
{
let correct_receipts =
provider_factory.receipts_by_block(block.number().into())?.unwrap();
@@ -187,11 +143,6 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
};
error!(number=?block.number(), ?mismatch, "Gas usage mismatch");
if skip_invalid_blocks {
executor = evm_config.batch_executor(db_at(block.number()));
let _ = info_tx.send((block, err));
continue 'blocks;
}
return Err(err);
}
} else {
@@ -203,12 +154,9 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
}
let _ = stats_tx.send(block.gas_used());
// Reset DB once in a while to avoid OOM or read tx timeouts
if executor.size_hint() > 1_000_000 ||
executor_created.elapsed() > executor_lifetime
{
// Reset DB once in a while to avoid OOM
if executor.size_hint() > 1_000_000 {
executor = evm_config.batch_executor(db_at(block.number()));
executor_created = Instant::now();
}
}
@@ -223,7 +171,6 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
let mut last_logged_gas = 0;
let mut last_logged_blocks = 0;
let mut last_logged_time = Instant::now();
let mut invalid_blocks = Vec::new();
let mut interval = tokio::time::interval(Duration::from_secs(10));
@@ -233,10 +180,6 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
total_executed_blocks += 1;
total_executed_gas += gas_used;
}
Some((block, err)) = info_rx.recv() => {
error!(?err, block=?block.num_hash(), "Invalid block");
invalid_blocks.push(block.num_hash());
}
result = tasks.join_next() => {
if let Some(result) = result {
if matches!(result, Err(_) | Ok(Err(_))) {
@@ -267,25 +210,12 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
}
}
if invalid_blocks.is_empty() {
info!(
start_block = min_block,
end_block = max_block,
%total_executed_blocks,
throughput=?format_gas_throughput(total_executed_gas, instant.elapsed()),
"Re-executed successfully"
);
} else {
info!(
start_block = min_block,
end_block = max_block,
%total_executed_blocks,
invalid_block_count = invalid_blocks.len(),
?invalid_blocks,
throughput=?format_gas_throughput(total_executed_gas, instant.elapsed()),
"Re-executed with invalid blocks"
);
}
info!(
start_block = min_block,
end_block = max_block,
throughput=?format_gas_throughput(total_executed_gas, instant.elapsed()),
"Re-executed successfully"
);
Ok(())
}

View File

@@ -1,9 +1,10 @@
//! Database debugging tool
use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
use clap::Parser;
use itertools::Itertools;
use reth_chainspec::EthChainSpec;
use reth_cli::chainspec::ChainSpecParser;
use reth_db::{mdbx::tx::Tx, DatabaseError};
use reth_db::{mdbx::tx::Tx, static_file::iter_static_files, DatabaseError};
use reth_db_api::{
tables,
transaction::{DbTx, DbTxMut},
@@ -14,9 +15,7 @@ use reth_db_common::{
};
use reth_node_api::{HeaderTy, ReceiptTy, TxTy};
use reth_node_core::args::StageEnum;
use reth_provider::{
DBProvider, DatabaseProviderFactory, StaticFileProviderFactory, StaticFileWriter, TrieWriter,
};
use reth_provider::{DBProvider, DatabaseProviderFactory, StaticFileProviderFactory, TrieWriter};
use reth_prune::PruneSegment;
use reth_stages::StageId;
use reth_static_file_types::StaticFileSegment;
@@ -45,48 +44,21 @@ impl<C: ChainSpecParser> Command<C> {
StageEnum::Headers => Some(StaticFileSegment::Headers),
StageEnum::Bodies => Some(StaticFileSegment::Transactions),
StageEnum::Execution => Some(StaticFileSegment::Receipts),
StageEnum::Senders => Some(StaticFileSegment::TransactionSenders),
_ => None,
};
// Calling `StaticFileProviderRW::prune_*` will instruct the writer to prune rows only
// when `StaticFileProviderRW::commit` is called. We need to do that instead of
// deleting the jar files, otherwise if the task were to be interrupted after we
// have deleted them, BUT before we have committed the checkpoints to the database, we'd
// lose essential data.
// Delete static file segment data before inserting the genesis header below
if let Some(static_file_segment) = static_file_segment {
let static_file_provider = tool.provider_factory.static_file_provider();
if let Some(highest_block) =
static_file_provider.get_highest_static_file_block(static_file_segment)
{
let mut writer = static_file_provider.latest_writer(static_file_segment)?;
match static_file_segment {
StaticFileSegment::Headers => {
// Prune all headers leaving genesis intact.
writer.prune_headers(highest_block)?;
}
StaticFileSegment::Transactions => {
let to_delete = static_file_provider
.get_highest_static_file_tx(static_file_segment)
.map(|tx_num| tx_num + 1)
.unwrap_or_default();
writer.prune_transactions(to_delete, 0)?;
}
StaticFileSegment::Receipts => {
let to_delete = static_file_provider
.get_highest_static_file_tx(static_file_segment)
.map(|tx_num| tx_num + 1)
.unwrap_or_default();
writer.prune_receipts(to_delete, 0)?;
}
StaticFileSegment::TransactionSenders => {
let to_delete = static_file_provider
.get_highest_static_file_tx(static_file_segment)
.map(|tx_num| tx_num + 1)
.unwrap_or_default();
writer.prune_transaction_senders(to_delete, 0)?;
}
let static_files = iter_static_files(static_file_provider.directory())?;
if let Some(segment_static_files) = static_files.get(&static_file_segment) {
// Delete static files from the highest to the lowest block range
for (block_range, _) in segment_static_files
.iter()
.sorted_by_key(|(block_range, _)| block_range.start())
.rev()
{
static_file_provider.delete_jar(static_file_segment, block_range.start())?;
}
}
}

View File

@@ -42,7 +42,7 @@ where
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
)?,
),
to,
from,
evm_config,

View File

@@ -39,7 +39,7 @@ pub(crate) async fn dump_hashing_account_stage<N: ProviderNodeTypes<DB = Arc<Dat
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
)?,
),
to,
from,
)?;

View File

@@ -29,7 +29,7 @@ pub(crate) async fn dump_hashing_storage_stage<N: ProviderNodeTypes<DB = Arc<Dat
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
)?,
),
to,
from,
)?;

View File

@@ -62,7 +62,7 @@ where
Arc::new(output_db),
db_tool.chain(),
StaticFileProvider::read_write(output_datadir.static_files())?,
)?,
),
to,
from,
)?;

View File

@@ -84,9 +84,6 @@ pub struct Command<C: ChainSpecParser> {
/// Commits the changes in the database. WARNING: potentially destructive.
///
/// Useful when you want to run diagnostics on the database.
///
/// NOTE: This flag is currently required for the headers, bodies, and execution stages because
/// they use static files and must commit to properly unwind and run.
// TODO: We should consider allowing to run hooks at the end of the stage run,
// e.g. query the DB size, or any table data.
#[arg(long, short)]
@@ -108,14 +105,6 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
Comp: CliNodeComponents<N>,
F: FnOnce(Arc<C::ChainSpec>) -> Comp,
{
// Quit early if the stages requires a commit and `--commit` is not provided.
if self.requires_commit() && !self.commit {
return Err(eyre::eyre!(
"The stage {} requires overwriting existing static files and must commit, but `--commit` was not provided. Please pass `--commit` and try again.",
self.stage.to_string()
));
}
// Raise the fd limit of the process.
// Does not do anything on windows.
let _ = fdlimit::raise_fd_limit();
@@ -394,13 +383,4 @@ impl<C: ChainSpecParser> Command<C> {
pub fn chain_spec(&self) -> Option<&Arc<C::ChainSpec>> {
Some(&self.env.chain)
}
/// Returns whether or not the configured stage requires committing.
///
/// This is the case for stages that mainly modify static files, as there is no way to unwind
/// these stages without committing anyways. This is because static files do not have
/// transactions and we cannot change the view of headers without writing.
pub fn requires_commit(&self) -> bool {
matches!(self.stage, StageEnum::Headers | StageEnum::Bodies | StageEnum::Execution)
}
}

View File

@@ -42,9 +42,6 @@ jemalloc = ["dep:tikv-jemallocator"]
# Enables jemalloc profiling features
jemalloc-prof = ["jemalloc", "tikv-jemallocator?/profiling"]
# Enables unprefixed malloc (reproducible builds support)
jemalloc-unprefixed = ["jemalloc", "tikv-jemallocator?/unprefixed_malloc_on_supported_platforms"]
# Wraps the selected allocator in the tracy profiling allocator
tracy-allocator = ["dep:tracy-client"]

View File

@@ -1,103 +0,0 @@
//! Thread-safe cancellation primitives for cooperative task cancellation.
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
/// A thread-safe cancellation token that can be shared across threads.
///
/// This token allows cooperative cancellation by providing a way to signal
/// cancellation and check cancellation status. The token can be cloned and
/// shared across multiple threads, with all clones sharing the same cancellation state.
///
/// # Example
///
/// ```
/// use reth_cli_util::cancellation::CancellationToken;
/// use std::{thread, time::Duration};
///
/// let token = CancellationToken::new();
/// let worker_token = token.clone();
///
/// let handle = thread::spawn(move || {
/// while !worker_token.is_cancelled() {
/// // Do work...
/// thread::sleep(Duration::from_millis(100));
/// }
/// });
///
/// // Cancel from main thread
/// token.cancel();
/// handle.join().unwrap();
/// ```
#[derive(Clone, Debug)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
/// Creates a new cancellation token in the non-cancelled state.
pub fn new() -> Self {
Self { cancelled: Arc::new(AtomicBool::new(false)) }
}
/// Signals cancellation to all holders of this token and its clones.
///
/// Once cancelled, the token cannot be reset. This operation is thread-safe
/// and can be called multiple times without issue.
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
}
/// Checks whether cancellation has been requested.
///
/// Returns `true` if [`cancel`](Self::cancel) has been called on this token
/// or any of its clones.
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Relaxed)
}
/// Creates a guard that automatically cancels this token when dropped.
///
/// This is useful for ensuring cancellation happens when a scope exits,
/// either normally or via panic.
///
/// # Example
///
/// ```
/// use reth_cli_util::cancellation::CancellationToken;
///
/// let token = CancellationToken::new();
/// {
/// let _guard = token.drop_guard();
/// assert!(!token.is_cancelled());
/// // Guard dropped here, triggering cancellation
/// }
/// assert!(token.is_cancelled());
/// ```
pub fn drop_guard(&self) -> CancellationGuard {
CancellationGuard { token: self.clone() }
}
}
impl Default for CancellationToken {
fn default() -> Self {
Self::new()
}
}
/// A guard that cancels its associated [`CancellationToken`] when dropped.
///
/// Created by calling [`CancellationToken::drop_guard`]. When this guard is dropped,
/// it automatically calls [`cancel`](CancellationToken::cancel) on the token.
#[derive(Debug)]
pub struct CancellationGuard {
token: CancellationToken,
}
impl Drop for CancellationGuard {
fn drop(&mut self) {
self.token.cancel();
}
}

View File

@@ -9,11 +9,10 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
pub mod allocator;
pub mod cancellation;
/// Helper function to load a secret key from a file.
pub mod load_secret_key;
pub use load_secret_key::{get_secret_key, parse_secret_key_from_hex};
pub use load_secret_key::get_secret_key;
/// Cli parsers functions.
pub mod parsers;

View File

@@ -30,10 +30,6 @@ pub enum SecretKeyError {
/// Path to the secret key file.
secret_file: PathBuf,
},
/// Invalid hex string format.
#[error("invalid hex string: {0}")]
InvalidHexString(String),
}
/// Attempts to load a [`SecretKey`] from a specified path. If no file exists there, then it
@@ -64,75 +60,3 @@ pub fn get_secret_key(secret_key_path: &Path) -> Result<SecretKey, SecretKeyErro
}),
}
}
/// Parses a [`SecretKey`] from a hex string.
///
/// The hex string can optionally start with "0x".
pub fn parse_secret_key_from_hex(hex_str: &str) -> Result<SecretKey, SecretKeyError> {
// Remove "0x" prefix if present
let hex_str = hex_str.strip_prefix("0x").unwrap_or(hex_str);
// Decode the hex string
let bytes = alloy_primitives::hex::decode(hex_str)
.map_err(|e| SecretKeyError::InvalidHexString(e.to_string()))?;
// Parse into SecretKey
SecretKey::from_slice(&bytes).map_err(SecretKeyError::SecretKeyDecodeError)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_secret_key_from_hex_without_prefix() {
// Valid 32-byte hex string (64 characters)
let hex = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
let result = parse_secret_key_from_hex(hex);
assert!(result.is_ok());
let secret_key = result.unwrap();
assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), hex);
}
#[test]
fn test_parse_secret_key_from_hex_with_0x_prefix() {
// Valid 32-byte hex string with 0x prefix
let hex = "0x4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
let result = parse_secret_key_from_hex(hex);
assert!(result.is_ok());
let secret_key = result.unwrap();
let expected = "4c0883a69102937d6231471b5dbb6204fe512961708279f8c5c58b3b9c4e8b8f";
assert_eq!(alloy_primitives::hex::encode(secret_key.secret_bytes()), expected);
}
#[test]
fn test_parse_secret_key_from_hex_invalid_length() {
// Invalid length (not 32 bytes)
let hex = "4c0883a69102937d";
let result = parse_secret_key_from_hex(hex);
assert!(result.is_err());
}
#[test]
fn test_parse_secret_key_from_hex_invalid_chars() {
// Invalid hex characters
let hex = "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
let result = parse_secret_key_from_hex(hex);
assert!(result.is_err());
if let Err(SecretKeyError::InvalidHexString(_)) = result {
// Expected error type
} else {
panic!("Expected InvalidHexString error");
}
}
#[test]
fn test_parse_secret_key_from_hex_empty() {
let hex = "";
let result = parse_secret_key_from_hex(hex);
assert!(result.is_err());
}
}

View File

@@ -31,16 +31,6 @@ pub fn parse_duration_from_secs_or_ms(
}
}
/// Helper to format a [Duration] to the format that can be parsed by
/// [`parse_duration_from_secs_or_ms`].
pub fn format_duration_as_secs_or_ms(duration: Duration) -> String {
if duration.as_millis().is_multiple_of(1000) {
format!("{}", duration.as_secs())
} else {
format!("{}ms", duration.as_millis())
}
}
/// Parse [`BlockHashOrNumber`]
pub fn hash_or_num_value_parser(value: &str) -> eyre::Result<BlockHashOrNumber, eyre::Error> {
match B256::from_str(value) {

View File

@@ -126,8 +126,7 @@ pub fn install() {
libc::sigaltstack(&raw const alt_stack, ptr::null_mut());
let mut sa: libc::sigaction = mem::zeroed();
sa.sa_sigaction =
print_stack_trace as unsafe extern "C" fn(libc::c_int) as libc::sighandler_t;
sa.sa_sigaction = print_stack_trace as libc::sighandler_t;
sa.sa_flags = libc::SA_NODEFER | libc::SA_RESETHAND | libc::SA_ONSTACK;
libc::sigemptyset(&raw mut sa.sa_mask);
libc::sigaction(libc::SIGSEGV, &raw const sa, ptr::null_mut());

View File

@@ -15,7 +15,6 @@ workspace = true
reth-network-types.workspace = true
reth-prune-types.workspace = true
reth-stages-types.workspace = true
reth-static-file-types.workspace = true
# serde
serde = { workspace = true, optional = true }
@@ -23,7 +22,7 @@ humantime-serde = { workspace = true, optional = true }
# toml
toml = { workspace = true, optional = true }
eyre.workspace = true
eyre = { workspace = true, optional = true }
# value objects
url.workspace = true
@@ -32,6 +31,7 @@ url.workspace = true
serde = [
"dep:serde",
"dep:toml",
"dep:eyre",
"dep:humantime-serde",
"reth-network-types/serde",
"reth-prune-types/serde",

View File

@@ -2,9 +2,7 @@
use reth_network_types::{PeersConfig, SessionsConfig};
use reth_prune_types::PruneModes;
use reth_stages_types::ExecutionStageThresholds;
use reth_static_file_types::StaticFileSegment;
use std::{
collections::HashMap,
path::{Path, PathBuf},
time::Duration,
};
@@ -31,9 +29,6 @@ pub struct Config {
pub peers: PeersConfig,
/// Configuration for peer sessions.
pub sessions: SessionsConfig,
/// Configuration for static files.
#[cfg_attr(feature = "serde", serde(default))]
pub static_files: StaticFilesConfig,
}
impl Config {
@@ -416,77 +411,6 @@ impl EtlConfig {
}
}
/// Static files configuration.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct StaticFilesConfig {
/// Number of blocks per file for each segment.
pub blocks_per_file: BlocksPerFileConfig,
}
/// Configuration for the number of blocks per file for each segment.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct BlocksPerFileConfig {
/// Number of blocks per file for the headers segment.
pub headers: Option<u64>,
/// Number of blocks per file for the transactions segment.
pub transactions: Option<u64>,
/// Number of blocks per file for the receipts segment.
pub receipts: Option<u64>,
/// Number of blocks per file for the transaction senders segment.
pub transaction_senders: Option<u64>,
}
impl StaticFilesConfig {
/// Validates the static files configuration.
///
/// Returns an error if any blocks per file value is zero.
pub fn validate(&self) -> eyre::Result<()> {
let BlocksPerFileConfig { headers, transactions, receipts, transaction_senders } =
self.blocks_per_file;
eyre::ensure!(headers != Some(0), "Headers segment blocks per file must be greater than 0");
eyre::ensure!(
transactions != Some(0),
"Transactions segment blocks per file must be greater than 0"
);
eyre::ensure!(
receipts != Some(0),
"Receipts segment blocks per file must be greater than 0"
);
eyre::ensure!(
transaction_senders != Some(0),
"Transaction senders segment blocks per file must be greater than 0"
);
Ok(())
}
/// Converts the blocks per file configuration into a [`HashMap`] per segment.
pub fn as_blocks_per_file_map(&self) -> HashMap<StaticFileSegment, u64> {
let BlocksPerFileConfig { headers, transactions, receipts, transaction_senders } =
self.blocks_per_file;
let mut map = HashMap::new();
// Iterating over all possible segments allows us to do an exhaustive match here,
// to not forget to configure new segments in the future.
for segment in StaticFileSegment::iter() {
let blocks_per_file = match segment {
StaticFileSegment::Headers => headers,
StaticFileSegment::Transactions => transactions,
StaticFileSegment::Receipts => receipts,
StaticFileSegment::TransactionSenders => transaction_senders,
};
if let Some(blocks_per_file) = blocks_per_file {
map.insert(segment, blocks_per_file);
}
}
map
}
}
/// History stage configuration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]

View File

@@ -1,6 +1,8 @@
//! Collection of methods for block validation.
use alloy_consensus::{BlockHeader as _, Transaction, EMPTY_OMMER_ROOT_HASH};
use alloy_consensus::{
constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader as _, Transaction, EMPTY_OMMER_ROOT_HASH,
};
use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, eip7840::BlobParams};
use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks};
use reth_consensus::{ConsensusError, TxGasLimitTooHighErr};
@@ -223,9 +225,13 @@ where
/// Validates that the EIP-4844 header fields exist and conform to the spec. This ensures that:
///
/// * `blob_gas_used` exists as a header field
/// * `excess_blob_gas` exists as a header field
/// * `parent_beacon_block_root` exists as a header field
/// * `blob_gas_used` is a multiple of `DATA_GAS_PER_BLOB`
/// * `excess_blob_gas` is a multiple of `DATA_GAS_PER_BLOB`
/// * `blob_gas_used` doesn't exceed the max allowed blob gas based on the given params
///
/// Note: This does not enforce any restrictions on `blob_gas_used`
pub fn validate_4844_header_standalone<H: BlockHeader>(
header: &H,
blob_params: BlobParams,
@@ -258,12 +264,9 @@ pub fn validate_4844_header_standalone<H: BlockHeader>(
/// From yellow paper: extraData: An arbitrary byte array containing data relevant to this block.
/// This must be 32 bytes or fewer; formally Hx.
#[inline]
pub fn validate_header_extra_data<H: BlockHeader>(
header: &H,
max_size: usize,
) -> Result<(), ConsensusError> {
pub fn validate_header_extra_data<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
let extra_data_len = header.extra_data().len();
if extra_data_len > max_size {
if extra_data_len > MAXIMUM_EXTRA_DATA_SIZE {
Err(ConsensusError::ExtraDataExceedsMax { len: extra_data_len })
} else {
Ok(())
@@ -500,21 +503,4 @@ mod tests {
}))
);
}
#[test]
fn validate_header_extra_data_with_custom_limit() {
// Test with default 32 bytes - should pass
let header_32 = Header { extra_data: Bytes::from(vec![0; 32]), ..Default::default() };
assert!(validate_header_extra_data(&header_32, 32).is_ok());
// Test exceeding default - should fail
let header_33 = Header { extra_data: Bytes::from(vec![0; 33]), ..Default::default() };
assert_eq!(
validate_header_extra_data(&header_33, 32),
Err(ConsensusError::ExtraDataExceedsMax { len: 33 })
);
// Test with custom larger limit - should pass
assert!(validate_header_extra_data(&header_33, 64).is_ok());
}
}

View File

@@ -16,7 +16,7 @@ use alloy_consensus::Header;
use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256};
use reth_execution_types::BlockExecutionResult;
use reth_primitives_traits::{
constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
constants::{MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
transaction::error::InvalidTransactionError,
Block, GotExpected, GotExpectedBoxed, NodePrimitives, RecoveredBlock, SealedBlock,
SealedHeader,
@@ -349,7 +349,7 @@ pub enum ConsensusError {
},
/// Error when the child gas limit exceeds the maximum allowed increase.
#[error("child gas_limit {child_gas_limit} exceeds the max allowed increase ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
#[error("child gas_limit {child_gas_limit} max increase is {parent_gas_limit}/1024")]
GasLimitInvalidIncrease {
/// The parent gas limit.
parent_gas_limit: u64,
@@ -378,7 +378,7 @@ pub enum ConsensusError {
},
/// Error when the child gas limit exceeds the maximum allowed decrease.
#[error("child gas_limit {child_gas_limit} is below the max allowed decrease ({parent_gas_limit}/{GAS_LIMIT_BOUND_DIVISOR})")]
#[error("child gas_limit {child_gas_limit} max decrease is {parent_gas_limit}/1024")]
GasLimitInvalidDecrease {
/// The parent gas limit.
parent_gas_limit: u64,

View File

@@ -61,42 +61,34 @@ where
type Block = PrimitiveBlock;
async fn subscribe_blocks(&self, tx: Sender<Self::Block>) {
loop {
let Ok(mut stream) = self.full_block_stream().await.inspect_err(|err| {
warn!(
target: "consensus::debug-client",
%err,
url=%self.url,
"Failed to subscribe to blocks",
);
}) else {
return
};
let Ok(mut stream) = self.full_block_stream().await.inspect_err(|err| {
warn!(
target: "consensus::debug-client",
%err,
url=%self.url,
"Failed to subscribe to blocks",
);
}) else {
return
};
while let Some(res) = stream.next().await {
match res {
Ok(block) => {
if tx.send((self.convert)(block)).await.is_err() {
// Channel closed.
break;
}
}
Err(err) => {
warn!(
target: "consensus::debug-client",
%err,
url=%self.url,
"Failed to fetch a block",
);
while let Some(res) = stream.next().await {
match res {
Ok(block) => {
if tx.send((self.convert)(block)).await.is_err() {
// Channel closed.
break;
}
}
Err(err) => {
warn!(
target: "consensus::debug-client",
%err,
url=%self.url,
"Failed to fetch a block",
);
}
}
// if stream terminated we want to re-establish it again
debug!(
target: "consensus::debug-client",
url=%self.url,
"Re-estbalishing block subscription",
);
}
}

View File

@@ -3,12 +3,13 @@
use node::NodeTestContext;
use reth_chainspec::ChainSpec;
use reth_db::{test_utils::TempDatabase, DatabaseEnv};
use reth_engine_local::LocalPayloadAttributesBuilder;
use reth_network_api::test_utils::PeersHandleProvider;
use reth_node_builder::{
components::NodeComponentsBuilder,
rpc::{EngineValidatorAddOn, RethRpcAddOns},
FullNodeTypesAdapter, Node, NodeAdapter, NodeComponents, NodeTypes, NodeTypesWithDBAdapter,
PayloadTypes,
PayloadAttributesBuilder, PayloadTypes,
};
use reth_provider::providers::{BlockchainProvider, NodeTypesForProvider};
use reth_tasks::TaskManager;
@@ -53,6 +54,8 @@ pub async fn setup<N>(
) -> eyre::Result<(Vec<NodeHelperType<N>>, TaskManager, Wallet)>
where
N: NodeBuilderHelper,
LocalPayloadAttributesBuilder<N::ChainSpec>:
PayloadAttributesBuilder<<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes>,
{
E2ETestSetupBuilder::new(num_nodes, chain_spec, attributes_generator)
.with_node_config_modifier(move |config| config.set_dev(is_dev))
@@ -74,6 +77,8 @@ pub async fn setup_engine<N>(
)>
where
N: NodeBuilderHelper,
LocalPayloadAttributesBuilder<N::ChainSpec>:
PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
{
setup_engine_with_connection::<N>(
num_nodes,
@@ -101,6 +106,8 @@ pub async fn setup_engine_with_connection<N>(
)>
where
N: NodeBuilderHelper,
LocalPayloadAttributesBuilder<N::ChainSpec>:
PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
{
E2ETestSetupBuilder::new(num_nodes, chain_spec, attributes_generator)
.with_tree_config_modifier(move |_| tree_config.clone())
@@ -153,10 +160,13 @@ where
>,
ChainSpec: From<ChainSpec> + Clone,
>,
LocalPayloadAttributesBuilder<Self::ChainSpec>:
PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes>,
{
}
impl<T> NodeBuilderHelper for T where
impl<T> NodeBuilderHelper for T
where
Self: Default
+ NodeTypesForProvider<
Payload: PayloadTypes<
@@ -177,6 +187,8 @@ impl<T> NodeBuilderHelper for T where
Adapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
>,
ChainSpec: From<ChainSpec> + Clone,
>
>,
LocalPayloadAttributesBuilder<Self::ChainSpec>:
PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes>,
{
}

View File

@@ -4,18 +4,18 @@
//! configurations through closures that modify `NodeConfig` and `TreeConfig`.
use crate::{node::NodeTestContext, wallet::Wallet, NodeBuilderHelper, NodeHelperType, TmpDB};
use futures_util::future::TryJoinAll;
use reth_chainspec::EthChainSpec;
use reth_engine_local::LocalPayloadAttributesBuilder;
use reth_node_builder::{
EngineNodeLauncher, NodeBuilder, NodeConfig, NodeHandle, NodeTypes, NodeTypesWithDBAdapter,
PayloadTypes,
PayloadAttributesBuilder, PayloadTypes,
};
use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs};
use reth_provider::providers::BlockchainProvider;
use reth_rpc_server_types::RpcModuleSelection;
use reth_tasks::TaskManager;
use std::sync::Arc;
use tracing::{span, Instrument, Level};
use tracing::{span, Level};
/// Type alias for tree config modifier closure
type TreeConfigModifier =
@@ -37,6 +37,8 @@ where
+ Sync
+ Copy
+ 'static,
LocalPayloadAttributesBuilder<N::ChainSpec>:
PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
{
num_nodes: usize,
chain_spec: Arc<N::ChainSpec>,
@@ -54,6 +56,8 @@ where
+ Sync
+ Copy
+ 'static,
LocalPayloadAttributesBuilder<N::ChainSpec>:
PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
{
/// Creates a new builder with the required parameters.
pub fn new(num_nodes: usize, chain_spec: Arc<N::ChainSpec>, attributes_generator: F) -> Self {
@@ -118,71 +122,66 @@ where
reth_node_api::TreeConfig::default()
};
let mut nodes = (0..self.num_nodes)
.map(async |idx| {
// Create base node config
let base_config = NodeConfig::new(self.chain_spec.clone())
.with_network(network_config.clone())
.with_unused_ports()
.with_rpc(
RpcServerArgs::default()
.with_unused_ports()
.with_http()
.with_http_api(RpcModuleSelection::All),
);
// Apply node config modifier if present
let node_config = if let Some(modifier) = &self.node_config_modifier {
modifier(base_config)
} else {
base_config
};
let span = span!(Level::INFO, "node", idx);
let node = N::default();
let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config)
.testing_node(exec.clone())
.with_types_and_provider::<N, BlockchainProvider<_>>()
.with_components(node.components_builder())
.with_add_ons(node.add_ons())
.launch_with_fn(|builder| {
let launcher = EngineNodeLauncher::new(
builder.task_executor().clone(),
builder.config().datadir(),
tree_config.clone(),
);
builder.launch_with(launcher)
})
.instrument(span)
.await?;
let node = NodeTestContext::new(node, self.attributes_generator).await?;
let genesis = node.block_hash(0);
node.update_forkchoice(genesis, genesis).await?;
eyre::Ok(node)
})
.collect::<TryJoinAll<_>>()
.await?;
let mut nodes: Vec<NodeTestContext<_, _>> = Vec::with_capacity(self.num_nodes);
for idx in 0..self.num_nodes {
let (prev, current) = nodes.split_at_mut(idx);
let current = current.first_mut().unwrap();
// Create base node config
let base_config = NodeConfig::new(self.chain_spec.clone())
.with_network(network_config.clone())
.with_unused_ports()
.with_rpc(
RpcServerArgs::default()
.with_unused_ports()
.with_http()
.with_http_api(RpcModuleSelection::All),
);
// Apply node config modifier if present
let node_config = if let Some(modifier) = &self.node_config_modifier {
modifier(base_config)
} else {
base_config
};
let span = span!(Level::INFO, "node", idx);
let _enter = span.enter();
let node = N::default();
let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config)
.testing_node(exec.clone())
.with_types_and_provider::<N, BlockchainProvider<_>>()
.with_components(node.components_builder())
.with_add_ons(node.add_ons())
.launch_with_fn(|builder| {
let launcher = EngineNodeLauncher::new(
builder.task_executor().clone(),
builder.config().datadir(),
tree_config.clone(),
);
builder.launch_with(launcher)
})
.await?;
let mut node = NodeTestContext::new(node, self.attributes_generator).await?;
let genesis = node.block_hash(0);
node.update_forkchoice(genesis, genesis).await?;
// Connect nodes if requested
if self.connect_nodes {
if let Some(prev_idx) = idx.checked_sub(1) {
prev[prev_idx].connect(current).await;
if let Some(previous_node) = nodes.last_mut() {
previous_node.connect(&mut node).await;
}
// Connect last node with the first if there are more than two
if idx + 1 == self.num_nodes &&
self.num_nodes > 2 &&
let Some(first) = prev.first_mut()
let Some(first_node) = nodes.first_mut()
{
current.connect(first).await;
node.connect(first_node).await;
}
}
nodes.push(node);
}
Ok((nodes, tasks, Wallet::default().with_chain_id(self.chain_spec.chain().into())))
@@ -197,6 +196,8 @@ where
+ Sync
+ Copy
+ 'static,
LocalPayloadAttributesBuilder<N::ChainSpec>:
PayloadAttributesBuilder<<N::Payload as PayloadTypes>::PayloadAttributes>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("E2ETestSetupBuilder")

View File

@@ -125,7 +125,7 @@ pub async fn setup_engine_with_chain_import(
db.clone(),
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_write(static_files_path.clone())?,
)?;
);
// Initialize genesis if needed
reth_db_common::init::init_genesis(&provider_factory)?;
@@ -324,8 +324,7 @@ mod tests {
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_write(static_files_path.clone())
.unwrap(),
)
.expect("failed to create provider factory");
);
// Initialize genesis
reth_db_common::init::init_genesis(&provider_factory).unwrap();
@@ -385,8 +384,7 @@ mod tests {
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_only(static_files_path, false)
.unwrap(),
)
.expect("failed to create provider factory");
);
let provider = provider_factory.database_provider_ro().unwrap();
@@ -477,8 +475,7 @@ mod tests {
db.clone(),
chain_spec.clone(),
reth_provider::providers::StaticFileProvider::read_write(static_files_path).unwrap(),
)
.expect("failed to create provider factory");
);
// Initialize genesis
reth_db_common::init::init_genesis(&provider_factory).unwrap();

View File

@@ -2,12 +2,13 @@
use crate::{
testsuite::actions::{Action, ActionBox},
NodeBuilderHelper,
NodeBuilderHelper, PayloadAttributesBuilder,
};
use alloy_primitives::B256;
use eyre::Result;
use jsonrpsee::http_client::HttpClient;
use reth_node_api::{EngineTypes, PayloadTypes};
use reth_engine_local::LocalPayloadAttributesBuilder;
use reth_node_api::{EngineTypes, NodeTypes, PayloadTypes};
use reth_payload_builder::PayloadId;
use std::{collections::HashMap, marker::PhantomData};
pub mod actions;
@@ -348,6 +349,9 @@ where
pub async fn run<N>(mut self) -> Result<()>
where
N: NodeBuilderHelper<Payload = I>,
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
let mut setup = self.setup.take();

View File

@@ -1,11 +1,15 @@
//! Test setup utilities for configuring the initial state.
use crate::{setup_engine_with_connection, testsuite::Environment, NodeBuilderHelper};
use crate::{
setup_engine_with_connection, testsuite::Environment, NodeBuilderHelper,
PayloadAttributesBuilder,
};
use alloy_eips::BlockNumberOrTag;
use alloy_primitives::B256;
use alloy_rpc_types_engine::{ForkchoiceState, PayloadAttributes};
use eyre::{eyre, Result};
use reth_chainspec::ChainSpec;
use reth_engine_local::LocalPayloadAttributesBuilder;
use reth_ethereum_primitives::Block;
use reth_network_p2p::sync::{NetworkSyncUpdater, SyncState};
use reth_node_api::{EngineTypes, NodeTypes, PayloadTypes, TreeConfig};
@@ -134,19 +138,28 @@ where
) -> Result<()>
where
N: NodeBuilderHelper<Payload = I>,
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
// Note: this future is quite large so we box it
Box::pin(self.apply_with_import_(env, rlp_path)).await
Box::pin(self.apply_with_import_::<N>(env, rlp_path)).await
}
/// Apply setup using pre-imported chain data from RLP file
async fn apply_with_import_(
async fn apply_with_import_<N>(
&mut self,
env: &mut Environment<I>,
rlp_path: &Path,
) -> Result<()> {
) -> Result<()>
where
N: NodeBuilderHelper<Payload = I>,
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
// Create nodes with imported chain data
let import_result = self.create_nodes_with_import(rlp_path).await?;
let import_result = self.create_nodes_with_import::<N>(rlp_path).await?;
// Extract node clients
let mut node_clients = Vec::new();
@@ -173,6 +186,9 @@ where
pub async fn apply<N>(&mut self, env: &mut Environment<I>) -> Result<()>
where
N: NodeBuilderHelper<Payload = I>,
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
// Note: this future is quite large so we box it
Box::pin(self.apply_::<N>(env)).await
@@ -182,6 +198,9 @@ where
async fn apply_<N>(&mut self, env: &mut Environment<I>) -> Result<()>
where
N: NodeBuilderHelper<Payload = I>,
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
// If import_rlp_path is set, use apply_with_import instead
if let Some(rlp_path) = self.import_rlp_path.take() {
@@ -240,10 +259,16 @@ where
/// Note: Currently this only supports `EthereumNode` due to the import process
/// being Ethereum-specific. The generic parameter N is kept for consistency
/// with other methods but is not used.
async fn create_nodes_with_import(
async fn create_nodes_with_import<N>(
&self,
rlp_path: &Path,
) -> Result<crate::setup_import::ChainImportResult> {
) -> Result<crate::setup_import::ChainImportResult>
where
N: NodeBuilderHelper<Payload = I>,
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
let chain_spec =
self.chain_spec.clone().ok_or_else(|| eyre!("Chain specification is required"))?;
@@ -276,6 +301,9 @@ where
+ use<N, I>
where
N: NodeBuilderHelper<Payload = I>,
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
>,
{
move |timestamp| {
let attributes = PayloadAttributes {

View File

@@ -278,7 +278,7 @@ where
let bundle_state_sorted = sort_bundle_state_for_comparison(re_executed_state);
let output_state_sorted = sort_bundle_state_for_comparison(original_state);
let filename = format!("{}.bundle_state.diff", block_prefix);
let diff_path = self.save_diff(filename, &output_state_sorted, &bundle_state_sorted)?;
let diff_path = self.save_diff(filename, &bundle_state_sorted, &output_state_sorted)?;
warn!(
target: "engine::invalid_block_hooks::witness",
@@ -308,13 +308,13 @@ where
if let Some((original_updates, original_root)) = trie_updates {
if re_executed_root != original_root {
let filename = format!("{}.state_root.diff", block_prefix);
let diff_path = self.save_diff(filename, &original_root, &re_executed_root)?;
let diff_path = self.save_diff(filename, &re_executed_root, &original_root)?;
warn!(target: "engine::invalid_block_hooks::witness", ?original_root, ?re_executed_root, diff_path = %diff_path.display(), "State root mismatch after re-execution");
}
if re_executed_root != block.state_root() {
let filename = format!("{}.header_state_root.diff", block_prefix);
let diff_path = self.save_diff(filename, &block.state_root(), &re_executed_root)?;
let diff_path = self.save_diff(filename, &re_executed_root, &block.state_root())?;
warn!(target: "engine::invalid_block_hooks::witness", header_state_root=?block.state_root(), ?re_executed_root, diff_path = %diff_path.display(), "Re-executed state root does not match block state root");
}

View File

@@ -15,7 +15,6 @@ reth-engine-primitives = { workspace = true, features = ["std"] }
reth-ethereum-engine-primitives.workspace = true
reth-payload-builder.workspace = true
reth-payload-primitives.workspace = true
reth-primitives-traits.workspace = true
reth-storage-api.workspace = true
reth-transaction-pool.workspace = true
@@ -44,5 +43,4 @@ op = [
"dep:op-alloy-rpc-types-engine",
"dep:reth-optimism-chainspec",
"reth-payload-primitives/op",
"reth-primitives-traits/op",
]

View File

@@ -1,5 +1,6 @@
//! Contains the implementation of the mining mode for the local engine.
use alloy_consensus::BlockHeader;
use alloy_primitives::{TxHash, B256};
use alloy_rpc_types_engine::ForkchoiceState;
use eyre::OptionExt;
@@ -9,7 +10,6 @@ use reth_payload_builder::PayloadBuilderHandle;
use reth_payload_primitives::{
BuiltPayload, EngineApiMessageVersion, PayloadAttributesBuilder, PayloadKind, PayloadTypes,
};
use reth_primitives_traits::{HeaderTy, SealedHeaderFor};
use reth_storage_api::BlockReader;
use reth_transaction_pool::TransactionPool;
use std::{
@@ -17,7 +17,7 @@ use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
time::Duration,
time::{Duration, UNIX_EPOCH},
};
use tokio::time::Interval;
use tokio_stream::wrappers::ReceiverStream;
@@ -106,8 +106,8 @@ pub struct LocalMiner<T: PayloadTypes, B, Pool: TransactionPool + Unpin> {
mode: MiningMode<Pool>,
/// The payload builder for the engine
payload_builder: PayloadBuilderHandle<T>,
/// Latest block in the chain so far.
last_header: SealedHeaderFor<<T::BuiltPayload as BuiltPayload>::Primitives>,
/// Timestamp for the next block.
last_timestamp: u64,
/// Stores latest mined blocks.
last_block_hashes: VecDeque<B256>,
}
@@ -115,21 +115,18 @@ pub struct LocalMiner<T: PayloadTypes, B, Pool: TransactionPool + Unpin> {
impl<T, B, Pool> LocalMiner<T, B, Pool>
where
T: PayloadTypes,
B: PayloadAttributesBuilder<
T::PayloadAttributes,
HeaderTy<<T::BuiltPayload as BuiltPayload>::Primitives>,
>,
B: PayloadAttributesBuilder<<T as PayloadTypes>::PayloadAttributes>,
Pool: TransactionPool + Unpin,
{
/// Spawns a new [`LocalMiner`] with the given parameters.
pub fn new(
provider: impl BlockReader<Header = HeaderTy<<T::BuiltPayload as BuiltPayload>::Primitives>>,
provider: impl BlockReader,
payload_attributes_builder: B,
to_engine: ConsensusEngineHandle<T>,
mode: MiningMode<Pool>,
payload_builder: PayloadBuilderHandle<T>,
) -> Self {
let last_header =
let latest_header =
provider.sealed_header(provider.best_block_number().unwrap()).unwrap().unwrap();
Self {
@@ -137,8 +134,8 @@ where
to_engine,
mode,
payload_builder,
last_block_hashes: VecDeque::from([last_header.hash()]),
last_header,
last_timestamp: latest_header.timestamp(),
last_block_hashes: VecDeque::from([latest_header.hash()]),
}
}
@@ -196,11 +193,19 @@ where
/// Generates payload attributes for a new block, passes them to FCU and inserts built payload
/// through newPayload.
async fn advance(&mut self) -> eyre::Result<()> {
let timestamp = std::cmp::max(
self.last_timestamp.saturating_add(1),
std::time::SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("cannot be earlier than UNIX_EPOCH")
.as_secs(),
);
let res = self
.to_engine
.fork_choice_updated(
self.forkchoice_state(),
Some(self.payload_attributes_builder.build(&self.last_header)),
Some(self.payload_attributes_builder.build(timestamp)),
EngineApiMessageVersion::default(),
)
.await?;
@@ -217,7 +222,8 @@ where
eyre::bail!("No payload")
};
let header = payload.block().sealed_header().clone();
let block = payload.block();
let payload = T::block_to_payload(payload.block().clone());
let res = self.to_engine.new_payload(payload).await?;
@@ -225,8 +231,8 @@ where
eyre::bail!("Invalid payload")
}
self.last_block_hashes.push_back(header.hash());
self.last_header = header;
self.last_timestamp = timestamp;
self.last_block_hashes.push_back(block.hash());
// ensure we keep at most 64 blocks
if self.last_block_hashes.len() > 64 {
self.last_block_hashes.pop_front();

View File

@@ -1,12 +1,10 @@
//! The implementation of the [`PayloadAttributesBuilder`] for the
//! [`LocalMiner`](super::LocalMiner).
use alloy_consensus::BlockHeader;
use alloy_primitives::{Address, B256};
use reth_chainspec::{EthChainSpec, EthereumHardforks};
use reth_chainspec::EthereumHardforks;
use reth_ethereum_engine_primitives::EthPayloadAttributes;
use reth_payload_primitives::PayloadAttributesBuilder;
use reth_primitives_traits::SealedHeader;
use std::sync::Arc;
/// The attributes builder for local Ethereum payload.
@@ -15,36 +13,21 @@ use std::sync::Arc;
pub struct LocalPayloadAttributesBuilder<ChainSpec> {
/// The chainspec
pub chain_spec: Arc<ChainSpec>,
/// Whether to enforce increasing timestamp.
pub enforce_increasing_timestamp: bool,
}
impl<ChainSpec> LocalPayloadAttributesBuilder<ChainSpec> {
/// Creates a new instance of the builder.
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
Self { chain_spec, enforce_increasing_timestamp: true }
}
/// Creates a new instance of the builder without enforcing increasing timestamps.
pub fn without_increasing_timestamp(self) -> Self {
Self { enforce_increasing_timestamp: false, ..self }
Self { chain_spec }
}
}
impl<ChainSpec> PayloadAttributesBuilder<EthPayloadAttributes, ChainSpec::Header>
impl<ChainSpec> PayloadAttributesBuilder<EthPayloadAttributes>
for LocalPayloadAttributesBuilder<ChainSpec>
where
ChainSpec: EthChainSpec + EthereumHardforks + 'static,
ChainSpec: Send + Sync + EthereumHardforks + 'static,
{
fn build(&self, parent: &SealedHeader<ChainSpec::Header>) -> EthPayloadAttributes {
let mut timestamp =
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
if self.enforce_increasing_timestamp {
timestamp = std::cmp::max(parent.timestamp().saturating_add(1), timestamp);
}
fn build(&self, timestamp: u64) -> EthPayloadAttributes {
EthPayloadAttributes {
timestamp,
prev_randao: B256::random(),
@@ -62,18 +45,14 @@ where
}
#[cfg(feature = "op")]
impl<ChainSpec>
PayloadAttributesBuilder<op_alloy_rpc_types_engine::OpPayloadAttributes, ChainSpec::Header>
impl<ChainSpec> PayloadAttributesBuilder<op_alloy_rpc_types_engine::OpPayloadAttributes>
for LocalPayloadAttributesBuilder<ChainSpec>
where
ChainSpec: EthChainSpec + EthereumHardforks + 'static,
ChainSpec: Send + Sync + EthereumHardforks + 'static,
{
fn build(
&self,
parent: &SealedHeader<ChainSpec::Header>,
) -> op_alloy_rpc_types_engine::OpPayloadAttributes {
fn build(&self, timestamp: u64) -> op_alloy_rpc_types_engine::OpPayloadAttributes {
op_alloy_rpc_types_engine::OpPayloadAttributes {
payload_attributes: self.build(parent),
payload_attributes: self.build(timestamp),
// Add dummy system transaction
transactions: Some(vec![
reth_optimism_chainspec::constants::TX_SET_L1_BLOCK_OP_MAINNET_BLOCK_124665056

View File

@@ -30,7 +30,7 @@ fn default_account_worker_count() -> usize {
}
/// The size of proof targets chunk to spawn in one multiproof calculation.
pub const DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE: usize = 60;
pub const DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE: usize = 10;
/// Default number of reserved CPU cores for non-reth processes.
///
@@ -97,7 +97,7 @@ pub struct TreeConfig {
state_provider_metrics: bool,
/// Cross-block cache size in bytes.
cross_block_cache_size: u64,
/// Whether the host has enough parallelism to run state root in parallel.
/// Whether the host has enough parallelism to run state root task.
has_enough_parallelism: bool,
/// Whether multiproof task should chunk proof targets.
multiproof_chunking_enabled: bool,
@@ -385,17 +385,12 @@ impl TreeConfig {
self
}
/// Setter for whether or not the host has enough parallelism to run state root in parallel.
/// Setter for has enough parallelism.
pub const fn with_has_enough_parallelism(mut self, has_enough_parallelism: bool) -> Self {
self.has_enough_parallelism = has_enough_parallelism;
self
}
/// Whether or not the host has enough parallelism to run state root in parallel.
pub const fn has_enough_parallelism(&self) -> bool {
self.has_enough_parallelism
}
/// Setter for state provider metrics.
pub const fn with_state_provider_metrics(mut self, state_provider_metrics: bool) -> Self {
self.state_provider_metrics = state_provider_metrics;

View File

@@ -4,6 +4,7 @@ use crate::ForkchoiceStatus;
use alloc::boxed::Box;
use alloy_consensus::BlockHeader;
use alloy_eips::BlockNumHash;
use alloy_primitives::B256;
use alloy_rpc_types_engine::ForkchoiceState;
use core::{
fmt::{Display, Formatter, Result},
@@ -32,6 +33,8 @@ pub enum ConsensusEngineEvent<N: NodePrimitives = EthPrimitives> {
CanonicalChainCommitted(Box<SealedHeader<N::BlockHeader>>, Duration),
/// The consensus engine processed an invalid block.
InvalidBlock(Box<SealedBlock<N::Block>>),
/// The consensus engine is involved in live sync, and has specific progress
LiveSyncProgress(ConsensusEngineLiveSyncProgress),
}
impl<N: NodePrimitives> ConsensusEngineEvent<N> {
@@ -70,9 +73,24 @@ where
Self::InvalidBlock(block) => {
write!(f, "InvalidBlock({:?})", block.num_hash())
}
Self::LiveSyncProgress(progress) => {
write!(f, "LiveSyncProgress({progress:?})")
}
Self::BlockReceived(num_hash) => {
write!(f, "BlockReceived({num_hash:?})")
}
}
}
}
/// Progress of the consensus engine during live sync.
#[derive(Clone, Debug)]
pub enum ConsensusEngineLiveSyncProgress {
/// The consensus engine is downloading blocks from the network.
DownloadingBlocks {
/// The number of blocks remaining to download.
remaining_blocks: u64,
/// The target block hash to download.
target: B256,
},
}

View File

@@ -17,7 +17,7 @@ use reth_payload_primitives::{
EngineApiMessageVersion, EngineObjectValidationError, InvalidPayloadAttributesError,
NewPayloadError, PayloadAttributes, PayloadOrAttributes, PayloadTypes,
};
use reth_primitives_traits::{Block, RecoveredBlock, SealedBlock};
use reth_primitives_traits::{Block, RecoveredBlock};
use reth_trie_common::HashedPostState;
use serde::{de::DeserializeOwned, Serialize};
@@ -131,21 +131,6 @@ pub trait PayloadValidator<Types: PayloadTypes>: Send + Sync + Unpin + 'static {
/// The block type used by the engine.
type Block: Block;
/// Converts the given payload into a sealed block without recovering signatures.
///
/// This function validates the payload and converts it into a [`SealedBlock`] which contains
/// the block hash but does not perform signature recovery on transactions.
///
/// This is more efficient than [`Self::ensure_well_formed_payload`] when signature recovery
/// is not needed immediately or will be performed later.
///
/// Implementers should ensure that the checks are done in the order that conforms with the
/// engine-API specification.
fn convert_payload_to_block(
&self,
payload: Types::ExecutionData,
) -> Result<SealedBlock<Self::Block>, NewPayloadError>;
/// Ensures that the given payload does not violate any consensus rules that concern the block's
/// layout.
///
@@ -157,10 +142,7 @@ pub trait PayloadValidator<Types: PayloadTypes>: Send + Sync + Unpin + 'static {
fn ensure_well_formed_payload(
&self,
payload: Types::ExecutionData,
) -> Result<RecoveredBlock<Self::Block>, NewPayloadError> {
let sealed_block = self.convert_payload_to_block(payload)?;
sealed_block.try_recover().map_err(|e| NewPayloadError::Other(e.into()))
}
) -> Result<RecoveredBlock<Self::Block>, NewPayloadError>;
/// Verifies payload post-execution w.r.t. hashed state updates.
fn validate_block_post_execution_with_hashed_state(

View File

@@ -229,15 +229,9 @@ fn bench_state_root(c: &mut Criterion) {
black_box({
let mut handle = payload_processor.spawn(
Default::default(),
(
core::iter::empty::<
Result<
Recovered<TransactionSigned>,
core::convert::Infallible,
>,
>(),
std::convert::identity,
),
core::iter::empty::<
Result<Recovered<TransactionSigned>, core::convert::Infallible>,
>(),
StateProviderBuilder::new(provider.clone(), genesis_hash, None),
OverlayStateProviderFactory::new(provider),
&TreeConfig::default(),

View File

@@ -9,7 +9,7 @@ use reth_network_p2p::{
full_block::{FetchFullBlockFuture, FetchFullBlockRangeFuture, FullBlockClient},
BlockClient,
};
use reth_primitives_traits::{Block, SealedBlock};
use reth_primitives_traits::{Block, RecoveredBlock, SealedBlock};
use std::{
cmp::{Ordering, Reverse},
collections::{binary_heap::PeekMut, BinaryHeap, HashSet, VecDeque},
@@ -44,7 +44,7 @@ pub enum DownloadAction {
#[derive(Debug)]
pub enum DownloadOutcome<B: Block> {
/// Downloaded blocks.
Blocks(Vec<SealedBlock<B>>),
Blocks(Vec<RecoveredBlock<B>>),
/// New download started.
NewDownloadStarted {
/// How many blocks are pending in this download.
@@ -68,7 +68,7 @@ where
inflight_block_range_requests: Vec<FetchFullBlockRangeFuture<Client>>,
/// Buffered blocks from downloads - this is a min-heap of blocks, using the block number for
/// ordering. This means the blocks will be popped from the heap with ascending block numbers.
set_buffered_blocks: BinaryHeap<Reverse<OrderedSealedBlock<B>>>,
set_buffered_blocks: BinaryHeap<Reverse<OrderedRecoveredBlock<B>>>,
/// Engine download metrics.
metrics: BlockDownloaderMetrics,
/// Pending events to be emitted.
@@ -226,8 +226,15 @@ where
let mut request = self.inflight_block_range_requests.swap_remove(idx);
if let Poll::Ready(blocks) = request.poll_unpin(cx) {
trace!(target: "engine::download", len=?blocks.len(), first=?blocks.first().map(|b| b.num_hash()), last=?blocks.last().map(|b| b.num_hash()), "Received full block range, buffering");
self.set_buffered_blocks
.extend(blocks.into_iter().map(OrderedSealedBlock).map(Reverse));
self.set_buffered_blocks.extend(
blocks
.into_iter()
.map(|b| {
let senders = b.senders().unwrap_or_default();
OrderedRecoveredBlock(RecoveredBlock::new_sealed(b, senders))
})
.map(Reverse),
);
} else {
// still pending
self.inflight_block_range_requests.push(request);
@@ -241,7 +248,8 @@ where
}
// drain all unique element of the block buffer if there are any
let mut downloaded_blocks = Vec::with_capacity(self.set_buffered_blocks.len());
let mut downloaded_blocks: Vec<RecoveredBlock<B>> =
Vec::with_capacity(self.set_buffered_blocks.len());
while let Some(block) = self.set_buffered_blocks.pop() {
// peek ahead and pop duplicates
while let Some(peek) = self.set_buffered_blocks.peek_mut() {
@@ -257,31 +265,32 @@ where
}
}
/// A wrapper type around [`SealedBlock`] that implements the [Ord]
/// A wrapper type around [`RecoveredBlock`] that implements the [Ord]
/// trait by block number.
#[derive(Debug, Clone, PartialEq, Eq)]
struct OrderedSealedBlock<B: Block>(SealedBlock<B>);
struct OrderedRecoveredBlock<B: Block>(RecoveredBlock<B>);
impl<B: Block> PartialOrd for OrderedSealedBlock<B> {
impl<B: Block> PartialOrd for OrderedRecoveredBlock<B> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<B: Block> Ord for OrderedSealedBlock<B> {
impl<B: Block> Ord for OrderedRecoveredBlock<B> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.number().cmp(&other.0.number())
}
}
impl<B: Block> From<SealedBlock<B>> for OrderedSealedBlock<B> {
impl<B: Block> From<SealedBlock<B>> for OrderedRecoveredBlock<B> {
fn from(block: SealedBlock<B>) -> Self {
Self(block)
let senders = block.senders().unwrap_or_default();
Self(RecoveredBlock::new_sealed(block, senders))
}
}
impl<B: Block> From<OrderedSealedBlock<B>> for SealedBlock<B> {
fn from(value: OrderedSealedBlock<B>) -> Self {
impl<B: Block> From<OrderedRecoveredBlock<B>> for RecoveredBlock<B> {
fn from(value: OrderedRecoveredBlock<B>) -> Self {
value.0
}
}

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