mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
98 Commits
wt-pr2b
...
opt/batch-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bcd6e03db | ||
|
|
11d9f38077 | ||
|
|
226ce14ca1 | ||
|
|
a6e1dea2d7 | ||
|
|
71ed68e944 | ||
|
|
adecbd7814 | ||
|
|
26a37f3c00 | ||
|
|
0bfa7fa5fa | ||
|
|
18bec10a0b | ||
|
|
1e33821e19 | ||
|
|
da92733be8 | ||
|
|
c41c8e6cae | ||
|
|
1ccc174e7b | ||
|
|
f1459fcf91 | ||
|
|
94235d64a8 | ||
|
|
7fe60017cf | ||
|
|
f9ec2fafa0 | ||
|
|
768a687189 | ||
|
|
b87cde5479 | ||
|
|
ab685579f0 | ||
|
|
c7faafd183 | ||
|
|
935a2cc056 | ||
|
|
507cf58db0 | ||
|
|
6cfd369d17 | ||
|
|
934f462d01 | ||
|
|
d4f28b02ff | ||
|
|
963bfeeeed | ||
|
|
adbe6d9da0 | ||
|
|
6d19c0ed8e | ||
|
|
4baf2baec4 | ||
|
|
0b5f79e8c9 | ||
|
|
afe164baca | ||
|
|
31fdbe914c | ||
|
|
6870747246 | ||
|
|
0ad8c772e1 | ||
|
|
5440d0d89a | ||
|
|
0eea4d76e9 | ||
|
|
8a1702cd74 | ||
|
|
7feb56d5f6 | ||
|
|
0aa922c4e8 | ||
|
|
ccff9a08f0 | ||
|
|
eb788cc7cf | ||
|
|
fb05a0654f | ||
|
|
d5a36dcc00 | ||
|
|
ffbef9e3cd | ||
|
|
820c112e8e | ||
|
|
9285f7eafc | ||
|
|
9a4c6d8a11 | ||
|
|
963c26550a | ||
|
|
3648483512 | ||
|
|
ab418642b4 | ||
|
|
decb56fae1 | ||
|
|
ee1ec8f9f0 | ||
|
|
d7bf87da52 | ||
|
|
dd0c6d279f | ||
|
|
c137ed836f | ||
|
|
a543752f7d | ||
|
|
b814893221 | ||
|
|
fcef82261d | ||
|
|
d3846d98a9 | ||
|
|
1f536cce65 | ||
|
|
0ddaf1b26c | ||
|
|
830cd5e355 | ||
|
|
f77d7d5983 | ||
|
|
a2237c534e | ||
|
|
1bd8fab887 | ||
|
|
22a68756c7 | ||
|
|
d99c0ffd62 | ||
|
|
ad476e2b5c | ||
|
|
6df249c1f1 | ||
|
|
5a076df09a | ||
|
|
f07629eac0 | ||
|
|
f643e93c35 | ||
|
|
653362a436 | ||
|
|
a02508600c | ||
|
|
937a7f226d | ||
|
|
a0df561117 | ||
|
|
be5a4ac7a6 | ||
|
|
0c854b6f14 | ||
|
|
28a31cd579 | ||
|
|
da12451c9c | ||
|
|
247ce3c4e9 | ||
|
|
bf43ebaa29 | ||
|
|
a01ecce73f | ||
|
|
3e55c6ca6e | ||
|
|
2ac7d719f3 | ||
|
|
965705ff88 | ||
|
|
ebe2ca1366 | ||
|
|
cc242f83fd | ||
|
|
12cf3d685b | ||
|
|
ad5b533ad1 | ||
|
|
118f15f345 | ||
|
|
97481f69e5 | ||
|
|
f692ac7d1e | ||
|
|
4b1c341ced | ||
|
|
865f8f8951 | ||
|
|
492fc20fd1 | ||
|
|
ad9886abb8 |
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -37,7 +37,7 @@ crates/storage/db/ @joshieDo
|
||||
crates/storage/errors/ @joshieDo
|
||||
crates/storage/libmdbx-rs/ @shekhirin
|
||||
crates/storage/nippy-jar/ @joshieDo @shekhirin
|
||||
crates/storage/provider/ @joshieDo @shekhirin
|
||||
crates/storage/provider/ @joshieDo @shekhirin @yongkangc
|
||||
crates/storage/storage-api/ @joshieDo
|
||||
crates/tasks/ @mattsse
|
||||
crates/tokio-util/ @mattsse
|
||||
|
||||
7
.github/workflows/check-alloy.yml
vendored
7
.github/workflows/check-alloy.yml
vendored
@@ -60,7 +60,6 @@ jobs:
|
||||
tail -50 Cargo.toml
|
||||
|
||||
- name: Check workspace
|
||||
run: cargo check --workspace --all-features
|
||||
|
||||
- name: Check Optimism
|
||||
run: cargo check -p reth-optimism-node --all-features
|
||||
run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
54
.github/workflows/docker-git.yml
vendored
54
.github/workflows/docker-git.yml
vendored
@@ -1,54 +0,0 @@
|
||||
# Publishes the Docker image, only to be used with `workflow_dispatch`. The
|
||||
# images from this workflow will be tagged with the git sha of the branch used
|
||||
# and will NOT tag it as `latest`.
|
||||
|
||||
name: docker-git
|
||||
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
|
||||
env:
|
||||
REPO_NAME: ${{ github.repository_owner }}/reth
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/reth
|
||||
OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth
|
||||
CARGO_TERM_COLOR: always
|
||||
DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth
|
||||
OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth
|
||||
DOCKER_USERNAME: ${{ github.actor }}
|
||||
GIT_SHA: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and push
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build:
|
||||
- name: 'Build and push the git-sha-tagged reth image'
|
||||
command: 'make PROFILE=maxperf GIT_SHA=$GIT_SHA docker-build-push-git-sha'
|
||||
- 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: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Install cross main
|
||||
id: cross_main
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross
|
||||
- name: Log in to Docker
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
- name: Set up Docker builder
|
||||
run: |
|
||||
docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64
|
||||
docker buildx create --use --name cross-builder
|
||||
- name: Build and push ${{ matrix.build.name }}
|
||||
run: ${{ matrix.build.command }}
|
||||
65
.github/workflows/docker-nightly.yml
vendored
65
.github/workflows/docker-nightly.yml
vendored
@@ -1,65 +0,0 @@
|
||||
# Publishes the nightly Docker image.
|
||||
|
||||
name: docker-nightly
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
env:
|
||||
REPO_NAME: ${{ github.repository_owner }}/reth
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/reth
|
||||
OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth
|
||||
CARGO_TERM_COLOR: always
|
||||
DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth
|
||||
OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth
|
||||
DOCKER_USERNAME: ${{ github.actor }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: build and push
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build:
|
||||
- name: 'Build and push the nightly reth image'
|
||||
command: 'make PROFILE=maxperf docker-build-push-nightly'
|
||||
- name: 'Build and push the nightly edge profiling reth image'
|
||||
command: 'make PROFILE=profiling docker-build-push-nightly-edge-profiling'
|
||||
- name: 'Build and push the nightly profiling reth image'
|
||||
command: 'make PROFILE=profiling docker-build-push-nightly-profiling'
|
||||
- name: 'Build and push the nightly op-reth image'
|
||||
command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push-nightly'
|
||||
- name: 'Build and push the nightly edge 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-edge-profiling'
|
||||
- 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
|
||||
- name: Remove bloatware
|
||||
uses: laverdet/remove-bloatware@v1.0.0
|
||||
with:
|
||||
docker: true
|
||||
lang: rust
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Install cross main
|
||||
id: cross_main
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross
|
||||
- name: Log in to Docker
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
- name: Set up Docker builder
|
||||
run: |
|
||||
docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64
|
||||
docker buildx create --use --name cross-builder
|
||||
- name: Build and push ${{ matrix.build.name }}
|
||||
run: ${{ matrix.build.command }}
|
||||
155
.github/workflows/docker.yml
vendored
155
.github/workflows/docker.yml
vendored
@@ -1,4 +1,9 @@
|
||||
# Publishes the Docker image.
|
||||
# Publishes Docker images.
|
||||
#
|
||||
# Triggers:
|
||||
# - Push tag v*: builds release (RC or latest)
|
||||
# - Schedule: builds nightly + profiling
|
||||
# - Manual: builds git-sha or nightly
|
||||
|
||||
name: docker
|
||||
|
||||
@@ -6,84 +11,94 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
|
||||
env:
|
||||
IMAGE_NAME: ${{ github.repository_owner }}/reth
|
||||
OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth
|
||||
CARGO_TERM_COLOR: always
|
||||
DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth
|
||||
OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth
|
||||
DOCKER_USERNAME: ${{ github.actor }}
|
||||
schedule:
|
||||
- cron: "0 1 * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_type:
|
||||
description: "Build type"
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- git-sha
|
||||
- nightly
|
||||
default: git-sha
|
||||
dry_run:
|
||||
description: "Skip pushing images (dry run)"
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
build-rc:
|
||||
if: contains(github.ref, '-rc')
|
||||
name: build and push as release candidate
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build:
|
||||
- name: "Build and push reth image"
|
||||
command: "make IMAGE_NAME=$IMAGE_NAME DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME PROFILE=maxperf docker-build-push"
|
||||
- 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: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Install cross main
|
||||
id: cross_main
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross
|
||||
- name: Log in to Docker
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
- name: Set up Docker builder
|
||||
run: |
|
||||
docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64
|
||||
docker buildx create --use --name cross-builder
|
||||
- name: Build and push ${{ matrix.build.name }}
|
||||
run: ${{ matrix.build.command }}
|
||||
|
||||
build:
|
||||
if: ${{ !contains(github.ref, '-rc') }}
|
||||
name: build and push as latest
|
||||
name: Build Docker images
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
build:
|
||||
- name: "Build and push reth image"
|
||||
command: "make IMAGE_NAME=$IMAGE_NAME DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME PROFILE=maxperf docker-build-push-latest"
|
||||
- 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"
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Install cross main
|
||||
id: cross_main
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get git info for vergen
|
||||
id: git
|
||||
run: |
|
||||
cargo install cross --git https://github.com/cross-rs/cross
|
||||
- name: Log in to Docker
|
||||
echo "sha=${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||
echo "describe=$(git describe --always --tags)" >> "$GITHUB_OUTPUT"
|
||||
echo "dirty=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Determine build parameters
|
||||
id: params
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
- name: Set up Docker builder
|
||||
run: |
|
||||
docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64
|
||||
docker buildx create --use --name cross-builder
|
||||
- name: Build and push ${{ matrix.build.name }}
|
||||
run: ${{ matrix.build.command }}
|
||||
REGISTRY="ghcr.io/${{ github.repository_owner }}"
|
||||
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "targets=ethereum optimism" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Add 'latest' tag for non-RC releases
|
||||
if [[ ! "$VERSION" =~ -rc ]]; then
|
||||
echo "ethereum_tags=${REGISTRY}/reth:${VERSION},${REGISTRY}/reth:latest" >> "$GITHUB_OUTPUT"
|
||||
echo "optimism_tags=${REGISTRY}/op-reth:${VERSION},${REGISTRY}/op-reth:latest" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "ethereum_tags=${REGISTRY}/reth:${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "optimism_tags=${REGISTRY}/op-reth:${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]] || [[ "${{ inputs.build_type }}" == "nightly" ]]; then
|
||||
echo "targets=nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "ethereum_tags=${REGISTRY}/reth:nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "optimism_tags=${REGISTRY}/op-reth:nightly" >> "$GITHUB_OUTPUT"
|
||||
|
||||
else
|
||||
# git-sha build
|
||||
echo "targets=ethereum optimism" >> "$GITHUB_OUTPUT"
|
||||
echo "ethereum_tags=${REGISTRY}/reth:${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||
echo "optimism_tags=${REGISTRY}/op-reth:${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Build and push images
|
||||
uses: depot/bake-action@v1
|
||||
env:
|
||||
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
|
||||
VERGEN_GIT_DESCRIBE: ${{ steps.git.outputs.describe }}
|
||||
VERGEN_GIT_DIRTY: ${{ steps.git.outputs.dirty }}
|
||||
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
||||
with:
|
||||
project: ${{ vars.DEPOT_PROJECT_ID }}
|
||||
files: docker-bake.hcl
|
||||
targets: ${{ steps.params.outputs.targets }}
|
||||
push: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}
|
||||
set: |
|
||||
ethereum.tags=${{ steps.params.outputs.ethereum_tags }}
|
||||
optimism.tags=${{ steps.params.outputs.optimism_tags }}
|
||||
|
||||
449
Cargo.lock
generated
449
Cargo.lock
generated
@@ -106,9 +106,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "alloy-chains"
|
||||
version = "0.2.28"
|
||||
version = "0.2.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3842d8c52fcd3378039f4703dba392dca8b546b1c8ed6183048f8dab95b2be78"
|
||||
checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
@@ -121,9 +121,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-consensus"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c3a590d13de3944675987394715f37537b50b856e3b23a0e66e97d963edbf38"
|
||||
checksum = "ed1958f0294ecc05ebe7b3c9a8662a3e221c2523b7f2bcd94c7a651efbd510bf"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -149,9 +149,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-consensus-any"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f28f769d5ea999f0d8a105e434f483456a15b4e1fcb08edbbbe1650a497ff6d"
|
||||
checksum = "f752e99497ddc39e22d547d7dfe516af10c979405a034ed90e69b914b7dddeae"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -164,9 +164,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-contract"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990fa65cd132a99d3c3795a82b9f93ec82b81c7de3bab0bf26ca5c73286f7186"
|
||||
checksum = "f2140796bc79150b1b7375daeab99750f0ff5e27b1f8b0aa81ccde229c7f02a2"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-dyn-abi",
|
||||
@@ -249,25 +249,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-eip7928"
|
||||
version = "0.3.0"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6adac476434bf024279164dcdca299309f0c7d1e3557024eb7a83f8d9d01c6b5"
|
||||
checksum = "d3231de68d5d6e75332b7489cfcc7f4dfabeba94d990a10e4b923af0e6623540"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
"arbitrary",
|
||||
"borsh",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-eips"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09535cbc646b0e0c6fcc12b7597eaed12cf86dff4c4fba9507a61e71b94f30eb"
|
||||
checksum = "813a67f87e56b38554d18b182616ee5006e8e2bf9df96a0df8bf29dff1d52e3f"
|
||||
dependencies = [
|
||||
"alloy-eip2124",
|
||||
"alloy-eip2930",
|
||||
"alloy-eip7702",
|
||||
"alloy-eip7928",
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
"alloy-serde",
|
||||
@@ -287,9 +289,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-evm"
|
||||
version = "0.26.3"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a96827207397445a919a8adc49289b53cc74e48e460411740bba31cec2fc307d"
|
||||
checksum = "1582933a9fc27c0953220eb4f18f6492ff577822e9a8d848890ff59f6b4f5beb"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -309,9 +311,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-genesis"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1005520ccf89fa3d755e46c1d992a9e795466c2e7921be2145ef1f749c5727de"
|
||||
checksum = "05864eef929c4d28895ae4b4d8ac9c6753c4df66e873b9c8fafc8089b59c1502"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -350,9 +352,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-json-rpc"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b626409c98ba43aaaa558361bca21440c88fd30df7542c7484b9c7a1489cdb"
|
||||
checksum = "d2dd146b3de349a6ffaa4e4e319ab3a90371fb159fb0bddeb1c7bbe8b1792eff"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-sol-types",
|
||||
@@ -365,9 +367,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-network"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89924fdcfeee0e0fa42b1f10af42f92802b5d16be614a70897382565663bf7cf"
|
||||
checksum = "8c12278ffbb8872dfba3b2f17d8ea5e8503c2df5155d9bc5ee342794bde505c3"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-consensus-any",
|
||||
@@ -391,9 +393,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-network-primitives"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f0dbe56ff50065713ff8635d8712a0895db3ad7f209db9793ad8fcb6b1734aa"
|
||||
checksum = "833037c04917bc2031541a60e8249e4ab5500e24c637c1c62e95e963a655d66f"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -404,9 +406,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-op-evm"
|
||||
version = "0.26.3"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54dc5c46a92fc7267055a174d30efb34e2599a0047102a4d38a025ae521435ba"
|
||||
checksum = "6f19214adae08ea95600c3ede76bcbf0c40b36a263534a8f441a4c732f60e868"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -467,9 +469,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-provider"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b56f7a77513308a21a2ba0e9d57785a9d9d2d609e77f4e71a78a1192b83ff2d"
|
||||
checksum = "eafa840b0afe01c889a3012bb2fde770a544f74eab2e2870303eb0a5fb869c48"
|
||||
dependencies = [
|
||||
"alloy-chains",
|
||||
"alloy-consensus",
|
||||
@@ -493,7 +495,7 @@ dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"auto_impl",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"either",
|
||||
"futures",
|
||||
"futures-utils-wasm",
|
||||
@@ -512,9 +514,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-pubsub"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94813abbd7baa30c700ea02e7f92319dbcb03bff77aeea92a3a9af7ba19c5c70"
|
||||
checksum = "57b3a3b3e4efc9f4d30e3326b6bd6811231d16ef94837e18a802b44ca55119e6"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-primitives",
|
||||
@@ -556,9 +558,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-client"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff01723afc25ec4c5b04de399155bef7b6a96dfde2475492b1b7b4e7a4f46445"
|
||||
checksum = "12768ae6303ec764905a8a7cd472aea9072f9f9c980d18151e26913da8ae0123"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-primitives",
|
||||
@@ -582,9 +584,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91bf006bb06b7d812591b6ac33395cb92f46c6a65cda11ee30b348338214f0f"
|
||||
checksum = "0622d8bcac2f16727590aa33f4c3f05ea98130e7e4b4924bce8be85da5ad0dae"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-engine",
|
||||
@@ -595,9 +597,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-admin"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b934c3bcdc6617563b45deb36a40881c8230b94d0546ea739dff7edb3aa2f6fd"
|
||||
checksum = "c38c5ac70457ecc74e87fe1a5a19f936419224ded0eb0636241452412ca92733"
|
||||
dependencies = [
|
||||
"alloy-genesis",
|
||||
"alloy-primitives",
|
||||
@@ -607,9 +609,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-anvil"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e82145856df8abb1fefabef58cdec0f7d9abf337d4abd50c1ed7e581634acdd"
|
||||
checksum = "ae8eb0e5d6c48941b61ab76fabab4af66f7d88309a98aa14ad3dec7911c1eba3"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -619,9 +621,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-any"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212ca1c1dab27f531d3858f8b1a2d6bfb2da664be0c1083971078eb7b71abe4b"
|
||||
checksum = "a1cf5a093e437dfd62df48e480f24e1a3807632358aad6816d7a52875f1c04aa"
|
||||
dependencies = [
|
||||
"alloy-consensus-any",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -630,9 +632,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-beacon"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d92a9b4b268fac505ef7fb1dac9bb129d4fd7de7753f22a5b6e9f666f7f7de6"
|
||||
checksum = "e07949e912479ef3b848e1cf8db54b534bdd7bc58e6c23f28ea9488960990c8c"
|
||||
dependencies = [
|
||||
"alloy-eips",
|
||||
"alloy-primitives",
|
||||
@@ -650,9 +652,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-debug"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab1ebed118b701c497e6541d2d11dfa6f3c6ae31a3c52999daa802fcdcc16b7"
|
||||
checksum = "925ff0f48c2169c050f0ae7a82769bdf3f45723d6742ebb6a5efb4ed2f491b26"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"derive_more",
|
||||
@@ -662,9 +664,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-engine"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "232f00fcbcd3ee3b9399b96223a8fc884d17742a70a44f9d7cef275f93e6e872"
|
||||
checksum = "336ef381c7409f23c69f6e79bddc1917b6e832cff23e7a5cf84b9381d53582e6"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -683,9 +685,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-eth"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5715d0bf7efbd360873518bd9f6595762136b5327a9b759a8c42ccd9b5e44945"
|
||||
checksum = "28e97603095020543a019ab133e0e3dc38cd0819f19f19bdd70c642404a54751"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-consensus-any",
|
||||
@@ -705,9 +707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-mev"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7b61941d2add2ee64646612d3eda92cbbde8e6c933489760b6222c8898c79be"
|
||||
checksum = "2805153975e25d38e37ee100880e642d5b24e421ed3014a7d2dae1d9be77562e"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -720,9 +722,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-trace"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9763cc931a28682bd4b9a68af90057b0fbe80e2538a82251afd69d7ae00bbebf"
|
||||
checksum = "f1aec4e1c66505d067933ea1a949a4fb60a19c4cfc2f109aa65873ea99e62ea8"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -734,9 +736,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-rpc-types-txpool"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "359a8caaa98cb49eed62d03f5bc511dd6dd5dee292238e8627a6e5690156df0f"
|
||||
checksum = "25b73c1d6e4f1737a20d246dad5a0abd6c1b76ec4c3d153684ef8c6f1b6bb4f4"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -746,9 +748,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-serde"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ed8531cae8d21ee1c6571d0995f8c9f0652a6ef6452fde369283edea6ab7138"
|
||||
checksum = "946a0d413dbb5cd9adba0de5f8a1a34d5b77deda9b69c1d7feed8fc875a1aa26"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"arbitrary",
|
||||
@@ -758,9 +760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-signer"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb10ccd49d0248df51063fce6b716f68a315dd912d55b32178c883fd48b4021d"
|
||||
checksum = "2f7481dc8316768f042495eaf305d450c32defbc9bce09d8bf28afcd956895bb"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"async-trait",
|
||||
@@ -773,9 +775,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-signer-local"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4d992d44e6c414ece580294abbadb50e74cfd4eaa69787350a4dfd4b20eaa1b"
|
||||
checksum = "1259dac1f534a4c66c1d65237c89915d0010a2a91d6c3b0bada24dc5ee0fb917"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-network",
|
||||
@@ -862,9 +864,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f50a9516736d22dd834cc2240e5bf264f338667cc1d9e514b55ec5a78b987ca"
|
||||
checksum = "78f169b85eb9334871db986e7eaf59c58a03d86a30cc68b846573d47ed0656bb"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"auto_impl",
|
||||
@@ -885,9 +887,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport-http"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a18b541a6197cf9a084481498a766fdf32fefda0c35ea6096df7d511025e9f1"
|
||||
checksum = "019821102e70603e2c141954418255bec539ef64ac4117f8e84fb493769acf73"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-transport",
|
||||
@@ -900,9 +902,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport-ipc"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8075911680ebc537578cacf9453464fd394822a0f68614884a9c63f9fbaf5e89"
|
||||
checksum = "e574ca2f490fb5961d2cdd78188897392c46615cd88b35c202d34bbc31571a81"
|
||||
dependencies = [
|
||||
"alloy-json-rpc",
|
||||
"alloy-pubsub",
|
||||
@@ -920,9 +922,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-transport-ws"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "921d37a57e2975e5215f7dd0f28873ed5407c7af630d4831a4b5c737de4b0b8b"
|
||||
checksum = "b92dea6996269769f74ae56475570e3586910661e037b7b52d50c9641f76c68f"
|
||||
dependencies = [
|
||||
"alloy-pubsub",
|
||||
"alloy-transport",
|
||||
@@ -957,9 +959,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-tx-macros"
|
||||
version = "1.4.3"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2289a842d02fe63f8c466db964168bb2c7a9fdfb7b24816dbb17d45520575fb"
|
||||
checksum = "45ceac797eb8a56bdf5ab1fab353072c17d472eab87645ca847afe720db3246d"
|
||||
dependencies = [
|
||||
"darling 0.21.3",
|
||||
"proc-macro2",
|
||||
@@ -1772,7 +1774,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"cow-utils",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"dynify",
|
||||
"fast-float2",
|
||||
"float16",
|
||||
@@ -1966,12 +1968,6 @@ version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d"
|
||||
|
||||
[[package]]
|
||||
name = "bytecount"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.24.0"
|
||||
@@ -2061,19 +2057,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform 0.1.9",
|
||||
"semver 1.0.27",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.19.2"
|
||||
@@ -2125,9 +2108,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.53"
|
||||
version = "1.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932"
|
||||
checksum = "6354c81bbfd62d9cfa9cb3c773c2b7b2a3a482d569de977fd0e961f6e7c00583"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
@@ -2928,19 +2911,6 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "6.1.0"
|
||||
@@ -3493,15 +3463,6 @@ dependencies = [
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum_hashing"
|
||||
version = "0.7.0"
|
||||
@@ -4080,11 +4041,12 @@ checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db"
|
||||
|
||||
[[package]]
|
||||
name = "fixed-cache"
|
||||
version = "0.1.5"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25d3af83468398d500e9bc19e001812dcb1a11e4d3d6a5956c789aa3c11a8cb5"
|
||||
checksum = "0aaafa7294e9617eb29e5c684a3af33324ef512a1bf596af2d1938a03798da29"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"typeid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4849,7 +4811,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -5591,9 +5553,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-identity"
|
||||
@@ -5965,21 +5927,6 @@ dependencies = [
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mini-moka"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-utils",
|
||||
"dashmap 5.5.3",
|
||||
"skeptic",
|
||||
"smallvec",
|
||||
"tagptr",
|
||||
"triomphe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
@@ -6206,9 +6153,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
@@ -6513,9 +6460,9 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391"
|
||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry"
|
||||
@@ -7020,9 +6967,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.105"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -7170,17 +7117,6 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"memchr",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.6"
|
||||
@@ -7224,7 +7160,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@@ -7261,16 +7197,16 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.43"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a"
|
||||
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -7907,6 +7843,7 @@ dependencies = [
|
||||
"reth-stages-types",
|
||||
"reth-static-file",
|
||||
"reth-static-file-types",
|
||||
"reth-storage-api",
|
||||
"reth-tasks",
|
||||
"reth-trie",
|
||||
"reth-trie-common",
|
||||
@@ -8462,13 +8399,13 @@ dependencies = [
|
||||
"assert_matches",
|
||||
"codspeed-criterion-compat",
|
||||
"crossbeam-channel",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"eyre",
|
||||
"fixed-cache",
|
||||
"futures",
|
||||
"metrics",
|
||||
"metrics-util",
|
||||
"mini-moka",
|
||||
"moka",
|
||||
"parking_lot",
|
||||
"proptest",
|
||||
@@ -9108,7 +9045,7 @@ dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"byteorder",
|
||||
"codspeed-criterion-compat",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"parking_lot",
|
||||
"rand 0.9.2",
|
||||
@@ -9193,6 +9130,7 @@ dependencies = [
|
||||
"reth-eth-wire-types",
|
||||
"reth-ethereum-forks",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-evm-ethereum",
|
||||
"reth-fs-util",
|
||||
"reth-metrics",
|
||||
"reth-net-banlist",
|
||||
@@ -10060,6 +9998,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"reth-chain-state",
|
||||
"reth-chainspec",
|
||||
"reth-evm",
|
||||
"reth-metrics",
|
||||
"reth-optimism-chainspec",
|
||||
"reth-optimism-evm",
|
||||
@@ -10215,7 +10154,7 @@ dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-engine",
|
||||
"assert_matches",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"eyre",
|
||||
"itertools 0.14.0",
|
||||
"metrics",
|
||||
@@ -10278,6 +10217,7 @@ dependencies = [
|
||||
"reth-stages",
|
||||
"reth-stages-types",
|
||||
"reth-static-file-types",
|
||||
"reth-storage-api",
|
||||
"reth-testing-utils",
|
||||
"reth-tokio-util",
|
||||
"reth-tracing",
|
||||
@@ -11114,6 +11054,8 @@ dependencies = [
|
||||
"reth-chainspec",
|
||||
"reth-eth-wire-types",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-evm",
|
||||
"reth-evm-ethereum",
|
||||
"reth-execution-types",
|
||||
"reth-fs-util",
|
||||
"reth-metrics",
|
||||
@@ -11122,6 +11064,7 @@ dependencies = [
|
||||
"reth-storage-api",
|
||||
"reth-tasks",
|
||||
"reth-tracing",
|
||||
"revm",
|
||||
"revm-interpreter",
|
||||
"revm-primitives",
|
||||
"rustc-hash",
|
||||
@@ -11242,7 +11185,7 @@ dependencies = [
|
||||
"alloy-rlp",
|
||||
"codspeed-criterion-compat",
|
||||
"crossbeam-channel",
|
||||
"dashmap 6.1.0",
|
||||
"dashmap",
|
||||
"derive_more",
|
||||
"itertools 0.14.0",
|
||||
"metrics",
|
||||
@@ -11465,9 +11408,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-inspectors"
|
||||
version = "0.34.0"
|
||||
version = "0.34.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a1ce3f52a052d78cc251714d57bf05dc8bc75e269677de11805d3153300a2cd"
|
||||
checksum = "a24ca988ae1f7a0bb5688630579c00e867cd9f1df0a2f040623887f63d3b414c"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -12378,21 +12321,6 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "skeptic"
|
||||
version = "0.13.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"cargo_metadata 0.14.2",
|
||||
"error-chain",
|
||||
"glob",
|
||||
"pulldown-cmark",
|
||||
"tempfile",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sketches-ddsketch"
|
||||
version = "0.3.0"
|
||||
@@ -12461,9 +12389,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881"
|
||||
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
@@ -12864,9 +12792,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.45"
|
||||
version = "0.3.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
|
||||
checksum = "9da98b7d9b7dad93488a84b8248efc35352b0b2657397d4167e7ad67e5d535e5"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -12882,15 +12810,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.7"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
|
||||
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.25"
|
||||
version = "0.2.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd"
|
||||
checksum = "78cc610bac2dcee56805c99642447d4c5dbde4d01f752ffea0199aee1f601dc4"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -12953,7 +12881,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.6.1",
|
||||
"socket2 0.6.2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
@@ -13433,12 +13361,6 @@ dependencies = [
|
||||
"rlp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "triomphe"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39"
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
@@ -13464,6 +13386,12 @@ dependencies = [
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.19.0"
|
||||
@@ -13620,9 +13548,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
|
||||
checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f"
|
||||
dependencies = [
|
||||
"getrandom 0.3.4",
|
||||
"js-sys",
|
||||
@@ -14637,138 +14565,3 @@ dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-consensus"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-contract"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-eips"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-genesis"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-json-rpc"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-network"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-network-primitives"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-provider"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-pubsub"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-client"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-admin"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-anvil"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-beacon"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-debug"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-engine"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-eth"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-mev"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-trace"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-rpc-types-txpool"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-serde"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-signer"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-signer-local"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-transport"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-transport-http"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-transport-ipc"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
[[patch.unused]]
|
||||
name = "alloy-transport-ws"
|
||||
version = "1.5.1"
|
||||
source = "git+https://github.com/alloy-rs/alloy?branch=main#05fd66e6f05399b71dfc9c802e6ee182b19e8575"
|
||||
|
||||
97
Cargo.toml
97
Cargo.toml
@@ -481,14 +481,14 @@ revm-primitives = { version = "22.0.0", default-features = false }
|
||||
revm-interpreter = { version = "32.0.0", default-features = false }
|
||||
revm-database-interface = { version = "9.0.0", default-features = false }
|
||||
op-revm = { version = "15.0.0", default-features = false }
|
||||
revm-inspectors = "0.34.0"
|
||||
revm-inspectors = "0.34.1"
|
||||
|
||||
# eth
|
||||
alloy-chains = { version = "0.2.5", default-features = false }
|
||||
alloy-dyn-abi = "1.4.3"
|
||||
alloy-dyn-abi = "1.5.2"
|
||||
alloy-eip2124 = { version = "0.2.0", default-features = false }
|
||||
alloy-eip7928 = { version = "0.3.0", default-features = false }
|
||||
alloy-evm = { version = "0.26.3", default-features = false }
|
||||
alloy-evm = { version = "0.27.0", default-features = false }
|
||||
alloy-primitives = { version = "1.5.0", default-features = false, features = ["map-foldhash"] }
|
||||
alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] }
|
||||
alloy-sol-macro = "1.5.0"
|
||||
@@ -497,36 +497,36 @@ alloy-trie = { version = "0.9.1", default-features = false }
|
||||
|
||||
alloy-hardforks = "0.4.5"
|
||||
|
||||
alloy-consensus = { version = "1.4.3", default-features = false }
|
||||
alloy-contract = { version = "1.4.3", default-features = false }
|
||||
alloy-eips = { version = "1.4.3", default-features = false }
|
||||
alloy-genesis = { version = "1.4.3", default-features = false }
|
||||
alloy-json-rpc = { version = "1.4.3", default-features = false }
|
||||
alloy-network = { version = "1.4.3", default-features = false }
|
||||
alloy-network-primitives = { version = "1.4.3", default-features = false }
|
||||
alloy-provider = { version = "1.4.3", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-client = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types = { version = "1.4.3", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.4.3", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.4.3", default-features = false }
|
||||
alloy-serde = { version = "1.4.3", default-features = false }
|
||||
alloy-signer = { version = "1.4.3", default-features = false }
|
||||
alloy-signer-local = { version = "1.4.3", default-features = false }
|
||||
alloy-transport = { version = "1.4.3" }
|
||||
alloy-transport-http = { version = "1.4.3", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.4.3", default-features = false }
|
||||
alloy-transport-ws = { version = "1.4.3", default-features = false }
|
||||
alloy-consensus = { version = "1.5.2", default-features = false }
|
||||
alloy-contract = { version = "1.5.2", default-features = false }
|
||||
alloy-eips = { version = "1.5.2", default-features = false }
|
||||
alloy-genesis = { version = "1.5.2", default-features = false }
|
||||
alloy-json-rpc = { version = "1.5.2", default-features = false }
|
||||
alloy-network = { version = "1.5.2", default-features = false }
|
||||
alloy-network-primitives = { version = "1.5.2", default-features = false }
|
||||
alloy-provider = { version = "1.5.2", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-client = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types = { version = "1.5.2", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "1.5.2", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "1.5.2", default-features = false }
|
||||
alloy-serde = { version = "1.5.2", default-features = false }
|
||||
alloy-signer = { version = "1.5.2", default-features = false }
|
||||
alloy-signer-local = { version = "1.5.2", default-features = false }
|
||||
alloy-transport = { version = "1.5.2" }
|
||||
alloy-transport-http = { version = "1.5.2", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "1.5.2", default-features = false }
|
||||
alloy-transport-ws = { version = "1.5.2", default-features = false }
|
||||
|
||||
# op
|
||||
alloy-op-evm = { version = "0.26.3", default-features = false }
|
||||
alloy-op-evm = { version = "0.27.0", default-features = false }
|
||||
alloy-op-hardforks = "0.4.4"
|
||||
op-alloy-rpc-types = { version = "0.23.1", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.23.1", default-features = false }
|
||||
@@ -588,7 +588,7 @@ tracing-appender = "0.2"
|
||||
url = { version = "2.3", default-features = false }
|
||||
zstd = "0.13"
|
||||
byteorder = "1"
|
||||
mini-moka = "0.10"
|
||||
fixed-cache = { version = "0.1.7", features = ["stats"] }
|
||||
moka = "0.12"
|
||||
tar-no-std = { version = "0.3.2", default-features = false }
|
||||
miniz_oxide = { version = "0.8.4", default-features = false }
|
||||
@@ -790,39 +790,10 @@ ipnet = "2.11"
|
||||
# jsonrpsee-http-client = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
|
||||
# jsonrpsee-types = { git = "https://github.com/paradigmxyz/jsonrpsee", branch = "matt/make-rpc-service-pub" }
|
||||
|
||||
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "a69f0b45a6b0286e16072cb8399e02ce6ceca353" }
|
||||
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "a69f0b45a6b0286e16072cb8399e02ce6ceca353" }
|
||||
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "df124c0" }
|
||||
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "df124c0" }
|
||||
|
||||
# revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "3020ea8" }
|
||||
|
||||
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "072c248" }
|
||||
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "072c248" }
|
||||
|
||||
# Patched by patch-alloy.sh
|
||||
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-contract = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-eips = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-genesis = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-network = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-provider = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-serde = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-signer = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-transport = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", branch = "main" }
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# This image is meant to enable cross-architecture builds.
|
||||
# It assumes the reth binary has already been compiled for `$TARGETPLATFORM` and is
|
||||
# locatable in `./dist/bin/$TARGETARCH`
|
||||
FROM --platform=$TARGETPLATFORM ubuntu:22.04
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth
|
||||
LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0"
|
||||
|
||||
# Filled by docker buildx
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY ./dist/bin/$TARGETARCH/reth /usr/local/bin/reth
|
||||
|
||||
EXPOSE 30303 30303/udp 9001 8545 8546
|
||||
ENTRYPOINT ["/usr/local/bin/reth"]
|
||||
99
Dockerfile.depot
Normal file
99
Dockerfile.depot
Normal file
@@ -0,0 +1,99 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Unified Dockerfile for reth and op-reth, optimized for Depot builds
|
||||
# Usage:
|
||||
# reth: --build-arg BINARY=reth
|
||||
# op-reth: --build-arg BINARY=op-reth --build-arg MANIFEST_PATH=crates/optimism/bin
|
||||
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
|
||||
WORKDIR /app
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth
|
||||
LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0"
|
||||
|
||||
RUN apt-get update && apt-get install -y libclang-dev pkg-config
|
||||
|
||||
# Install sccache for compilation caching
|
||||
RUN cargo install sccache --locked
|
||||
ENV RUSTC_WRAPPER=sccache
|
||||
ENV SCCACHE_DIR=/sccache
|
||||
ENV SCCACHE_WEBDAV_ENDPOINT=https://cache.depot.dev
|
||||
|
||||
# Builds a cargo-chef plan
|
||||
FROM chef AS planner
|
||||
COPY --exclude=.git . .
|
||||
RUN cargo chef prepare --recipe-path recipe.json
|
||||
|
||||
FROM chef AS builder
|
||||
COPY --from=planner /app/recipe.json recipe.json
|
||||
|
||||
# Binary to build (reth or op-reth)
|
||||
ARG BINARY=reth
|
||||
|
||||
# Manifest path for the binary
|
||||
ARG MANIFEST_PATH=bin/reth
|
||||
|
||||
# Build profile, release by default
|
||||
ARG BUILD_PROFILE=release
|
||||
ENV BUILD_PROFILE=$BUILD_PROFILE
|
||||
|
||||
# Extra Cargo flags
|
||||
ARG RUSTFLAGS=""
|
||||
ENV RUSTFLAGS="$RUSTFLAGS"
|
||||
|
||||
# Extra Cargo features
|
||||
ARG FEATURES=""
|
||||
ENV FEATURES=$FEATURES
|
||||
|
||||
# Git info for vergen (since .git is excluded from Docker context)
|
||||
ARG VERGEN_GIT_SHA=""
|
||||
ARG VERGEN_GIT_DESCRIBE=""
|
||||
ARG VERGEN_GIT_DIRTY="false"
|
||||
ENV VERGEN_GIT_SHA=$VERGEN_GIT_SHA
|
||||
ENV VERGEN_GIT_DESCRIBE=$VERGEN_GIT_DESCRIBE
|
||||
ENV VERGEN_GIT_DIRTY=$VERGEN_GIT_DIRTY
|
||||
|
||||
# Build dependencies
|
||||
RUN --mount=type=secret,id=DEPOT_TOKEN,env=SCCACHE_WEBDAV_TOKEN \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
|
||||
--mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
|
||||
--mount=type=cache,target=$SCCACHE_DIR,sharing=shared \
|
||||
cargo chef cook --profile $BUILD_PROFILE --features "$FEATURES" --locked --recipe-path recipe.json --manifest-path $MANIFEST_PATH/Cargo.toml
|
||||
|
||||
# Build application
|
||||
COPY --exclude=.git . .
|
||||
RUN --mount=type=secret,id=DEPOT_TOKEN,env=SCCACHE_WEBDAV_TOKEN \
|
||||
--mount=type=cache,target=/usr/local/cargo/registry,sharing=shared \
|
||||
--mount=type=cache,target=/usr/local/cargo/git,sharing=shared \
|
||||
--mount=type=cache,target=$SCCACHE_DIR,sharing=shared \
|
||||
cargo build --profile $BUILD_PROFILE --features "$FEATURES" --locked --bin $BINARY --manifest-path $MANIFEST_PATH/Cargo.toml
|
||||
|
||||
RUN sccache --show-stats || true
|
||||
|
||||
# Copy binary to a known location (ARG not resolved in COPY)
|
||||
# Note: Custom profiles like maxperf/profiling output to target/<profile>/, not target/release/
|
||||
RUN cp /app/target/$BUILD_PROFILE/$BINARY /app/binary || \
|
||||
cp /app/target/release/$BINARY /app/binary
|
||||
|
||||
FROM ubuntu:24.04 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Binary name for entrypoint
|
||||
ARG BINARY=reth
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy binary from build stage and create canonical symlink for entrypoint
|
||||
COPY --from=builder /app/binary /usr/local/bin/
|
||||
RUN mv /usr/local/bin/binary /usr/local/bin/$BINARY && \
|
||||
ln -s /usr/local/bin/$BINARY /usr/local/bin/reth-binary && \
|
||||
chmod +x /usr/local/bin/$BINARY
|
||||
|
||||
# Copy licenses
|
||||
COPY LICENSE-* ./
|
||||
|
||||
EXPOSE 30303 30303/udp 9001 8545 8546
|
||||
ENTRYPOINT ["/usr/local/bin/reth-binary"]
|
||||
@@ -1,15 +0,0 @@
|
||||
# This image is meant to enable cross-architecture builds.
|
||||
# It assumes the reth binary has already been compiled for `$TARGETPLATFORM` and is
|
||||
# locatable in `./dist/bin/$TARGETARCH`
|
||||
FROM --platform=$TARGETPLATFORM ubuntu:22.04
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/paradigmxyz/reth
|
||||
LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0"
|
||||
|
||||
# Filled by docker buildx
|
||||
ARG TARGETARCH
|
||||
|
||||
COPY ./dist/bin/$TARGETARCH/op-reth /usr/local/bin/op-reth
|
||||
|
||||
EXPOSE 30303 30303/udp 9001 8545 8546
|
||||
ENTRYPOINT ["/usr/local/bin/op-reth"]
|
||||
134
Makefile
134
Makefile
@@ -35,9 +35,6 @@ EEST_TESTS_TAG := v4.5.0
|
||||
EEST_TESTS_URL := https://github.com/ethereum/execution-spec-tests/releases/download/$(EEST_TESTS_TAG)/fixtures_stable.tar.gz
|
||||
EEST_TESTS_DIR := ./testing/ef-tests/execution-spec-tests
|
||||
|
||||
# The docker image name
|
||||
DOCKER_IMAGE_NAME ?= ghcr.io/paradigmxyz/reth
|
||||
|
||||
##@ Help
|
||||
|
||||
.PHONY: help
|
||||
@@ -242,137 +239,6 @@ install-reth-bench: ## Build and install the reth binary under `$(CARGO_HOME)/bi
|
||||
--features "$(FEATURES)" \
|
||||
--profile "$(PROFILE)"
|
||||
|
||||
##@ Docker
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --driver docker-container --name cross-builder`
|
||||
.PHONY: docker-build-push
|
||||
docker-build-push: ## Build and push a cross-arch Docker image tagged with the latest git tag.
|
||||
$(call docker_build_push,$(GIT_TAG),$(GIT_TAG))
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --driver docker-container --name cross-builder`
|
||||
.PHONY: docker-build-push-git-sha
|
||||
docker-build-push-git-sha: ## Build and push a cross-arch Docker image tagged with the latest git sha.
|
||||
$(call docker_build_push,$(GIT_SHA),$(GIT_SHA))
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --driver docker-container --name cross-builder`
|
||||
.PHONY: docker-build-push-latest
|
||||
docker-build-push-latest: ## Build and push a cross-arch Docker image tagged with the latest git tag and `latest`.
|
||||
$(call docker_build_push,$(GIT_TAG),latest)
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --name cross-builder`
|
||||
.PHONY: docker-build-push-nightly
|
||||
docker-build-push-nightly: ## Build and push cross-arch Docker image tagged with the latest git tag with a `-nightly` suffix, and `latest-nightly`.
|
||||
$(call docker_build_push,nightly,nightly)
|
||||
|
||||
.PHONY: docker-build-push-nightly-edge-profiling
|
||||
docker-build-push-nightly-edge-profiling: FEATURES := $(FEATURES) edge
|
||||
docker-build-push-nightly-edge-profiling: ## Build and push cross-arch Docker image with edge features tagged with `nightly-edge-profiling`.
|
||||
$(call docker_build_push,nightly-edge-profiling,nightly-edge-profiling)
|
||||
|
||||
# Create a cross-arch Docker image with the given tags and push it
|
||||
define docker_build_push
|
||||
$(MAKE) FEATURES="$(FEATURES)" build-x86_64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/amd64
|
||||
cp $(CARGO_TARGET_DIR)/x86_64-unknown-linux-gnu/$(PROFILE)/reth $(BIN_DIR)/amd64/reth
|
||||
|
||||
$(MAKE) FEATURES="$(FEATURES)" build-aarch64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/arm64
|
||||
cp $(CARGO_TARGET_DIR)/aarch64-unknown-linux-gnu/$(PROFILE)/reth $(BIN_DIR)/arm64/reth
|
||||
|
||||
docker buildx build --file ./Dockerfile.cross . \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag $(DOCKER_IMAGE_NAME):$(1) \
|
||||
--tag $(DOCKER_IMAGE_NAME):$(2) \
|
||||
--provenance=false \
|
||||
--push
|
||||
endef
|
||||
|
||||
##@ Optimism docker
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --driver docker-container --name cross-builder`
|
||||
.PHONY: op-docker-build-push
|
||||
op-docker-build-push: ## Build and push a cross-arch Docker image tagged with the latest git tag.
|
||||
$(call op_docker_build_push,$(GIT_TAG),$(GIT_TAG))
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --driver docker-container --name cross-builder`
|
||||
.PHONY: op-docker-build-push-git-sha
|
||||
op-docker-build-push-git-sha: ## Build and push a cross-arch Docker image tagged with the latest git sha.
|
||||
$(call op_docker_build_push,$(GIT_SHA),$(GIT_SHA))
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --driver docker-container --name cross-builder`
|
||||
.PHONY: op-docker-build-push-latest
|
||||
op-docker-build-push-latest: ## Build and push a cross-arch Docker image tagged with the latest git tag and `latest`.
|
||||
$(call op_docker_build_push,$(GIT_TAG),latest)
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --name cross-builder`
|
||||
.PHONY: op-docker-build-push-nightly
|
||||
op-docker-build-push-nightly: ## Build and push cross-arch Docker image tagged with the latest git tag with a `-nightly` suffix, and `latest-nightly`.
|
||||
$(call op_docker_build_push,nightly,nightly)
|
||||
|
||||
.PHONY: op-docker-build-push-nightly-edge-profiling
|
||||
op-docker-build-push-nightly-edge-profiling: FEATURES := $(FEATURES) edge
|
||||
op-docker-build-push-nightly-edge-profiling: ## Build and push cross-arch Docker image with edge features tagged with `nightly-edge-profiling`.
|
||||
$(call op_docker_build_push,nightly-edge-profiling,nightly-edge-profiling)
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --name cross-builder`
|
||||
.PHONY: docker-build-push-nightly-profiling
|
||||
docker-build-push-nightly-profiling: ## Build and push cross-arch Docker image with profiling profile tagged with nightly-profiling.
|
||||
$(call docker_build_push,nightly-profiling,nightly-profiling)
|
||||
|
||||
# Note: This requires a buildx builder with emulation support. For example:
|
||||
#
|
||||
# `docker run --privileged --rm tonistiigi/binfmt --install amd64,arm64`
|
||||
# `docker buildx create --use --name cross-builder`
|
||||
.PHONY: op-docker-build-push-nightly-profiling
|
||||
op-docker-build-push-nightly-profiling: ## Build and push cross-arch Docker image tagged with the latest git tag with a `-nightly` suffix, and `latest-nightly`.
|
||||
$(call op_docker_build_push,nightly-profiling,nightly-profiling)
|
||||
|
||||
|
||||
# Create a cross-arch Docker image with the given tags and push it
|
||||
define op_docker_build_push
|
||||
$(MAKE) FEATURES="$(FEATURES)" op-build-x86_64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/amd64
|
||||
cp $(CARGO_TARGET_DIR)/x86_64-unknown-linux-gnu/$(PROFILE)/op-reth $(BIN_DIR)/amd64/op-reth
|
||||
|
||||
$(MAKE) FEATURES="$(FEATURES)" op-build-aarch64-unknown-linux-gnu
|
||||
mkdir -p $(BIN_DIR)/arm64
|
||||
cp $(CARGO_TARGET_DIR)/aarch64-unknown-linux-gnu/$(PROFILE)/op-reth $(BIN_DIR)/arm64/op-reth
|
||||
|
||||
docker buildx build --file ./DockerfileOp.cross . \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag $(DOCKER_IMAGE_NAME):$(1) \
|
||||
--tag $(DOCKER_IMAGE_NAME):$(2) \
|
||||
--provenance=false \
|
||||
--push
|
||||
endef
|
||||
|
||||
##@ Other
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
@@ -32,7 +32,7 @@ alloy-eips.workspace = true
|
||||
alloy-json-rpc.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
alloy-network.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-primitives = { workspace = true, features = ["rand"] }
|
||||
alloy-provider = { workspace = true, features = ["engine-api", "pubsub", "reqwest-rustls-tls"], default-features = false }
|
||||
alloy-pubsub.workspace = true
|
||||
alloy-rpc-client = { workspace = true, features = ["pubsub"] }
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
use crate::{
|
||||
authenticated_transport::AuthenticatedTransportConnect,
|
||||
bench::{
|
||||
helpers::{build_payload, prepare_payload_request, rpc_block_to_header},
|
||||
helpers::{build_payload, parse_gas_limit, prepare_payload_request, rpc_block_to_header},
|
||||
output::GasRampPayloadFile,
|
||||
},
|
||||
valid_payload::{call_forkchoice_updated, call_new_payload, payload_to_new_payload},
|
||||
@@ -22,29 +22,6 @@ use reth_primitives_traits::constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIM
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
use tracing::info;
|
||||
|
||||
/// Parses a gas limit value with optional suffix: K for thousand, M for million, G for billion.
|
||||
///
|
||||
/// Examples: "30000000", "30M", "1G", "2G"
|
||||
fn parse_gas_limit(s: &str) -> eyre::Result<u64> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
return Err(eyre::eyre!("empty value"));
|
||||
}
|
||||
|
||||
let (num_str, multiplier) = if let Some(prefix) = s.strip_suffix(['G', 'g']) {
|
||||
(prefix, 1_000_000_000u64)
|
||||
} else if let Some(prefix) = s.strip_suffix(['M', 'm']) {
|
||||
(prefix, 1_000_000u64)
|
||||
} else if let Some(prefix) = s.strip_suffix(['K', 'k']) {
|
||||
(prefix, 1_000u64)
|
||||
} else {
|
||||
(s, 1u64)
|
||||
};
|
||||
|
||||
let base: u64 = num_str.trim().parse()?;
|
||||
base.checked_mul(multiplier).ok_or_else(|| eyre::eyre!("value overflow"))
|
||||
}
|
||||
|
||||
/// `reth benchmark gas-limit-ramp` command.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Command {
|
||||
@@ -237,50 +214,3 @@ const fn should_stop(mode: RampMode, blocks_processed: u64, current_gas_limit: u
|
||||
RampMode::TargetGasLimit(target) => current_gas_limit >= target,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_plain_number() {
|
||||
assert_eq!(parse_gas_limit("30000000").unwrap(), 30_000_000);
|
||||
assert_eq!(parse_gas_limit("1").unwrap(), 1);
|
||||
assert_eq!(parse_gas_limit("0").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_k_suffix() {
|
||||
assert_eq!(parse_gas_limit("1K").unwrap(), 1_000);
|
||||
assert_eq!(parse_gas_limit("30k").unwrap(), 30_000);
|
||||
assert_eq!(parse_gas_limit("100K").unwrap(), 100_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_m_suffix() {
|
||||
assert_eq!(parse_gas_limit("1M").unwrap(), 1_000_000);
|
||||
assert_eq!(parse_gas_limit("30m").unwrap(), 30_000_000);
|
||||
assert_eq!(parse_gas_limit("100M").unwrap(), 100_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_g_suffix() {
|
||||
assert_eq!(parse_gas_limit("1G").unwrap(), 1_000_000_000);
|
||||
assert_eq!(parse_gas_limit("2g").unwrap(), 2_000_000_000);
|
||||
assert_eq!(parse_gas_limit("10G").unwrap(), 10_000_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_with_whitespace() {
|
||||
assert_eq!(parse_gas_limit(" 1G ").unwrap(), 1_000_000_000);
|
||||
assert_eq!(parse_gas_limit("2 M").unwrap(), 2_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_errors() {
|
||||
assert!(parse_gas_limit("").is_err());
|
||||
assert!(parse_gas_limit("abc").is_err());
|
||||
assert!(parse_gas_limit("G").is_err());
|
||||
assert!(parse_gas_limit("-1G").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
//! This command fetches transactions from existing blocks and packs them into a single
|
||||
//! large block using the `testing_buildBlockV1` RPC endpoint.
|
||||
|
||||
use crate::authenticated_transport::AuthenticatedTransportConnect;
|
||||
use crate::{
|
||||
authenticated_transport::AuthenticatedTransportConnect, bench::helpers::parse_gas_limit,
|
||||
};
|
||||
use alloy_eips::{BlockNumberOrTag, Typed2718};
|
||||
use alloy_primitives::{Bytes, B256};
|
||||
use alloy_provider::{ext::EngineApi, network::AnyNetwork, Provider, RootProvider};
|
||||
@@ -202,13 +204,26 @@ pub struct Command {
|
||||
jwt_secret: std::path::PathBuf,
|
||||
|
||||
/// Target gas to pack into the block.
|
||||
#[arg(long, value_name = "TARGET_GAS", default_value = "30000000")]
|
||||
/// Accepts short notation: K for thousand, M for million, G for billion (e.g., 1G = 1
|
||||
/// billion).
|
||||
#[arg(long, value_name = "TARGET_GAS", default_value = "30000000", value_parser = parse_gas_limit)]
|
||||
target_gas: u64,
|
||||
|
||||
/// Starting block number to fetch transactions from.
|
||||
/// If not specified, starts from the engine's latest block.
|
||||
/// Block number to start fetching transactions from (required).
|
||||
///
|
||||
/// This must be the last canonical block BEFORE any gas limit ramping was performed.
|
||||
/// The command collects transactions from historical blocks starting at this number
|
||||
/// to pack into large blocks.
|
||||
///
|
||||
/// How to determine this value:
|
||||
/// - If starting from a fresh node (no gas limit ramp yet): use the current chain tip
|
||||
/// - If gas limit ramping has already been performed: use the block number that was the chain
|
||||
/// tip BEFORE ramping began (you must track this yourself)
|
||||
///
|
||||
/// Using a block after ramping started will cause transaction collection to fail
|
||||
/// because those blocks contain synthetic transactions that cannot be replayed.
|
||||
#[arg(long, value_name = "FROM_BLOCK")]
|
||||
from_block: Option<u64>,
|
||||
from_block: u64,
|
||||
|
||||
/// Execute the payload (call newPayload + forkchoiceUpdated).
|
||||
/// If false, only builds the payload and prints it.
|
||||
@@ -284,7 +299,7 @@ impl Command {
|
||||
format!("Failed to create output directory: {:?}", self.output_dir)
|
||||
})?;
|
||||
|
||||
let start_block = self.from_block.unwrap_or(parent_number);
|
||||
let start_block = self.from_block;
|
||||
|
||||
// Use pipelined execution when generating multiple payloads
|
||||
if self.count > 1 {
|
||||
|
||||
@@ -1,6 +1,29 @@
|
||||
//! Common helpers for reth-bench commands.
|
||||
|
||||
use crate::valid_payload::call_forkchoice_updated;
|
||||
|
||||
/// Parses a gas limit value with optional suffix: K for thousand, M for million, G for billion.
|
||||
///
|
||||
/// Examples: "30000000", "30M", "1G", "2G"
|
||||
pub(crate) fn parse_gas_limit(s: &str) -> eyre::Result<u64> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
return Err(eyre::eyre!("empty value"));
|
||||
}
|
||||
|
||||
let (num_str, multiplier) = if let Some(prefix) = s.strip_suffix(['G', 'g']) {
|
||||
(prefix, 1_000_000_000u64)
|
||||
} else if let Some(prefix) = s.strip_suffix(['M', 'm']) {
|
||||
(prefix, 1_000_000u64)
|
||||
} else if let Some(prefix) = s.strip_suffix(['K', 'k']) {
|
||||
(prefix, 1_000u64)
|
||||
} else {
|
||||
(s, 1u64)
|
||||
};
|
||||
|
||||
let base: u64 = num_str.trim().parse()?;
|
||||
base.checked_mul(multiplier).ok_or_else(|| eyre::eyre!("value overflow"))
|
||||
}
|
||||
use alloy_consensus::Header;
|
||||
use alloy_eips::eip4844::kzg_to_versioned_hash;
|
||||
use alloy_primitives::{Address, B256};
|
||||
@@ -194,3 +217,50 @@ pub(crate) async fn get_payload_with_sidecar(
|
||||
_ => panic!("This tool does not support getPayload versions past v5"),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_plain_number() {
|
||||
assert_eq!(parse_gas_limit("30000000").unwrap(), 30_000_000);
|
||||
assert_eq!(parse_gas_limit("1").unwrap(), 1);
|
||||
assert_eq!(parse_gas_limit("0").unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_k_suffix() {
|
||||
assert_eq!(parse_gas_limit("1K").unwrap(), 1_000);
|
||||
assert_eq!(parse_gas_limit("30k").unwrap(), 30_000);
|
||||
assert_eq!(parse_gas_limit("100K").unwrap(), 100_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_m_suffix() {
|
||||
assert_eq!(parse_gas_limit("1M").unwrap(), 1_000_000);
|
||||
assert_eq!(parse_gas_limit("30m").unwrap(), 30_000_000);
|
||||
assert_eq!(parse_gas_limit("100M").unwrap(), 100_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_g_suffix() {
|
||||
assert_eq!(parse_gas_limit("1G").unwrap(), 1_000_000_000);
|
||||
assert_eq!(parse_gas_limit("2g").unwrap(), 2_000_000_000);
|
||||
assert_eq!(parse_gas_limit("10G").unwrap(), 10_000_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_with_whitespace() {
|
||||
assert_eq!(parse_gas_limit(" 1G ").unwrap(), 1_000_000_000);
|
||||
assert_eq!(parse_gas_limit("2 M").unwrap(), 2_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_gas_limit_errors() {
|
||||
assert!(parse_gas_limit("").is_err());
|
||||
assert!(parse_gas_limit("abc").is_err());
|
||||
assert!(parse_gas_limit("G").is_err());
|
||||
assert!(parse_gas_limit("-1G").is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ mod new_payload_fcu;
|
||||
mod new_payload_only;
|
||||
mod output;
|
||||
mod replay_payloads;
|
||||
mod send_invalid_payload;
|
||||
mod send_payload;
|
||||
|
||||
/// `reth bench` command
|
||||
@@ -74,6 +75,18 @@ pub enum Subcommands {
|
||||
/// `reth-bench replay-payloads --payload-dir ./payloads --engine-rpc-url
|
||||
/// http://localhost:8551 --jwt-secret ~/.local/share/reth/mainnet/jwt.hex`
|
||||
ReplayPayloads(replay_payloads::Command),
|
||||
|
||||
/// Generate and send an invalid `engine_newPayload` request for testing.
|
||||
///
|
||||
/// Takes a valid block and modifies fields to make it invalid, allowing you to test
|
||||
/// Engine API rejection behavior. Block hash is recalculated after modifications
|
||||
/// unless `--invalid-block-hash` or `--skip-hash-recalc` is used.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// `cast block latest --full --json | reth-bench send-invalid-payload --rpc-url localhost:5000
|
||||
/// --jwt-secret $(cat ~/.local/share/reth/mainnet/jwt.hex) --invalid-state-root`
|
||||
SendInvalidPayload(Box<send_invalid_payload::Command>),
|
||||
}
|
||||
|
||||
impl BenchmarkCommand {
|
||||
@@ -89,6 +102,7 @@ impl BenchmarkCommand {
|
||||
Subcommands::SendPayload(command) => command.execute(ctx).await,
|
||||
Subcommands::GenerateBigBlock(command) => command.execute(ctx).await,
|
||||
Subcommands::ReplayPayloads(command) => command.execute(ctx).await,
|
||||
Subcommands::SendInvalidPayload(command) => (*command).execute(ctx).await,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
219
bin/reth-bench/src/bench/send_invalid_payload/invalidation.rs
Normal file
219
bin/reth-bench/src/bench/send_invalid_payload/invalidation.rs
Normal file
@@ -0,0 +1,219 @@
|
||||
use alloy_eips::eip4895::Withdrawal;
|
||||
use alloy_primitives::{Address, Bloom, Bytes, B256, U256};
|
||||
use alloy_rpc_types_engine::{ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3};
|
||||
|
||||
/// Configuration for invalidating payload fields
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct InvalidationConfig {
|
||||
// Explicit value overrides (Option<T>)
|
||||
pub(super) parent_hash: Option<B256>,
|
||||
pub(super) fee_recipient: Option<Address>,
|
||||
pub(super) state_root: Option<B256>,
|
||||
pub(super) receipts_root: Option<B256>,
|
||||
pub(super) logs_bloom: Option<Bloom>,
|
||||
pub(super) prev_randao: Option<B256>,
|
||||
pub(super) block_number: Option<u64>,
|
||||
pub(super) gas_limit: Option<u64>,
|
||||
pub(super) gas_used: Option<u64>,
|
||||
pub(super) timestamp: Option<u64>,
|
||||
pub(super) extra_data: Option<Bytes>,
|
||||
pub(super) base_fee_per_gas: Option<u64>,
|
||||
pub(super) block_hash: Option<B256>,
|
||||
pub(super) blob_gas_used: Option<u64>,
|
||||
pub(super) excess_blob_gas: Option<u64>,
|
||||
|
||||
// Auto-invalidation flags
|
||||
pub(super) invalidate_parent_hash: bool,
|
||||
pub(super) invalidate_state_root: bool,
|
||||
pub(super) invalidate_receipts_root: bool,
|
||||
pub(super) invalidate_gas_used: bool,
|
||||
pub(super) invalidate_block_number: bool,
|
||||
pub(super) invalidate_timestamp: bool,
|
||||
pub(super) invalidate_base_fee: bool,
|
||||
pub(super) invalidate_transactions: bool,
|
||||
pub(super) invalidate_block_hash: bool,
|
||||
pub(super) invalidate_withdrawals: bool,
|
||||
pub(super) invalidate_blob_gas_used: bool,
|
||||
pub(super) invalidate_excess_blob_gas: bool,
|
||||
}
|
||||
|
||||
impl InvalidationConfig {
|
||||
/// Returns true if `block_hash` is being explicitly set or auto-invalidated.
|
||||
/// When true, the caller should skip recalculating the block hash since it will be overwritten.
|
||||
pub(super) const fn should_skip_hash_recalc(&self) -> bool {
|
||||
self.block_hash.is_some() || self.invalidate_block_hash
|
||||
}
|
||||
|
||||
/// Applies invalidations to a V1 payload, returns list of what was changed.
|
||||
pub(super) fn apply_to_payload_v1(&self, payload: &mut ExecutionPayloadV1) -> Vec<String> {
|
||||
let mut changes = Vec::new();
|
||||
|
||||
// Explicit value overrides
|
||||
if let Some(parent_hash) = self.parent_hash {
|
||||
payload.parent_hash = parent_hash;
|
||||
changes.push(format!("parent_hash = {parent_hash}"));
|
||||
}
|
||||
|
||||
if let Some(fee_recipient) = self.fee_recipient {
|
||||
payload.fee_recipient = fee_recipient;
|
||||
changes.push(format!("fee_recipient = {fee_recipient}"));
|
||||
}
|
||||
|
||||
if let Some(state_root) = self.state_root {
|
||||
payload.state_root = state_root;
|
||||
changes.push(format!("state_root = {state_root}"));
|
||||
}
|
||||
|
||||
if let Some(receipts_root) = self.receipts_root {
|
||||
payload.receipts_root = receipts_root;
|
||||
changes.push(format!("receipts_root = {receipts_root}"));
|
||||
}
|
||||
|
||||
if let Some(logs_bloom) = self.logs_bloom {
|
||||
payload.logs_bloom = logs_bloom;
|
||||
changes.push("logs_bloom = <custom>".to_string());
|
||||
}
|
||||
|
||||
if let Some(prev_randao) = self.prev_randao {
|
||||
payload.prev_randao = prev_randao;
|
||||
changes.push(format!("prev_randao = {prev_randao}"));
|
||||
}
|
||||
|
||||
if let Some(block_number) = self.block_number {
|
||||
payload.block_number = block_number;
|
||||
changes.push(format!("block_number = {block_number}"));
|
||||
}
|
||||
|
||||
if let Some(gas_limit) = self.gas_limit {
|
||||
payload.gas_limit = gas_limit;
|
||||
changes.push(format!("gas_limit = {gas_limit}"));
|
||||
}
|
||||
|
||||
if let Some(gas_used) = self.gas_used {
|
||||
payload.gas_used = gas_used;
|
||||
changes.push(format!("gas_used = {gas_used}"));
|
||||
}
|
||||
|
||||
if let Some(timestamp) = self.timestamp {
|
||||
payload.timestamp = timestamp;
|
||||
changes.push(format!("timestamp = {timestamp}"));
|
||||
}
|
||||
|
||||
if let Some(ref extra_data) = self.extra_data {
|
||||
payload.extra_data = extra_data.clone();
|
||||
changes.push(format!("extra_data = {} bytes", extra_data.len()));
|
||||
}
|
||||
|
||||
if let Some(base_fee_per_gas) = self.base_fee_per_gas {
|
||||
payload.base_fee_per_gas = U256::from_limbs([base_fee_per_gas, 0, 0, 0]);
|
||||
changes.push(format!("base_fee_per_gas = {base_fee_per_gas}"));
|
||||
}
|
||||
|
||||
if let Some(block_hash) = self.block_hash {
|
||||
payload.block_hash = block_hash;
|
||||
changes.push(format!("block_hash = {block_hash}"));
|
||||
}
|
||||
|
||||
// Auto-invalidation flags
|
||||
if self.invalidate_parent_hash {
|
||||
let random_hash = B256::random();
|
||||
payload.parent_hash = random_hash;
|
||||
changes.push(format!("parent_hash = {random_hash} (auto-invalidated: random)"));
|
||||
}
|
||||
|
||||
if self.invalidate_state_root {
|
||||
payload.state_root = B256::ZERO;
|
||||
changes.push("state_root = ZERO (auto-invalidated: empty trie root)".to_string());
|
||||
}
|
||||
|
||||
if self.invalidate_receipts_root {
|
||||
payload.receipts_root = B256::ZERO;
|
||||
changes.push("receipts_root = ZERO (auto-invalidated)".to_string());
|
||||
}
|
||||
|
||||
if self.invalidate_gas_used {
|
||||
let invalid_gas = payload.gas_limit + 1;
|
||||
payload.gas_used = invalid_gas;
|
||||
changes.push(format!("gas_used = {invalid_gas} (auto-invalidated: exceeds gas_limit)"));
|
||||
}
|
||||
|
||||
if self.invalidate_block_number {
|
||||
let invalid_number = payload.block_number + 999;
|
||||
payload.block_number = invalid_number;
|
||||
changes.push(format!("block_number = {invalid_number} (auto-invalidated: huge gap)"));
|
||||
}
|
||||
|
||||
if self.invalidate_timestamp {
|
||||
payload.timestamp = 0;
|
||||
changes.push("timestamp = 0 (auto-invalidated: impossibly old)".to_string());
|
||||
}
|
||||
|
||||
if self.invalidate_base_fee {
|
||||
payload.base_fee_per_gas = U256::ZERO;
|
||||
changes
|
||||
.push("base_fee_per_gas = 0 (auto-invalidated: invalid post-London)".to_string());
|
||||
}
|
||||
|
||||
if self.invalidate_transactions {
|
||||
let invalid_tx = Bytes::from_static(&[0xff, 0xff, 0xff]);
|
||||
payload.transactions.insert(0, invalid_tx);
|
||||
changes.push("transactions = prepended invalid RLP (auto-invalidated)".to_string());
|
||||
}
|
||||
|
||||
if self.invalidate_block_hash {
|
||||
let random_hash = B256::random();
|
||||
payload.block_hash = random_hash;
|
||||
changes.push(format!("block_hash = {random_hash} (auto-invalidated: random)"));
|
||||
}
|
||||
|
||||
changes
|
||||
}
|
||||
|
||||
/// Applies invalidations to a V2 payload, returns list of what was changed.
|
||||
pub(super) fn apply_to_payload_v2(&self, payload: &mut ExecutionPayloadV2) -> Vec<String> {
|
||||
let mut changes = self.apply_to_payload_v1(&mut payload.payload_inner);
|
||||
|
||||
// Handle withdrawals invalidation (V2+)
|
||||
if self.invalidate_withdrawals {
|
||||
let fake_withdrawal = Withdrawal {
|
||||
index: u64::MAX,
|
||||
validator_index: u64::MAX,
|
||||
address: Address::ZERO,
|
||||
amount: u64::MAX,
|
||||
};
|
||||
payload.withdrawals.push(fake_withdrawal);
|
||||
changes.push("withdrawals = added fake withdrawal (auto-invalidated)".to_string());
|
||||
}
|
||||
|
||||
changes
|
||||
}
|
||||
|
||||
/// Applies invalidations to a V3 payload, returns list of what was changed.
|
||||
pub(super) fn apply_to_payload_v3(&self, payload: &mut ExecutionPayloadV3) -> Vec<String> {
|
||||
let mut changes = self.apply_to_payload_v2(&mut payload.payload_inner);
|
||||
|
||||
// Explicit overrides for V3 fields
|
||||
if let Some(blob_gas_used) = self.blob_gas_used {
|
||||
payload.blob_gas_used = blob_gas_used;
|
||||
changes.push(format!("blob_gas_used = {blob_gas_used}"));
|
||||
}
|
||||
|
||||
if let Some(excess_blob_gas) = self.excess_blob_gas {
|
||||
payload.excess_blob_gas = excess_blob_gas;
|
||||
changes.push(format!("excess_blob_gas = {excess_blob_gas}"));
|
||||
}
|
||||
|
||||
// Auto-invalidation for V3 fields
|
||||
if self.invalidate_blob_gas_used {
|
||||
payload.blob_gas_used = u64::MAX;
|
||||
changes.push("blob_gas_used = MAX (auto-invalidated)".to_string());
|
||||
}
|
||||
|
||||
if self.invalidate_excess_blob_gas {
|
||||
payload.excess_blob_gas = u64::MAX;
|
||||
changes.push("excess_blob_gas = MAX (auto-invalidated)".to_string());
|
||||
}
|
||||
|
||||
changes
|
||||
}
|
||||
}
|
||||
367
bin/reth-bench/src/bench/send_invalid_payload/mod.rs
Normal file
367
bin/reth-bench/src/bench/send_invalid_payload/mod.rs
Normal file
@@ -0,0 +1,367 @@
|
||||
//! Command for sending invalid payloads to test Engine API rejection.
|
||||
|
||||
mod invalidation;
|
||||
use invalidation::InvalidationConfig;
|
||||
|
||||
use alloy_primitives::{Address, B256};
|
||||
use alloy_provider::network::AnyRpcBlock;
|
||||
use alloy_rpc_types_engine::ExecutionPayload;
|
||||
use clap::Parser;
|
||||
use eyre::{OptionExt, Result};
|
||||
use op_alloy_consensus::OpTxEnvelope;
|
||||
use reth_cli_runner::CliContext;
|
||||
use std::io::{BufReader, Read, Write};
|
||||
|
||||
/// Command for generating and sending an invalid `engine_newPayload` request.
|
||||
///
|
||||
/// Takes a valid block and modifies fields to make it invalid for testing
|
||||
/// Engine API rejection behavior. Block hash is recalculated after modifications
|
||||
/// unless `--invalidate-block-hash` or `--skip-hash-recalc` is used.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Command {
|
||||
// ==================== Input Options ====================
|
||||
/// Path to the JSON file containing the block. If not specified, stdin will be used.
|
||||
#[arg(short, long, help_heading = "Input Options")]
|
||||
path: Option<String>,
|
||||
|
||||
/// The engine RPC URL to use.
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
help_heading = "Input Options",
|
||||
required_if_eq_any([("mode", "execute"), ("mode", "cast")]),
|
||||
required_unless_present("mode")
|
||||
)]
|
||||
rpc_url: Option<String>,
|
||||
|
||||
/// The JWT secret to use. Can be either a path to a file containing the secret or the secret
|
||||
/// itself.
|
||||
#[arg(short, long, help_heading = "Input Options")]
|
||||
jwt_secret: Option<String>,
|
||||
|
||||
/// The newPayload version to use (3 or 4).
|
||||
#[arg(long, default_value_t = 3, help_heading = "Input Options")]
|
||||
new_payload_version: u8,
|
||||
|
||||
/// The output mode to use.
|
||||
#[arg(long, value_enum, default_value = "execute", help_heading = "Input Options")]
|
||||
mode: Mode,
|
||||
|
||||
// ==================== Explicit Value Overrides ====================
|
||||
/// Override the parent hash with a specific value.
|
||||
#[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
|
||||
parent_hash: Option<B256>,
|
||||
|
||||
/// Override the fee recipient (coinbase) with a specific address.
|
||||
#[arg(long, value_name = "ADDR", help_heading = "Explicit Value Overrides")]
|
||||
fee_recipient: Option<Address>,
|
||||
|
||||
/// Override the state root with a specific value.
|
||||
#[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
|
||||
state_root: Option<B256>,
|
||||
|
||||
/// Override the receipts root with a specific value.
|
||||
#[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
|
||||
receipts_root: Option<B256>,
|
||||
|
||||
/// Override the block number with a specific value.
|
||||
#[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
|
||||
block_number: Option<u64>,
|
||||
|
||||
/// Override the gas limit with a specific value.
|
||||
#[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
|
||||
gas_limit: Option<u64>,
|
||||
|
||||
/// Override the gas used with a specific value.
|
||||
#[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
|
||||
gas_used: Option<u64>,
|
||||
|
||||
/// Override the timestamp with a specific value.
|
||||
#[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
|
||||
timestamp: Option<u64>,
|
||||
|
||||
/// Override the base fee per gas with a specific value.
|
||||
#[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
|
||||
base_fee_per_gas: Option<u64>,
|
||||
|
||||
/// Override the block hash with a specific value (skips hash recalculation).
|
||||
#[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
|
||||
block_hash: Option<B256>,
|
||||
|
||||
/// Override the blob gas used with a specific value.
|
||||
#[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
|
||||
blob_gas_used: Option<u64>,
|
||||
|
||||
/// Override the excess blob gas with a specific value.
|
||||
#[arg(long, value_name = "U64", help_heading = "Explicit Value Overrides")]
|
||||
excess_blob_gas: Option<u64>,
|
||||
|
||||
/// Override the parent beacon block root with a specific value.
|
||||
#[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
|
||||
parent_beacon_block_root: Option<B256>,
|
||||
|
||||
/// Override the requests hash with a specific value (EIP-7685).
|
||||
#[arg(long, value_name = "HASH", help_heading = "Explicit Value Overrides")]
|
||||
requests_hash: Option<B256>,
|
||||
|
||||
// ==================== Auto-Invalidation Flags ====================
|
||||
/// Invalidate the parent hash by setting it to a random value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_parent_hash: bool,
|
||||
|
||||
/// Invalidate the state root by setting it to a random value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_state_root: bool,
|
||||
|
||||
/// Invalidate the receipts root by setting it to a random value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_receipts_root: bool,
|
||||
|
||||
/// Invalidate the gas used by setting it to an incorrect value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_gas_used: bool,
|
||||
|
||||
/// Invalidate the block number by setting it to an incorrect value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_block_number: bool,
|
||||
|
||||
/// Invalidate the timestamp by setting it to an incorrect value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_timestamp: bool,
|
||||
|
||||
/// Invalidate the base fee by setting it to an incorrect value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_base_fee: bool,
|
||||
|
||||
/// Invalidate the transactions by modifying them.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_transactions: bool,
|
||||
|
||||
/// Invalidate the block hash by not recalculating it after modifications.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_block_hash: bool,
|
||||
|
||||
/// Invalidate the withdrawals by modifying them.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_withdrawals: bool,
|
||||
|
||||
/// Invalidate the blob gas used by setting it to an incorrect value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_blob_gas_used: bool,
|
||||
|
||||
/// Invalidate the excess blob gas by setting it to an incorrect value.
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_excess_blob_gas: bool,
|
||||
|
||||
/// Invalidate the requests hash by setting it to a random value (EIP-7685).
|
||||
#[arg(long, default_value_t = false, help_heading = "Auto-Invalidation Flags")]
|
||||
invalidate_requests_hash: bool,
|
||||
|
||||
// ==================== Meta Flags ====================
|
||||
/// Skip block hash recalculation after modifications.
|
||||
#[arg(long, default_value_t = false, help_heading = "Meta Flags")]
|
||||
skip_hash_recalc: bool,
|
||||
|
||||
/// Print what would be done without actually sending the payload.
|
||||
#[arg(long, default_value_t = false, help_heading = "Meta Flags")]
|
||||
dry_run: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, clap::ValueEnum)]
|
||||
enum Mode {
|
||||
/// Execute the `cast` command. This works with blocks of any size, because it pipes the
|
||||
/// payload into the `cast` command.
|
||||
Execute,
|
||||
/// Print the `cast` command. Caution: this may not work with large blocks because of the
|
||||
/// command length limit.
|
||||
Cast,
|
||||
/// Print the JSON payload. Can be piped into `cast` command if the block is small enough.
|
||||
Json,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Read input from either a file or stdin
|
||||
fn read_input(&self) -> Result<String> {
|
||||
Ok(match &self.path {
|
||||
Some(path) => reth_fs_util::read_to_string(path)?,
|
||||
None => String::from_utf8(
|
||||
BufReader::new(std::io::stdin()).bytes().collect::<Result<Vec<_>, _>>()?,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Load JWT secret from either a file or use the provided string directly
|
||||
fn load_jwt_secret(&self) -> Result<Option<String>> {
|
||||
match &self.jwt_secret {
|
||||
Some(secret) => match std::fs::read_to_string(secret) {
|
||||
Ok(contents) => Ok(Some(contents.trim().to_string())),
|
||||
Err(_) => Ok(Some(secret.clone())),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build `InvalidationConfig` from command flags
|
||||
const fn build_invalidation_config(&self) -> InvalidationConfig {
|
||||
InvalidationConfig {
|
||||
parent_hash: self.parent_hash,
|
||||
fee_recipient: self.fee_recipient,
|
||||
state_root: self.state_root,
|
||||
receipts_root: self.receipts_root,
|
||||
logs_bloom: None,
|
||||
prev_randao: None,
|
||||
block_number: self.block_number,
|
||||
gas_limit: self.gas_limit,
|
||||
gas_used: self.gas_used,
|
||||
timestamp: self.timestamp,
|
||||
extra_data: None,
|
||||
base_fee_per_gas: self.base_fee_per_gas,
|
||||
block_hash: self.block_hash,
|
||||
blob_gas_used: self.blob_gas_used,
|
||||
excess_blob_gas: self.excess_blob_gas,
|
||||
invalidate_parent_hash: self.invalidate_parent_hash,
|
||||
invalidate_state_root: self.invalidate_state_root,
|
||||
invalidate_receipts_root: self.invalidate_receipts_root,
|
||||
invalidate_gas_used: self.invalidate_gas_used,
|
||||
invalidate_block_number: self.invalidate_block_number,
|
||||
invalidate_timestamp: self.invalidate_timestamp,
|
||||
invalidate_base_fee: self.invalidate_base_fee,
|
||||
invalidate_transactions: self.invalidate_transactions,
|
||||
invalidate_block_hash: self.invalidate_block_hash,
|
||||
invalidate_withdrawals: self.invalidate_withdrawals,
|
||||
invalidate_blob_gas_used: self.invalidate_blob_gas_used,
|
||||
invalidate_excess_blob_gas: self.invalidate_excess_blob_gas,
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute the command
|
||||
pub async fn execute(self, _ctx: CliContext) -> Result<()> {
|
||||
let block_json = self.read_input()?;
|
||||
let jwt_secret = self.load_jwt_secret()?;
|
||||
|
||||
let block = serde_json::from_str::<AnyRpcBlock>(&block_json)?
|
||||
.into_inner()
|
||||
.map_header(|header| header.map(|h| h.into_header_with_defaults()))
|
||||
.try_map_transactions(|tx| tx.try_into_either::<OpTxEnvelope>())?
|
||||
.into_consensus();
|
||||
|
||||
let config = self.build_invalidation_config();
|
||||
|
||||
let parent_beacon_block_root =
|
||||
self.parent_beacon_block_root.or(block.header.parent_beacon_block_root);
|
||||
let blob_versioned_hashes =
|
||||
block.body.blob_versioned_hashes_iter().copied().collect::<Vec<_>>();
|
||||
let use_v4 = block.header.requests_hash.is_some();
|
||||
let requests_hash = self.requests_hash.or(block.header.requests_hash);
|
||||
|
||||
let mut execution_payload = ExecutionPayload::from_block_slow(&block).0;
|
||||
|
||||
let changes = match &mut execution_payload {
|
||||
ExecutionPayload::V1(p) => config.apply_to_payload_v1(p),
|
||||
ExecutionPayload::V2(p) => config.apply_to_payload_v2(p),
|
||||
ExecutionPayload::V3(p) => config.apply_to_payload_v3(p),
|
||||
};
|
||||
|
||||
let skip_recalc = self.skip_hash_recalc || config.should_skip_hash_recalc();
|
||||
if !skip_recalc {
|
||||
let new_hash = match execution_payload.clone().into_block_raw() {
|
||||
Ok(block) => block.header.hash_slow(),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"Warning: Could not recalculate block hash: {e}. Using original hash."
|
||||
);
|
||||
match &execution_payload {
|
||||
ExecutionPayload::V1(p) => p.block_hash,
|
||||
ExecutionPayload::V2(p) => p.payload_inner.block_hash,
|
||||
ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match &mut execution_payload {
|
||||
ExecutionPayload::V1(p) => p.block_hash = new_hash,
|
||||
ExecutionPayload::V2(p) => p.payload_inner.block_hash = new_hash,
|
||||
ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash = new_hash,
|
||||
}
|
||||
}
|
||||
|
||||
if self.dry_run {
|
||||
println!("=== Dry Run ===");
|
||||
println!("Changes that would be applied:");
|
||||
for change in &changes {
|
||||
println!(" - {}", change);
|
||||
}
|
||||
if changes.is_empty() {
|
||||
println!(" (no changes)");
|
||||
}
|
||||
if skip_recalc {
|
||||
println!(" - Block hash recalculation: SKIPPED");
|
||||
} else {
|
||||
println!(" - Block hash recalculation: PERFORMED");
|
||||
}
|
||||
println!("\nResulting payload JSON:");
|
||||
let json = serde_json::to_string_pretty(&execution_payload)?;
|
||||
println!("{}", json);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let json_request = if use_v4 {
|
||||
serde_json::to_string(&(
|
||||
execution_payload,
|
||||
blob_versioned_hashes,
|
||||
parent_beacon_block_root,
|
||||
requests_hash.unwrap_or_default(),
|
||||
))?
|
||||
} else {
|
||||
serde_json::to_string(&(
|
||||
execution_payload,
|
||||
blob_versioned_hashes,
|
||||
parent_beacon_block_root,
|
||||
))?
|
||||
};
|
||||
|
||||
match self.mode {
|
||||
Mode::Execute => {
|
||||
let mut command = std::process::Command::new("cast");
|
||||
let method = if use_v4 { "engine_newPayloadV4" } else { "engine_newPayloadV3" };
|
||||
command.arg("rpc").arg(method).arg("--raw");
|
||||
if let Some(rpc_url) = self.rpc_url {
|
||||
command.arg("--rpc-url").arg(rpc_url);
|
||||
}
|
||||
if let Some(secret) = &jwt_secret {
|
||||
command.arg("--jwt-secret").arg(secret);
|
||||
}
|
||||
|
||||
let mut process = command.stdin(std::process::Stdio::piped()).spawn()?;
|
||||
|
||||
process
|
||||
.stdin
|
||||
.take()
|
||||
.ok_or_eyre("stdin not available")?
|
||||
.write_all(json_request.as_bytes())?;
|
||||
|
||||
process.wait()?;
|
||||
}
|
||||
Mode::Cast => {
|
||||
let mut cmd = format!(
|
||||
"cast rpc engine_newPayloadV{} --raw '{}'",
|
||||
self.new_payload_version, json_request
|
||||
);
|
||||
|
||||
if let Some(rpc_url) = self.rpc_url {
|
||||
cmd += &format!(" --rpc-url {rpc_url}");
|
||||
}
|
||||
if let Some(secret) = &jwt_secret {
|
||||
cmd += &format!(" --jwt-secret {secret}");
|
||||
}
|
||||
|
||||
println!("{cmd}");
|
||||
}
|
||||
Mode::Json => {
|
||||
println!("{json_request}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@
|
||||
//! - `jemalloc-unprefixed`: Uses unprefixed jemalloc symbols.
|
||||
//! - `tracy-allocator`: Enables [Tracy](https://github.com/wolfpld/tracy) profiler allocator
|
||||
//! integration for memory profiling.
|
||||
//! - `snmalloc`: Uses [snmalloc](https://github.com/snmalloc/snmalloc) as the global allocator. Use
|
||||
//! `--no-default-features` when enabling this, as jemalloc takes precedence.
|
||||
//! - `snmalloc`: Uses [snmalloc](https://github.com/microsoft/snmalloc) as the global allocator.
|
||||
//! Use `--no-default-features` when enabling this, as jemalloc takes precedence.
|
||||
//! - `snmalloc-native`: Uses snmalloc with native CPU optimizations. Use `--no-default-features`
|
||||
//! when enabling this.
|
||||
//!
|
||||
|
||||
@@ -206,11 +206,33 @@ impl DeferredTrieData {
|
||||
Default::default(), // prefix_sets are per-block, not cumulative
|
||||
);
|
||||
// Only trigger COW clone if there's actually data to add.
|
||||
if !sorted_hashed_state.is_empty() {
|
||||
Arc::make_mut(&mut overlay.state).extend_ref_and_sort(&sorted_hashed_state);
|
||||
#[cfg(feature = "rayon")]
|
||||
{
|
||||
rayon::join(
|
||||
|| {
|
||||
if !sorted_hashed_state.is_empty() {
|
||||
Arc::make_mut(&mut overlay.state)
|
||||
.extend_ref_and_sort(&sorted_hashed_state);
|
||||
}
|
||||
},
|
||||
|| {
|
||||
if !sorted_trie_updates.is_empty() {
|
||||
Arc::make_mut(&mut overlay.nodes)
|
||||
.extend_ref_and_sort(&sorted_trie_updates);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
if !sorted_trie_updates.is_empty() {
|
||||
Arc::make_mut(&mut overlay.nodes).extend_ref_and_sort(&sorted_trie_updates);
|
||||
#[cfg(not(feature = "rayon"))]
|
||||
{
|
||||
if !sorted_hashed_state.is_empty() {
|
||||
Arc::make_mut(&mut overlay.state)
|
||||
.extend_ref_and_sort(&sorted_hashed_state);
|
||||
}
|
||||
if !sorted_trie_updates.is_empty() {
|
||||
Arc::make_mut(&mut overlay.nodes)
|
||||
.extend_ref_and_sort(&sorted_trie_updates);
|
||||
}
|
||||
}
|
||||
overlay
|
||||
}
|
||||
@@ -243,53 +265,8 @@ impl DeferredTrieData {
|
||||
/// In normal operation, the parent always has a cached overlay and this
|
||||
/// function is never called.
|
||||
///
|
||||
/// When the `rayon` feature is enabled, uses parallel collection and merge:
|
||||
/// 1. Collects ancestor data in parallel (each `wait_cloned()` may compute)
|
||||
/// 2. Merges hashed state and trie updates in parallel with each other
|
||||
/// 3. Uses tree reduction within each merge for O(log n) depth
|
||||
#[cfg(feature = "rayon")]
|
||||
fn merge_ancestors_into_overlay(
|
||||
ancestors: &[Self],
|
||||
sorted_hashed_state: &HashedPostStateSorted,
|
||||
sorted_trie_updates: &TrieUpdatesSorted,
|
||||
) -> TrieInputSorted {
|
||||
// Early exit: no ancestors means just wrap current block's data
|
||||
if ancestors.is_empty() {
|
||||
return TrieInputSorted::new(
|
||||
Arc::new(sorted_trie_updates.clone()),
|
||||
Arc::new(sorted_hashed_state.clone()),
|
||||
Default::default(),
|
||||
);
|
||||
}
|
||||
|
||||
// Collect ancestor data, unzipping states and updates into Arc slices
|
||||
let (states, updates): (Vec<_>, Vec<_>) = ancestors
|
||||
.iter()
|
||||
.map(|a| {
|
||||
let data = a.wait_cloned();
|
||||
(data.hashed_state, data.trie_updates)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
// Merge state and nodes in parallel with each other using tree reduction
|
||||
let (state, nodes) = rayon::join(
|
||||
|| {
|
||||
let mut merged = HashedPostStateSorted::merge_parallel(&states);
|
||||
merged.extend_ref_and_sort(sorted_hashed_state);
|
||||
merged
|
||||
},
|
||||
|| {
|
||||
let mut merged = TrieUpdatesSorted::merge_parallel(&updates);
|
||||
merged.extend_ref_and_sort(sorted_trie_updates);
|
||||
merged
|
||||
},
|
||||
);
|
||||
|
||||
TrieInputSorted::new(Arc::new(nodes), Arc::new(state), Default::default())
|
||||
}
|
||||
|
||||
/// Merge all ancestors and current block's data into a single overlay (sequential fallback).
|
||||
#[cfg(not(feature = "rayon"))]
|
||||
/// Iterates ancestors oldest -> newest, then extends with current block's data,
|
||||
/// so later state takes precedence.
|
||||
fn merge_ancestors_into_overlay(
|
||||
ancestors: &[Self],
|
||||
sorted_hashed_state: &HashedPostStateSorted,
|
||||
@@ -307,8 +284,17 @@ impl DeferredTrieData {
|
||||
}
|
||||
|
||||
// Extend with current block's sorted data last (takes precedence)
|
||||
state_mut.extend_ref_and_sort(sorted_hashed_state);
|
||||
nodes_mut.extend_ref_and_sort(sorted_trie_updates);
|
||||
#[cfg(feature = "rayon")]
|
||||
rayon::join(
|
||||
|| state_mut.extend_ref_and_sort(sorted_hashed_state),
|
||||
|| nodes_mut.extend_ref_and_sort(sorted_trie_updates),
|
||||
);
|
||||
|
||||
#[cfg(not(feature = "rayon"))]
|
||||
{
|
||||
state_mut.extend_ref_and_sort(sorted_hashed_state);
|
||||
nodes_mut.extend_ref_and_sort(sorted_trie_updates);
|
||||
}
|
||||
|
||||
overlay
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@ use reth_primitives_traits::{
|
||||
SignedTransaction,
|
||||
};
|
||||
use reth_storage_api::StateProviderBox;
|
||||
use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, LazyTrieData, TrieInputSorted};
|
||||
use reth_trie::{
|
||||
updates::TrieUpdatesSorted, HashedPostStateSorted, LazyTrieData, SortedTrieData,
|
||||
TrieInputSorted,
|
||||
};
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Instant};
|
||||
use tokio::sync::{broadcast, watch};
|
||||
|
||||
@@ -948,22 +951,36 @@ impl<N: NodePrimitives<SignedTx: SignedTransaction>> NewCanonicalChain<N> {
|
||||
match blocks {
|
||||
[] => Chain::default(),
|
||||
[first, rest @ ..] => {
|
||||
let trie_data_handle = first.trie_data_handle();
|
||||
let mut chain = Chain::from_block(
|
||||
first.recovered_block().clone(),
|
||||
ExecutionOutcome::from((
|
||||
first.execution_outcome().clone(),
|
||||
first.block_number(),
|
||||
)),
|
||||
LazyTrieData::ready(first.hashed_state(), first.trie_updates()),
|
||||
LazyTrieData::deferred(move || {
|
||||
let trie_data = trie_data_handle.wait_cloned();
|
||||
SortedTrieData {
|
||||
hashed_state: trie_data.hashed_state,
|
||||
trie_updates: trie_data.trie_updates,
|
||||
}
|
||||
}),
|
||||
);
|
||||
for exec in rest {
|
||||
let trie_data_handle = exec.trie_data_handle();
|
||||
chain.append_block(
|
||||
exec.recovered_block().clone(),
|
||||
ExecutionOutcome::from((
|
||||
exec.execution_outcome().clone(),
|
||||
exec.block_number(),
|
||||
)),
|
||||
LazyTrieData::ready(exec.hashed_state(), exec.trie_updates()),
|
||||
LazyTrieData::deferred(move || {
|
||||
let trie_data = trie_data_handle.wait_cloned();
|
||||
SortedTrieData {
|
||||
hashed_state: trie_data.hashed_state,
|
||||
trie_updates: trie_data.trie_updates,
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
chain
|
||||
|
||||
@@ -25,6 +25,7 @@ pub use alloy_chains::{Chain, ChainKind, NamedChain};
|
||||
/// Re-export for convenience
|
||||
pub use reth_ethereum_forks::*;
|
||||
|
||||
pub use alloy_evm::EvmLimitParams;
|
||||
pub use api::EthChainSpec;
|
||||
pub use info::ChainInfo;
|
||||
#[cfg(any(test, feature = "test-utils"))]
|
||||
|
||||
@@ -50,6 +50,7 @@ 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-storage-api.workspace = true
|
||||
reth-trie = { workspace = true, features = ["metrics"] }
|
||||
reth-trie-db = { workspace = true, features = ["metrics"] }
|
||||
reth-trie-common.workspace = true
|
||||
|
||||
@@ -21,6 +21,7 @@ use reth_node_builder::NodeTypesWithDB;
|
||||
use reth_primitives_traits::ValueWithSubKey;
|
||||
use reth_provider::{providers::ProviderNodeTypes, ChangeSetReader, StaticFileProviderFactory};
|
||||
use reth_static_file_types::StaticFileSegment;
|
||||
use reth_storage_api::StorageChangeSetReader;
|
||||
use tracing::error;
|
||||
|
||||
/// The arguments for the `reth db get` command
|
||||
@@ -82,6 +83,33 @@ impl Command {
|
||||
table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })?
|
||||
}
|
||||
Subcommand::StaticFile { segment, key, subkey, raw } => {
|
||||
if let StaticFileSegment::StorageChangeSets = segment {
|
||||
let storage_key =
|
||||
table_subkey::<tables::StorageChangeSets>(subkey.as_deref()).ok();
|
||||
let key = table_key::<tables::StorageChangeSets>(&key)?;
|
||||
|
||||
let provider = tool.provider_factory.static_file_provider();
|
||||
|
||||
if let Some(storage_key) = storage_key {
|
||||
let entry = provider.get_storage_before_block(
|
||||
key.block_number(),
|
||||
key.address(),
|
||||
storage_key,
|
||||
)?;
|
||||
|
||||
if let Some(entry) = entry {
|
||||
println!("{}", serde_json::to_string_pretty(&entry)?);
|
||||
} else {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let changesets = provider.storage_changeset(key.block_number())?;
|
||||
println!("{}", serde_json::to_string_pretty(&changesets)?);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (key, subkey, mask): (u64, _, _) = match segment {
|
||||
StaticFileSegment::Headers => (
|
||||
table_key::<tables::Headers>(&key)?,
|
||||
@@ -112,6 +140,9 @@ impl Command {
|
||||
AccountChangesetMask::MASK,
|
||||
)
|
||||
}
|
||||
StaticFileSegment::StorageChangeSets => {
|
||||
unreachable!("storage changesets handled above");
|
||||
}
|
||||
};
|
||||
|
||||
// handle account changesets differently if a subkey is provided.
|
||||
@@ -190,6 +221,9 @@ impl Command {
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
unreachable!("account changeset static files are special cased before this match")
|
||||
}
|
||||
StaticFileSegment::StorageChangeSets => {
|
||||
unreachable!("storage changeset static files are special cased before this match")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,19 +61,21 @@ impl Command {
|
||||
}
|
||||
|
||||
/// Generate [`ListFilter`] from command.
|
||||
pub fn list_filter(&self) -> ListFilter {
|
||||
let search = self
|
||||
.search
|
||||
.as_ref()
|
||||
.map(|search| {
|
||||
pub fn list_filter(&self) -> eyre::Result<ListFilter> {
|
||||
let search = match self.search.as_deref() {
|
||||
Some(search) => {
|
||||
if let Some(search) = search.strip_prefix("0x") {
|
||||
return hex::decode(search).unwrap()
|
||||
hex::decode(search).wrap_err(
|
||||
"Invalid hex content after 0x prefix in --search (expected valid hex like 0xdeadbeef).",
|
||||
)?
|
||||
} else {
|
||||
search.as_bytes().to_vec()
|
||||
}
|
||||
search.as_bytes().to_vec()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
}
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
ListFilter {
|
||||
Ok(ListFilter {
|
||||
skip: self.skip,
|
||||
len: self.len,
|
||||
search,
|
||||
@@ -82,7 +84,7 @@ impl Command {
|
||||
min_value_size: self.min_value_size,
|
||||
reverse: self.reverse,
|
||||
only_count: self.count,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +117,7 @@ impl<N: NodeTypes> TableViewer<()> for ListTableViewer<'_, N> {
|
||||
}
|
||||
|
||||
|
||||
let list_filter = self.args.list_filter();
|
||||
let list_filter = self.args.list_filter()?;
|
||||
|
||||
if self.args.json || self.args.count {
|
||||
let (list, count) = self.tool.list::<T>(&list_filter)?;
|
||||
|
||||
@@ -162,7 +162,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C>
|
||||
let access_rights =
|
||||
if command.dry_run { AccessRights::RO } else { AccessRights::RW };
|
||||
db_exec!(self.env, tool, N, access_rights, {
|
||||
command.execute(&tool, ctx.task_executor.clone(), &data_dir)?;
|
||||
command.execute(&tool, ctx.task_executor, &data_dir)?;
|
||||
});
|
||||
}
|
||||
Subcommands::StaticFileHeader(command) => {
|
||||
|
||||
@@ -69,6 +69,11 @@ pub enum SetCommand {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
/// Store storage changesets in static files instead of the database
|
||||
StorageChangesets {
|
||||
#[clap(action(ArgAction::Set))]
|
||||
value: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@@ -115,6 +120,7 @@ impl Command {
|
||||
transaction_hash_numbers_in_rocksdb: _,
|
||||
account_history_in_rocksdb: _,
|
||||
account_changesets_in_static_files: _,
|
||||
storage_changesets_in_static_files: _,
|
||||
} = settings.unwrap_or_else(StorageSettings::legacy);
|
||||
|
||||
// Update the setting based on the key
|
||||
@@ -167,6 +173,14 @@ impl Command {
|
||||
settings.account_history_in_rocksdb = value;
|
||||
println!("Set account_history_in_rocksdb = {}", value);
|
||||
}
|
||||
SetCommand::StorageChangesets { value } => {
|
||||
if settings.storage_changesets_in_static_files == value {
|
||||
println!("storage_changesets_in_static_files is already set to {}", value);
|
||||
return Ok(());
|
||||
}
|
||||
settings.storage_changesets_in_static_files = value;
|
||||
println!("Set storage_changesets_in_static_files = {}", value);
|
||||
}
|
||||
}
|
||||
|
||||
// Write updated settings
|
||||
|
||||
@@ -205,6 +205,16 @@ impl Command {
|
||||
.add_cell(Cell::new(human_bytes(total_size as f64)))
|
||||
.add_cell(Cell::new(human_bytes(total_pending as f64)));
|
||||
table.add_row(row);
|
||||
|
||||
let wal_size = tool.provider_factory.rocksdb_provider().wal_size_bytes();
|
||||
let mut row = Row::new();
|
||||
row.add_cell(Cell::new("WAL"))
|
||||
.add_cell(Cell::new(""))
|
||||
.add_cell(Cell::new(""))
|
||||
.add_cell(Cell::new(""))
|
||||
.add_cell(Cell::new(human_bytes(wal_size as f64)))
|
||||
.add_cell(Cell::new(""));
|
||||
table.add_row(row);
|
||||
}
|
||||
|
||||
table
|
||||
|
||||
@@ -26,6 +26,14 @@ pub struct ImportCommand<C: ChainSpecParser> {
|
||||
#[arg(long, value_name = "CHUNK_LEN", verbatim_doc_comment)]
|
||||
chunk_len: Option<u64>,
|
||||
|
||||
/// Fail immediately when an invalid block is encountered.
|
||||
///
|
||||
/// By default, the import will stop at the last valid block if an invalid block is
|
||||
/// encountered during execution or validation, leaving the database at the last valid
|
||||
/// block state. When this flag is set, the import will instead fail with an error.
|
||||
#[arg(long, verbatim_doc_comment)]
|
||||
fail_on_invalid_block: bool,
|
||||
|
||||
/// The path(s) to block file(s) for import.
|
||||
///
|
||||
/// The online stages (headers and bodies) are replaced by a file import, after which the
|
||||
@@ -52,7 +60,11 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportComm
|
||||
|
||||
info!(target: "reth::cli", "Starting import of {} file(s)", self.paths.len());
|
||||
|
||||
let import_config = ImportConfig { no_state: self.no_state, chunk_len: self.chunk_len };
|
||||
let import_config = ImportConfig {
|
||||
no_state: self.no_state,
|
||||
chunk_len: self.chunk_len,
|
||||
fail_on_invalid_block: self.fail_on_invalid_block,
|
||||
};
|
||||
|
||||
let executor = components.evm_config().clone();
|
||||
let consensus = Arc::new(components.consensus().clone());
|
||||
@@ -81,7 +93,20 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportComm
|
||||
total_decoded_blocks += result.total_decoded_blocks;
|
||||
total_decoded_txns += result.total_decoded_txns;
|
||||
|
||||
if !result.is_complete() {
|
||||
// Check if we stopped due to an invalid block
|
||||
if result.stopped_on_invalid_block {
|
||||
info!(target: "reth::cli",
|
||||
"Stopped at last valid block {} due to invalid block {} in file: {}. Imported {} blocks, {} transactions",
|
||||
result.last_valid_block.unwrap_or(0),
|
||||
result.bad_block.unwrap_or(0),
|
||||
path.display(),
|
||||
result.total_imported_blocks,
|
||||
result.total_imported_txns);
|
||||
// Stop importing further files and exit successfully
|
||||
break;
|
||||
}
|
||||
|
||||
if !result.is_successful() {
|
||||
return Err(eyre::eyre!(
|
||||
"Chain was partially imported from file: {}. Imported {}/{} blocks, {}/{} transactions",
|
||||
path.display(),
|
||||
@@ -98,7 +123,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> ImportComm
|
||||
}
|
||||
|
||||
info!(target: "reth::cli",
|
||||
"All files imported successfully. Total: {}/{} blocks, {}/{} transactions",
|
||||
"Import complete. Total: {}/{} blocks, {}/{} transactions",
|
||||
total_imported_blocks, total_decoded_blocks, total_imported_txns, total_decoded_txns);
|
||||
|
||||
Ok(())
|
||||
@@ -139,4 +164,20 @@ mod tests {
|
||||
assert_eq!(args.paths[1], PathBuf::from("file2.rlp"));
|
||||
assert_eq!(args.paths[2], PathBuf::from("file3.rlp"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_import_command_with_fail_on_invalid_block() {
|
||||
let args: ImportCommand<EthereumChainSpecParser> =
|
||||
ImportCommand::parse_from(["reth", "--fail-on-invalid-block", "chain.rlp"]);
|
||||
assert!(args.fail_on_invalid_block);
|
||||
assert_eq!(args.paths.len(), 1);
|
||||
assert_eq!(args.paths[0], PathBuf::from("chain.rlp"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_import_command_default_stops_on_invalid_block() {
|
||||
let args: ImportCommand<EthereumChainSpecParser> =
|
||||
ImportCommand::parse_from(["reth", "chain.rlp"]);
|
||||
assert!(!args.fail_on_invalid_block);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ use reth_provider::{
|
||||
StageCheckpointReader,
|
||||
};
|
||||
use reth_prune::PruneModes;
|
||||
use reth_stages::{prelude::*, Pipeline, StageId, StageSet};
|
||||
use reth_stages::{prelude::*, ControlFlow, Pipeline, StageId, StageSet};
|
||||
use reth_static_file::StaticFileProducer;
|
||||
use std::{path::Path, sync::Arc};
|
||||
use tokio::sync::watch;
|
||||
use tracing::{debug, error, info};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
/// Configuration for importing blocks from RLP files.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -35,6 +35,9 @@ pub struct ImportConfig {
|
||||
pub no_state: bool,
|
||||
/// Chunk byte length to read from file.
|
||||
pub chunk_len: Option<u64>,
|
||||
/// If true, fail immediately when an invalid block is encountered.
|
||||
/// By default (false), the import stops at the last valid block and exits successfully.
|
||||
pub fail_on_invalid_block: bool,
|
||||
}
|
||||
|
||||
/// Result of an import operation.
|
||||
@@ -48,6 +51,12 @@ pub struct ImportResult {
|
||||
pub total_imported_blocks: usize,
|
||||
/// Total number of transactions imported into the database.
|
||||
pub total_imported_txns: usize,
|
||||
/// Whether the import was stopped due to an invalid block.
|
||||
pub stopped_on_invalid_block: bool,
|
||||
/// The block number that was invalid, if any.
|
||||
pub bad_block: Option<u64>,
|
||||
/// The last valid block number when stopped due to invalid block.
|
||||
pub last_valid_block: Option<u64>,
|
||||
}
|
||||
|
||||
impl ImportResult {
|
||||
@@ -56,6 +65,14 @@ impl ImportResult {
|
||||
self.total_decoded_blocks == self.total_imported_blocks &&
|
||||
self.total_decoded_txns == self.total_imported_txns
|
||||
}
|
||||
|
||||
/// Returns true if the import was successful, considering stop-on-invalid-block mode.
|
||||
///
|
||||
/// In stop-on-invalid-block mode, a partial import is considered successful if we
|
||||
/// stopped due to an invalid block (leaving the DB at the last valid block).
|
||||
pub fn is_successful(&self) -> bool {
|
||||
self.is_complete() || self.stopped_on_invalid_block
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports blocks from an RLP-encoded file into the database.
|
||||
@@ -103,6 +120,11 @@ where
|
||||
let static_file_producer =
|
||||
StaticFileProducer::new(provider_factory.clone(), PruneModes::default());
|
||||
|
||||
// Track if we stopped due to an invalid block
|
||||
let mut stopped_on_invalid_block = false;
|
||||
let mut bad_block_number: Option<u64> = None;
|
||||
let mut last_valid_block_number: Option<u64> = None;
|
||||
|
||||
while let Some(file_client) =
|
||||
reader.next_chunk::<BlockTy<N>>(consensus.clone(), Some(sealed_header)).await?
|
||||
{
|
||||
@@ -137,12 +159,51 @@ where
|
||||
|
||||
// Run pipeline
|
||||
info!(target: "reth::import", "Starting sync pipeline");
|
||||
tokio::select! {
|
||||
res = pipeline.run() => res?,
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!(target: "reth::import", "Import interrupted by user");
|
||||
break;
|
||||
},
|
||||
if import_config.fail_on_invalid_block {
|
||||
// Original behavior: fail on unwind
|
||||
tokio::select! {
|
||||
res = pipeline.run() => res?,
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!(target: "reth::import", "Import interrupted by user");
|
||||
break;
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// Default behavior: Use run_loop() to handle unwinds gracefully
|
||||
let result = tokio::select! {
|
||||
res = pipeline.run_loop() => res,
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!(target: "reth::import", "Import interrupted by user");
|
||||
break;
|
||||
},
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(ControlFlow::Unwind { target, bad_block }) => {
|
||||
// An invalid block was encountered; stop at last valid block
|
||||
let bad = bad_block.block.number;
|
||||
warn!(
|
||||
target: "reth::import",
|
||||
bad_block = bad,
|
||||
last_valid_block = target,
|
||||
"Invalid block encountered during import; stopping at last valid block"
|
||||
);
|
||||
stopped_on_invalid_block = true;
|
||||
bad_block_number = Some(bad);
|
||||
last_valid_block_number = Some(target);
|
||||
break;
|
||||
}
|
||||
Ok(ControlFlow::Continue { block_number }) => {
|
||||
debug!(target: "reth::import", block_number, "Pipeline chunk completed");
|
||||
}
|
||||
Ok(ControlFlow::NoProgress { block_number }) => {
|
||||
debug!(target: "reth::import", ?block_number, "Pipeline made no progress");
|
||||
}
|
||||
Err(e) => {
|
||||
// Propagate other pipeline errors
|
||||
return Err(e.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed_header = provider_factory
|
||||
@@ -160,9 +221,20 @@ where
|
||||
total_decoded_txns,
|
||||
total_imported_blocks,
|
||||
total_imported_txns,
|
||||
stopped_on_invalid_block,
|
||||
bad_block: bad_block_number,
|
||||
last_valid_block: last_valid_block_number,
|
||||
};
|
||||
|
||||
if !result.is_complete() {
|
||||
if result.stopped_on_invalid_block {
|
||||
info!(target: "reth::import",
|
||||
total_imported_blocks,
|
||||
total_imported_txns,
|
||||
bad_block = ?result.bad_block,
|
||||
last_valid_block = ?result.last_valid_block,
|
||||
"Import stopped at last valid block due to invalid block"
|
||||
);
|
||||
} else if !result.is_complete() {
|
||||
error!(target: "reth::import",
|
||||
total_decoded_blocks,
|
||||
total_imported_blocks,
|
||||
|
||||
34
crates/cli/commands/src/p2p/enode.rs
Normal file
34
crates/cli/commands/src/p2p/enode.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
//! Enode identifier command
|
||||
|
||||
use clap::Parser;
|
||||
use reth_cli_util::get_secret_key;
|
||||
use reth_network_peers::NodeRecord;
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
/// Print the enode identifier for a given secret key.
|
||||
#[derive(Parser, Debug)]
|
||||
pub struct Command {
|
||||
/// Path to the secret key file for discovery.
|
||||
pub discovery_secret: PathBuf,
|
||||
|
||||
/// Optional IP address to include in the enode URL.
|
||||
///
|
||||
/// If not provided, defaults to 0.0.0.0.
|
||||
#[arg(long)]
|
||||
pub ip: Option<IpAddr>,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
/// Execute the enode command.
|
||||
pub fn execute(self) -> eyre::Result<()> {
|
||||
let sk = get_secret_key(&self.discovery_secret)?;
|
||||
let ip = self.ip.unwrap_or(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
|
||||
let addr = SocketAddr::new(ip, 30303);
|
||||
let enr = NodeRecord::from_secret_key(addr, &sk);
|
||||
println!("{enr}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ use reth_node_core::{
|
||||
};
|
||||
|
||||
pub mod bootnode;
|
||||
pub mod enode;
|
||||
pub mod rlpx;
|
||||
|
||||
/// `reth p2p` command
|
||||
@@ -85,6 +86,9 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + Hardforks + EthereumHardforks>
|
||||
Subcommands::Bootnode(command) => {
|
||||
command.execute().await?;
|
||||
}
|
||||
Subcommands::Enode(command) => {
|
||||
command.execute()?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -99,6 +103,7 @@ impl<C: ChainSpecParser> Command<C> {
|
||||
Subcommands::Body { args, .. } => Some(&args.chain),
|
||||
Subcommands::Rlpx(_) => None,
|
||||
Subcommands::Bootnode(_) => None,
|
||||
Subcommands::Enode(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,6 +131,8 @@ pub enum Subcommands<C: ChainSpecParser> {
|
||||
Rlpx(rlpx::Command),
|
||||
/// Bootnode command
|
||||
Bootnode(bootnode::Command),
|
||||
/// Print enode identifier
|
||||
Enode(enode::Command),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
@@ -225,4 +232,16 @@ mod tests {
|
||||
let _args: Command<EthereumChainSpecParser> =
|
||||
Command::parse_from(["reth", "body", "--chain", "mainnet", "1000"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_enode_cmd() {
|
||||
let _args: Command<EthereumChainSpecParser> =
|
||||
Command::parse_from(["reth", "enode", "/tmp/secret"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_enode_cmd_with_ip() {
|
||||
let _args: Command<EthereumChainSpecParser> =
|
||||
Command::parse_from(["reth", "enode", "/tmp/secret", "--ip", "192.168.1.1"]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ use reth_db_common::{
|
||||
use reth_node_api::{HeaderTy, ReceiptTy, TxTy};
|
||||
use reth_node_core::args::StageEnum;
|
||||
use reth_provider::{
|
||||
DBProvider, DatabaseProviderFactory, StaticFileProviderFactory, StaticFileWriter,
|
||||
DBProvider, RocksDBProviderFactory, StaticFileProviderFactory, StaticFileWriter,
|
||||
StorageSettingsCache,
|
||||
};
|
||||
use reth_prune::PruneSegment;
|
||||
use reth_stages::StageId;
|
||||
@@ -90,11 +91,14 @@ impl<C: ChainSpecParser> Command<C> {
|
||||
StaticFileSegment::AccountChangeSets => {
|
||||
writer.prune_account_changesets(highest_block)?;
|
||||
}
|
||||
StaticFileSegment::StorageChangeSets => {
|
||||
writer.prune_storage_changesets(highest_block)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let provider_rw = tool.provider_factory.database_provider_rw()?;
|
||||
let provider_rw = tool.provider_factory.unwind_provider_rw()?;
|
||||
let tx = provider_rw.tx_ref();
|
||||
|
||||
match self.stage {
|
||||
@@ -168,8 +172,20 @@ impl<C: ChainSpecParser> Command<C> {
|
||||
)?;
|
||||
}
|
||||
StageEnum::AccountHistory | StageEnum::StorageHistory => {
|
||||
tx.clear::<tables::AccountsHistory>()?;
|
||||
tx.clear::<tables::StoragesHistory>()?;
|
||||
let settings = provider_rw.cached_storage_settings();
|
||||
let rocksdb = tool.provider_factory.rocksdb_provider();
|
||||
|
||||
if settings.account_history_in_rocksdb {
|
||||
rocksdb.clear::<tables::AccountsHistory>()?;
|
||||
} else {
|
||||
tx.clear::<tables::AccountsHistory>()?;
|
||||
}
|
||||
|
||||
if settings.storages_history_in_rocksdb {
|
||||
rocksdb.clear::<tables::StoragesHistory>()?;
|
||||
} else {
|
||||
tx.clear::<tables::StoragesHistory>()?;
|
||||
}
|
||||
|
||||
reset_stage_checkpoint(tx, StageId::IndexAccountHistory)?;
|
||||
reset_stage_checkpoint(tx, StageId::IndexStorageHistory)?;
|
||||
@@ -177,7 +193,14 @@ impl<C: ChainSpecParser> Command<C> {
|
||||
insert_genesis_history(&provider_rw, self.env.chain.genesis().alloc.iter())?;
|
||||
}
|
||||
StageEnum::TxLookup => {
|
||||
tx.clear::<tables::TransactionHashNumbers>()?;
|
||||
if provider_rw.cached_storage_settings().transaction_hash_numbers_in_rocksdb {
|
||||
tool.provider_factory
|
||||
.rocksdb_provider()
|
||||
.clear::<tables::TransactionHashNumbers>()?;
|
||||
} else {
|
||||
tx.clear::<tables::TransactionHashNumbers>()?;
|
||||
}
|
||||
|
||||
reset_prune_checkpoint(tx, PruneSegment::TransactionLookup)?;
|
||||
|
||||
reset_stage_checkpoint(tx, StageId::TransactionLookup)?;
|
||||
|
||||
@@ -438,6 +438,8 @@ pub struct BlocksPerFileConfig {
|
||||
pub transaction_senders: Option<u64>,
|
||||
/// Number of blocks per file for the account changesets segment.
|
||||
pub account_change_sets: Option<u64>,
|
||||
/// Number of blocks per file for the storage changesets segment.
|
||||
pub storage_change_sets: Option<u64>,
|
||||
}
|
||||
|
||||
impl StaticFilesConfig {
|
||||
@@ -451,6 +453,7 @@ impl StaticFilesConfig {
|
||||
receipts,
|
||||
transaction_senders,
|
||||
account_change_sets,
|
||||
storage_change_sets,
|
||||
} = self.blocks_per_file;
|
||||
eyre::ensure!(headers != Some(0), "Headers segment blocks per file must be greater than 0");
|
||||
eyre::ensure!(
|
||||
@@ -469,6 +472,10 @@ impl StaticFilesConfig {
|
||||
account_change_sets != Some(0),
|
||||
"Account changesets segment blocks per file must be greater than 0"
|
||||
);
|
||||
eyre::ensure!(
|
||||
storage_change_sets != Some(0),
|
||||
"Storage changesets segment blocks per file must be greater than 0"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -480,6 +487,7 @@ impl StaticFilesConfig {
|
||||
receipts,
|
||||
transaction_senders,
|
||||
account_change_sets,
|
||||
storage_change_sets,
|
||||
} = self.blocks_per_file;
|
||||
|
||||
let mut map = StaticFileMap::default();
|
||||
@@ -492,6 +500,7 @@ impl StaticFilesConfig {
|
||||
StaticFileSegment::Receipts => receipts,
|
||||
StaticFileSegment::TransactionSenders => transaction_senders,
|
||||
StaticFileSegment::AccountChangeSets => account_change_sets,
|
||||
StaticFileSegment::StorageChangeSets => storage_change_sets,
|
||||
};
|
||||
|
||||
if let Some(blocks_per_file) = blocks_per_file {
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
//! Collection of methods for block validation.
|
||||
|
||||
use alloy_consensus::{BlockHeader as _, Transaction, EMPTY_OMMER_ROOT_HASH};
|
||||
use alloy_consensus::{BlockHeader as _, 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};
|
||||
use reth_consensus::ConsensusError;
|
||||
use reth_primitives_traits::{
|
||||
constants::{
|
||||
GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MAX_TX_GAS_LIMIT_OSAKA, MINIMUM_GAS_LIMIT,
|
||||
},
|
||||
transaction::TxHashRef,
|
||||
constants::{GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MINIMUM_GAS_LIMIT},
|
||||
Block, BlockBody, BlockHeader, GotExpected, SealedBlock, SealedHeader,
|
||||
};
|
||||
|
||||
@@ -146,7 +143,7 @@ pub fn validate_block_pre_execution<B, ChainSpec>(
|
||||
) -> Result<(), ConsensusError>
|
||||
where
|
||||
B: Block,
|
||||
ChainSpec: EthereumHardforks,
|
||||
ChainSpec: EthChainSpec + EthereumHardforks,
|
||||
{
|
||||
post_merge_hardfork_fields(block, chain_spec)?;
|
||||
|
||||
@@ -154,19 +151,6 @@ where
|
||||
if let Err(error) = block.ensure_transaction_root_valid() {
|
||||
return Err(ConsensusError::BodyTransactionRootDiff(error.into()))
|
||||
}
|
||||
// EIP-7825 validation
|
||||
if chain_spec.is_osaka_active_at_timestamp(block.timestamp()) {
|
||||
for tx in block.body().transactions() {
|
||||
if tx.gas_limit() > MAX_TX_GAS_LIMIT_OSAKA {
|
||||
return Err(TxGasLimitTooHighErr {
|
||||
tx_hash: *tx.tx_hash(),
|
||||
gas_limit: tx.gas_limit(),
|
||||
max_allowed: MAX_TX_GAS_LIMIT_OSAKA,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -72,3 +72,11 @@ derive_more.workspace = true
|
||||
[[test]]
|
||||
name = "e2e_testsuite"
|
||||
path = "tests/e2e-testsuite/main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "rocksdb"
|
||||
path = "tests/rocksdb/main.rs"
|
||||
required-features = ["edge"]
|
||||
|
||||
[features]
|
||||
edge = ["reth-node-core/edge", "reth-provider/rocksdb", "reth-cli-commands/edge"]
|
||||
|
||||
@@ -103,7 +103,10 @@ where
|
||||
N: NodeBuilderHelper,
|
||||
{
|
||||
E2ETestSetupBuilder::new(num_nodes, chain_spec, attributes_generator)
|
||||
.with_tree_config_modifier(move |_| tree_config.clone())
|
||||
.with_tree_config_modifier(move |base| {
|
||||
// Apply caller's tree_config but preserve the small cache size from base
|
||||
tree_config.clone().with_cross_block_cache_size(base.cross_block_cache_size())
|
||||
})
|
||||
.with_node_config_modifier(move |config| config.set_dev(is_dev))
|
||||
.with_connect_nodes(connect_nodes)
|
||||
.build()
|
||||
|
||||
@@ -112,11 +112,13 @@ where
|
||||
..NetworkArgs::default()
|
||||
};
|
||||
|
||||
// Apply tree config modifier if present
|
||||
// Apply tree config modifier if present, with test-appropriate defaults
|
||||
let base_tree_config =
|
||||
reth_node_api::TreeConfig::default().with_cross_block_cache_size(1024 * 1024);
|
||||
let tree_config = if let Some(modifier) = self.tree_config_modifier {
|
||||
modifier(reth_node_api::TreeConfig::default())
|
||||
modifier(base_tree_config)
|
||||
} else {
|
||||
reth_node_api::TreeConfig::default()
|
||||
base_tree_config
|
||||
};
|
||||
|
||||
let mut nodes = (0..self.num_nodes)
|
||||
|
||||
@@ -38,6 +38,18 @@ impl TransactionTestContext {
|
||||
signed.encoded_2718().into()
|
||||
}
|
||||
|
||||
/// Creates a transfer with a specific nonce and signs it, returning bytes.
|
||||
/// Uses high `max_fee_per_gas` (1000 gwei) to ensure tx acceptance regardless of basefee.
|
||||
pub async fn transfer_tx_bytes_with_nonce(
|
||||
chain_id: u64,
|
||||
wallet: PrivateKeySigner,
|
||||
nonce: u64,
|
||||
) -> Bytes {
|
||||
let tx = tx(chain_id, 21000, None, None, nonce, Some(1000e9 as u128));
|
||||
let signed = Self::sign_tx(wallet, tx).await;
|
||||
signed.encoded_2718().into()
|
||||
}
|
||||
|
||||
/// Creates a deployment transaction and signs it, returning an envelope.
|
||||
pub async fn deploy_tx(
|
||||
chain_id: u64,
|
||||
|
||||
470
crates/e2e-test-utils/tests/rocksdb/main.rs
Normal file
470
crates/e2e-test-utils/tests/rocksdb/main.rs
Normal file
@@ -0,0 +1,470 @@
|
||||
//! E2E tests for `RocksDB` provider functionality.
|
||||
|
||||
#![cfg(all(feature = "edge", unix))]
|
||||
|
||||
use alloy_consensus::BlockHeader;
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rpc_types_eth::{Transaction, TransactionReceipt};
|
||||
use eyre::Result;
|
||||
use jsonrpsee::core::client::ClientT;
|
||||
use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET};
|
||||
use reth_db::tables;
|
||||
use reth_e2e_test_utils::{transaction::TransactionTestContext, wallet, E2ETestSetupBuilder};
|
||||
use reth_node_builder::NodeConfig;
|
||||
use reth_node_core::args::RocksDbArgs;
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_payload_builder::EthPayloadBuilderAttributes;
|
||||
use reth_provider::RocksDBProviderFactory;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
const ROCKSDB_POLL_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
const ROCKSDB_POLL_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
/// Polls RPC until the given `tx_hash` is visible as pending (not yet mined).
|
||||
/// Prevents race conditions where `advance_block` is called before txs are in the pool.
|
||||
/// Returns the pending transaction.
|
||||
async fn wait_for_pending_tx<C: ClientT>(client: &C, tx_hash: B256) -> Transaction {
|
||||
let start = std::time::Instant::now();
|
||||
loop {
|
||||
let tx: Option<Transaction> = client
|
||||
.request("eth_getTransactionByHash", [tx_hash])
|
||||
.await
|
||||
.expect("RPC request failed");
|
||||
if let Some(tx) = tx {
|
||||
assert!(
|
||||
tx.block_number.is_none(),
|
||||
"Expected pending tx but tx_hash={tx_hash:?} is already mined in block {:?}",
|
||||
tx.block_number
|
||||
);
|
||||
return tx;
|
||||
}
|
||||
assert!(
|
||||
start.elapsed() < ROCKSDB_POLL_TIMEOUT,
|
||||
"Timed out after {:?} waiting for tx_hash={tx_hash:?} to appear in pending pool",
|
||||
start.elapsed()
|
||||
);
|
||||
tokio::time::sleep(ROCKSDB_POLL_INTERVAL).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Polls `RocksDB` until the given `tx_hash` appears in `TransactionHashNumbers`.
|
||||
/// Returns the `tx_number` on success, or panics on timeout.
|
||||
async fn poll_tx_in_rocksdb<P: RocksDBProviderFactory>(provider: &P, tx_hash: B256) -> u64 {
|
||||
let start = std::time::Instant::now();
|
||||
let mut interval = ROCKSDB_POLL_INTERVAL;
|
||||
loop {
|
||||
// Re-acquire handle each iteration to avoid stale snapshot reads
|
||||
let rocksdb = provider.rocksdb_provider();
|
||||
let tx_number: Option<u64> =
|
||||
rocksdb.get::<tables::TransactionHashNumbers>(tx_hash).expect("RocksDB get failed");
|
||||
if let Some(n) = tx_number {
|
||||
return n;
|
||||
}
|
||||
assert!(
|
||||
start.elapsed() < ROCKSDB_POLL_TIMEOUT,
|
||||
"Timed out after {:?} waiting for tx_hash={tx_hash:?} in RocksDB",
|
||||
start.elapsed()
|
||||
);
|
||||
tokio::time::sleep(interval).await;
|
||||
// Simple backoff: 50ms -> 100ms -> 200ms (capped)
|
||||
interval = std::cmp::min(interval * 2, Duration::from_millis(200));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the test chain spec for `RocksDB` tests.
|
||||
fn test_chain_spec() -> Arc<ChainSpec> {
|
||||
Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(
|
||||
serde_json::from_str(include_str!("../../src/testsuite/assets/genesis.json"))
|
||||
.expect("failed to parse genesis.json"),
|
||||
)
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns test payload attributes for the given timestamp.
|
||||
fn test_attributes_generator(timestamp: u64) -> EthPayloadBuilderAttributes {
|
||||
let attributes = alloy_rpc_types_engine::PayloadAttributes {
|
||||
timestamp,
|
||||
prev_randao: B256::ZERO,
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
|
||||
/// Enables `RocksDB` for `TransactionHashNumbers` table.
|
||||
///
|
||||
/// Note: Static file changesets are disabled because `persistence_threshold(0)` causes
|
||||
/// a race where the static file writer expects sequential block numbers but receives
|
||||
/// them out of order, resulting in `UnexpectedStaticFileBlockNumber` errors.
|
||||
fn with_rocksdb_enabled<C>(mut config: NodeConfig<C>) -> NodeConfig<C> {
|
||||
config.rocksdb = RocksDbArgs { tx_hash: true, ..Default::default() };
|
||||
config.static_files.storage_changesets = false;
|
||||
config.static_files.account_changesets = false;
|
||||
config
|
||||
}
|
||||
|
||||
/// Smoke test: node boots with `RocksDB` routing enabled.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_node_startup() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
|
||||
let (nodes, _tasks, _wallet) =
|
||||
E2ETestSetupBuilder::<EthereumNode, _>::new(1, chain_spec, test_attributes_generator)
|
||||
.with_node_config_modifier(with_rocksdb_enabled)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(nodes.len(), 1);
|
||||
|
||||
// Verify RocksDB provider is functional (can query without error)
|
||||
let rocksdb = nodes[0].inner.provider.rocksdb_provider();
|
||||
let missing_hash = B256::from([0xab; 32]);
|
||||
let result: Option<u64> = rocksdb.get::<tables::TransactionHashNumbers>(missing_hash)?;
|
||||
assert!(result.is_none(), "Missing hash should return None");
|
||||
|
||||
let genesis_hash = nodes[0].block_hash(0);
|
||||
assert_ne!(genesis_hash, B256::ZERO);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Block mining works with `RocksDB` storage.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_block_mining() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
let (mut nodes, _tasks, _wallet) =
|
||||
E2ETestSetupBuilder::<EthereumNode, _>::new(1, chain_spec, test_attributes_generator)
|
||||
.with_node_config_modifier(with_rocksdb_enabled)
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(nodes.len(), 1);
|
||||
|
||||
let genesis_hash = nodes[0].block_hash(0);
|
||||
assert_ne!(genesis_hash, B256::ZERO);
|
||||
|
||||
// Mine 3 blocks with transactions
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
let client = nodes[0].rpc_client().expect("RPC client should be available");
|
||||
|
||||
for i in 1..=3u64 {
|
||||
let raw_tx =
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), i - 1)
|
||||
.await;
|
||||
let tx_hash = nodes[0].rpc.inject_tx(raw_tx).await?;
|
||||
|
||||
// Wait for tx to enter pending pool before mining
|
||||
wait_for_pending_tx(&client, tx_hash).await;
|
||||
|
||||
let payload = nodes[0].advance_block().await?;
|
||||
let block = payload.block();
|
||||
assert_eq!(block.number(), i);
|
||||
assert_ne!(block.hash(), B256::ZERO);
|
||||
|
||||
// Verify tx was actually included in the block
|
||||
let receipt: Option<TransactionReceipt> =
|
||||
client.request("eth_getTransactionReceipt", [tx_hash]).await?;
|
||||
let receipt = receipt.expect("Receipt should exist after mining");
|
||||
assert_eq!(receipt.block_number, Some(i), "Tx should be in block {i}");
|
||||
}
|
||||
|
||||
// Verify all blocks are stored
|
||||
for i in 0..=3 {
|
||||
let block_hash = nodes[0].block_hash(i);
|
||||
assert_ne!(block_hash, B256::ZERO);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tx hash lookup exercises `TransactionHashNumbers` table.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_transaction_queries() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
let (mut nodes, _tasks, _) = E2ETestSetupBuilder::<EthereumNode, _>::new(
|
||||
1,
|
||||
chain_spec.clone(),
|
||||
test_attributes_generator,
|
||||
)
|
||||
.with_node_config_modifier(with_rocksdb_enabled)
|
||||
.with_tree_config_modifier(|config| config.with_persistence_threshold(0))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
assert_eq!(nodes.len(), 1);
|
||||
|
||||
// Inject and mine a transaction
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
let client = nodes[0].rpc_client().expect("RPC client should be available");
|
||||
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(chain_id, signer).await;
|
||||
let tx_hash = nodes[0].rpc.inject_tx(raw_tx).await?;
|
||||
|
||||
// Wait for tx to enter pending pool before mining
|
||||
wait_for_pending_tx(&client, tx_hash).await;
|
||||
|
||||
let payload = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload.block().number(), 1);
|
||||
|
||||
// Query each transaction by hash
|
||||
let tx: Option<Transaction> = client.request("eth_getTransactionByHash", [tx_hash]).await?;
|
||||
let tx = tx.expect("Transaction should be found");
|
||||
assert_eq!(tx.block_number, Some(1));
|
||||
|
||||
let receipt: Option<TransactionReceipt> =
|
||||
client.request("eth_getTransactionReceipt", [tx_hash]).await?;
|
||||
let receipt = receipt.expect("Receipt should be found");
|
||||
assert_eq!(receipt.block_number, Some(1));
|
||||
assert!(receipt.status());
|
||||
|
||||
// Direct RocksDB assertion - poll with timeout since persistence is async
|
||||
let tx_number = poll_tx_in_rocksdb(&nodes[0].inner.provider, tx_hash).await;
|
||||
assert_eq!(tx_number, 0, "First tx should have TxNumber 0");
|
||||
|
||||
// Verify missing hash returns None
|
||||
let missing_hash = B256::from([0xde; 32]);
|
||||
let rocksdb = nodes[0].inner.provider.rocksdb_provider();
|
||||
let missing_tx_number: Option<u64> =
|
||||
rocksdb.get::<tables::TransactionHashNumbers>(missing_hash)?;
|
||||
assert!(missing_tx_number.is_none());
|
||||
|
||||
let missing_tx: Option<Transaction> =
|
||||
client.request("eth_getTransactionByHash", [missing_hash]).await?;
|
||||
assert!(missing_tx.is_none(), "expected no transaction for missing hash");
|
||||
|
||||
let missing_receipt: Option<TransactionReceipt> =
|
||||
client.request("eth_getTransactionReceipt", [missing_hash]).await?;
|
||||
assert!(missing_receipt.is_none(), "expected no receipt for missing hash");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Multiple transactions in the same block are correctly persisted to `RocksDB`.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_multi_tx_same_block() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
let (mut nodes, _tasks, _) = E2ETestSetupBuilder::<EthereumNode, _>::new(
|
||||
1,
|
||||
chain_spec.clone(),
|
||||
test_attributes_generator,
|
||||
)
|
||||
.with_node_config_modifier(with_rocksdb_enabled)
|
||||
.with_tree_config_modifier(|config| config.with_persistence_threshold(0))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
// Create 3 txs from the same wallet with sequential nonces
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
let client = nodes[0].rpc_client().expect("RPC client");
|
||||
|
||||
let mut tx_hashes = Vec::new();
|
||||
for nonce in 0..3 {
|
||||
let raw_tx =
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), nonce)
|
||||
.await;
|
||||
let tx_hash = nodes[0].rpc.inject_tx(raw_tx).await?;
|
||||
tx_hashes.push(tx_hash);
|
||||
}
|
||||
|
||||
// Wait for all txs to appear in pending pool before mining
|
||||
for tx_hash in &tx_hashes {
|
||||
wait_for_pending_tx(&client, *tx_hash).await;
|
||||
}
|
||||
|
||||
// Mine one block containing all 3 txs
|
||||
let payload = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload.block().number(), 1);
|
||||
|
||||
// Verify block contains all 3 txs
|
||||
let block: Option<alloy_rpc_types_eth::Block> =
|
||||
client.request("eth_getBlockByNumber", ("0x1", true)).await?;
|
||||
let block = block.expect("Block 1 should exist");
|
||||
assert_eq!(block.transactions.len(), 3, "Block should contain 3 txs");
|
||||
|
||||
// Verify each tx via RPC
|
||||
for tx_hash in &tx_hashes {
|
||||
let tx: Option<Transaction> = client.request("eth_getTransactionByHash", [tx_hash]).await?;
|
||||
let tx = tx.expect("Transaction should be found");
|
||||
assert_eq!(tx.block_number, Some(1), "All txs should be in block 1");
|
||||
}
|
||||
|
||||
// Poll RocksDB for all tx hashes and collect tx_numbers
|
||||
let mut tx_numbers = Vec::new();
|
||||
for tx_hash in &tx_hashes {
|
||||
let n = poll_tx_in_rocksdb(&nodes[0].inner.provider, *tx_hash).await;
|
||||
tx_numbers.push(n);
|
||||
}
|
||||
|
||||
// Verify tx_numbers form the set {0, 1, 2}
|
||||
tx_numbers.sort();
|
||||
assert_eq!(tx_numbers, vec![0, 1, 2], "TxNumbers should be 0, 1, 2");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Transactions across multiple blocks have globally continuous `tx_numbers`.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_txs_across_blocks() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
let (mut nodes, _tasks, _) = E2ETestSetupBuilder::<EthereumNode, _>::new(
|
||||
1,
|
||||
chain_spec.clone(),
|
||||
test_attributes_generator,
|
||||
)
|
||||
.with_node_config_modifier(with_rocksdb_enabled)
|
||||
.with_tree_config_modifier(|config| config.with_persistence_threshold(0))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
let client = nodes[0].rpc_client().expect("RPC client");
|
||||
|
||||
// Block 1: 2 transactions
|
||||
let tx_hash_0 = nodes[0]
|
||||
.rpc
|
||||
.inject_tx(
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), 0).await,
|
||||
)
|
||||
.await?;
|
||||
let tx_hash_1 = nodes[0]
|
||||
.rpc
|
||||
.inject_tx(
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), 1).await,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Wait for both txs to appear in pending pool
|
||||
wait_for_pending_tx(&client, tx_hash_0).await;
|
||||
wait_for_pending_tx(&client, tx_hash_1).await;
|
||||
|
||||
let payload1 = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload1.block().number(), 1);
|
||||
|
||||
// Block 2: 1 transaction
|
||||
let tx_hash_2 = nodes[0]
|
||||
.rpc
|
||||
.inject_tx(
|
||||
TransactionTestContext::transfer_tx_bytes_with_nonce(chain_id, signer.clone(), 2).await,
|
||||
)
|
||||
.await?;
|
||||
|
||||
wait_for_pending_tx(&client, tx_hash_2).await;
|
||||
|
||||
let payload2 = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload2.block().number(), 2);
|
||||
|
||||
// Verify block contents via RPC
|
||||
let tx0: Option<Transaction> = client.request("eth_getTransactionByHash", [tx_hash_0]).await?;
|
||||
let tx1: Option<Transaction> = client.request("eth_getTransactionByHash", [tx_hash_1]).await?;
|
||||
let tx2: Option<Transaction> = client.request("eth_getTransactionByHash", [tx_hash_2]).await?;
|
||||
|
||||
assert_eq!(tx0.expect("tx0").block_number, Some(1));
|
||||
assert_eq!(tx1.expect("tx1").block_number, Some(1));
|
||||
assert_eq!(tx2.expect("tx2").block_number, Some(2));
|
||||
|
||||
// Poll RocksDB and verify global tx_number continuity
|
||||
let all_tx_hashes = [tx_hash_0, tx_hash_1, tx_hash_2];
|
||||
let mut tx_numbers = Vec::new();
|
||||
for tx_hash in &all_tx_hashes {
|
||||
let n = poll_tx_in_rocksdb(&nodes[0].inner.provider, *tx_hash).await;
|
||||
tx_numbers.push(n);
|
||||
}
|
||||
|
||||
// Verify they form a continuous sequence {0, 1, 2}
|
||||
tx_numbers.sort();
|
||||
assert_eq!(tx_numbers, vec![0, 1, 2], "TxNumbers should be globally continuous: 0, 1, 2");
|
||||
|
||||
// Re-query block 1 txs after block 2 is mined (regression guard)
|
||||
let tx0_again: Option<Transaction> =
|
||||
client.request("eth_getTransactionByHash", [tx_hash_0]).await?;
|
||||
assert!(tx0_again.is_some(), "Block 1 tx should still be queryable after block 2");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pending transactions should NOT appear in `RocksDB` until mined.
|
||||
#[tokio::test]
|
||||
async fn test_rocksdb_pending_tx_not_in_storage() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = test_chain_spec();
|
||||
let chain_id = chain_spec.chain().id();
|
||||
|
||||
let (mut nodes, _tasks, _) = E2ETestSetupBuilder::<EthereumNode, _>::new(
|
||||
1,
|
||||
chain_spec.clone(),
|
||||
test_attributes_generator,
|
||||
)
|
||||
.with_node_config_modifier(with_rocksdb_enabled)
|
||||
.with_tree_config_modifier(|config| config.with_persistence_threshold(0))
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
let wallets = wallet::Wallet::new(1).with_chain_id(chain_id).wallet_gen();
|
||||
let signer = wallets[0].clone();
|
||||
|
||||
// Inject tx but do NOT mine
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(chain_id, signer).await;
|
||||
let tx_hash = nodes[0].rpc.inject_tx(raw_tx).await?;
|
||||
|
||||
// Verify tx is in pending pool via RPC
|
||||
let client = nodes[0].rpc_client().expect("RPC client");
|
||||
wait_for_pending_tx(&client, tx_hash).await;
|
||||
|
||||
let pending_tx: Option<Transaction> =
|
||||
client.request("eth_getTransactionByHash", [tx_hash]).await?;
|
||||
assert!(pending_tx.is_some(), "Pending tx should be visible via RPC");
|
||||
assert!(pending_tx.unwrap().block_number.is_none(), "Pending tx should have no block_number");
|
||||
|
||||
// Assert tx is NOT in RocksDB before mining (single check - tx is confirmed pending)
|
||||
let rocksdb = nodes[0].inner.provider.rocksdb_provider();
|
||||
let tx_number: Option<u64> = rocksdb.get::<tables::TransactionHashNumbers>(tx_hash)?;
|
||||
assert!(
|
||||
tx_number.is_none(),
|
||||
"Pending tx should NOT be in RocksDB before mining, but found tx_number={:?}",
|
||||
tx_number
|
||||
);
|
||||
|
||||
// Now mine the block
|
||||
let payload = nodes[0].advance_block().await?;
|
||||
assert_eq!(payload.block().number(), 1);
|
||||
|
||||
// Poll until tx appears in RocksDB
|
||||
let tx_number = poll_tx_in_rocksdb(&nodes[0].inner.provider, tx_hash).await;
|
||||
assert_eq!(tx_number, 0, "First tx should have tx_number 0");
|
||||
|
||||
// Verify tx is now mined via RPC
|
||||
let mined_tx: Option<Transaction> =
|
||||
client.request("eth_getTransactionByHash", [tx_hash]).await?;
|
||||
assert_eq!(mined_tx.expect("mined tx").block_number, Some(1));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -34,6 +34,11 @@ 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;
|
||||
|
||||
/// The size of proof targets chunk to spawn in one multiproof calculation when V2 proofs are
|
||||
/// enabled. This is 4x the default chunk size to take advantage of more efficient V2 proof
|
||||
/// computation.
|
||||
pub const DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE_V2: usize = DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE * 4;
|
||||
|
||||
/// Default number of reserved CPU cores for non-reth processes.
|
||||
///
|
||||
/// This will be deducted from the thread count of main reth global threadpool.
|
||||
@@ -45,7 +50,17 @@ pub const DEFAULT_PREWARM_MAX_CONCURRENCY: usize = 16;
|
||||
const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = EPOCH_SLOTS as u32 * 2;
|
||||
const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256;
|
||||
const DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE: usize = 4;
|
||||
const DEFAULT_CROSS_BLOCK_CACHE_SIZE: u64 = 4 * 1024 * 1024 * 1024;
|
||||
const DEFAULT_CROSS_BLOCK_CACHE_SIZE: usize = default_cross_block_cache_size();
|
||||
|
||||
const fn default_cross_block_cache_size() -> usize {
|
||||
if cfg!(test) {
|
||||
1024 * 1024 // 1 MB in tests
|
||||
} else if cfg!(target_pointer_width = "32") {
|
||||
usize::MAX // max possible on wasm32 / 32-bit
|
||||
} else {
|
||||
4 * 1024 * 1024 * 1024 // 4 GB on 64-bit
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if the host has enough parallelism to run the payload processor.
|
||||
///
|
||||
@@ -95,12 +110,10 @@ pub struct TreeConfig {
|
||||
disable_state_cache: bool,
|
||||
/// Whether to disable parallel prewarming.
|
||||
disable_prewarming: bool,
|
||||
/// Whether to disable the parallel sparse trie state root algorithm.
|
||||
disable_parallel_sparse_trie: bool,
|
||||
/// Whether to enable state provider metrics.
|
||||
state_provider_metrics: bool,
|
||||
/// Cross-block cache size in bytes.
|
||||
cross_block_cache_size: u64,
|
||||
cross_block_cache_size: usize,
|
||||
/// Whether the host has enough parallelism to run state root task.
|
||||
has_enough_parallelism: bool,
|
||||
/// Whether multiproof task should chunk proof targets.
|
||||
@@ -153,7 +166,6 @@ impl Default for TreeConfig {
|
||||
always_compare_trie_updates: false,
|
||||
disable_state_cache: false,
|
||||
disable_prewarming: false,
|
||||
disable_parallel_sparse_trie: false,
|
||||
state_provider_metrics: false,
|
||||
cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE,
|
||||
has_enough_parallelism: has_enough_parallelism(),
|
||||
@@ -186,9 +198,8 @@ impl TreeConfig {
|
||||
always_compare_trie_updates: bool,
|
||||
disable_state_cache: bool,
|
||||
disable_prewarming: bool,
|
||||
disable_parallel_sparse_trie: bool,
|
||||
state_provider_metrics: bool,
|
||||
cross_block_cache_size: u64,
|
||||
cross_block_cache_size: usize,
|
||||
has_enough_parallelism: bool,
|
||||
multiproof_chunking_enabled: bool,
|
||||
multiproof_chunk_size: usize,
|
||||
@@ -213,7 +224,6 @@ impl TreeConfig {
|
||||
always_compare_trie_updates,
|
||||
disable_state_cache,
|
||||
disable_prewarming,
|
||||
disable_parallel_sparse_trie,
|
||||
state_provider_metrics,
|
||||
cross_block_cache_size,
|
||||
has_enough_parallelism,
|
||||
@@ -267,6 +277,17 @@ impl TreeConfig {
|
||||
self.multiproof_chunk_size
|
||||
}
|
||||
|
||||
/// Return the multiproof task chunk size, using the V2 default if V2 proofs are enabled
|
||||
/// and the chunk size is at the default value.
|
||||
pub const fn effective_multiproof_chunk_size(&self) -> usize {
|
||||
if self.enable_proof_v2 && self.multiproof_chunk_size == DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE
|
||||
{
|
||||
DEFAULT_MULTIPROOF_TASK_CHUNK_SIZE_V2
|
||||
} else {
|
||||
self.multiproof_chunk_size
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of reserved CPU cores for non-reth processes
|
||||
pub const fn reserved_cpu_cores(&self) -> usize {
|
||||
self.reserved_cpu_cores
|
||||
@@ -283,11 +304,6 @@ impl TreeConfig {
|
||||
self.state_provider_metrics
|
||||
}
|
||||
|
||||
/// Returns whether or not the parallel sparse trie is disabled.
|
||||
pub const fn disable_parallel_sparse_trie(&self) -> bool {
|
||||
self.disable_parallel_sparse_trie
|
||||
}
|
||||
|
||||
/// Returns whether or not state cache is disabled.
|
||||
pub const fn disable_state_cache(&self) -> bool {
|
||||
self.disable_state_cache
|
||||
@@ -305,7 +321,7 @@ impl TreeConfig {
|
||||
}
|
||||
|
||||
/// Returns the cross-block cache size.
|
||||
pub const fn cross_block_cache_size(&self) -> u64 {
|
||||
pub const fn cross_block_cache_size(&self) -> usize {
|
||||
self.cross_block_cache_size
|
||||
}
|
||||
|
||||
@@ -408,7 +424,7 @@ impl TreeConfig {
|
||||
}
|
||||
|
||||
/// Setter for cross block cache size.
|
||||
pub const fn with_cross_block_cache_size(mut self, cross_block_cache_size: u64) -> Self {
|
||||
pub const fn with_cross_block_cache_size(mut self, cross_block_cache_size: usize) -> Self {
|
||||
self.cross_block_cache_size = cross_block_cache_size;
|
||||
self
|
||||
}
|
||||
@@ -425,15 +441,6 @@ impl TreeConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Setter for whether to disable the parallel sparse trie
|
||||
pub const fn with_disable_parallel_sparse_trie(
|
||||
mut self,
|
||||
disable_parallel_sparse_trie: bool,
|
||||
) -> Self {
|
||||
self.disable_parallel_sparse_trie = disable_parallel_sparse_trie;
|
||||
self
|
||||
}
|
||||
|
||||
/// Setter for whether multiproof task should chunk proof targets.
|
||||
pub const fn with_multiproof_chunking_enabled(
|
||||
mut self,
|
||||
|
||||
@@ -62,7 +62,8 @@ pub trait EngineTypes:
|
||||
+ TryInto<Self::ExecutionPayloadEnvelopeV2>
|
||||
+ TryInto<Self::ExecutionPayloadEnvelopeV3>
|
||||
+ TryInto<Self::ExecutionPayloadEnvelopeV4>
|
||||
+ TryInto<Self::ExecutionPayloadEnvelopeV5>,
|
||||
+ TryInto<Self::ExecutionPayloadEnvelopeV5>
|
||||
+ TryInto<Self::ExecutionPayloadEnvelopeV6>,
|
||||
> + DeserializeOwned
|
||||
+ Serialize
|
||||
{
|
||||
@@ -106,6 +107,14 @@ pub trait EngineTypes:
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
/// Execution Payload V6 envelope type.
|
||||
type ExecutionPayloadEnvelopeV6: DeserializeOwned
|
||||
+ Serialize
|
||||
+ Clone
|
||||
+ Unpin
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static;
|
||||
}
|
||||
|
||||
/// Type that validates the payloads processed by the engine API.
|
||||
|
||||
@@ -53,7 +53,7 @@ revm-primitives.workspace = true
|
||||
futures.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "macros"] }
|
||||
mini-moka = { workspace = true, features = ["sync"] }
|
||||
fixed-cache.workspace = true
|
||||
moka = { workspace = true, features = ["sync"] }
|
||||
smallvec.workspace = true
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,9 @@ pub(crate) struct EngineApiMetrics {
|
||||
pub(crate) block_validation: BlockValidationMetrics,
|
||||
/// Canonical chain and reorg related metrics
|
||||
pub tree: TreeMetrics,
|
||||
/// Metrics for EIP-7928 Block-Level Access Lists (BAL).
|
||||
#[allow(dead_code)]
|
||||
pub(crate) bal: BalMetrics,
|
||||
}
|
||||
|
||||
impl EngineApiMetrics {
|
||||
@@ -239,6 +242,8 @@ pub(crate) struct NewPayloadStatusMetrics {
|
||||
pub(crate) new_payload_error: Counter,
|
||||
/// The total gas of valid new payload messages received.
|
||||
pub(crate) new_payload_total_gas: Histogram,
|
||||
/// The gas used for the last valid new payload.
|
||||
pub(crate) new_payload_total_gas_last: Gauge,
|
||||
/// The gas per second of valid new payload messages received.
|
||||
pub(crate) new_payload_gas_per_second: Histogram,
|
||||
/// The gas per second for the last new payload call.
|
||||
@@ -251,6 +256,8 @@ pub(crate) struct NewPayloadStatusMetrics {
|
||||
pub(crate) time_between_new_payloads: Histogram,
|
||||
/// Time from previous payload start to current payload start (total interval).
|
||||
pub(crate) new_payload_interval: Histogram,
|
||||
/// Time diff between forkchoice updated call response and the next new payload call request.
|
||||
pub(crate) forkchoice_updated_new_payload_time_diff: Histogram,
|
||||
}
|
||||
|
||||
impl NewPayloadStatusMetrics {
|
||||
@@ -258,6 +265,7 @@ impl NewPayloadStatusMetrics {
|
||||
pub(crate) fn update_response_metrics(
|
||||
&mut self,
|
||||
start: Instant,
|
||||
latest_forkchoice_updated_at: &mut Option<Instant>,
|
||||
result: &Result<TreeOutcome<PayloadStatus>, InsertBlockFatalError>,
|
||||
gas_used: u64,
|
||||
) {
|
||||
@@ -277,6 +285,7 @@ impl NewPayloadStatusMetrics {
|
||||
PayloadStatusEnum::Valid => {
|
||||
self.new_payload_valid.increment(1);
|
||||
self.new_payload_total_gas.record(gas_used as f64);
|
||||
self.new_payload_total_gas_last.set(gas_used as f64);
|
||||
let gas_per_second = gas_used as f64 / elapsed.as_secs_f64();
|
||||
self.new_payload_gas_per_second.record(gas_per_second);
|
||||
self.new_payload_gas_per_second_last.set(gas_per_second);
|
||||
@@ -290,9 +299,40 @@ impl NewPayloadStatusMetrics {
|
||||
self.new_payload_messages.increment(1);
|
||||
self.new_payload_latency.record(elapsed);
|
||||
self.new_payload_last.set(elapsed);
|
||||
if let Some(latest_forkchoice_updated_at) = latest_forkchoice_updated_at.take() {
|
||||
self.forkchoice_updated_new_payload_time_diff
|
||||
.record(start - latest_forkchoice_updated_at);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Metrics for EIP-7928 Block-Level Access Lists (BAL).
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-metrics/issues/5>
|
||||
#[allow(dead_code)]
|
||||
#[derive(Metrics, Clone)]
|
||||
#[metrics(scope = "execution.block_access_list")]
|
||||
pub(crate) struct BalMetrics {
|
||||
/// Size of the BAL in bytes for the current block.
|
||||
pub(crate) size_bytes: Gauge,
|
||||
/// Total number of blocks with valid BALs.
|
||||
pub(crate) valid_total: Counter,
|
||||
/// Total number of blocks with invalid BALs.
|
||||
pub(crate) invalid_total: Counter,
|
||||
/// Time taken to validate the BAL against actual execution.
|
||||
pub(crate) validation_time_seconds: Histogram,
|
||||
/// Number of account changes in the BAL.
|
||||
pub(crate) account_changes: Gauge,
|
||||
/// Number of storage changes in the BAL.
|
||||
pub(crate) storage_changes: Gauge,
|
||||
/// Number of balance changes in the BAL.
|
||||
pub(crate) balance_changes: Gauge,
|
||||
/// Number of nonce changes in the BAL.
|
||||
pub(crate) nonce_changes: Gauge,
|
||||
/// Number of code changes in the BAL.
|
||||
pub(crate) code_changes: Gauge,
|
||||
}
|
||||
|
||||
/// Metrics for non-execution related block validation.
|
||||
#[derive(Metrics, Clone)]
|
||||
#[metrics(scope = "sync.block_validation")]
|
||||
@@ -301,6 +341,8 @@ pub(crate) struct BlockValidationMetrics {
|
||||
pub(crate) state_root_storage_tries_updated_total: Counter,
|
||||
/// Total number of times the parallel state root computation fell back to regular.
|
||||
pub(crate) state_root_parallel_fallback_total: Counter,
|
||||
/// Total number of times the state root task failed but the fallback succeeded.
|
||||
pub(crate) state_root_task_fallback_success_total: Counter,
|
||||
/// Latest state root duration, ie the time spent blocked waiting for the state root.
|
||||
pub(crate) state_root_duration: Gauge,
|
||||
/// Histogram for state root duration ie the time spent blocked waiting for the state root
|
||||
|
||||
@@ -32,7 +32,8 @@ use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, Sealed
|
||||
use reth_provider::{
|
||||
BlockExecutionOutput, BlockExecutionResult, BlockNumReader, BlockReader, ChangeSetReader,
|
||||
DatabaseProviderFactory, HashedPostStateProvider, ProviderError, StageCheckpointReader,
|
||||
StateProviderBox, StateProviderFactory, StateReader, TransactionVariant,
|
||||
StateProviderBox, StateProviderFactory, StateReader, StorageChangeSetReader,
|
||||
TransactionVariant,
|
||||
};
|
||||
use reth_revm::database::StateProviderDatabase;
|
||||
use reth_stages_api::ControlFlow;
|
||||
@@ -84,6 +85,12 @@ pub mod state;
|
||||
/// backfill this gap.
|
||||
pub(crate) const MIN_BLOCKS_FOR_PIPELINE_RUN: u64 = EPOCH_SLOTS;
|
||||
|
||||
/// The minimum number of blocks to retain in the changeset cache after eviction.
|
||||
///
|
||||
/// This ensures that recent trie changesets are kept in memory for potential reorgs,
|
||||
/// even when the finalized block is not set (e.g., on L2s like Optimism).
|
||||
const CHANGESET_CACHE_RETENTION_BLOCKS: u64 = 64;
|
||||
|
||||
/// A builder for creating state providers that can be used across threads.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StateProviderBuilder<N: NodePrimitives, P> {
|
||||
@@ -317,6 +324,7 @@ where
|
||||
<P as DatabaseProviderFactory>::Provider: BlockReader<Block = N::Block, Header = N::BlockHeader>
|
||||
+ StageCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader,
|
||||
C: ConfigureEvm<Primitives = N> + 'static,
|
||||
T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
|
||||
@@ -1376,19 +1384,27 @@ where
|
||||
debug!(target: "engine::tree", ?last_persisted_block_hash, ?last_persisted_block_number, elapsed=?start_time.elapsed(), "Finished persisting, calling finish");
|
||||
self.persistence_state.finish(last_persisted_block_hash, last_persisted_block_number);
|
||||
|
||||
// Evict trie changesets for blocks below the finalized block, but keep at least 64 blocks
|
||||
if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
|
||||
let min_threshold = last_persisted_block_number.saturating_sub(64);
|
||||
let eviction_threshold = finalized.number.min(min_threshold);
|
||||
debug!(
|
||||
target: "engine::tree",
|
||||
last_persisted = last_persisted_block_number,
|
||||
finalized_number = finalized.number,
|
||||
eviction_threshold,
|
||||
"Evicting changesets below threshold"
|
||||
);
|
||||
self.changeset_cache.evict(eviction_threshold);
|
||||
}
|
||||
// Evict trie changesets for blocks below the eviction threshold.
|
||||
// Keep at least CHANGESET_CACHE_RETENTION_BLOCKS from the persisted tip, and also respect
|
||||
// the finalized block if set.
|
||||
let min_threshold =
|
||||
last_persisted_block_number.saturating_sub(CHANGESET_CACHE_RETENTION_BLOCKS);
|
||||
let eviction_threshold =
|
||||
if let Some(finalized) = self.canonical_in_memory_state.get_finalized_num_hash() {
|
||||
// Use the minimum of finalized block and retention threshold to be conservative
|
||||
finalized.number.min(min_threshold)
|
||||
} else {
|
||||
// When finalized is not set (e.g., on L2s), use the retention threshold
|
||||
min_threshold
|
||||
};
|
||||
debug!(
|
||||
target: "engine::tree",
|
||||
last_persisted = last_persisted_block_number,
|
||||
finalized_number = ?self.canonical_in_memory_state.get_finalized_num_hash().map(|f| f.number),
|
||||
eviction_threshold,
|
||||
"Evicting changesets below threshold"
|
||||
);
|
||||
self.changeset_cache.evict(eviction_threshold);
|
||||
|
||||
self.on_new_persisted_block()?;
|
||||
Ok(())
|
||||
@@ -1476,6 +1492,10 @@ where
|
||||
self.on_maybe_tree_event(res.event.take())?;
|
||||
}
|
||||
|
||||
if let Err(ref err) = output {
|
||||
error!(target: "engine::tree", %err, ?state, "Error processing forkchoice update");
|
||||
}
|
||||
|
||||
self.metrics.engine.forkchoice_updated.update_response_metrics(
|
||||
start,
|
||||
&mut self.metrics.engine.new_payload.latest_finish_at,
|
||||
@@ -1498,10 +1518,12 @@ where
|
||||
let gas_used = payload.gas_used();
|
||||
let num_hash = payload.num_hash();
|
||||
let mut output = self.on_new_payload(payload);
|
||||
self.metrics
|
||||
.engine
|
||||
.new_payload
|
||||
.update_response_metrics(start, &output, gas_used);
|
||||
self.metrics.engine.new_payload.update_response_metrics(
|
||||
start,
|
||||
&mut self.metrics.engine.forkchoice_updated.latest_finish_at,
|
||||
&output,
|
||||
gas_used,
|
||||
);
|
||||
|
||||
let maybe_event =
|
||||
output.as_mut().ok().and_then(|out| out.event.take());
|
||||
|
||||
@@ -1,188 +0,0 @@
|
||||
//! Configured sparse trie enum for switching between serial and parallel implementations.
|
||||
|
||||
use alloy_primitives::B256;
|
||||
use reth_trie::{BranchNodeMasks, Nibbles, ProofTrieNode, TrieNode};
|
||||
use reth_trie_sparse::{
|
||||
errors::SparseTrieResult, provider::TrieNodeProvider, LeafLookup, LeafLookupError,
|
||||
SerialSparseTrie, SparseTrieInterface, SparseTrieUpdates,
|
||||
};
|
||||
use reth_trie_sparse_parallel::ParallelSparseTrie;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Enum for switching between serial and parallel sparse trie implementations.
|
||||
///
|
||||
/// This type allows runtime selection between different sparse trie implementations,
|
||||
/// providing flexibility in choosing the appropriate implementation based on workload
|
||||
/// characteristics.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum ConfiguredSparseTrie {
|
||||
/// Serial implementation of the sparse trie.
|
||||
Serial(Box<SerialSparseTrie>),
|
||||
/// Parallel implementation of the sparse trie.
|
||||
Parallel(Box<ParallelSparseTrie>),
|
||||
}
|
||||
|
||||
impl From<SerialSparseTrie> for ConfiguredSparseTrie {
|
||||
fn from(trie: SerialSparseTrie) -> Self {
|
||||
Self::Serial(Box::new(trie))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParallelSparseTrie> for ConfiguredSparseTrie {
|
||||
fn from(trie: ParallelSparseTrie) -> Self {
|
||||
Self::Parallel(Box::new(trie))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ConfiguredSparseTrie {
|
||||
fn default() -> Self {
|
||||
Self::Serial(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl SparseTrieInterface for ConfiguredSparseTrie {
|
||||
fn with_root(
|
||||
self,
|
||||
root: TrieNode,
|
||||
masks: Option<BranchNodeMasks>,
|
||||
retain_updates: bool,
|
||||
) -> SparseTrieResult<Self> {
|
||||
match self {
|
||||
Self::Serial(trie) => {
|
||||
trie.with_root(root, masks, retain_updates).map(|t| Self::Serial(Box::new(t)))
|
||||
}
|
||||
Self::Parallel(trie) => {
|
||||
trie.with_root(root, masks, retain_updates).map(|t| Self::Parallel(Box::new(t)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_updates(self, retain_updates: bool) -> Self {
|
||||
match self {
|
||||
Self::Serial(trie) => Self::Serial(Box::new(trie.with_updates(retain_updates))),
|
||||
Self::Parallel(trie) => Self::Parallel(Box::new(trie.with_updates(retain_updates))),
|
||||
}
|
||||
}
|
||||
|
||||
fn reserve_nodes(&mut self, additional: usize) {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.reserve_nodes(additional),
|
||||
Self::Parallel(trie) => trie.reserve_nodes(additional),
|
||||
}
|
||||
}
|
||||
|
||||
fn reveal_node(
|
||||
&mut self,
|
||||
path: Nibbles,
|
||||
node: TrieNode,
|
||||
masks: Option<BranchNodeMasks>,
|
||||
) -> SparseTrieResult<()> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.reveal_node(path, node, masks),
|
||||
Self::Parallel(trie) => trie.reveal_node(path, node, masks),
|
||||
}
|
||||
}
|
||||
|
||||
fn reveal_nodes(&mut self, nodes: Vec<ProofTrieNode>) -> SparseTrieResult<()> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.reveal_nodes(nodes),
|
||||
Self::Parallel(trie) => trie.reveal_nodes(nodes),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_leaf<P: TrieNodeProvider>(
|
||||
&mut self,
|
||||
full_path: Nibbles,
|
||||
value: Vec<u8>,
|
||||
provider: P,
|
||||
) -> SparseTrieResult<()> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.update_leaf(full_path, value, provider),
|
||||
Self::Parallel(trie) => trie.update_leaf(full_path, value, provider),
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_leaf<P: TrieNodeProvider>(
|
||||
&mut self,
|
||||
full_path: &Nibbles,
|
||||
provider: P,
|
||||
) -> SparseTrieResult<()> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.remove_leaf(full_path, provider),
|
||||
Self::Parallel(trie) => trie.remove_leaf(full_path, provider),
|
||||
}
|
||||
}
|
||||
|
||||
fn root(&mut self) -> B256 {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.root(),
|
||||
Self::Parallel(trie) => trie.root(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_subtrie_hashes(&mut self) {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.update_subtrie_hashes(),
|
||||
Self::Parallel(trie) => trie.update_subtrie_hashes(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_leaf_value(&self, full_path: &Nibbles) -> Option<&Vec<u8>> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.get_leaf_value(full_path),
|
||||
Self::Parallel(trie) => trie.get_leaf_value(full_path),
|
||||
}
|
||||
}
|
||||
|
||||
fn find_leaf(
|
||||
&self,
|
||||
full_path: &Nibbles,
|
||||
expected_value: Option<&Vec<u8>>,
|
||||
) -> Result<LeafLookup, LeafLookupError> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.find_leaf(full_path, expected_value),
|
||||
Self::Parallel(trie) => trie.find_leaf(full_path, expected_value),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_updates(&mut self) -> SparseTrieUpdates {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.take_updates(),
|
||||
Self::Parallel(trie) => trie.take_updates(),
|
||||
}
|
||||
}
|
||||
|
||||
fn wipe(&mut self) {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.wipe(),
|
||||
Self::Parallel(trie) => trie.wipe(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.clear(),
|
||||
Self::Parallel(trie) => trie.clear(),
|
||||
}
|
||||
}
|
||||
|
||||
fn updates_ref(&self) -> Cow<'_, SparseTrieUpdates> {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.updates_ref(),
|
||||
Self::Parallel(trie) => trie.updates_ref(),
|
||||
}
|
||||
}
|
||||
fn shrink_nodes_to(&mut self, size: usize) {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.shrink_nodes_to(size),
|
||||
Self::Parallel(trie) => trie.shrink_nodes_to(size),
|
||||
}
|
||||
}
|
||||
|
||||
fn shrink_values_to(&mut self, size: usize) {
|
||||
match self {
|
||||
Self::Serial(trie) => trie.shrink_values_to(size),
|
||||
Self::Parallel(trie) => trie.shrink_values_to(size),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
use super::precompile_cache::PrecompileCacheMap;
|
||||
use crate::tree::{
|
||||
cached_state::{
|
||||
CachedStateMetrics, CachedStateProvider, ExecutionCache as StateExecutionCache,
|
||||
ExecutionCacheBuilder, SavedCache,
|
||||
},
|
||||
cached_state::{CachedStateMetrics, CachedStateProvider, ExecutionCache, SavedCache},
|
||||
payload_processor::{
|
||||
prewarm::{PrewarmCacheTask, PrewarmContext, PrewarmMode, PrewarmTaskEvent},
|
||||
sparse_trie::StateRootComputeOutcome,
|
||||
@@ -15,7 +12,7 @@ use crate::tree::{
|
||||
};
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::eip1898::BlockWithParent;
|
||||
use alloy_evm::{block::StateChangeSource, ToTxEnv};
|
||||
use alloy_evm::block::StateChangeSource;
|
||||
use alloy_primitives::B256;
|
||||
use crossbeam_channel::Sender as CrossbeamSender;
|
||||
use executor::WorkloadExecutor;
|
||||
@@ -25,6 +22,7 @@ use parking_lot::RwLock;
|
||||
use prewarm::PrewarmMetrics;
|
||||
use rayon::prelude::*;
|
||||
use reth_evm::{
|
||||
block::ExecutableTxParts,
|
||||
execute::{ExecutableTxFor, WithTxEnv},
|
||||
ConfigureEvm, EvmEnvFor, ExecutableTxIterator, ExecutableTxTuple, OnStateHook, SpecFor,
|
||||
TxEnvFor,
|
||||
@@ -43,7 +41,7 @@ use reth_trie_parallel::{
|
||||
};
|
||||
use reth_trie_sparse::{
|
||||
provider::{TrieNodeProvider, TrieNodeProviderFactory},
|
||||
ClearedSparseStateTrie, SparseStateTrie, SparseTrie,
|
||||
ClearedSparseStateTrie, RevealableSparseTrie, SparseStateTrie,
|
||||
};
|
||||
use reth_trie_sparse_parallel::{ParallelSparseTrie, ParallelismThresholds};
|
||||
use std::{
|
||||
@@ -59,15 +57,12 @@ use std::{
|
||||
use tracing::{debug, debug_span, instrument, warn, Span};
|
||||
|
||||
pub mod bal;
|
||||
mod configured_sparse_trie;
|
||||
pub mod executor;
|
||||
pub mod multiproof;
|
||||
pub mod prewarm;
|
||||
pub mod receipt_root_task;
|
||||
pub mod sparse_trie;
|
||||
|
||||
use configured_sparse_trie::ConfiguredSparseTrie;
|
||||
|
||||
/// Default parallelism thresholds to use with the [`ParallelSparseTrie`].
|
||||
///
|
||||
/// These values were determined by performing benchmarks using gradually increasing values to judge
|
||||
@@ -101,7 +96,7 @@ pub const SPARSE_TRIE_MAX_VALUES_SHRINK_CAPACITY: usize = 1_000_000;
|
||||
|
||||
/// Type alias for [`PayloadHandle`] returned by payload processor spawn methods.
|
||||
type IteratorPayloadHandle<Evm, I, N> = PayloadHandle<
|
||||
WithTxEnv<TxEnvFor<Evm>, <I as ExecutableTxTuple>::Tx>,
|
||||
WithTxEnv<TxEnvFor<Evm>, <I as ExecutableTxIterator<Evm>>::Recovered>,
|
||||
<I as ExecutableTxTuple>::Error,
|
||||
<N as NodePrimitives>::Receipt,
|
||||
>;
|
||||
@@ -115,11 +110,11 @@ where
|
||||
/// The executor used by to spawn tasks.
|
||||
executor: WorkloadExecutor,
|
||||
/// The most recent cache used for execution.
|
||||
execution_cache: ExecutionCache,
|
||||
execution_cache: PayloadExecutionCache,
|
||||
/// Metrics for trie operations
|
||||
trie_metrics: MultiProofTaskMetrics,
|
||||
/// Cross-block cache size in bytes.
|
||||
cross_block_cache_size: u64,
|
||||
cross_block_cache_size: usize,
|
||||
/// Whether transactions should not be executed on prewarming task.
|
||||
disable_transaction_prewarming: bool,
|
||||
/// Whether state cache should be disable
|
||||
@@ -133,12 +128,8 @@ where
|
||||
/// A cleared `SparseStateTrie`, kept around to be reused for the state root computation so
|
||||
/// that allocations can be minimized.
|
||||
sparse_state_trie: Arc<
|
||||
parking_lot::Mutex<
|
||||
Option<ClearedSparseStateTrie<ConfiguredSparseTrie, ConfiguredSparseTrie>>,
|
||||
>,
|
||||
parking_lot::Mutex<Option<ClearedSparseStateTrie<ParallelSparseTrie, ParallelSparseTrie>>>,
|
||||
>,
|
||||
/// Whether to disable the parallel sparse trie.
|
||||
disable_parallel_sparse_trie: bool,
|
||||
/// Maximum concurrency for prewarm task.
|
||||
prewarm_max_concurrency: usize,
|
||||
/// Whether to disable cache metrics recording.
|
||||
@@ -173,7 +164,6 @@ where
|
||||
precompile_cache_disabled: config.precompile_cache_disabled(),
|
||||
precompile_cache_map,
|
||||
sparse_state_trie: Arc::default(),
|
||||
disable_parallel_sparse_trie: config.disable_parallel_sparse_trie(),
|
||||
prewarm_max_concurrency: config.prewarm_max_concurrency(),
|
||||
disable_cache_metrics: config.disable_cache_metrics(),
|
||||
}
|
||||
@@ -247,6 +237,9 @@ where
|
||||
let (to_sparse_trie, sparse_trie_rx) = channel();
|
||||
let (to_multi_proof, from_multi_proof) = crossbeam_channel::unbounded();
|
||||
|
||||
// Extract V2 proofs flag early so we can pass it to prewarm
|
||||
let v2_proofs_enabled = config.enable_proof_v2();
|
||||
|
||||
// Handle BAL-based optimization if available
|
||||
let prewarm_handle = if let Some(bal) = bal {
|
||||
// When BAL is present, use BAL prewarming and send BAL to multiproof
|
||||
@@ -263,6 +256,7 @@ where
|
||||
provider_builder.clone(),
|
||||
None, // Don't send proof targets when BAL is present
|
||||
Some(bal),
|
||||
v2_proofs_enabled,
|
||||
)
|
||||
} else {
|
||||
// Normal path: spawn with transaction prewarming
|
||||
@@ -273,6 +267,7 @@ where
|
||||
provider_builder.clone(),
|
||||
Some(to_multi_proof.clone()),
|
||||
None,
|
||||
v2_proofs_enabled,
|
||||
)
|
||||
};
|
||||
|
||||
@@ -280,7 +275,6 @@ where
|
||||
let task_ctx = ProofTaskCtx::new(multiproof_provider_factory);
|
||||
let storage_worker_count = config.storage_worker_count();
|
||||
let account_worker_count = config.account_worker_count();
|
||||
let v2_proofs_enabled = config.enable_proof_v2();
|
||||
let proof_handle = ProofWorkerHandle::new(
|
||||
self.executor.handle().clone(),
|
||||
task_ctx,
|
||||
@@ -292,10 +286,13 @@ where
|
||||
let multi_proof_task = MultiProofTask::new(
|
||||
proof_handle.clone(),
|
||||
to_sparse_trie,
|
||||
config.multiproof_chunking_enabled().then_some(config.multiproof_chunk_size()),
|
||||
config
|
||||
.multiproof_chunking_enabled()
|
||||
.then_some(config.effective_multiproof_chunk_size()),
|
||||
to_multi_proof.clone(),
|
||||
from_multi_proof,
|
||||
);
|
||||
)
|
||||
.with_v2_proofs_enabled(v2_proofs_enabled);
|
||||
|
||||
// spawn multi-proof task
|
||||
let parent_span = span.clone();
|
||||
@@ -305,7 +302,7 @@ where
|
||||
// Build a state provider for the multiproof task
|
||||
let provider = provider_builder.build().expect("failed to build provider");
|
||||
let provider = if let Some(saved_cache) = saved_cache {
|
||||
let (cache, metrics, _) = saved_cache.split();
|
||||
let (cache, metrics, _disable_metrics) = saved_cache.split();
|
||||
Box::new(CachedStateProvider::new(provider, cache, metrics))
|
||||
as Box<dyn StateProvider>
|
||||
} else {
|
||||
@@ -344,8 +341,9 @@ where
|
||||
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
|
||||
{
|
||||
let (prewarm_rx, execution_rx, size_hint) = self.spawn_tx_iterator(transactions);
|
||||
// This path doesn't use multiproof, so V2 proofs flag doesn't matter
|
||||
let prewarm_handle =
|
||||
self.spawn_caching_with(env, prewarm_rx, size_hint, provider_builder, None, bal);
|
||||
self.spawn_caching_with(env, prewarm_rx, size_hint, provider_builder, None, bal, false);
|
||||
PayloadHandle {
|
||||
to_multi_proof: None,
|
||||
prewarm_handle,
|
||||
@@ -361,8 +359,8 @@ where
|
||||
&self,
|
||||
transactions: I,
|
||||
) -> (
|
||||
mpsc::Receiver<WithTxEnv<TxEnvFor<Evm>, I::Tx>>,
|
||||
mpsc::Receiver<Result<WithTxEnv<TxEnvFor<Evm>, I::Tx>, I::Error>>,
|
||||
mpsc::Receiver<WithTxEnv<TxEnvFor<Evm>, I::Recovered>>,
|
||||
mpsc::Receiver<Result<WithTxEnv<TxEnvFor<Evm>, I::Recovered>, I::Error>>,
|
||||
usize,
|
||||
) {
|
||||
let (transactions, convert) = transactions.into();
|
||||
@@ -377,7 +375,10 @@ where
|
||||
self.executor.spawn_blocking(move || {
|
||||
transactions.enumerate().for_each_with(ooo_tx, |ooo_tx, (idx, tx)| {
|
||||
let tx = convert(tx);
|
||||
let tx = tx.map(|tx| WithTxEnv { tx_env: tx.to_tx_env(), tx: Arc::new(tx) });
|
||||
let tx = tx.map(|tx| {
|
||||
let (tx_env, tx) = tx.into_parts();
|
||||
WithTxEnv { tx_env, tx: Arc::new(tx) }
|
||||
});
|
||||
// Only send Ok(_) variants to prewarming task.
|
||||
if let Ok(tx) = &tx {
|
||||
let _ = prewarm_tx.send(tx.clone());
|
||||
@@ -412,6 +413,7 @@ where
|
||||
}
|
||||
|
||||
/// Spawn prewarming optionally wired to the multiproof task for target updates.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
fn spawn_caching_with<P>(
|
||||
&self,
|
||||
env: ExecutionEnv<Evm>,
|
||||
@@ -420,6 +422,7 @@ where
|
||||
provider_builder: StateProviderBuilder<N, P>,
|
||||
to_multi_proof: Option<CrossbeamSender<MultiProofMessage>>,
|
||||
bal: Option<Arc<BlockAccessList>>,
|
||||
v2_proofs_enabled: bool,
|
||||
) -> CacheTaskHandle<N::Receipt>
|
||||
where
|
||||
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
|
||||
@@ -442,6 +445,7 @@ where
|
||||
terminate_execution: Arc::new(AtomicBool::new(false)),
|
||||
precompile_cache_disabled: self.precompile_cache_disabled,
|
||||
precompile_cache_map: self.precompile_cache_map.clone(),
|
||||
v2_proofs_enabled,
|
||||
};
|
||||
|
||||
let (prewarm_task, to_prewarm_task) = PrewarmCacheTask::new(
|
||||
@@ -480,8 +484,11 @@ where
|
||||
cache
|
||||
} else {
|
||||
debug!("creating new execution cache on cache miss");
|
||||
let cache = ExecutionCacheBuilder::default().build_caches(self.cross_block_cache_size);
|
||||
SavedCache::new(parent_hash, cache, CachedStateMetrics::zeroed())
|
||||
let start = Instant::now();
|
||||
let cache = ExecutionCache::new(self.cross_block_cache_size);
|
||||
let metrics = CachedStateMetrics::zeroed();
|
||||
metrics.record_cache_creation(start.elapsed());
|
||||
SavedCache::new(parent_hash, cache, metrics)
|
||||
.with_disable_cache_metrics(self.disable_cache_metrics)
|
||||
}
|
||||
}
|
||||
@@ -499,7 +506,6 @@ where
|
||||
BPF::StorageNodeProvider: TrieNodeProvider + Send + Sync,
|
||||
{
|
||||
let cleared_sparse_trie = Arc::clone(&self.sparse_state_trie);
|
||||
let disable_parallel_sparse_trie = self.disable_parallel_sparse_trie;
|
||||
let trie_metrics = self.trie_metrics.clone();
|
||||
let span = Span::current();
|
||||
|
||||
@@ -509,14 +515,10 @@ where
|
||||
// Reuse a stored SparseStateTrie, or create a new one using the desired configuration
|
||||
// if there's none to reuse.
|
||||
let sparse_state_trie = cleared_sparse_trie.lock().take().unwrap_or_else(|| {
|
||||
let default_trie = SparseTrie::blind_from(if disable_parallel_sparse_trie {
|
||||
ConfiguredSparseTrie::Serial(Default::default())
|
||||
} else {
|
||||
ConfiguredSparseTrie::Parallel(Box::new(
|
||||
ParallelSparseTrie::default()
|
||||
.with_parallelism_thresholds(PARALLEL_SPARSE_TRIE_PARALLELISM_THRESHOLDS),
|
||||
))
|
||||
});
|
||||
let default_trie = RevealableSparseTrie::blind_from(
|
||||
ParallelSparseTrie::default()
|
||||
.with_parallelism_thresholds(PARALLEL_SPARSE_TRIE_PARALLELISM_THRESHOLDS),
|
||||
);
|
||||
ClearedSparseStateTrie::from_state_trie(
|
||||
SparseStateTrie::new()
|
||||
.with_accounts_trie(default_trie.clone())
|
||||
@@ -525,12 +527,13 @@ where
|
||||
)
|
||||
});
|
||||
|
||||
let task = SparseTrieTask::<_, ConfiguredSparseTrie, ConfiguredSparseTrie>::new_with_cleared_trie(
|
||||
sparse_trie_rx,
|
||||
proof_worker_handle,
|
||||
trie_metrics,
|
||||
sparse_state_trie,
|
||||
);
|
||||
let task =
|
||||
SparseTrieTask::<_, ParallelSparseTrie, ParallelSparseTrie>::new_with_cleared_trie(
|
||||
sparse_trie_rx,
|
||||
proof_worker_handle,
|
||||
trie_metrics,
|
||||
sparse_state_trie,
|
||||
);
|
||||
|
||||
let (result, trie) = task.run();
|
||||
// Send state root computation result
|
||||
@@ -572,28 +575,27 @@ where
|
||||
parent_hash = %block_with_parent.parent,
|
||||
"Cannot find cache for parent hash, skip updating cache with new state for inserted executed block",
|
||||
);
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// Take existing cache (if any) or create fresh caches
|
||||
let (caches, cache_metrics) = match cached.take() {
|
||||
Some(existing) => {
|
||||
let (c, m, _) = existing.split();
|
||||
(c, m)
|
||||
}
|
||||
let (caches, cache_metrics, _) = match cached.take() {
|
||||
Some(existing) => existing.split(),
|
||||
None => (
|
||||
ExecutionCacheBuilder::default().build_caches(self.cross_block_cache_size),
|
||||
ExecutionCache::new(self.cross_block_cache_size),
|
||||
CachedStateMetrics::zeroed(),
|
||||
false,
|
||||
),
|
||||
};
|
||||
|
||||
// Insert the block's bundle state into cache
|
||||
let new_cache = SavedCache::new(block_with_parent.block.hash, caches, cache_metrics)
|
||||
.with_disable_cache_metrics(disable_cache_metrics);
|
||||
let new_cache =
|
||||
SavedCache::new(block_with_parent.block.hash, caches, cache_metrics)
|
||||
.with_disable_cache_metrics(disable_cache_metrics);
|
||||
if new_cache.cache().insert_state(bundle_state).is_err() {
|
||||
*cached = None;
|
||||
debug!(target: "engine::caching", "cleared execution cache on update error");
|
||||
return;
|
||||
return
|
||||
}
|
||||
new_cache.update_metrics();
|
||||
|
||||
@@ -657,7 +659,7 @@ impl<Tx, Err, R: Send + Sync + 'static> PayloadHandle<Tx, Err, R> {
|
||||
}
|
||||
|
||||
/// Returns a clone of the caches used by prewarming
|
||||
pub(super) fn caches(&self) -> Option<StateExecutionCache> {
|
||||
pub(super) fn caches(&self) -> Option<ExecutionCache> {
|
||||
self.prewarm_handle.saved_cache.as_ref().map(|cache| cache.cache().clone())
|
||||
}
|
||||
|
||||
@@ -761,29 +763,29 @@ impl<R> Drop for CacheTaskHandle<R> {
|
||||
/// ## Cache Safety
|
||||
///
|
||||
/// **CRITICAL**: Cache update operations require exclusive access. All concurrent cache users
|
||||
/// (such as prewarming tasks) must be terminated before calling `update_with_guard`, otherwise
|
||||
/// the cache may be corrupted or cleared.
|
||||
/// (such as prewarming tasks) must be terminated before calling
|
||||
/// [`PayloadExecutionCache::update_with_guard`], otherwise the cache may be corrupted or cleared.
|
||||
///
|
||||
/// ## Cache vs Prewarming Distinction
|
||||
///
|
||||
/// **`ExecutionCache`**:
|
||||
/// **[`PayloadExecutionCache`]**:
|
||||
/// - Stores parent block's execution state after completion
|
||||
/// - Used to fetch parent data for next block's execution
|
||||
/// - Must be exclusively accessed during save operations
|
||||
///
|
||||
/// **`PrewarmCacheTask`**:
|
||||
/// **[`PrewarmCacheTask`]**:
|
||||
/// - Speculatively loads accounts/storage that might be used in transaction execution
|
||||
/// - Prepares data for state root proof computation
|
||||
/// - Runs concurrently but must not interfere with cache saves
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ExecutionCache {
|
||||
struct PayloadExecutionCache {
|
||||
/// Guarded cloneable cache identified by a block hash.
|
||||
inner: Arc<RwLock<Option<SavedCache>>>,
|
||||
/// Metrics for cache operations.
|
||||
metrics: ExecutionCacheMetrics,
|
||||
}
|
||||
|
||||
impl ExecutionCache {
|
||||
impl PayloadExecutionCache {
|
||||
/// Returns the cache for `parent_hash` if it's available for use.
|
||||
///
|
||||
/// A cache is considered available when:
|
||||
@@ -819,11 +821,15 @@ impl ExecutionCache {
|
||||
"Existing cache found"
|
||||
);
|
||||
|
||||
if hash_matches && available {
|
||||
return Some(c.clone());
|
||||
}
|
||||
|
||||
if hash_matches && !available {
|
||||
if available {
|
||||
// If the has is available (no other threads are using it), but has a mismatching
|
||||
// parent hash, we can just clear it and keep using without re-creating from
|
||||
// scratch.
|
||||
if !hash_matches {
|
||||
c.clear();
|
||||
}
|
||||
return Some(c.clone())
|
||||
} else if hash_matches {
|
||||
self.metrics.execution_cache_in_use.increment(1);
|
||||
}
|
||||
} else {
|
||||
@@ -896,9 +902,9 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ExecutionCache;
|
||||
use super::PayloadExecutionCache;
|
||||
use crate::tree::{
|
||||
cached_state::{CachedStateMetrics, ExecutionCacheBuilder, SavedCache},
|
||||
cached_state::{CachedStateMetrics, ExecutionCache, SavedCache},
|
||||
payload_processor::{
|
||||
evm_state_to_hashed_post_state, executor::WorkloadExecutor, PayloadProcessor,
|
||||
},
|
||||
@@ -928,13 +934,13 @@ mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
fn make_saved_cache(hash: B256) -> SavedCache {
|
||||
let execution_cache = ExecutionCacheBuilder::default().build_caches(1_000);
|
||||
let execution_cache = ExecutionCache::new(1_000);
|
||||
SavedCache::new(hash, execution_cache, CachedStateMetrics::zeroed())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execution_cache_allows_single_checkout() {
|
||||
let execution_cache = ExecutionCache::default();
|
||||
let execution_cache = PayloadExecutionCache::default();
|
||||
let hash = B256::from([1u8; 32]);
|
||||
|
||||
execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(hash)));
|
||||
@@ -953,7 +959,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn execution_cache_checkout_releases_on_drop() {
|
||||
let execution_cache = ExecutionCache::default();
|
||||
let execution_cache = PayloadExecutionCache::default();
|
||||
let hash = B256::from([2u8; 32]);
|
||||
|
||||
execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(hash)));
|
||||
@@ -969,19 +975,21 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execution_cache_mismatch_parent_returns_none() {
|
||||
let execution_cache = ExecutionCache::default();
|
||||
fn execution_cache_mismatch_parent_clears_and_returns() {
|
||||
let execution_cache = PayloadExecutionCache::default();
|
||||
let hash = B256::from([3u8; 32]);
|
||||
|
||||
execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(hash)));
|
||||
|
||||
let miss = execution_cache.get_cache_for(B256::from([4u8; 32]));
|
||||
assert!(miss.is_none(), "checkout should fail for different parent hash");
|
||||
// When the parent hash doesn't match, the cache is cleared and returned for reuse
|
||||
let different_hash = B256::from([4u8; 32]);
|
||||
let cache = execution_cache.get_cache_for(different_hash);
|
||||
assert!(cache.is_some(), "cache should be returned for reuse after clearing")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn execution_cache_update_after_release_succeeds() {
|
||||
let execution_cache = ExecutionCache::default();
|
||||
let execution_cache = PayloadExecutionCache::default();
|
||||
let initial = B256::from([5u8; 32]);
|
||||
|
||||
execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(initial)));
|
||||
|
||||
@@ -11,14 +11,18 @@ use reth_metrics::Metrics;
|
||||
use reth_provider::AccountReader;
|
||||
use reth_revm::state::EvmState;
|
||||
use reth_trie::{
|
||||
added_removed_keys::MultiAddedRemovedKeys, DecodedMultiProof, HashedPostState, HashedStorage,
|
||||
added_removed_keys::MultiAddedRemovedKeys, proof_v2, HashedPostState, HashedStorage,
|
||||
MultiProofTargets,
|
||||
};
|
||||
#[cfg(test)]
|
||||
use reth_trie_parallel::stats::ParallelTrieTracker;
|
||||
use reth_trie_parallel::{
|
||||
proof::ParallelProof,
|
||||
proof_task::{
|
||||
AccountMultiproofInput, ProofResultContext, ProofResultMessage, ProofWorkerHandle,
|
||||
AccountMultiproofInput, ProofResult, ProofResultContext, ProofResultMessage,
|
||||
ProofWorkerHandle,
|
||||
},
|
||||
targets_v2::{ChunkedMultiProofTargetsV2, MultiProofTargetsV2},
|
||||
};
|
||||
use revm_primitives::map::{hash_map, B256Map};
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Instant};
|
||||
@@ -63,12 +67,12 @@ const DEFAULT_MAX_TARGETS_FOR_CHUNKING: usize = 300;
|
||||
|
||||
/// A trie update that can be applied to sparse trie alongside the proofs for touched parts of the
|
||||
/// state.
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct SparseTrieUpdate {
|
||||
/// The state update that was used to calculate the proof
|
||||
pub(crate) state: HashedPostState,
|
||||
/// The calculated multiproof
|
||||
pub(crate) multiproof: DecodedMultiProof,
|
||||
pub(crate) multiproof: ProofResult,
|
||||
}
|
||||
|
||||
impl SparseTrieUpdate {
|
||||
@@ -80,7 +84,11 @@ impl SparseTrieUpdate {
|
||||
/// Construct update from multiproof.
|
||||
#[cfg(test)]
|
||||
pub(super) fn from_multiproof(multiproof: reth_trie::MultiProof) -> alloy_rlp::Result<Self> {
|
||||
Ok(Self { multiproof: multiproof.try_into()?, ..Default::default() })
|
||||
let stats = ParallelTrieTracker::default().finish();
|
||||
Ok(Self {
|
||||
state: HashedPostState::default(),
|
||||
multiproof: ProofResult::Legacy(multiproof.try_into()?, stats),
|
||||
})
|
||||
}
|
||||
|
||||
/// Extend update with contents of the other.
|
||||
@@ -94,7 +102,7 @@ impl SparseTrieUpdate {
|
||||
#[derive(Debug)]
|
||||
pub(super) enum MultiProofMessage {
|
||||
/// Prefetch proof targets
|
||||
PrefetchProofs(MultiProofTargets),
|
||||
PrefetchProofs(VersionedMultiProofTargets),
|
||||
/// New state update from transaction execution with its source
|
||||
StateUpdate(Source, EvmState),
|
||||
/// State update that can be applied to the sparse trie without any new proofs.
|
||||
@@ -223,12 +231,155 @@ pub(crate) fn evm_state_to_hashed_post_state(update: EvmState) -> HashedPostStat
|
||||
hashed_state
|
||||
}
|
||||
|
||||
/// Extends a `MultiProofTargets` with the contents of a `VersionedMultiProofTargets`,
|
||||
/// regardless of which variant the latter is.
|
||||
fn extend_multiproof_targets(dest: &mut MultiProofTargets, src: &VersionedMultiProofTargets) {
|
||||
match src {
|
||||
VersionedMultiProofTargets::Legacy(targets) => {
|
||||
dest.extend_ref(targets);
|
||||
}
|
||||
VersionedMultiProofTargets::V2(targets) => {
|
||||
// Add all account targets
|
||||
for target in &targets.account_targets {
|
||||
dest.entry(target.key()).or_default();
|
||||
}
|
||||
|
||||
// Add all storage targets
|
||||
for (hashed_address, slots) in &targets.storage_targets {
|
||||
let slot_set = dest.entry(*hashed_address).or_default();
|
||||
for slot in slots {
|
||||
slot_set.insert(slot.key());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of multiproof targets which can be either in the legacy or V2 representations.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum VersionedMultiProofTargets {
|
||||
/// Legacy targets
|
||||
Legacy(MultiProofTargets),
|
||||
/// V2 targets
|
||||
V2(MultiProofTargetsV2),
|
||||
}
|
||||
|
||||
impl VersionedMultiProofTargets {
|
||||
/// Returns true if there are no account or storage targets.
|
||||
fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Self::Legacy(targets) => targets.is_empty(),
|
||||
Self::V2(targets) => targets.is_empty(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of account targets in the multiproof target
|
||||
fn account_targets_len(&self) -> usize {
|
||||
match self {
|
||||
Self::Legacy(targets) => targets.len(),
|
||||
Self::V2(targets) => targets.account_targets.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of storage targets in the multiproof target
|
||||
fn storage_targets_len(&self) -> usize {
|
||||
match self {
|
||||
Self::Legacy(targets) => targets.values().map(|slots| slots.len()).sum::<usize>(),
|
||||
Self::V2(targets) => {
|
||||
targets.storage_targets.values().map(|slots| slots.len()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of accounts in the multiproof targets.
|
||||
fn len(&self) -> usize {
|
||||
match self {
|
||||
Self::Legacy(targets) => targets.len(),
|
||||
Self::V2(targets) => targets.account_targets.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the total storage slot count across all accounts.
|
||||
fn storage_count(&self) -> usize {
|
||||
match self {
|
||||
Self::Legacy(targets) => targets.values().map(|slots| slots.len()).sum(),
|
||||
Self::V2(targets) => targets.storage_targets.values().map(|slots| slots.len()).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of items that will be considered during chunking.
|
||||
fn chunking_length(&self) -> usize {
|
||||
match self {
|
||||
Self::Legacy(targets) => targets.chunking_length(),
|
||||
Self::V2(targets) => {
|
||||
// For V2, count accounts + storage slots
|
||||
targets.account_targets.len() +
|
||||
targets.storage_targets.values().map(|slots| slots.len()).sum::<usize>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retains the targets representing the difference with another `MultiProofTargets`.
|
||||
/// Removes all targets that are already present in `other`.
|
||||
fn retain_difference(&mut self, other: &MultiProofTargets) {
|
||||
match self {
|
||||
Self::Legacy(targets) => {
|
||||
targets.retain_difference(other);
|
||||
}
|
||||
Self::V2(targets) => {
|
||||
// Remove account targets that exist in other
|
||||
targets.account_targets.retain(|target| !other.contains_key(&target.key()));
|
||||
|
||||
// For each account in storage_targets, remove slots that exist in other
|
||||
targets.storage_targets.retain(|hashed_address, slots| {
|
||||
if let Some(other_slots) = other.get(hashed_address) {
|
||||
slots.retain(|slot| !other_slots.contains(&slot.key()));
|
||||
!slots.is_empty()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends this `VersionedMultiProofTargets` with the contents of another.
|
||||
///
|
||||
/// Panics if the variants do not match.
|
||||
fn extend(&mut self, other: Self) {
|
||||
match (self, other) {
|
||||
(Self::Legacy(dest), Self::Legacy(src)) => {
|
||||
dest.extend(src);
|
||||
}
|
||||
(Self::V2(dest), Self::V2(src)) => {
|
||||
dest.account_targets.extend(src.account_targets);
|
||||
for (addr, slots) in src.storage_targets {
|
||||
dest.storage_targets.entry(addr).or_default().extend(slots);
|
||||
}
|
||||
}
|
||||
_ => panic!("Cannot extend VersionedMultiProofTargets with mismatched variants"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Chunks this `VersionedMultiProofTargets` into smaller chunks of the given size.
|
||||
fn chunks(self, chunk_size: usize) -> Box<dyn Iterator<Item = Self>> {
|
||||
match self {
|
||||
Self::Legacy(targets) => {
|
||||
Box::new(MultiProofTargets::chunks(targets, chunk_size).map(Self::Legacy))
|
||||
}
|
||||
Self::V2(targets) => {
|
||||
Box::new(ChunkedMultiProofTargetsV2::new(targets, chunk_size).map(Self::V2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Input parameters for dispatching a multiproof calculation.
|
||||
#[derive(Debug)]
|
||||
struct MultiproofInput {
|
||||
source: Option<Source>,
|
||||
hashed_state_update: HashedPostState,
|
||||
proof_targets: MultiProofTargets,
|
||||
proof_targets: VersionedMultiProofTargets,
|
||||
proof_sequence_number: u64,
|
||||
state_root_message_sender: CrossbeamSender<MultiProofMessage>,
|
||||
multi_added_removed_keys: Option<Arc<MultiAddedRemovedKeys>>,
|
||||
@@ -263,8 +414,6 @@ pub struct MultiproofManager {
|
||||
proof_result_tx: CrossbeamSender<ProofResultMessage>,
|
||||
/// Metrics
|
||||
metrics: MultiProofTaskMetrics,
|
||||
/// Whether to use V2 storage proofs
|
||||
v2_proofs_enabled: bool,
|
||||
}
|
||||
|
||||
impl MultiproofManager {
|
||||
@@ -278,9 +427,7 @@ impl MultiproofManager {
|
||||
metrics.max_storage_workers.set(proof_worker_handle.total_storage_workers() as f64);
|
||||
metrics.max_account_workers.set(proof_worker_handle.total_account_workers() as f64);
|
||||
|
||||
let v2_proofs_enabled = proof_worker_handle.v2_proofs_enabled();
|
||||
|
||||
Self { metrics, proof_worker_handle, proof_result_tx, v2_proofs_enabled }
|
||||
Self { metrics, proof_worker_handle, proof_result_tx }
|
||||
}
|
||||
|
||||
/// Dispatches a new multiproof calculation to worker pools.
|
||||
@@ -325,41 +472,48 @@ impl MultiproofManager {
|
||||
multi_added_removed_keys,
|
||||
} = multiproof_input;
|
||||
|
||||
let account_targets = proof_targets.len();
|
||||
let storage_targets = proof_targets.values().map(|slots| slots.len()).sum::<usize>();
|
||||
|
||||
trace!(
|
||||
target: "engine::tree::payload_processor::multiproof",
|
||||
proof_sequence_number,
|
||||
?proof_targets,
|
||||
account_targets,
|
||||
storage_targets,
|
||||
account_targets = proof_targets.account_targets_len(),
|
||||
storage_targets = proof_targets.storage_targets_len(),
|
||||
?source,
|
||||
"Dispatching multiproof to workers"
|
||||
);
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
// Extend prefix sets with targets
|
||||
let frozen_prefix_sets =
|
||||
ParallelProof::extend_prefix_sets_with_targets(&Default::default(), &proof_targets);
|
||||
// Workers will send ProofResultMessage directly to proof_result_rx
|
||||
let proof_result_sender = ProofResultContext::new(
|
||||
self.proof_result_tx.clone(),
|
||||
proof_sequence_number,
|
||||
hashed_state_update,
|
||||
start,
|
||||
);
|
||||
|
||||
// Dispatch account multiproof to worker pool with result sender
|
||||
let input = AccountMultiproofInput {
|
||||
targets: proof_targets,
|
||||
prefix_sets: frozen_prefix_sets,
|
||||
collect_branch_node_masks: true,
|
||||
multi_added_removed_keys,
|
||||
// Workers will send ProofResultMessage directly to proof_result_rx
|
||||
proof_result_sender: ProofResultContext::new(
|
||||
self.proof_result_tx.clone(),
|
||||
proof_sequence_number,
|
||||
hashed_state_update,
|
||||
start,
|
||||
),
|
||||
v2_proofs_enabled: self.v2_proofs_enabled,
|
||||
let input = match proof_targets {
|
||||
VersionedMultiProofTargets::Legacy(proof_targets) => {
|
||||
// Extend prefix sets with targets
|
||||
let frozen_prefix_sets = ParallelProof::extend_prefix_sets_with_targets(
|
||||
&Default::default(),
|
||||
&proof_targets,
|
||||
);
|
||||
|
||||
AccountMultiproofInput::Legacy {
|
||||
targets: proof_targets,
|
||||
prefix_sets: frozen_prefix_sets,
|
||||
collect_branch_node_masks: true,
|
||||
multi_added_removed_keys,
|
||||
proof_result_sender,
|
||||
}
|
||||
}
|
||||
VersionedMultiProofTargets::V2(proof_targets) => {
|
||||
AccountMultiproofInput::V2 { targets: proof_targets, proof_result_sender }
|
||||
}
|
||||
};
|
||||
|
||||
// Dispatch account multiproof to worker pool with result sender
|
||||
if let Err(e) = self.proof_worker_handle.dispatch_account_multiproof(input) {
|
||||
error!(target: "engine::tree::payload_processor::multiproof", ?e, "Failed to dispatch account multiproof");
|
||||
return;
|
||||
@@ -561,6 +715,9 @@ pub(super) struct MultiProofTask {
|
||||
/// there are any active workers and force chunking across workers. This is to prevent tasks
|
||||
/// which are very long from hitting a single worker.
|
||||
max_targets_for_chunking: usize,
|
||||
/// Whether or not V2 proof calculation is enabled. If enabled then [`MultiProofTargetsV2`]
|
||||
/// will be produced by state updates.
|
||||
v2_proofs_enabled: bool,
|
||||
}
|
||||
|
||||
impl MultiProofTask {
|
||||
@@ -592,9 +749,16 @@ impl MultiProofTask {
|
||||
),
|
||||
metrics,
|
||||
max_targets_for_chunking: DEFAULT_MAX_TARGETS_FOR_CHUNKING,
|
||||
v2_proofs_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables V2 proof target generation on state updates.
|
||||
pub(super) const fn with_v2_proofs_enabled(mut self, v2_proofs_enabled: bool) -> Self {
|
||||
self.v2_proofs_enabled = v2_proofs_enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Handles request for proof prefetch.
|
||||
///
|
||||
/// Returns how many multiproof tasks were dispatched for the prefetch request.
|
||||
@@ -602,37 +766,40 @@ impl MultiProofTask {
|
||||
level = "debug",
|
||||
target = "engine::tree::payload_processor::multiproof",
|
||||
skip_all,
|
||||
fields(accounts = targets.len(), chunks = 0)
|
||||
fields(accounts = targets.account_targets_len(), chunks = 0)
|
||||
)]
|
||||
fn on_prefetch_proof(&mut self, mut targets: MultiProofTargets) -> u64 {
|
||||
fn on_prefetch_proof(&mut self, mut targets: VersionedMultiProofTargets) -> u64 {
|
||||
// Remove already fetched proof targets to avoid redundant work.
|
||||
targets.retain_difference(&self.fetched_proof_targets);
|
||||
self.fetched_proof_targets.extend_ref(&targets);
|
||||
extend_multiproof_targets(&mut self.fetched_proof_targets, &targets);
|
||||
|
||||
// Make sure all target accounts have an `AddedRemovedKeySet` in the
|
||||
// For Legacy multiproofs, make sure all target accounts have an `AddedRemovedKeySet` in the
|
||||
// [`MultiAddedRemovedKeys`]. Even if there are not any known removed keys for the account,
|
||||
// we still want to optimistically fetch extension children for the leaf addition case.
|
||||
self.multi_added_removed_keys.touch_accounts(targets.keys().copied());
|
||||
|
||||
// Clone+Arc MultiAddedRemovedKeys for sharing with the dispatched multiproof tasks
|
||||
let multi_added_removed_keys = Arc::new(MultiAddedRemovedKeys {
|
||||
account: self.multi_added_removed_keys.account.clone(),
|
||||
storages: targets
|
||||
.keys()
|
||||
.filter_map(|account| {
|
||||
self.multi_added_removed_keys
|
||||
.storages
|
||||
.get(account)
|
||||
.cloned()
|
||||
.map(|keys| (*account, keys))
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
// V2 multiproofs don't need this.
|
||||
//
|
||||
// Only clone the AddedRemovedKeys for accounts in the targets, not the entire accumulated
|
||||
// set, to avoid O(n) cloning with many buffered blocks.
|
||||
let multi_added_removed_keys =
|
||||
if let VersionedMultiProofTargets::Legacy(legacy_targets) = &targets {
|
||||
self.multi_added_removed_keys.touch_accounts(legacy_targets.keys().copied());
|
||||
Some(Arc::new(MultiAddedRemovedKeys {
|
||||
account: self.multi_added_removed_keys.account.clone(),
|
||||
storages: legacy_targets
|
||||
.keys()
|
||||
.filter_map(|k| {
|
||||
self.multi_added_removed_keys.storages.get(k).map(|v| (*k, v.clone()))
|
||||
})
|
||||
.collect(),
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.metrics.prefetch_proof_targets_accounts_histogram.record(targets.len() as f64);
|
||||
self.metrics
|
||||
.prefetch_proof_targets_storages_histogram
|
||||
.record(targets.values().map(|slots| slots.len()).sum::<usize>() as f64);
|
||||
.record(targets.storage_count() as f64);
|
||||
|
||||
let chunking_len = targets.chunking_length();
|
||||
let available_account_workers =
|
||||
@@ -646,7 +813,7 @@ impl MultiProofTask {
|
||||
self.max_targets_for_chunking,
|
||||
available_account_workers,
|
||||
available_storage_workers,
|
||||
MultiProofTargets::chunks,
|
||||
VersionedMultiProofTargets::chunks,
|
||||
|proof_targets| {
|
||||
self.multiproof_manager.dispatch(MultiproofInput {
|
||||
source: None,
|
||||
@@ -654,7 +821,7 @@ impl MultiProofTask {
|
||||
proof_targets,
|
||||
proof_sequence_number: self.proof_sequencer.next_sequence(),
|
||||
state_root_message_sender: self.tx.clone(),
|
||||
multi_added_removed_keys: Some(multi_added_removed_keys.clone()),
|
||||
multi_added_removed_keys: multi_added_removed_keys.clone(),
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -757,6 +924,7 @@ impl MultiProofTask {
|
||||
self.multiproof_manager.proof_worker_handle.available_account_workers();
|
||||
let available_storage_workers =
|
||||
self.multiproof_manager.proof_worker_handle.available_storage_workers();
|
||||
|
||||
let num_chunks = dispatch_with_chunking(
|
||||
not_fetched_state_update,
|
||||
chunking_len,
|
||||
@@ -770,8 +938,9 @@ impl MultiProofTask {
|
||||
&hashed_state_update,
|
||||
&self.fetched_proof_targets,
|
||||
&multi_added_removed_keys,
|
||||
self.v2_proofs_enabled,
|
||||
);
|
||||
spawned_proof_targets.extend_ref(&proof_targets);
|
||||
extend_multiproof_targets(&mut spawned_proof_targets, &proof_targets);
|
||||
|
||||
self.multiproof_manager.dispatch(MultiproofInput {
|
||||
source: Some(source),
|
||||
@@ -871,7 +1040,10 @@ impl MultiProofTask {
|
||||
batch_metrics.proofs_processed += 1;
|
||||
if let Some(combined_update) = self.on_proof(
|
||||
sequence_number,
|
||||
SparseTrieUpdate { state, multiproof: Default::default() },
|
||||
SparseTrieUpdate {
|
||||
state,
|
||||
multiproof: ProofResult::empty(self.v2_proofs_enabled),
|
||||
},
|
||||
) {
|
||||
let _ = self.to_sparse_trie.send(combined_update);
|
||||
}
|
||||
@@ -898,8 +1070,7 @@ impl MultiProofTask {
|
||||
}
|
||||
|
||||
let account_targets = merged_targets.len();
|
||||
let storage_targets =
|
||||
merged_targets.values().map(|slots| slots.len()).sum::<usize>();
|
||||
let storage_targets = merged_targets.storage_count();
|
||||
batch_metrics.prefetch_proofs_requested += self.on_prefetch_proof(merged_targets);
|
||||
trace!(
|
||||
target: "engine::tree::payload_processor::multiproof",
|
||||
@@ -1003,7 +1174,10 @@ impl MultiProofTask {
|
||||
|
||||
if let Some(combined_update) = self.on_proof(
|
||||
sequence_number,
|
||||
SparseTrieUpdate { state, multiproof: Default::default() },
|
||||
SparseTrieUpdate {
|
||||
state,
|
||||
multiproof: ProofResult::empty(self.v2_proofs_enabled),
|
||||
},
|
||||
) {
|
||||
let _ = self.to_sparse_trie.send(combined_update);
|
||||
}
|
||||
@@ -1106,7 +1280,7 @@ impl MultiProofTask {
|
||||
|
||||
let update = SparseTrieUpdate {
|
||||
state: proof_result.state,
|
||||
multiproof: proof_result_data.proof,
|
||||
multiproof: proof_result_data,
|
||||
};
|
||||
|
||||
if let Some(combined_update) =
|
||||
@@ -1196,7 +1370,7 @@ struct MultiproofBatchCtx {
|
||||
/// received.
|
||||
updates_finished_time: Option<Instant>,
|
||||
/// Reusable buffer for accumulating prefetch targets during batching.
|
||||
accumulated_prefetch_targets: Vec<MultiProofTargets>,
|
||||
accumulated_prefetch_targets: Vec<VersionedMultiProofTargets>,
|
||||
}
|
||||
|
||||
impl MultiproofBatchCtx {
|
||||
@@ -1242,40 +1416,77 @@ fn get_proof_targets(
|
||||
state_update: &HashedPostState,
|
||||
fetched_proof_targets: &MultiProofTargets,
|
||||
multi_added_removed_keys: &MultiAddedRemovedKeys,
|
||||
) -> MultiProofTargets {
|
||||
let mut targets = MultiProofTargets::default();
|
||||
v2_enabled: bool,
|
||||
) -> VersionedMultiProofTargets {
|
||||
if v2_enabled {
|
||||
let mut targets = MultiProofTargetsV2::default();
|
||||
|
||||
// first collect all new accounts (not previously fetched)
|
||||
for hashed_address in state_update.accounts.keys() {
|
||||
if !fetched_proof_targets.contains_key(hashed_address) {
|
||||
targets.insert(*hashed_address, HashSet::default());
|
||||
// first collect all new accounts (not previously fetched)
|
||||
for &hashed_address in state_update.accounts.keys() {
|
||||
if !fetched_proof_targets.contains_key(&hashed_address) {
|
||||
targets.account_targets.push(hashed_address.into());
|
||||
}
|
||||
}
|
||||
|
||||
// then process storage slots for all accounts in the state update
|
||||
for (hashed_address, storage) in &state_update.storages {
|
||||
let fetched = fetched_proof_targets.get(hashed_address);
|
||||
|
||||
// If the storage is wiped, we still need to fetch the account proof.
|
||||
if storage.wiped && fetched.is_none() {
|
||||
targets.account_targets.push(Into::<proof_v2::Target>::into(*hashed_address));
|
||||
continue
|
||||
}
|
||||
|
||||
let changed_slots = storage
|
||||
.storage
|
||||
.keys()
|
||||
.filter(|slot| !fetched.is_some_and(|f| f.contains(*slot)))
|
||||
.map(|slot| Into::<proof_v2::Target>::into(*slot))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !changed_slots.is_empty() {
|
||||
targets.account_targets.push((*hashed_address).into());
|
||||
targets.storage_targets.insert(*hashed_address, changed_slots);
|
||||
}
|
||||
}
|
||||
|
||||
VersionedMultiProofTargets::V2(targets)
|
||||
} else {
|
||||
let mut targets = MultiProofTargets::default();
|
||||
|
||||
// first collect all new accounts (not previously fetched)
|
||||
for hashed_address in state_update.accounts.keys() {
|
||||
if !fetched_proof_targets.contains_key(hashed_address) {
|
||||
targets.insert(*hashed_address, HashSet::default());
|
||||
}
|
||||
}
|
||||
|
||||
// then process storage slots for all accounts in the state update
|
||||
for (hashed_address, storage) in &state_update.storages {
|
||||
let fetched = fetched_proof_targets.get(hashed_address);
|
||||
let storage_added_removed_keys = multi_added_removed_keys.get_storage(hashed_address);
|
||||
let mut changed_slots = storage
|
||||
.storage
|
||||
.keys()
|
||||
.filter(|slot| {
|
||||
!fetched.is_some_and(|f| f.contains(*slot)) ||
|
||||
storage_added_removed_keys.is_some_and(|k| k.is_removed(slot))
|
||||
})
|
||||
.peekable();
|
||||
|
||||
// If the storage is wiped, we still need to fetch the account proof.
|
||||
if storage.wiped && fetched.is_none() {
|
||||
targets.entry(*hashed_address).or_default();
|
||||
}
|
||||
|
||||
if changed_slots.peek().is_some() {
|
||||
targets.entry(*hashed_address).or_default().extend(changed_slots);
|
||||
}
|
||||
}
|
||||
|
||||
VersionedMultiProofTargets::Legacy(targets)
|
||||
}
|
||||
|
||||
// then process storage slots for all accounts in the state update
|
||||
for (hashed_address, storage) in &state_update.storages {
|
||||
let fetched = fetched_proof_targets.get(hashed_address);
|
||||
let storage_added_removed_keys = multi_added_removed_keys.get_storage(hashed_address);
|
||||
let mut changed_slots = storage
|
||||
.storage
|
||||
.keys()
|
||||
.filter(|slot| {
|
||||
!fetched.is_some_and(|f| f.contains(*slot)) ||
|
||||
storage_added_removed_keys.is_some_and(|k| k.is_removed(slot))
|
||||
})
|
||||
.peekable();
|
||||
|
||||
// If the storage is wiped, we still need to fetch the account proof.
|
||||
if storage.wiped && fetched.is_none() {
|
||||
targets.entry(*hashed_address).or_default();
|
||||
}
|
||||
|
||||
if changed_slots.peek().is_some() {
|
||||
targets.entry(*hashed_address).or_default().extend(changed_slots);
|
||||
}
|
||||
}
|
||||
|
||||
targets
|
||||
}
|
||||
|
||||
/// Dispatches work items as a single unit or in chunks based on target size and worker
|
||||
@@ -1316,14 +1527,15 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tree::cached_state::CachedStateProvider;
|
||||
|
||||
use super::*;
|
||||
use crate::tree::cached_state::{CachedStateProvider, ExecutionCacheBuilder};
|
||||
use alloy_eip7928::{AccountChanges, BalanceChange};
|
||||
use alloy_primitives::Address;
|
||||
use reth_provider::{
|
||||
providers::OverlayStateProviderFactory, test_utils::create_test_provider_factory,
|
||||
BlockNumReader, BlockReader, ChangeSetReader, DatabaseProviderFactory, LatestStateProvider,
|
||||
PruneCheckpointReader, StageCheckpointReader, StateProviderBox,
|
||||
PruneCheckpointReader, StageCheckpointReader, StateProviderBox, StorageChangeSetReader,
|
||||
};
|
||||
use reth_trie::MultiProof;
|
||||
use reth_trie_db::ChangesetCache;
|
||||
@@ -1350,6 +1562,7 @@ mod tests {
|
||||
+ StageCheckpointReader
|
||||
+ PruneCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader,
|
||||
> + Clone
|
||||
+ Send
|
||||
@@ -1376,7 +1589,7 @@ mod tests {
|
||||
{
|
||||
let db_provider = factory.database_provider_ro().unwrap();
|
||||
let state_provider: StateProviderBox = Box::new(LatestStateProvider::new(db_provider));
|
||||
let cache = ExecutionCacheBuilder::default().build_caches(1000);
|
||||
let cache = crate::tree::cached_state::ExecutionCache::new(1000);
|
||||
CachedStateProvider::new(state_provider, cache, Default::default())
|
||||
}
|
||||
|
||||
@@ -1481,12 +1694,24 @@ mod tests {
|
||||
state
|
||||
}
|
||||
|
||||
fn unwrap_legacy_targets(targets: VersionedMultiProofTargets) -> MultiProofTargets {
|
||||
match targets {
|
||||
VersionedMultiProofTargets::Legacy(targets) => targets,
|
||||
VersionedMultiProofTargets::V2(_) => panic!("Expected Legacy targets"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_proof_targets_new_account_targets() {
|
||||
let state = create_get_proof_targets_state();
|
||||
let fetched = MultiProofTargets::default();
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new());
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&MultiAddedRemovedKeys::new(),
|
||||
false,
|
||||
));
|
||||
|
||||
// should return all accounts as targets since nothing was fetched before
|
||||
assert_eq!(targets.len(), state.accounts.len());
|
||||
@@ -1500,7 +1725,12 @@ mod tests {
|
||||
let state = create_get_proof_targets_state();
|
||||
let fetched = MultiProofTargets::default();
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new());
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&MultiAddedRemovedKeys::new(),
|
||||
false,
|
||||
));
|
||||
|
||||
// verify storage slots are included for accounts with storage
|
||||
for (addr, storage) in &state.storages {
|
||||
@@ -1528,7 +1758,12 @@ mod tests {
|
||||
// mark the account as already fetched
|
||||
fetched.insert(*fetched_addr, HashSet::default());
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new());
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&MultiAddedRemovedKeys::new(),
|
||||
false,
|
||||
));
|
||||
|
||||
// should not include the already fetched account since it has no storage updates
|
||||
assert!(!targets.contains_key(fetched_addr));
|
||||
@@ -1548,7 +1783,12 @@ mod tests {
|
||||
fetched_slots.insert(fetched_slot);
|
||||
fetched.insert(*addr, fetched_slots);
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new());
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&MultiAddedRemovedKeys::new(),
|
||||
false,
|
||||
));
|
||||
|
||||
// should not include the already fetched storage slot
|
||||
let target_slots = &targets[addr];
|
||||
@@ -1561,7 +1801,12 @@ mod tests {
|
||||
let state = HashedPostState::default();
|
||||
let fetched = MultiProofTargets::default();
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new());
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&MultiAddedRemovedKeys::new(),
|
||||
false,
|
||||
));
|
||||
|
||||
assert!(targets.is_empty());
|
||||
}
|
||||
@@ -1588,7 +1833,12 @@ mod tests {
|
||||
fetched_slots.insert(slot1);
|
||||
fetched.insert(addr1, fetched_slots);
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new());
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&MultiAddedRemovedKeys::new(),
|
||||
false,
|
||||
));
|
||||
|
||||
assert!(targets.contains_key(&addr2));
|
||||
assert!(!targets[&addr1].contains(&slot1));
|
||||
@@ -1614,7 +1864,12 @@ mod tests {
|
||||
assert!(!state.accounts.contains_key(&addr));
|
||||
assert!(!fetched.contains_key(&addr));
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new());
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&MultiAddedRemovedKeys::new(),
|
||||
false,
|
||||
));
|
||||
|
||||
// verify that we still get the storage slots for the unmodified account
|
||||
assert!(targets.contains_key(&addr));
|
||||
@@ -1656,7 +1911,12 @@ mod tests {
|
||||
removed_state.storages.insert(addr, removed_storage);
|
||||
multi_added_removed_keys.update_with_state(&removed_state);
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &multi_added_removed_keys);
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&multi_added_removed_keys,
|
||||
false,
|
||||
));
|
||||
|
||||
// slot1 should be included despite being fetched, because it's marked as removed
|
||||
assert!(targets.contains_key(&addr));
|
||||
@@ -1683,7 +1943,12 @@ mod tests {
|
||||
storage.storage.insert(slot1, U256::from(100));
|
||||
state.storages.insert(addr, storage);
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &multi_added_removed_keys);
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&multi_added_removed_keys,
|
||||
false,
|
||||
));
|
||||
|
||||
// account should be included because storage is wiped and account wasn't fetched
|
||||
assert!(targets.contains_key(&addr));
|
||||
@@ -1726,7 +1991,12 @@ mod tests {
|
||||
removed_state.storages.insert(addr, removed_storage);
|
||||
multi_added_removed_keys.update_with_state(&removed_state);
|
||||
|
||||
let targets = get_proof_targets(&state, &fetched, &multi_added_removed_keys);
|
||||
let targets = unwrap_legacy_targets(get_proof_targets(
|
||||
&state,
|
||||
&fetched,
|
||||
&multi_added_removed_keys,
|
||||
false,
|
||||
));
|
||||
|
||||
// only slots in the state update can be included, so slot3 should not appear
|
||||
assert!(!targets.contains_key(&addr));
|
||||
@@ -1753,9 +2023,12 @@ mod tests {
|
||||
targets3.insert(addr3, HashSet::default());
|
||||
|
||||
let tx = task.tx.clone();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(targets1)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(targets2)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(targets3)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(targets1)))
|
||||
.unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(targets2)))
|
||||
.unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(targets3)))
|
||||
.unwrap();
|
||||
|
||||
let proofs_requested =
|
||||
if let Ok(MultiProofMessage::PrefetchProofs(targets)) = task.rx.recv() {
|
||||
@@ -1769,11 +2042,12 @@ mod tests {
|
||||
|
||||
assert_eq!(num_batched, 3);
|
||||
assert_eq!(merged_targets.len(), 3);
|
||||
assert!(merged_targets.contains_key(&addr1));
|
||||
assert!(merged_targets.contains_key(&addr2));
|
||||
assert!(merged_targets.contains_key(&addr3));
|
||||
let legacy_targets = unwrap_legacy_targets(merged_targets);
|
||||
assert!(legacy_targets.contains_key(&addr1));
|
||||
assert!(legacy_targets.contains_key(&addr2));
|
||||
assert!(legacy_targets.contains_key(&addr3));
|
||||
|
||||
task.on_prefetch_proof(merged_targets)
|
||||
task.on_prefetch_proof(VersionedMultiProofTargets::Legacy(legacy_targets))
|
||||
} else {
|
||||
panic!("Expected PrefetchProofs message");
|
||||
};
|
||||
@@ -1848,11 +2122,16 @@ mod tests {
|
||||
|
||||
// Queue: [PrefetchProofs1, PrefetchProofs2, StateUpdate1, StateUpdate2, PrefetchProofs3]
|
||||
let tx = task.tx.clone();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(targets1)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(targets2)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(targets1)))
|
||||
.unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(targets2)))
|
||||
.unwrap();
|
||||
tx.send(MultiProofMessage::StateUpdate(source.into(), state_update1)).unwrap();
|
||||
tx.send(MultiProofMessage::StateUpdate(source.into(), state_update2)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(targets3.clone())).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(
|
||||
targets3.clone(),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
// Step 1: Receive and batch PrefetchProofs (should get targets1 + targets2)
|
||||
let mut pending_msg: Option<MultiProofMessage> = None;
|
||||
@@ -1878,9 +2157,10 @@ mod tests {
|
||||
// Should have batched exactly 2 PrefetchProofs (not 3!)
|
||||
assert_eq!(num_batched, 2, "Should batch only until different message type");
|
||||
assert_eq!(merged_targets.len(), 2);
|
||||
assert!(merged_targets.contains_key(&addr1));
|
||||
assert!(merged_targets.contains_key(&addr2));
|
||||
assert!(!merged_targets.contains_key(&addr3), "addr3 should NOT be in first batch");
|
||||
let legacy_targets = unwrap_legacy_targets(merged_targets);
|
||||
assert!(legacy_targets.contains_key(&addr1));
|
||||
assert!(legacy_targets.contains_key(&addr2));
|
||||
assert!(!legacy_targets.contains_key(&addr3), "addr3 should NOT be in first batch");
|
||||
} else {
|
||||
panic!("Expected PrefetchProofs message");
|
||||
}
|
||||
@@ -1905,7 +2185,8 @@ mod tests {
|
||||
match task.rx.try_recv() {
|
||||
Ok(MultiProofMessage::PrefetchProofs(targets)) => {
|
||||
assert_eq!(targets.len(), 1);
|
||||
assert!(targets.contains_key(&addr3));
|
||||
let legacy_targets = unwrap_legacy_targets(targets);
|
||||
assert!(legacy_targets.contains_key(&addr3));
|
||||
}
|
||||
_ => panic!("PrefetchProofs3 was lost!"),
|
||||
}
|
||||
@@ -1951,9 +2232,13 @@ mod tests {
|
||||
let source = StateChangeSource::Transaction(99);
|
||||
|
||||
let tx = task.tx.clone();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(prefetch1)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(prefetch1)))
|
||||
.unwrap();
|
||||
tx.send(MultiProofMessage::StateUpdate(source.into(), state_update)).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(prefetch2.clone())).unwrap();
|
||||
tx.send(MultiProofMessage::PrefetchProofs(VersionedMultiProofTargets::Legacy(
|
||||
prefetch2.clone(),
|
||||
)))
|
||||
.unwrap();
|
||||
|
||||
let mut ctx = MultiproofBatchCtx::new(Instant::now());
|
||||
let mut batch_metrics = MultiproofBatchMetrics::default();
|
||||
@@ -1986,7 +2271,8 @@ mod tests {
|
||||
match task.rx.try_recv() {
|
||||
Ok(MultiProofMessage::PrefetchProofs(targets)) => {
|
||||
assert_eq!(targets.len(), 1);
|
||||
assert!(targets.contains_key(&prefetch_addr2));
|
||||
let legacy_targets = unwrap_legacy_targets(targets);
|
||||
assert!(legacy_targets.contains_key(&prefetch_addr2));
|
||||
}
|
||||
other => panic!("Expected PrefetchProofs2 in channel, got {:?}", other),
|
||||
}
|
||||
|
||||
@@ -16,20 +16,19 @@ use crate::tree::{
|
||||
payload_processor::{
|
||||
bal::{total_slots, BALSlotIter},
|
||||
executor::WorkloadExecutor,
|
||||
multiproof::MultiProofMessage,
|
||||
ExecutionCache as PayloadExecutionCache,
|
||||
multiproof::{MultiProofMessage, VersionedMultiProofTargets},
|
||||
PayloadExecutionCache,
|
||||
},
|
||||
precompile_cache::{CachedPrecompile, PrecompileCacheMap},
|
||||
ExecutionEnv, StateProviderBuilder,
|
||||
};
|
||||
use alloy_consensus::transaction::TxHashRef;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::Typed2718;
|
||||
use alloy_evm::Database;
|
||||
use alloy_primitives::{keccak256, map::B256Set, B256};
|
||||
use crossbeam_channel::Sender as CrossbeamSender;
|
||||
use crossbeam_channel::{Receiver as CrossbeamReceiver, Sender as CrossbeamSender};
|
||||
use metrics::{Counter, Gauge, Histogram};
|
||||
use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, SpecFor};
|
||||
use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, RecoveredTx, SpecFor};
|
||||
use reth_metrics::Metrics;
|
||||
use reth_primitives_traits::NodePrimitives;
|
||||
use reth_provider::{
|
||||
@@ -66,19 +65,6 @@ struct IndexedTransaction<Tx> {
|
||||
tx: Tx,
|
||||
}
|
||||
|
||||
/// Maximum standard Ethereum transaction type value.
|
||||
///
|
||||
/// Standard transaction types are:
|
||||
/// - Type 0: Legacy transactions (original Ethereum)
|
||||
/// - Type 1: EIP-2930 (access list transactions)
|
||||
/// - Type 2: EIP-1559 (dynamic fee transactions)
|
||||
/// - Type 3: EIP-4844 (blob transactions)
|
||||
/// - Type 4: EIP-7702 (set code authorization transactions)
|
||||
///
|
||||
/// Any transaction with a type > 4 is considered a non-standard/system transaction,
|
||||
/// typically used by L2s for special purposes (e.g., Optimism deposit transactions use type 126).
|
||||
const MAX_STANDARD_TX_TYPE: u8 = 4;
|
||||
|
||||
/// A task that is responsible for caching and prewarming the cache by executing transactions
|
||||
/// individually in parallel.
|
||||
///
|
||||
@@ -177,8 +163,8 @@ where
|
||||
transaction_count_hint.min(max_concurrency)
|
||||
};
|
||||
|
||||
// Initialize worker handles container
|
||||
let handles = ctx.clone().spawn_workers(workers_needed, &executor, actions_tx.clone(), done_tx.clone());
|
||||
// Spawn workers
|
||||
let tx_sender = ctx.clone().spawn_workers(workers_needed, &executor, actions_tx.clone(), done_tx.clone());
|
||||
|
||||
// Distribute transactions to workers
|
||||
let mut tx_index = 0usize;
|
||||
@@ -193,37 +179,18 @@ where
|
||||
}
|
||||
|
||||
let indexed_tx = IndexedTransaction { index: tx_index, tx };
|
||||
let is_system_tx = indexed_tx.tx.tx().ty() > MAX_STANDARD_TX_TYPE;
|
||||
|
||||
// System transactions (type > 4) in the first position set critical metadata
|
||||
// that affects all subsequent transactions (e.g., L1 block info on L2s).
|
||||
// Broadcast the first system transaction to all workers to ensure they have
|
||||
// the critical state. This is particularly important for L2s like Optimism
|
||||
// where the first deposit transaction (type 126) contains essential block metadata.
|
||||
if tx_index == 0 && is_system_tx {
|
||||
for handle in &handles {
|
||||
// Ignore send errors: workers listen to terminate_execution and may
|
||||
// exit early when signaled. Sending to a disconnected worker is
|
||||
// possible and harmless and should happen at most once due to
|
||||
// the terminate_execution check above.
|
||||
let _ = handle.send(indexed_tx.clone());
|
||||
}
|
||||
} else {
|
||||
// Round-robin distribution for all other transactions
|
||||
let worker_idx = tx_index % workers_needed;
|
||||
// Ignore send errors: workers listen to terminate_execution and may
|
||||
// exit early when signaled. Sending to a disconnected worker is
|
||||
// possible and harmless and should happen at most once due to
|
||||
// the terminate_execution check above.
|
||||
let _ = handles[worker_idx].send(indexed_tx);
|
||||
}
|
||||
// Send transaction to the workers
|
||||
// Ignore send errors: workers listen to terminate_execution and may
|
||||
// exit early when signaled.
|
||||
let _ = tx_sender.send(indexed_tx);
|
||||
|
||||
tx_index += 1;
|
||||
}
|
||||
|
||||
// drop handle and wait for all tasks to finish and drop theirs
|
||||
// drop sender and wait for all tasks to finish
|
||||
drop(done_tx);
|
||||
drop(handles);
|
||||
drop(tx_sender);
|
||||
while done_rx.recv().is_ok() {}
|
||||
|
||||
let _ = actions_tx
|
||||
@@ -237,7 +204,7 @@ where
|
||||
}
|
||||
|
||||
/// If configured and the tx returned proof targets, emit the targets the transaction produced
|
||||
fn send_multi_proof_targets(&self, targets: Option<MultiProofTargets>) {
|
||||
fn send_multi_proof_targets(&self, targets: Option<VersionedMultiProofTargets>) {
|
||||
if self.is_execution_terminated() {
|
||||
// if execution is already terminated then we dont need to send more proof fetch
|
||||
// messages
|
||||
@@ -484,6 +451,8 @@ where
|
||||
pub(super) terminate_execution: Arc<AtomicBool>,
|
||||
pub(super) precompile_cache_disabled: bool,
|
||||
pub(super) precompile_cache_map: PrecompileCacheMap<SpecFor<Evm>>,
|
||||
/// Whether V2 proof calculation is enabled.
|
||||
pub(super) v2_proofs_enabled: bool,
|
||||
}
|
||||
|
||||
impl<N, P, Evm> PrewarmContext<N, P, Evm>
|
||||
@@ -492,10 +461,12 @@ where
|
||||
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
|
||||
Evm: ConfigureEvm<Primitives = N> + 'static,
|
||||
{
|
||||
/// Splits this context into an evm, an evm config, metrics, and the atomic bool for terminating
|
||||
/// execution.
|
||||
/// Splits this context into an evm, an evm config, metrics, the atomic bool for terminating
|
||||
/// execution, and whether V2 proofs are enabled.
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
|
||||
fn evm_for_ctx(self) -> Option<(EvmFor<Evm, impl Database>, PrewarmMetrics, Arc<AtomicBool>)> {
|
||||
fn evm_for_ctx(
|
||||
self,
|
||||
) -> Option<(EvmFor<Evm, impl Database>, PrewarmMetrics, Arc<AtomicBool>, bool)> {
|
||||
let Self {
|
||||
env,
|
||||
evm_config,
|
||||
@@ -505,6 +476,7 @@ where
|
||||
terminate_execution,
|
||||
precompile_cache_disabled,
|
||||
precompile_cache_map,
|
||||
v2_proofs_enabled,
|
||||
} = self;
|
||||
|
||||
let mut state_provider = match provider.build() {
|
||||
@@ -554,10 +526,10 @@ where
|
||||
});
|
||||
}
|
||||
|
||||
Some((evm, metrics, terminate_execution))
|
||||
Some((evm, metrics, terminate_execution, v2_proofs_enabled))
|
||||
}
|
||||
|
||||
/// Accepts an [`mpsc::Receiver`] of transactions and a handle to prewarm task. Executes
|
||||
/// Accepts a [`CrossbeamReceiver`] of transactions and a handle to prewarm task. Executes
|
||||
/// transactions and streams [`PrewarmTaskEvent::Outcome`] messages for each transaction.
|
||||
///
|
||||
/// This function processes transactions sequentially from the receiver and emits outcome events
|
||||
@@ -569,13 +541,16 @@ where
|
||||
#[instrument(level = "debug", target = "engine::tree::payload_processor::prewarm", skip_all)]
|
||||
fn transact_batch<Tx>(
|
||||
self,
|
||||
txs: mpsc::Receiver<IndexedTransaction<Tx>>,
|
||||
txs: CrossbeamReceiver<IndexedTransaction<Tx>>,
|
||||
sender: Sender<PrewarmTaskEvent<N::Receipt>>,
|
||||
done_tx: Sender<()>,
|
||||
) where
|
||||
Tx: ExecutableTxFor<Evm>,
|
||||
{
|
||||
let Some((mut evm, metrics, terminate_execution)) = self.evm_for_ctx() else { return };
|
||||
let Some((mut evm, metrics, terminate_execution, v2_proofs_enabled)) = self.evm_for_ctx()
|
||||
else {
|
||||
return
|
||||
};
|
||||
|
||||
while let Ok(IndexedTransaction { index, tx }) = {
|
||||
let _enter = debug_span!(target: "engine::tree::payload_processor::prewarm", "recv tx")
|
||||
@@ -601,7 +576,8 @@ where
|
||||
break
|
||||
}
|
||||
|
||||
let res = match evm.transact(&tx) {
|
||||
let (tx_env, tx) = tx.into_parts();
|
||||
let res = match evm.transact(tx_env) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
trace!(
|
||||
@@ -638,7 +614,8 @@ where
|
||||
let _enter =
|
||||
debug_span!(target: "engine::tree::payload_processor::prewarm", "prewarm outcome", index, tx_hash=%tx.tx().tx_hash())
|
||||
.entered();
|
||||
let (targets, storage_targets) = multiproof_targets_from_state(res.state);
|
||||
let (targets, storage_targets) =
|
||||
multiproof_targets_from_state(res.state, v2_proofs_enabled);
|
||||
metrics.prefetch_storage_targets.record(storage_targets as f64);
|
||||
let _ = sender.send(PrewarmTaskEvent::Outcome { proof_targets: Some(targets) });
|
||||
drop(_enter);
|
||||
@@ -651,35 +628,31 @@ where
|
||||
let _ = done_tx.send(());
|
||||
}
|
||||
|
||||
/// Spawns a worker task for transaction execution and returns its sender channel.
|
||||
/// Spawns worker tasks that pull transactions from a shared channel.
|
||||
///
|
||||
/// Returns the sender for distributing transactions to workers.
|
||||
fn spawn_workers<Tx>(
|
||||
self,
|
||||
workers_needed: usize,
|
||||
task_executor: &WorkloadExecutor,
|
||||
actions_tx: Sender<PrewarmTaskEvent<N::Receipt>>,
|
||||
done_tx: Sender<()>,
|
||||
) -> Vec<mpsc::Sender<IndexedTransaction<Tx>>>
|
||||
) -> CrossbeamSender<IndexedTransaction<Tx>>
|
||||
where
|
||||
Tx: ExecutableTxFor<Evm> + Send + 'static,
|
||||
{
|
||||
let mut handles = Vec::with_capacity(workers_needed);
|
||||
let mut receivers = Vec::with_capacity(workers_needed);
|
||||
let (tx_sender, tx_receiver) = crossbeam_channel::unbounded();
|
||||
|
||||
for _ in 0..workers_needed {
|
||||
let (tx, rx) = mpsc::channel();
|
||||
handles.push(tx);
|
||||
receivers.push(rx);
|
||||
}
|
||||
|
||||
// Spawn a separate task spawning workers in parallel.
|
||||
// Spawn workers that all pull from the shared receiver
|
||||
let executor = task_executor.clone();
|
||||
let span = Span::current();
|
||||
task_executor.spawn_blocking(move || {
|
||||
let _enter = span.entered();
|
||||
for (idx, rx) in receivers.into_iter().enumerate() {
|
||||
for idx in 0..workers_needed {
|
||||
let ctx = self.clone();
|
||||
let actions_tx = actions_tx.clone();
|
||||
let done_tx = done_tx.clone();
|
||||
let rx = tx_receiver.clone();
|
||||
let span = debug_span!(target: "engine::tree::payload_processor::prewarm", "prewarm worker", idx);
|
||||
executor.spawn_blocking(move || {
|
||||
let _enter = span.entered();
|
||||
@@ -688,7 +661,7 @@ where
|
||||
}
|
||||
});
|
||||
|
||||
handles
|
||||
tx_sender
|
||||
}
|
||||
|
||||
/// Spawns a worker task for BAL slot prefetching.
|
||||
@@ -783,9 +756,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a set of [`MultiProofTargets`] and the total amount of storage targets, based on the
|
||||
/// Returns a set of [`VersionedMultiProofTargets`] and the total amount of storage targets, based
|
||||
/// on the given state.
|
||||
fn multiproof_targets_from_state(
|
||||
state: EvmState,
|
||||
v2_enabled: bool,
|
||||
) -> (VersionedMultiProofTargets, usize) {
|
||||
if v2_enabled {
|
||||
multiproof_targets_v2_from_state(state)
|
||||
} else {
|
||||
multiproof_targets_legacy_from_state(state)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns legacy [`MultiProofTargets`] and the total amount of storage targets, based on the
|
||||
/// given state.
|
||||
fn multiproof_targets_from_state(state: EvmState) -> (MultiProofTargets, usize) {
|
||||
fn multiproof_targets_legacy_from_state(state: EvmState) -> (VersionedMultiProofTargets, usize) {
|
||||
let mut targets = MultiProofTargets::with_capacity(state.len());
|
||||
let mut storage_targets = 0;
|
||||
for (addr, account) in state {
|
||||
@@ -815,7 +801,50 @@ fn multiproof_targets_from_state(state: EvmState) -> (MultiProofTargets, usize)
|
||||
targets.insert(keccak256(addr), storage_set);
|
||||
}
|
||||
|
||||
(targets, storage_targets)
|
||||
(VersionedMultiProofTargets::Legacy(targets), storage_targets)
|
||||
}
|
||||
|
||||
/// Returns V2 [`reth_trie_parallel::targets_v2::MultiProofTargetsV2`] and the total amount of
|
||||
/// storage targets, based on the given state.
|
||||
fn multiproof_targets_v2_from_state(state: EvmState) -> (VersionedMultiProofTargets, usize) {
|
||||
use reth_trie::proof_v2;
|
||||
use reth_trie_parallel::targets_v2::MultiProofTargetsV2;
|
||||
|
||||
let mut targets = MultiProofTargetsV2::default();
|
||||
let mut storage_target_count = 0;
|
||||
for (addr, account) in state {
|
||||
// if the account was not touched, or if the account was selfdestructed, do not
|
||||
// fetch proofs for it
|
||||
//
|
||||
// Since selfdestruct can only happen in the same transaction, we can skip
|
||||
// prefetching proofs for selfdestructed accounts
|
||||
//
|
||||
// See: https://eips.ethereum.org/EIPS/eip-6780
|
||||
if !account.is_touched() || account.is_selfdestructed() {
|
||||
continue
|
||||
}
|
||||
|
||||
let hashed_address = keccak256(addr);
|
||||
targets.account_targets.push(hashed_address.into());
|
||||
|
||||
let mut storage_slots = Vec::with_capacity(account.storage.len());
|
||||
for (key, slot) in account.storage {
|
||||
// do nothing if unchanged
|
||||
if !slot.is_changed() {
|
||||
continue
|
||||
}
|
||||
|
||||
let hashed_slot = keccak256(B256::new(key.to_be_bytes()));
|
||||
storage_slots.push(proof_v2::Target::from(hashed_slot));
|
||||
}
|
||||
|
||||
storage_target_count += storage_slots.len();
|
||||
if !storage_slots.is_empty() {
|
||||
targets.storage_targets.insert(hashed_address, storage_slots);
|
||||
}
|
||||
}
|
||||
|
||||
(VersionedMultiProofTargets::V2(targets), storage_target_count)
|
||||
}
|
||||
|
||||
/// The events the pre-warm task can handle.
|
||||
@@ -840,7 +869,7 @@ pub(super) enum PrewarmTaskEvent<R> {
|
||||
/// The outcome of a pre-warm task
|
||||
Outcome {
|
||||
/// The prepared proof targets based on the evm state outcome
|
||||
proof_targets: Option<MultiProofTargets>,
|
||||
proof_targets: Option<VersionedMultiProofTargets>,
|
||||
},
|
||||
/// Finished executing all transactions
|
||||
FinishedTxExecution {
|
||||
|
||||
@@ -4,11 +4,11 @@ use crate::tree::payload_processor::multiproof::{MultiProofTaskMetrics, SparseTr
|
||||
use alloy_primitives::B256;
|
||||
use rayon::iter::{ParallelBridge, ParallelIterator};
|
||||
use reth_trie::{updates::TrieUpdates, Nibbles};
|
||||
use reth_trie_parallel::root::ParallelStateRootError;
|
||||
use reth_trie_parallel::{proof_task::ProofResult, root::ParallelStateRootError};
|
||||
use reth_trie_sparse::{
|
||||
errors::{SparseStateTrieResult, SparseTrieErrorKind},
|
||||
provider::{TrieNodeProvider, TrieNodeProviderFactory},
|
||||
ClearedSparseStateTrie, SerialSparseTrie, SparseStateTrie, SparseTrieInterface,
|
||||
ClearedSparseStateTrie, SerialSparseTrie, SparseStateTrie, SparseTrie,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
@@ -38,8 +38,8 @@ where
|
||||
BPF: TrieNodeProviderFactory + Send + Sync + Clone,
|
||||
BPF::AccountNodeProvider: TrieNodeProvider + Send + Sync,
|
||||
BPF::StorageNodeProvider: TrieNodeProvider + Send + Sync,
|
||||
A: SparseTrieInterface + Send + Sync + Default,
|
||||
S: SparseTrieInterface + Send + Sync + Default + Clone,
|
||||
A: SparseTrie + Send + Sync + Default,
|
||||
S: SparseTrie + Send + Sync + Default + Clone,
|
||||
{
|
||||
/// Creates a new sparse trie, pre-populating with a [`ClearedSparseStateTrie`].
|
||||
pub(super) fn new_with_cleared_trie(
|
||||
@@ -97,8 +97,8 @@ where
|
||||
debug!(
|
||||
target: "engine::root",
|
||||
num_updates,
|
||||
account_proofs = update.multiproof.account_subtree.len(),
|
||||
storage_proofs = update.multiproof.storages.len(),
|
||||
account_proofs = update.multiproof.account_proofs_len(),
|
||||
storage_proofs = update.multiproof.storage_proofs_len(),
|
||||
"Updating sparse trie"
|
||||
);
|
||||
|
||||
@@ -150,14 +150,21 @@ where
|
||||
BPF: TrieNodeProviderFactory + Send + Sync,
|
||||
BPF::AccountNodeProvider: TrieNodeProvider + Send + Sync,
|
||||
BPF::StorageNodeProvider: TrieNodeProvider + Send + Sync,
|
||||
A: SparseTrieInterface + Send + Sync + Default,
|
||||
S: SparseTrieInterface + Send + Sync + Default + Clone,
|
||||
A: SparseTrie + Send + Sync + Default,
|
||||
S: SparseTrie + Send + Sync + Default + Clone,
|
||||
{
|
||||
trace!(target: "engine::root::sparse", "Updating sparse trie");
|
||||
let started_at = Instant::now();
|
||||
|
||||
// Reveal new accounts and storage slots.
|
||||
trie.reveal_decoded_multiproof(multiproof)?;
|
||||
match multiproof {
|
||||
ProofResult::Legacy(decoded, _) => {
|
||||
trie.reveal_decoded_multiproof(decoded)?;
|
||||
}
|
||||
ProofResult::V2(decoded_v2) => {
|
||||
trie.reveal_decoded_multiproof_v2(decoded_v2)?;
|
||||
}
|
||||
}
|
||||
let reveal_multiproof_elapsed = started_at.elapsed();
|
||||
trace!(
|
||||
target: "engine::root::sparse",
|
||||
|
||||
@@ -39,7 +39,7 @@ use reth_provider::{
|
||||
providers::OverlayStateProviderFactory, BlockExecutionOutput, BlockNumReader, BlockReader,
|
||||
ChangeSetReader, DatabaseProviderFactory, DatabaseProviderROFactory, HashedPostStateProvider,
|
||||
ProviderError, PruneCheckpointReader, StageCheckpointReader, StateProvider,
|
||||
StateProviderFactory, StateReader,
|
||||
StateProviderFactory, StateReader, StorageChangeSetReader,
|
||||
};
|
||||
use reth_revm::db::{states::bundle_state::BundleRetention, State};
|
||||
use reth_trie::{updates::TrieUpdates, HashedPostState, StateRoot};
|
||||
@@ -144,6 +144,7 @@ where
|
||||
+ StageCheckpointReader
|
||||
+ PruneCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader,
|
||||
> + BlockReader<Header = N::BlockHeader>
|
||||
+ ChangeSetReader
|
||||
@@ -502,6 +503,7 @@ where
|
||||
|
||||
let root_time = Instant::now();
|
||||
let mut maybe_state_root = None;
|
||||
let mut state_root_task_failed = false;
|
||||
|
||||
match strategy {
|
||||
StateRootStrategy::StateRootTask => {
|
||||
@@ -520,10 +522,12 @@ where
|
||||
block_state_root = ?block.header().state_root(),
|
||||
"State root task returned incorrect state root"
|
||||
);
|
||||
state_root_task_failed = true;
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
debug!(target: "engine::tree::payload_validator", %error, "State root task failed");
|
||||
state_root_task_failed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -568,6 +572,11 @@ where
|
||||
self.compute_state_root_serial(overlay_factory.clone(), &hashed_state),
|
||||
block
|
||||
);
|
||||
|
||||
if state_root_task_failed {
|
||||
self.metrics.block_validation.state_root_task_fallback_success_total.increment(1);
|
||||
}
|
||||
|
||||
(root, updates, root_time.elapsed())
|
||||
};
|
||||
|
||||
@@ -1336,6 +1345,7 @@ where
|
||||
+ StageCheckpointReader
|
||||
+ PruneCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ BlockNumReader,
|
||||
> + BlockReader<Header = N::BlockHeader>
|
||||
+ StateProviderFactory
|
||||
|
||||
@@ -52,7 +52,7 @@ pub fn read_dir(
|
||||
checksums.next().transpose()?.ok_or_eyre("Got less checksums than ERA files")?;
|
||||
}
|
||||
|
||||
entries.sort_by(|(left, _), (right, _)| left.cmp(right));
|
||||
entries.sort_by_key(|(left, _)| *left);
|
||||
|
||||
Ok(stream::iter(entries.into_iter().skip_while(move |(n, _)| *n < start_index).map(
|
||||
move |(_, path)| {
|
||||
|
||||
@@ -20,6 +20,7 @@ use reth_era::{
|
||||
},
|
||||
};
|
||||
use reth_fs_util as fs;
|
||||
use reth_primitives_traits::Block;
|
||||
use reth_storage_api::{BlockNumReader, BlockReader, HeaderProvider};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
@@ -295,9 +296,11 @@ where
|
||||
return Err(eyre!("Expected block {expected_block_number}, got {actual_block_number}"));
|
||||
}
|
||||
|
||||
// CompressedBody must contain the block *body* (rlp(body)), not the full block (rlp(block)).
|
||||
let body = provider
|
||||
.block_by_number(actual_block_number)?
|
||||
.ok_or_else(|| eyre!("Block body not found for block {}", actual_block_number))?;
|
||||
.ok_or_else(|| eyre!("Block not found for block {}", actual_block_number))?
|
||||
.into_body();
|
||||
|
||||
let receipts = provider
|
||||
.receipts_by_block(actual_block_number.into())?
|
||||
|
||||
@@ -17,10 +17,11 @@ pub use payload::{payload_id, BlobSidecars, EthBuiltPayload, EthPayloadBuilderAt
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5};
|
||||
use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload};
|
||||
pub use alloy_rpc_types_engine::{
|
||||
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
|
||||
ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes,
|
||||
ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, ExecutionPayloadV1,
|
||||
PayloadAttributes as EthPayloadAttributes,
|
||||
};
|
||||
use reth_engine_primitives::EngineTypes;
|
||||
use reth_payload_primitives::{BuiltPayload, PayloadTypes};
|
||||
@@ -66,13 +67,15 @@ where
|
||||
+ TryInto<ExecutionPayloadEnvelopeV2>
|
||||
+ TryInto<ExecutionPayloadEnvelopeV3>
|
||||
+ TryInto<ExecutionPayloadEnvelopeV4>
|
||||
+ TryInto<ExecutionPayloadEnvelopeV5>,
|
||||
+ TryInto<ExecutionPayloadEnvelopeV5>
|
||||
+ TryInto<ExecutionPayloadEnvelopeV6>,
|
||||
{
|
||||
type ExecutionPayloadEnvelopeV1 = ExecutionPayloadV1;
|
||||
type ExecutionPayloadEnvelopeV2 = ExecutionPayloadEnvelopeV2;
|
||||
type ExecutionPayloadEnvelopeV3 = ExecutionPayloadEnvelopeV3;
|
||||
type ExecutionPayloadEnvelopeV4 = ExecutionPayloadEnvelopeV4;
|
||||
type ExecutionPayloadEnvelopeV5 = ExecutionPayloadEnvelopeV5;
|
||||
type ExecutionPayloadEnvelopeV6 = ExecutionPayloadEnvelopeV6;
|
||||
}
|
||||
|
||||
/// A default payload type for [`EthEngineTypes`]
|
||||
|
||||
@@ -11,8 +11,8 @@ use alloy_primitives::{Address, B256, U256};
|
||||
use alloy_rlp::Encodable;
|
||||
use alloy_rpc_types_engine::{
|
||||
BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
|
||||
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadFieldV2,
|
||||
ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId,
|
||||
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
|
||||
ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId,
|
||||
};
|
||||
use core::convert::Infallible;
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
@@ -160,6 +160,13 @@ impl EthBuiltPayload {
|
||||
execution_requests: requests.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Try converting built payload into [`ExecutionPayloadEnvelopeV6`].
|
||||
///
|
||||
/// Note: Amsterdam fork is not yet implemented, so this conversion is not yet supported.
|
||||
pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
|
||||
unimplemented!("ExecutionPayloadEnvelopeV6 not yet supported")
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NodePrimitives> BuiltPayload for EthBuiltPayload<N> {
|
||||
@@ -227,6 +234,14 @@ impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV5 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV6 {
|
||||
type Error = BuiltPayloadConversionError;
|
||||
|
||||
fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
|
||||
value.try_into_v6()
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum representing blob transaction sidecars belonging to [`EthBuiltPayload`].
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub enum BlobSidecars {
|
||||
|
||||
@@ -59,6 +59,7 @@ std = [
|
||||
"reth-storage-errors/std",
|
||||
]
|
||||
test-utils = [
|
||||
"std",
|
||||
"dep:parking_lot",
|
||||
"dep:derive_more",
|
||||
"reth-chainspec/test-utils",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use alloy_consensus::TxType;
|
||||
use alloy_evm::eth::receipt_builder::{ReceiptBuilder, ReceiptBuilderCtx};
|
||||
use reth_ethereum_primitives::{Receipt, TransactionSigned};
|
||||
use reth_evm::Evm;
|
||||
@@ -12,13 +13,10 @@ impl ReceiptBuilder for RethReceiptBuilder {
|
||||
type Transaction = TransactionSigned;
|
||||
type Receipt = Receipt;
|
||||
|
||||
fn build_receipt<E: Evm>(
|
||||
&self,
|
||||
ctx: ReceiptBuilderCtx<'_, Self::Transaction, E>,
|
||||
) -> Self::Receipt {
|
||||
let ReceiptBuilderCtx { tx, result, cumulative_gas_used, .. } = ctx;
|
||||
fn build_receipt<E: Evm>(&self, ctx: ReceiptBuilderCtx<'_, TxType, E>) -> Self::Receipt {
|
||||
let ReceiptBuilderCtx { tx_type, result, cumulative_gas_used, .. } = ctx;
|
||||
Receipt {
|
||||
tx_type: tx.tx_type(),
|
||||
tx_type,
|
||||
// Success flag was added in `EIP-658: Embedding transaction status code in
|
||||
// receipts`.
|
||||
success: result.is_success(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::EthEvmConfig;
|
||||
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
|
||||
use alloy_consensus::Header;
|
||||
use alloy_consensus::{Header, TxType};
|
||||
use alloy_eips::eip7685::Requests;
|
||||
use alloy_evm::precompiles::PrecompilesMap;
|
||||
use alloy_primitives::Bytes;
|
||||
@@ -11,14 +11,14 @@ use reth_evm::{
|
||||
block::{
|
||||
BlockExecutionError, BlockExecutor, BlockExecutorFactory, BlockExecutorFor, ExecutableTx,
|
||||
},
|
||||
eth::{EthBlockExecutionCtx, EthEvmContext},
|
||||
eth::{EthBlockExecutionCtx, EthEvmContext, EthTxResult},
|
||||
ConfigureEngineEvm, ConfigureEvm, Database, EthEvm, EthEvmFactory, Evm, EvmEnvFor, EvmFactory,
|
||||
ExecutableTxIterator, ExecutionCtxFor,
|
||||
ExecutableTxIterator, ExecutionCtxFor, RecoveredTx,
|
||||
};
|
||||
use reth_execution_types::{BlockExecutionResult, ExecutionOutcome};
|
||||
use reth_primitives_traits::{BlockTy, SealedBlock, SealedHeader};
|
||||
use revm::{
|
||||
context::result::{ExecutionResult, Output, ResultAndState, SuccessReason},
|
||||
context::result::{ExecutionResult, HaltReason, Output, ResultAndState, SuccessReason},
|
||||
database::State,
|
||||
Inspector,
|
||||
};
|
||||
@@ -90,6 +90,7 @@ impl<'a, DB: Database, I: Inspector<EthEvmContext<&'a mut State<DB>>>> BlockExec
|
||||
type Evm = EthEvm<&'a mut State<DB>, I, PrecompilesMap>;
|
||||
type Transaction = TransactionSigned;
|
||||
type Receipt = Receipt;
|
||||
type Result = EthTxResult<HaltReason, TxType>;
|
||||
|
||||
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
|
||||
Ok(())
|
||||
@@ -101,25 +102,25 @@ impl<'a, DB: Database, I: Inspector<EthEvmContext<&'a mut State<DB>>>> BlockExec
|
||||
|
||||
fn execute_transaction_without_commit(
|
||||
&mut self,
|
||||
_tx: impl ExecutableTx<Self>,
|
||||
) -> Result<ResultAndState<<Self::Evm as Evm>::HaltReason>, BlockExecutionError> {
|
||||
Ok(ResultAndState::new(
|
||||
ExecutionResult::Success {
|
||||
reason: SuccessReason::Return,
|
||||
gas_used: 0,
|
||||
gas_refunded: 0,
|
||||
logs: vec![],
|
||||
output: Output::Call(Bytes::from(vec![])),
|
||||
},
|
||||
Default::default(),
|
||||
))
|
||||
tx: impl ExecutableTx<Self>,
|
||||
) -> Result<Self::Result, BlockExecutionError> {
|
||||
Ok(EthTxResult {
|
||||
result: ResultAndState::new(
|
||||
ExecutionResult::Success {
|
||||
reason: SuccessReason::Return,
|
||||
gas_used: 0,
|
||||
gas_refunded: 0,
|
||||
logs: vec![],
|
||||
output: Output::Call(Bytes::from(vec![])),
|
||||
},
|
||||
Default::default(),
|
||||
),
|
||||
tx_type: tx.into_parts().1.tx().tx_type(),
|
||||
blob_gas_used: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_transaction(
|
||||
&mut self,
|
||||
_output: ResultAndState<<Self::Evm as Evm>::HaltReason>,
|
||||
_tx: impl ExecutableTx<Self>,
|
||||
) -> Result<u64, BlockExecutionError> {
|
||||
fn commit_transaction(&mut self, _output: Self::Result) -> Result<u64, BlockExecutionError> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -456,17 +456,22 @@ pub struct EthereumPoolBuilder {
|
||||
// TODO add options for txpool args
|
||||
}
|
||||
|
||||
impl<Types, Node> PoolBuilder<Node> for EthereumPoolBuilder
|
||||
impl<Types, Node, Evm> PoolBuilder<Node, Evm> for EthereumPoolBuilder
|
||||
where
|
||||
Types: NodeTypes<
|
||||
ChainSpec: EthereumHardforks,
|
||||
Primitives: NodePrimitives<SignedTx = TransactionSigned>,
|
||||
>,
|
||||
Node: FullNodeTypes<Types = Types>,
|
||||
Evm: ConfigureEvm<Primitives = PrimitivesTy<Types>> + Clone + 'static,
|
||||
{
|
||||
type Pool = EthTransactionPool<Node::Provider, DiskFileBlobStore>;
|
||||
type Pool = EthTransactionPool<Node::Provider, DiskFileBlobStore, Evm>;
|
||||
|
||||
async fn build_pool(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
|
||||
async fn build_pool(
|
||||
self,
|
||||
ctx: &BuilderContext<Node>,
|
||||
evm_config: Evm,
|
||||
) -> eyre::Result<Self::Pool> {
|
||||
let pool_config = ctx.pool_config();
|
||||
|
||||
let blobs_disabled = ctx.config().txpool.disable_blobs_support ||
|
||||
@@ -492,17 +497,17 @@ where
|
||||
let blob_store =
|
||||
reth_node_builder::components::create_blob_store_with_cache(ctx, blob_cache_size)?;
|
||||
|
||||
let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone())
|
||||
.with_head_timestamp(ctx.head().timestamp)
|
||||
.set_eip4844(!blobs_disabled)
|
||||
.kzg_settings(ctx.kzg_settings()?)
|
||||
.with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes)
|
||||
.with_local_transactions_config(pool_config.local_transactions_config.clone())
|
||||
.set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap)
|
||||
.with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit)
|
||||
.with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee)
|
||||
.with_additional_tasks(ctx.config().txpool.additional_validation_tasks)
|
||||
.build_with_tasks(ctx.task_executor().clone(), blob_store.clone());
|
||||
let validator =
|
||||
TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone(), evm_config)
|
||||
.set_eip4844(!blobs_disabled)
|
||||
.kzg_settings(ctx.kzg_settings()?)
|
||||
.with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes)
|
||||
.with_local_transactions_config(pool_config.local_transactions_config.clone())
|
||||
.set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap)
|
||||
.with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit)
|
||||
.with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee)
|
||||
.with_additional_tasks(ctx.config().txpool.additional_validation_tasks)
|
||||
.build_with_tasks(ctx.task_executor().clone(), blob_store.clone());
|
||||
|
||||
if validator.validator().eip4844() {
|
||||
// initializing the KZG settings can be expensive, this should be done upfront so that
|
||||
|
||||
357
crates/ethereum/node/tests/e2e/invalid_payload.rs
Normal file
357
crates/ethereum/node/tests/e2e/invalid_payload.rs
Normal file
@@ -0,0 +1,357 @@
|
||||
//! Tests for handling invalid payloads via Engine API.
|
||||
//!
|
||||
//! This module tests the scenario where a node receives invalid payloads (e.g., with modified
|
||||
//! state roots) before receiving valid ones, ensuring the node can recover and continue.
|
||||
|
||||
use crate::utils::eth_payload_attributes;
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rpc_types_engine::{ExecutionPayloadV3, PayloadStatusEnum};
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use reth_chainspec::{ChainSpecBuilder, MAINNET};
|
||||
use reth_e2e_test_utils::{setup_engine, transaction::TransactionTestContext};
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
|
||||
use reth_rpc_api::EngineApiClient;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Tests that a node can handle receiving an invalid payload (with wrong state root)
|
||||
/// followed by the correct payload, and continue operating normally.
|
||||
///
|
||||
/// Setup:
|
||||
/// - Node 1: Produces valid payloads and advances the chain
|
||||
/// - Node 2: Receives payloads from node 1, but we also inject modified payloads with invalid state
|
||||
/// roots in between to verify error handling
|
||||
#[tokio::test]
|
||||
async fn can_handle_invalid_payload_then_valid() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let seed: [u8; 32] = rand::rng().random();
|
||||
let mut rng = StdRng::from_seed(seed);
|
||||
println!("Seed: {seed:?}");
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap())
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
);
|
||||
|
||||
let (mut nodes, _tasks, wallet) = setup_engine::<EthereumNode>(
|
||||
2,
|
||||
chain_spec.clone(),
|
||||
false,
|
||||
Default::default(),
|
||||
eth_payload_attributes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut producer = nodes.pop().unwrap();
|
||||
let receiver = nodes.pop().unwrap();
|
||||
|
||||
// Get engine API client for the receiver node
|
||||
let receiver_engine = receiver.auth_server_handle().http_client();
|
||||
|
||||
// Inject a transaction to allow block building (advance_block waits for transactions)
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(1, wallet.inner).await;
|
||||
producer.rpc.inject_tx(raw_tx).await?;
|
||||
|
||||
// Build a valid payload on the producer
|
||||
let payload = producer.advance_block().await?;
|
||||
let valid_block = payload.block().clone();
|
||||
|
||||
// Create valid payload first, then corrupt the state root
|
||||
let mut invalid_payload = ExecutionPayloadV3::from_block_unchecked(
|
||||
valid_block.hash(),
|
||||
&valid_block.clone().into_block(),
|
||||
);
|
||||
let original_state_root = invalid_payload.payload_inner.payload_inner.state_root;
|
||||
invalid_payload.payload_inner.payload_inner.state_root = B256::random_with(&mut rng);
|
||||
|
||||
// Send the invalid payload to the receiver - should be rejected
|
||||
let invalid_result = EngineApiClient::<reth_node_ethereum::EthEngineTypes>::new_payload_v3(
|
||||
&receiver_engine,
|
||||
invalid_payload.clone(),
|
||||
vec![],
|
||||
valid_block.header().parent_beacon_block_root.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!(
|
||||
"Invalid payload response: {:?} (state_root changed from {original_state_root} to {})",
|
||||
invalid_result.status, invalid_payload.payload_inner.payload_inner.state_root
|
||||
);
|
||||
|
||||
// The invalid payload should be rejected
|
||||
assert!(
|
||||
matches!(
|
||||
invalid_result.status,
|
||||
PayloadStatusEnum::Invalid { .. } | PayloadStatusEnum::Syncing
|
||||
),
|
||||
"Expected INVALID or SYNCING status for invalid payload, got {:?}",
|
||||
invalid_result.status
|
||||
);
|
||||
|
||||
// Now send the valid payload - should be accepted
|
||||
let valid_payload = ExecutionPayloadV3::from_block_unchecked(
|
||||
valid_block.hash(),
|
||||
&valid_block.clone().into_block(),
|
||||
);
|
||||
|
||||
let valid_result = EngineApiClient::<reth_node_ethereum::EthEngineTypes>::new_payload_v3(
|
||||
&receiver_engine,
|
||||
valid_payload,
|
||||
vec![],
|
||||
valid_block.header().parent_beacon_block_root.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Valid payload response: {:?}", valid_result.status);
|
||||
|
||||
// The valid payload should be accepted
|
||||
assert!(
|
||||
matches!(
|
||||
valid_result.status,
|
||||
PayloadStatusEnum::Valid | PayloadStatusEnum::Syncing | PayloadStatusEnum::Accepted
|
||||
),
|
||||
"Expected VALID/SYNCING/ACCEPTED status for valid payload, got {:?}",
|
||||
valid_result.status
|
||||
);
|
||||
|
||||
// Update forkchoice on receiver to the valid block
|
||||
receiver.update_forkchoice(valid_block.hash(), valid_block.hash()).await?;
|
||||
|
||||
// Verify the receiver node is at the expected block
|
||||
let receiver_head = receiver.block_hash(1);
|
||||
let producer_head = producer.block_hash(1);
|
||||
assert_eq!(
|
||||
receiver_head, producer_head,
|
||||
"Receiver should have synced to the same chain as producer"
|
||||
);
|
||||
|
||||
println!(
|
||||
"Test passed: Receiver successfully handled invalid payloads and synced to valid chain"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that a node can handle multiple consecutive invalid payloads
|
||||
/// before receiving a valid one.
|
||||
#[tokio::test]
|
||||
async fn can_handle_multiple_invalid_payloads() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let seed: [u8; 32] = rand::rng().random();
|
||||
let mut rng = StdRng::from_seed(seed);
|
||||
println!("Seed: {seed:?}");
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap())
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
);
|
||||
|
||||
let (mut nodes, _tasks, wallet) = setup_engine::<EthereumNode>(
|
||||
2,
|
||||
chain_spec.clone(),
|
||||
false,
|
||||
Default::default(),
|
||||
eth_payload_attributes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut producer = nodes.pop().unwrap();
|
||||
let receiver = nodes.pop().unwrap();
|
||||
|
||||
let receiver_engine = receiver.auth_server_handle().http_client();
|
||||
|
||||
// Inject a transaction to allow block building
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(1, wallet.inner).await;
|
||||
producer.rpc.inject_tx(raw_tx).await?;
|
||||
|
||||
// Produce a valid block
|
||||
let payload = producer.advance_block().await?;
|
||||
let valid_block = payload.block().clone();
|
||||
|
||||
// Send multiple invalid payloads with different corruptions
|
||||
for i in 0..3 {
|
||||
// Create valid payload first, then corrupt the state root
|
||||
let mut invalid_payload = ExecutionPayloadV3::from_block_unchecked(
|
||||
valid_block.hash(),
|
||||
&valid_block.clone().into_block(),
|
||||
);
|
||||
invalid_payload.payload_inner.payload_inner.state_root = B256::random_with(&mut rng);
|
||||
|
||||
let result = EngineApiClient::<reth_node_ethereum::EthEngineTypes>::new_payload_v3(
|
||||
&receiver_engine,
|
||||
invalid_payload,
|
||||
vec![],
|
||||
valid_block.header().parent_beacon_block_root.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Invalid payload {i}: status = {:?}", result.status);
|
||||
|
||||
assert!(
|
||||
matches!(result.status, PayloadStatusEnum::Invalid { .. } | PayloadStatusEnum::Syncing),
|
||||
"Expected INVALID or SYNCING for invalid payload {i}, got {:?}",
|
||||
result.status
|
||||
);
|
||||
}
|
||||
|
||||
// Now send the valid payload
|
||||
let valid_payload = ExecutionPayloadV3::from_block_unchecked(
|
||||
valid_block.hash(),
|
||||
&valid_block.clone().into_block(),
|
||||
);
|
||||
|
||||
let valid_result = EngineApiClient::<reth_node_ethereum::EthEngineTypes>::new_payload_v3(
|
||||
&receiver_engine,
|
||||
valid_payload,
|
||||
vec![],
|
||||
valid_block.header().parent_beacon_block_root.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Valid payload: status = {:?}", valid_result.status);
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
valid_result.status,
|
||||
PayloadStatusEnum::Valid | PayloadStatusEnum::Syncing | PayloadStatusEnum::Accepted
|
||||
),
|
||||
"Expected valid status for correct payload, got {:?}",
|
||||
valid_result.status
|
||||
);
|
||||
|
||||
// Finalize the valid block
|
||||
receiver.update_forkchoice(valid_block.hash(), valid_block.hash()).await?;
|
||||
|
||||
println!("Test passed: Receiver handled multiple invalid payloads and accepted valid one");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests invalid payload handling with blocks that contain transactions.
|
||||
///
|
||||
/// This test sends real transactions to node 1, produces blocks with those transactions,
|
||||
/// then sends invalid (corrupted state root) and valid payloads to node 2.
|
||||
#[tokio::test]
|
||||
async fn can_handle_invalid_payload_with_transactions() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let seed: [u8; 32] = rand::rng().random();
|
||||
let mut rng = StdRng::from_seed(seed);
|
||||
println!("Seed: {seed:?}");
|
||||
|
||||
let chain_spec = Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap())
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
);
|
||||
|
||||
let (mut nodes, _tasks, wallet) = setup_engine::<EthereumNode>(
|
||||
2,
|
||||
chain_spec.clone(),
|
||||
false,
|
||||
Default::default(),
|
||||
eth_payload_attributes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut producer = nodes.pop().unwrap();
|
||||
let receiver = nodes.pop().unwrap();
|
||||
|
||||
let receiver_engine = receiver.auth_server_handle().http_client();
|
||||
|
||||
// Create and send a transaction to the producer node
|
||||
let raw_tx = TransactionTestContext::transfer_tx_bytes(1, wallet.inner).await;
|
||||
let tx_hash = producer.rpc.inject_tx(raw_tx).await?;
|
||||
println!("Injected transaction {tx_hash}");
|
||||
|
||||
// Build a block containing the transaction
|
||||
let payload = producer.advance_block().await?;
|
||||
let valid_block = payload.block().clone();
|
||||
|
||||
// Verify the block contains a transaction
|
||||
let tx_count = valid_block.body().transactions().count();
|
||||
println!("Block contains {tx_count} transaction(s)");
|
||||
assert!(tx_count > 0, "Block should contain at least one transaction");
|
||||
|
||||
// Create invalid payload by corrupting the state root
|
||||
let mut invalid_payload = ExecutionPayloadV3::from_block_unchecked(
|
||||
valid_block.hash(),
|
||||
&valid_block.clone().into_block(),
|
||||
);
|
||||
let original_state_root = invalid_payload.payload_inner.payload_inner.state_root;
|
||||
invalid_payload.payload_inner.payload_inner.state_root = B256::random_with(&mut rng);
|
||||
|
||||
// Send invalid payload - should be rejected
|
||||
let invalid_result = EngineApiClient::<reth_node_ethereum::EthEngineTypes>::new_payload_v3(
|
||||
&receiver_engine,
|
||||
invalid_payload.clone(),
|
||||
vec![],
|
||||
valid_block.header().parent_beacon_block_root.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!(
|
||||
"Invalid payload (with tx) response: {:?} (state_root changed from {original_state_root} to {})",
|
||||
invalid_result.status,
|
||||
invalid_payload.payload_inner.payload_inner.state_root
|
||||
);
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
invalid_result.status,
|
||||
PayloadStatusEnum::Invalid { .. } | PayloadStatusEnum::Syncing
|
||||
),
|
||||
"Expected INVALID or SYNCING for invalid payload with transactions, got {:?}",
|
||||
invalid_result.status
|
||||
);
|
||||
|
||||
// Send valid payload - should be accepted
|
||||
let valid_payload = ExecutionPayloadV3::from_block_unchecked(
|
||||
valid_block.hash(),
|
||||
&valid_block.clone().into_block(),
|
||||
);
|
||||
|
||||
let valid_result = EngineApiClient::<reth_node_ethereum::EthEngineTypes>::new_payload_v3(
|
||||
&receiver_engine,
|
||||
valid_payload,
|
||||
vec![],
|
||||
valid_block.header().parent_beacon_block_root.unwrap_or_default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
println!("Valid payload (with tx) response: {:?}", valid_result.status);
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
valid_result.status,
|
||||
PayloadStatusEnum::Valid | PayloadStatusEnum::Syncing | PayloadStatusEnum::Accepted
|
||||
),
|
||||
"Expected valid status for correct payload with transactions, got {:?}",
|
||||
valid_result.status
|
||||
);
|
||||
|
||||
// Update forkchoice
|
||||
receiver.update_forkchoice(valid_block.hash(), valid_block.hash()).await?;
|
||||
|
||||
// Verify both nodes are at the same head
|
||||
let receiver_head = receiver.block_hash(1);
|
||||
let producer_head = producer.block_hash(1);
|
||||
assert_eq!(
|
||||
receiver_head, producer_head,
|
||||
"Receiver should have synced to the same chain as producer"
|
||||
);
|
||||
|
||||
println!("Test passed: Receiver handled invalid payloads with transactions correctly");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -4,10 +4,12 @@ mod blobs;
|
||||
mod custom_genesis;
|
||||
mod dev;
|
||||
mod eth;
|
||||
mod invalid_payload;
|
||||
mod p2p;
|
||||
mod pool;
|
||||
mod prestate;
|
||||
mod rpc;
|
||||
mod selfdestruct;
|
||||
mod utils;
|
||||
|
||||
const fn main() {}
|
||||
|
||||
529
crates/ethereum/node/tests/e2e/selfdestruct.rs
Normal file
529
crates/ethereum/node/tests/e2e/selfdestruct.rs
Normal file
@@ -0,0 +1,529 @@
|
||||
//! E2E tests for SELFDESTRUCT behavior and output state verification.
|
||||
//!
|
||||
//! These tests verify that:
|
||||
//! - Pre-Dencun: SELFDESTRUCT clears storage and code, output state reflects this
|
||||
//! - Post-Dencun (EIP-6780): SELFDESTRUCT only works in same-tx creation, state persists
|
||||
//!
|
||||
//! We disable prewarming to ensure deterministic cache behavior and verify the execution
|
||||
//! output state contains the expected account status after SELFDESTRUCT.
|
||||
|
||||
use crate::utils::{eth_payload_attributes, eth_payload_attributes_shanghai};
|
||||
use alloy_network::{EthereumWallet, TransactionBuilder};
|
||||
use alloy_primitives::{bytes, Address, Bytes, TxKind, U256};
|
||||
use alloy_provider::{Provider, ProviderBuilder};
|
||||
use alloy_rpc_types_eth::TransactionRequest;
|
||||
use futures::StreamExt;
|
||||
use reth_chainspec::{ChainSpec, ChainSpecBuilder, MAINNET};
|
||||
use reth_e2e_test_utils::setup_engine;
|
||||
use reth_node_api::TreeConfig;
|
||||
use reth_node_ethereum::EthereumNode;
|
||||
use reth_revm::db::BundleAccount;
|
||||
use std::sync::Arc;
|
||||
|
||||
const MAX_FEE_PER_GAS: u128 = 20_000_000_000;
|
||||
const MAX_PRIORITY_FEE_PER_GAS: u128 = 1_000_000_000;
|
||||
|
||||
fn cancun_spec() -> Arc<ChainSpec> {
|
||||
Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap())
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
fn shanghai_spec() -> Arc<ChainSpec> {
|
||||
Arc::new(
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap())
|
||||
.shanghai_activated()
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
fn deploy_tx(from: Address, nonce: u64, init_code: Bytes) -> TransactionRequest {
|
||||
TransactionRequest::default()
|
||||
.with_from(from)
|
||||
.with_nonce(nonce)
|
||||
.with_gas_limit(500_000)
|
||||
.with_max_fee_per_gas(MAX_FEE_PER_GAS)
|
||||
.with_max_priority_fee_per_gas(MAX_PRIORITY_FEE_PER_GAS)
|
||||
.with_input(init_code)
|
||||
.with_kind(TxKind::Create)
|
||||
}
|
||||
|
||||
fn call_tx(from: Address, to: Address, nonce: u64) -> TransactionRequest {
|
||||
TransactionRequest::default()
|
||||
.with_from(from)
|
||||
.with_to(to)
|
||||
.with_nonce(nonce)
|
||||
.with_gas_limit(100_000)
|
||||
.with_max_fee_per_gas(MAX_FEE_PER_GAS)
|
||||
.with_max_priority_fee_per_gas(MAX_PRIORITY_FEE_PER_GAS)
|
||||
}
|
||||
|
||||
fn transfer_tx(from: Address, to: Address, nonce: u64, value: U256) -> TransactionRequest {
|
||||
TransactionRequest::default()
|
||||
.with_from(from)
|
||||
.with_to(to)
|
||||
.with_nonce(nonce)
|
||||
.with_value(value)
|
||||
.with_gas_limit(21_000)
|
||||
.with_max_fee_per_gas(MAX_FEE_PER_GAS)
|
||||
.with_max_priority_fee_per_gas(MAX_PRIORITY_FEE_PER_GAS)
|
||||
}
|
||||
|
||||
/// Creates init code for a contract that selfdestructs during deployment (same tx).
|
||||
/// This tests the EIP-6780 exception where SELFDESTRUCT in same tx as creation still works.
|
||||
///
|
||||
/// The contract:
|
||||
/// 1. Stores 0x42 at slot 0
|
||||
/// 2. Immediately selfdestructs to beneficiary (during init, before returning runtime)
|
||||
fn selfdestruct_in_constructor_init_code() -> Bytes {
|
||||
// Init code that selfdestructs during deployment:
|
||||
// PUSH1 0x42, PUSH1 0x00, SSTORE (store 0x42 at slot 0)
|
||||
// PUSH20 <beneficiary>, SELFDESTRUCT
|
||||
let mut init = Vec::new();
|
||||
init.extend_from_slice(&[0x60, 0x42, 0x60, 0x00, 0x55]); // PUSH1 0x42, PUSH1 0x00, SSTORE
|
||||
init.extend_from_slice(&[
|
||||
0x73, // PUSH20
|
||||
0xde, 0xad, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, // beneficiary address
|
||||
]);
|
||||
init.push(0xff); // SELFDESTRUCT
|
||||
|
||||
Bytes::from(init)
|
||||
}
|
||||
|
||||
/// Creates init code for a simple contract that:
|
||||
/// 1. Stores 0x42 at slot 0 during deployment
|
||||
/// 2. On any call: selfdestructs to beneficiary
|
||||
///
|
||||
/// This simpler contract avoids complex branching logic.
|
||||
fn selfdestruct_contract_init_code() -> Bytes {
|
||||
// Runtime: just selfdestruct on any call
|
||||
// PUSH20 <beneficiary>
|
||||
// SELFDESTRUCT
|
||||
let runtime = bytes!(
|
||||
"73dead000000000000000000000000000000000001" // PUSH20 beneficiary
|
||||
"ff" // SELFDESTRUCT
|
||||
);
|
||||
|
||||
let runtime_len = runtime.len(); // 22 bytes
|
||||
|
||||
// Init code: SSTORE(0, 0x42), CODECOPY, RETURN
|
||||
// Total init code before runtime = 17 bytes
|
||||
let init_len: u8 = 17;
|
||||
|
||||
let mut init = Vec::new();
|
||||
init.extend_from_slice(&[0x60, 0x42, 0x60, 0x00, 0x55]); // PUSH1 0x42, PUSH1 0x00, SSTORE
|
||||
init.extend_from_slice(&[0x60, runtime_len as u8, 0x60, init_len, 0x60, 0x00, 0x39]); // CODECOPY
|
||||
init.extend_from_slice(&[0x60, runtime_len as u8, 0x60, 0x00, 0xf3]); // RETURN
|
||||
init.extend_from_slice(&runtime);
|
||||
|
||||
Bytes::from(init)
|
||||
}
|
||||
|
||||
/// Tests SELFDESTRUCT behavior post-Dencun (Cancun+).
|
||||
///
|
||||
/// Post-Dencun (EIP-6780):
|
||||
/// - SELFDESTRUCT only deletes contract if called in same tx as creation
|
||||
/// - For existing contracts, SELFDESTRUCT only sends balance, code/storage persist
|
||||
/// - The output state should NOT mark the account as destroyed
|
||||
///
|
||||
/// This test verifies:
|
||||
/// 1. Contract deploys with storage
|
||||
/// 2. SELFDESTRUCT in later tx does NOT delete code/storage
|
||||
/// 3. Output state shows account is NOT destroyed
|
||||
#[tokio::test]
|
||||
async fn test_selfdestruct_post_dencun() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let tree_config = TreeConfig::default().without_prewarming(true).without_state_cache(false);
|
||||
let (mut nodes, _tasks, wallet) =
|
||||
setup_engine::<EthereumNode>(1, cancun_spec(), false, tree_config, eth_payload_attributes)
|
||||
.await?;
|
||||
let mut node = nodes.pop().unwrap();
|
||||
let signer = wallet.inner.clone();
|
||||
let provider = ProviderBuilder::new()
|
||||
.wallet(EthereumWallet::new(signer.clone()))
|
||||
.connect_http(node.rpc_url());
|
||||
|
||||
// Deploy contract that stores 0x42 at slot 0 and selfdestructs on any call
|
||||
let pending = provider
|
||||
.send_transaction(deploy_tx(signer.address(), 0, selfdestruct_contract_init_code()))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "Contract deployment should succeed");
|
||||
|
||||
let contract_address = receipt.contract_address.expect("Should have contract address");
|
||||
|
||||
// Consume the canonical notification for deployment block
|
||||
let _ = node.canonical_stream.next().await;
|
||||
|
||||
// Trigger SELFDESTRUCT by calling the contract
|
||||
let pending = provider.send_transaction(call_tx(signer.address(), contract_address, 1)).await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "Selfdestruct tx should succeed");
|
||||
|
||||
// Get the canonical notification for the selfdestruct block
|
||||
let notification = node.canonical_stream.next().await.unwrap();
|
||||
let chain = notification.committed();
|
||||
let execution_outcome = chain.execution_outcome();
|
||||
|
||||
// Verify the output state: post-Dencun, account should NOT be destroyed
|
||||
let account_state: Option<&BundleAccount> = execution_outcome.bundle.account(&contract_address);
|
||||
assert!(
|
||||
account_state.is_none() || !account_state.unwrap().was_destroyed(),
|
||||
"Post-Dencun (EIP-6780): Account should NOT be destroyed when SELFDESTRUCT called on existing contract"
|
||||
);
|
||||
|
||||
// Verify via RPC that code and storage persist
|
||||
let code_after = provider.get_code_at(contract_address).await?;
|
||||
assert!(!code_after.is_empty(), "Post-Dencun: Contract code should persist");
|
||||
|
||||
let slot0_after = provider.get_storage_at(contract_address, U256::ZERO).await?;
|
||||
assert_eq!(slot0_after, U256::from(0x42), "Post-Dencun: Storage should persist");
|
||||
|
||||
// Send another transaction to the contract address in a new block.
|
||||
// This tests cache behavior - if cache has stale data, execution would be incorrect.
|
||||
// Post-Dencun: calling the contract should trigger SELFDESTRUCT again (but only transfer
|
||||
// balance)
|
||||
let pending = provider.send_transaction(call_tx(signer.address(), contract_address, 2)).await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "Second call to contract should succeed");
|
||||
|
||||
// Consume the canonical notification
|
||||
let notification = node.canonical_stream.next().await.unwrap();
|
||||
let chain = notification.committed();
|
||||
let execution_outcome = chain.execution_outcome();
|
||||
|
||||
// Verify the output state still shows account NOT destroyed
|
||||
let account_state: Option<&BundleAccount> = execution_outcome.bundle.account(&contract_address);
|
||||
assert!(
|
||||
account_state.is_none() || !account_state.unwrap().was_destroyed(),
|
||||
"Post-Dencun: Account should still NOT be destroyed after second SELFDESTRUCT call"
|
||||
);
|
||||
|
||||
// Verify code and storage still persist after the second call
|
||||
let code_final = provider.get_code_at(contract_address).await?;
|
||||
assert!(!code_final.is_empty(), "Post-Dencun: Contract code should still persist");
|
||||
|
||||
let slot0_final = provider.get_storage_at(contract_address, U256::ZERO).await?;
|
||||
assert_eq!(slot0_final, U256::from(0x42), "Post-Dencun: Storage should still persist");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests SELFDESTRUCT in same transaction as creation (post-Dencun).
|
||||
///
|
||||
/// Post-Dencun (EIP-6780):
|
||||
/// - SELFDESTRUCT during the same transaction as creation DOES delete the contract
|
||||
/// - This is the exception to the rule that SELFDESTRUCT no longer deletes contracts
|
||||
///
|
||||
/// This test verifies:
|
||||
/// 1. Contract selfdestructs during its constructor
|
||||
/// 2. Contract is deleted (same-tx exception applies)
|
||||
/// 3. No code or storage remains
|
||||
/// 4. Since account never existed in DB before, bundle has no entry for it
|
||||
#[tokio::test]
|
||||
async fn test_selfdestruct_same_tx_post_dencun() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let tree_config = TreeConfig::default().without_prewarming(true).without_state_cache(false);
|
||||
let (mut nodes, _tasks, wallet) =
|
||||
setup_engine::<EthereumNode>(1, cancun_spec(), false, tree_config, eth_payload_attributes)
|
||||
.await?;
|
||||
let mut node = nodes.pop().unwrap();
|
||||
let signer = wallet.inner.clone();
|
||||
let provider = ProviderBuilder::new()
|
||||
.wallet(EthereumWallet::new(signer.clone()))
|
||||
.connect_http(node.rpc_url());
|
||||
|
||||
// Deploy contract that selfdestructs during its constructor
|
||||
let pending = provider
|
||||
.send_transaction(deploy_tx(signer.address(), 0, selfdestruct_in_constructor_init_code()))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "Contract deployment with selfdestruct should succeed");
|
||||
|
||||
// Calculate the contract address (CREATE uses sender + nonce)
|
||||
let contract_address = signer.address().create(0);
|
||||
|
||||
// Get the canonical notification for the deployment block
|
||||
let notification = node.canonical_stream.next().await.unwrap();
|
||||
let chain = notification.committed();
|
||||
let execution_outcome = chain.execution_outcome();
|
||||
|
||||
// Verify the output state: same-tx SELFDESTRUCT should destroy the account
|
||||
let account_state: Option<&BundleAccount> = execution_outcome.bundle.account(&contract_address);
|
||||
assert!(
|
||||
account_state.is_none(),
|
||||
"Post-Dencun same-tx: Account was created and selfdestructed in the same transaction, no trace in bundle state"
|
||||
);
|
||||
|
||||
// Verify via RPC that code and storage are cleared
|
||||
let code = provider.get_code_at(contract_address).await?;
|
||||
assert!(code.is_empty(), "Post-Dencun same-tx: Contract code should be deleted");
|
||||
|
||||
let slot0 = provider.get_storage_at(contract_address, U256::ZERO).await?;
|
||||
assert_eq!(slot0, U256::ZERO, "Post-Dencun same-tx: Storage should be cleared");
|
||||
|
||||
// Send ETH to the destroyed address in a new block to test cache behavior
|
||||
let pending = provider
|
||||
.send_transaction(transfer_tx(signer.address(), contract_address, 1, U256::from(1000)))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "ETH transfer to destroyed address should succeed");
|
||||
|
||||
// Consume the canonical notification
|
||||
let _ = node.canonical_stream.next().await;
|
||||
|
||||
// Verify code is still empty and account received ETH
|
||||
let code_final = provider.get_code_at(contract_address).await?;
|
||||
assert!(code_final.is_empty(), "Post-Dencun same-tx: Contract code should remain deleted");
|
||||
|
||||
let balance = provider.get_balance(contract_address).await?;
|
||||
assert_eq!(balance, U256::from(1000), "Post-Dencun same-tx: Account should have received ETH");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests SELFDESTRUCT behavior pre-Dencun (Shanghai).
|
||||
///
|
||||
/// Pre-Dencun:
|
||||
/// - SELFDESTRUCT deletes contract code and storage regardless of when contract was created
|
||||
/// - The output state MUST mark the account as destroyed
|
||||
///
|
||||
/// This test verifies:
|
||||
/// 1. Contract deploys with storage
|
||||
/// 2. SELFDESTRUCT deletes code and storage
|
||||
/// 3. Output state shows account IS destroyed
|
||||
#[tokio::test]
|
||||
async fn test_selfdestruct_pre_dencun() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let tree_config = TreeConfig::default().without_prewarming(true).without_state_cache(false);
|
||||
let (mut nodes, _tasks, wallet) = setup_engine::<EthereumNode>(
|
||||
1,
|
||||
shanghai_spec(),
|
||||
false,
|
||||
tree_config,
|
||||
eth_payload_attributes_shanghai,
|
||||
)
|
||||
.await?;
|
||||
let mut node = nodes.pop().unwrap();
|
||||
let signer = wallet.inner.clone();
|
||||
let provider = ProviderBuilder::new()
|
||||
.wallet(EthereumWallet::new(signer.clone()))
|
||||
.connect_http(node.rpc_url());
|
||||
|
||||
// Deploy contract that stores 0x42 at slot 0 and selfdestructs on any call
|
||||
let pending = provider
|
||||
.send_transaction(deploy_tx(signer.address(), 0, selfdestruct_contract_init_code()))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "Contract deployment should succeed");
|
||||
|
||||
let contract_address = receipt.contract_address.expect("Should have contract address");
|
||||
|
||||
// Consume the canonical notification for deployment block
|
||||
let _ = node.canonical_stream.next().await;
|
||||
|
||||
// Trigger SELFDESTRUCT by calling the contract
|
||||
let pending = provider.send_transaction(call_tx(signer.address(), contract_address, 1)).await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "Selfdestruct tx should succeed");
|
||||
|
||||
// Get the canonical notification for the selfdestruct block
|
||||
let notification = node.canonical_stream.next().await.unwrap();
|
||||
let chain = notification.committed();
|
||||
let execution_outcome = chain.execution_outcome();
|
||||
|
||||
// Verify the output state: pre-Dencun, account MUST be destroyed
|
||||
let account_state: Option<&BundleAccount> = execution_outcome.bundle.account(&contract_address);
|
||||
assert!(
|
||||
account_state.is_some_and(|a: &BundleAccount| a.was_destroyed()),
|
||||
"Pre-Dencun: Account MUST be marked as destroyed in output state"
|
||||
);
|
||||
|
||||
// Verify via RPC that code and storage are cleared
|
||||
let code_after = provider.get_code_at(contract_address).await?;
|
||||
assert!(code_after.is_empty(), "Pre-Dencun: Contract code should be deleted");
|
||||
|
||||
let slot0_after = provider.get_storage_at(contract_address, U256::ZERO).await?;
|
||||
assert_eq!(slot0_after, U256::ZERO, "Pre-Dencun: Storage should be cleared");
|
||||
|
||||
// Send ETH to the destroyed contract address in a new block.
|
||||
// This tests cache behavior - the cache should correctly reflect the account was destroyed.
|
||||
// Pre-Dencun: the contract no longer exists, so this is just a plain ETH transfer.
|
||||
let pending = provider
|
||||
.send_transaction(transfer_tx(signer.address(), contract_address, 2, U256::from(1000)))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "ETH transfer to destroyed contract address should succeed");
|
||||
|
||||
// Consume the canonical notification
|
||||
let notification = node.canonical_stream.next().await.unwrap();
|
||||
let chain = notification.committed();
|
||||
let execution_outcome = chain.execution_outcome();
|
||||
|
||||
// Verify the output state shows the account exists (received ETH) but has no code
|
||||
let account_state: Option<&BundleAccount> = execution_outcome.bundle.account(&contract_address);
|
||||
// After receiving ETH, the account should exist with balance but no code
|
||||
assert!(
|
||||
account_state.is_some(),
|
||||
"Pre-Dencun: Account should exist after receiving ETH (even though contract was destroyed)"
|
||||
);
|
||||
|
||||
// Verify code is still empty (contract was destroyed, only ETH was received)
|
||||
let code_final = provider.get_code_at(contract_address).await?;
|
||||
assert!(code_final.is_empty(), "Pre-Dencun: Contract code should remain deleted");
|
||||
|
||||
// Verify storage is still cleared
|
||||
let slot0_final = provider.get_storage_at(contract_address, U256::ZERO).await?;
|
||||
assert_eq!(slot0_final, U256::ZERO, "Pre-Dencun: Storage should remain cleared");
|
||||
|
||||
// Verify the account now has the ETH balance we sent
|
||||
let balance = provider.get_balance(contract_address).await?;
|
||||
assert_eq!(balance, U256::from(1000), "Pre-Dencun: Account should have received ETH");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests SELFDESTRUCT in same transaction as creation, where account previously had ETH
|
||||
/// (post-Dencun).
|
||||
///
|
||||
/// Post-Dencun (EIP-6780):
|
||||
/// - The same-tx exception applies when the CONTRACT is created in that transaction
|
||||
/// - Even if the address previously had ETH (as an EOA), deploying a contract there and
|
||||
/// selfdestructing in the same tx DOES delete the contract
|
||||
/// - The "created in same tx" refers to contract creation, not account existence
|
||||
///
|
||||
/// This test verifies:
|
||||
/// 1. Send ETH to the future contract address (address has balance but no code)
|
||||
/// 2. Deploy contract that selfdestructs during constructor to that address
|
||||
/// 3. Contract is deleted (same-tx exception applies - contract was created this tx)
|
||||
/// 4. Code and storage are cleared
|
||||
/// 5. Since account existed in DB before (had ETH), bundle marks it as Destroyed
|
||||
#[tokio::test]
|
||||
async fn test_selfdestruct_same_tx_preexisting_account_post_dencun() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let tree_config = TreeConfig::default().without_prewarming(true).without_state_cache(false);
|
||||
let (mut nodes, _tasks, wallet) =
|
||||
setup_engine::<EthereumNode>(1, cancun_spec(), false, tree_config, eth_payload_attributes)
|
||||
.await?;
|
||||
let mut node = nodes.pop().unwrap();
|
||||
let signer = wallet.inner.clone();
|
||||
let provider = ProviderBuilder::new()
|
||||
.wallet(EthereumWallet::new(signer.clone()))
|
||||
.connect_http(node.rpc_url());
|
||||
|
||||
// Calculate where the contract will be deployed (CREATE uses sender + nonce)
|
||||
// We'll use nonce 1 for deployment, so first send ETH with nonce 0
|
||||
let future_contract_address = signer.address().create(1);
|
||||
|
||||
// Send ETH to the future contract address first (makes it a pre-existing account)
|
||||
let pending = provider
|
||||
.send_transaction(transfer_tx(
|
||||
signer.address(),
|
||||
future_contract_address,
|
||||
0,
|
||||
U256::from(1000),
|
||||
))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "ETH transfer should succeed");
|
||||
|
||||
// Consume the canonical notification
|
||||
let _ = node.canonical_stream.next().await;
|
||||
|
||||
// Verify the account exists and has balance
|
||||
let balance_before = provider.get_balance(future_contract_address).await?;
|
||||
assert_eq!(balance_before, U256::from(1000), "Account should have ETH before deployment");
|
||||
|
||||
// Now deploy contract that selfdestructs during its constructor to the same address
|
||||
let pending = provider
|
||||
.send_transaction(deploy_tx(signer.address(), 1, selfdestruct_in_constructor_init_code()))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "Contract deployment with selfdestruct should succeed");
|
||||
|
||||
// Verify deployment went to the expected address
|
||||
assert_eq!(
|
||||
receipt.contract_address,
|
||||
Some(future_contract_address),
|
||||
"Contract should be deployed to pre-computed address"
|
||||
);
|
||||
|
||||
// Get the canonical notification for the deployment block
|
||||
let notification = node.canonical_stream.next().await.unwrap();
|
||||
let chain = notification.committed();
|
||||
let execution_outcome = chain.execution_outcome();
|
||||
|
||||
// Verify the output state: same-tx exception DOES apply because contract was created this tx
|
||||
// The account should be marked as destroyed. Since it had prior state (ETH balance),
|
||||
// the bundle will contain it with status Destroyed and original_info set.
|
||||
let account_state: Option<&BundleAccount> =
|
||||
execution_outcome.bundle.account(&future_contract_address);
|
||||
assert!(
|
||||
account_state.is_some_and(|a| a.was_destroyed()),
|
||||
"Post-Dencun same-tx with prior ETH: Account MUST be marked as destroyed"
|
||||
);
|
||||
|
||||
// Verify via RPC that code and storage are cleared
|
||||
let code = provider.get_code_at(future_contract_address).await?;
|
||||
assert!(code.is_empty(), "Post-Dencun same-tx: Contract code should be deleted");
|
||||
|
||||
let slot0 = provider.get_storage_at(future_contract_address, U256::ZERO).await?;
|
||||
assert_eq!(slot0, U256::ZERO, "Post-Dencun same-tx: Storage should be cleared");
|
||||
|
||||
// Balance should be zero (sent to beneficiary during SELFDESTRUCT)
|
||||
let balance_after = provider.get_balance(future_contract_address).await?;
|
||||
assert_eq!(
|
||||
balance_after,
|
||||
U256::ZERO,
|
||||
"Post-Dencun same-tx: Balance should be zero (sent to beneficiary)"
|
||||
);
|
||||
|
||||
// Send ETH to the destroyed address to verify cache behavior
|
||||
let pending = provider
|
||||
.send_transaction(transfer_tx(
|
||||
signer.address(),
|
||||
future_contract_address,
|
||||
2,
|
||||
U256::from(2000),
|
||||
))
|
||||
.await?;
|
||||
node.advance_block().await?;
|
||||
let receipt = pending.get_receipt().await?;
|
||||
assert!(receipt.status(), "ETH transfer should succeed");
|
||||
|
||||
// Consume notification
|
||||
let _ = node.canonical_stream.next().await;
|
||||
|
||||
// Verify the account received ETH and has no code (it's now just an EOA)
|
||||
let balance_final = provider.get_balance(future_contract_address).await?;
|
||||
assert_eq!(balance_final, U256::from(2000), "Account should have received ETH");
|
||||
|
||||
let code_final = provider.get_code_at(future_contract_address).await?;
|
||||
assert!(code_final.is_empty(), "Code should remain empty after ETH transfer");
|
||||
|
||||
let slot0_final = provider.get_storage_at(future_contract_address, U256::ZERO).await?;
|
||||
assert_eq!(slot0_final, U256::ZERO, "Storage should remain cleared");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -29,6 +29,19 @@ pub(crate) fn eth_payload_attributes(timestamp: u64) -> EthPayloadBuilderAttribu
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
|
||||
/// Helper function to create pre-Cancun (Shanghai) payload attributes.
|
||||
/// No `parent_beacon_block_root` field.
|
||||
pub(crate) fn eth_payload_attributes_shanghai(timestamp: u64) -> EthPayloadBuilderAttributes {
|
||||
let attributes = PayloadAttributes {
|
||||
timestamp,
|
||||
prev_randao: B256::ZERO,
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
|
||||
/// Advances node by producing blocks with random transactions.
|
||||
pub(crate) async fn advance_with_random_transactions<Provider>(
|
||||
node: &mut NodeHelperType<EthereumNode, Provider>,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::{execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor};
|
||||
use crate::{execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor, TxEnvFor};
|
||||
use alloy_evm::{block::ExecutableTxParts, RecoveredTx};
|
||||
use rayon::prelude::*;
|
||||
use reth_primitives_traits::TxTy;
|
||||
|
||||
/// [`ConfigureEvm`] extension providing methods for executing payloads.
|
||||
pub trait ConfigureEngineEvm<ExecutionData>: ConfigureEvm {
|
||||
@@ -61,11 +63,16 @@ where
|
||||
|
||||
/// Iterator over executable transactions.
|
||||
pub trait ExecutableTxIterator<Evm: ConfigureEvm>:
|
||||
ExecutableTxTuple<Tx: ExecutableTxFor<Evm>>
|
||||
ExecutableTxTuple<Tx: ExecutableTxFor<Evm, Recovered = Self::Recovered>>
|
||||
{
|
||||
/// HACK: for some reason, this duplicated AT is the only way to enforce the inner Recovered:
|
||||
/// Send + Sync bound. Effectively alias for `Self::Tx::Recovered`.
|
||||
type Recovered: RecoveredTx<TxTy<Evm::Primitives>> + Send + Sync;
|
||||
}
|
||||
|
||||
impl<T, Evm: ConfigureEvm> ExecutableTxIterator<Evm> for T where
|
||||
T: ExecutableTxTuple<Tx: ExecutableTxFor<Evm>>
|
||||
impl<T, Evm: ConfigureEvm> ExecutableTxIterator<Evm> for T
|
||||
where
|
||||
T: ExecutableTxTuple<Tx: ExecutableTxFor<Evm, Recovered: Send + Sync>>,
|
||||
{
|
||||
type Recovered = <T::Tx as ExecutableTxParts<TxEnvFor<Evm>, TxTy<Evm::Primitives>>>::Recovered;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use alloy_consensus::{BlockHeader, Header};
|
||||
use alloy_eips::eip2718::WithEncoded;
|
||||
pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory};
|
||||
use alloy_evm::{
|
||||
block::{CommitChanges, ExecutableTx},
|
||||
block::{CommitChanges, ExecutableTxParts},
|
||||
Evm, EvmEnv, EvmFactory, RecoveredTx, ToTxEnv,
|
||||
};
|
||||
use alloy_primitives::{Address, B256};
|
||||
@@ -75,9 +75,11 @@ pub trait Executor<DB: Database>: Sized {
|
||||
where
|
||||
I: IntoIterator<Item = &'a RecoveredBlock<<Self::Primitives as NodePrimitives>::Block>>,
|
||||
{
|
||||
let mut results = Vec::new();
|
||||
let blocks_iter = blocks.into_iter();
|
||||
let capacity = blocks_iter.size_hint().0;
|
||||
let mut results = Vec::with_capacity(capacity);
|
||||
let mut first_block = None;
|
||||
for block in blocks {
|
||||
for block in blocks_iter {
|
||||
if first_block.is_none() {
|
||||
first_block = Some(block.header().number());
|
||||
}
|
||||
@@ -401,49 +403,31 @@ where
|
||||
|
||||
/// Conversions for executable transactions.
|
||||
pub trait ExecutorTx<Executor: BlockExecutor> {
|
||||
/// Converts the transaction into [`ExecutableTx`].
|
||||
fn as_executable(&self) -> impl ExecutableTx<Executor>;
|
||||
|
||||
/// Converts the transaction into [`Recovered`].
|
||||
fn into_recovered(self) -> Recovered<Executor::Transaction>;
|
||||
/// Converts the transaction into a tuple of [`TxEnvFor`] and [`Recovered`].
|
||||
fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>);
|
||||
}
|
||||
|
||||
impl<Executor: BlockExecutor> ExecutorTx<Executor>
|
||||
for WithEncoded<Recovered<Executor::Transaction>>
|
||||
{
|
||||
fn as_executable(&self) -> impl ExecutableTx<Executor> {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_recovered(self) -> Recovered<Executor::Transaction> {
|
||||
self.1
|
||||
fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>) {
|
||||
(self.to_tx_env(), self.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Executor: BlockExecutor> ExecutorTx<Executor> for Recovered<Executor::Transaction> {
|
||||
fn as_executable(&self) -> impl ExecutableTx<Executor> {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_recovered(self) -> Self {
|
||||
self
|
||||
fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Self) {
|
||||
(self.to_tx_env(), self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Executor> ExecutorTx<Executor>
|
||||
for WithTxEnv<<<Executor as BlockExecutor>::Evm as Evm>::Tx, T>
|
||||
impl<Executor> ExecutorTx<Executor>
|
||||
for WithTxEnv<<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>>
|
||||
where
|
||||
T: ExecutorTx<Executor> + Clone,
|
||||
Executor: BlockExecutor,
|
||||
<<Executor as BlockExecutor>::Evm as Evm>::Tx: Clone,
|
||||
Self: RecoveredTx<Executor::Transaction>,
|
||||
Executor: BlockExecutor<Transaction: Clone>,
|
||||
{
|
||||
fn as_executable(&self) -> impl ExecutableTx<Executor> {
|
||||
self
|
||||
}
|
||||
|
||||
fn into_recovered(self) -> Recovered<Executor::Transaction> {
|
||||
Arc::unwrap_or_clone(self.tx).into_recovered()
|
||||
fn into_parts(self) -> (<Executor::Evm as Evm>::Tx, Recovered<Executor::Transaction>) {
|
||||
(self.tx_env, Arc::unwrap_or_clone(self.tx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,10 +463,11 @@ where
|
||||
&ExecutionResult<<<Self::Executor as BlockExecutor>::Evm as Evm>::HaltReason>,
|
||||
) -> CommitChanges,
|
||||
) -> Result<Option<u64>, BlockExecutionError> {
|
||||
let (tx_env, tx) = tx.into_parts();
|
||||
if let Some(gas_used) =
|
||||
self.executor.execute_transaction_with_commit_condition(tx.as_executable(), f)?
|
||||
self.executor.execute_transaction_with_commit_condition((tx_env, &tx), f)?
|
||||
{
|
||||
self.transactions.push(tx.into_recovered());
|
||||
self.transactions.push(tx);
|
||||
Ok(Some(gas_used))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -609,20 +594,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A helper trait marking a 'static type that can be converted into an [`ExecutableTx`] for block
|
||||
/// executor.
|
||||
/// A helper trait marking a 'static type that can be converted into an [`ExecutableTxParts`] for
|
||||
/// block executor.
|
||||
pub trait ExecutableTxFor<Evm: ConfigureEvm>:
|
||||
ToTxEnv<TxEnvFor<Evm>> + RecoveredTx<TxTy<Evm::Primitives>>
|
||||
ExecutableTxParts<TxEnvFor<Evm>, TxTy<Evm::Primitives>> + RecoveredTx<TxTy<Evm::Primitives>>
|
||||
{
|
||||
}
|
||||
|
||||
impl<T, Evm: ConfigureEvm> ExecutableTxFor<Evm> for T where
|
||||
T: ToTxEnv<TxEnvFor<Evm>> + RecoveredTx<TxTy<Evm::Primitives>>
|
||||
T: ExecutableTxParts<TxEnvFor<Evm>, TxTy<Evm::Primitives>> + RecoveredTx<TxTy<Evm::Primitives>>
|
||||
{
|
||||
}
|
||||
|
||||
/// A container for a transaction and a transaction environment.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct WithTxEnv<TxEnv, T> {
|
||||
/// The transaction environment for EVM.
|
||||
pub tx_env: TxEnv,
|
||||
@@ -630,6 +615,12 @@ pub struct WithTxEnv<TxEnv, T> {
|
||||
pub tx: Arc<T>,
|
||||
}
|
||||
|
||||
impl<TxEnv: Clone, T> Clone for WithTxEnv<TxEnv, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { tx_env: self.tx_env.clone(), tx: self.tx.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<TxEnv, Tx, T: RecoveredTx<Tx>> RecoveredTx<Tx> for WithTxEnv<TxEnv, T> {
|
||||
fn tx(&self) -> &Tx {
|
||||
self.tx.tx()
|
||||
@@ -640,9 +631,11 @@ impl<TxEnv, Tx, T: RecoveredTx<Tx>> RecoveredTx<Tx> for WithTxEnv<TxEnv, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<TxEnv: Clone, T> ToTxEnv<TxEnv> for WithTxEnv<TxEnv, T> {
|
||||
fn to_tx_env(&self) -> TxEnv {
|
||||
self.tx_env.clone()
|
||||
impl<TxEnv, T: RecoveredTx<Tx>, Tx> ExecutableTxParts<TxEnv, Tx> for WithTxEnv<TxEnv, T> {
|
||||
type Recovered = Arc<T>;
|
||||
|
||||
fn into_parts(self) -> (TxEnv, Self::Recovered) {
|
||||
(self.tx_env, self.tx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ use reth_execution_errors::BlockExecutionError;
|
||||
use reth_primitives_traits::{
|
||||
BlockTy, HeaderTy, NodePrimitives, ReceiptTy, SealedBlock, SealedHeader, TxTy,
|
||||
};
|
||||
use revm::{context::TxEnv, database::State};
|
||||
use revm::{context::TxEnv, database::State, primitives::hardfork::SpecId};
|
||||
|
||||
pub mod either;
|
||||
/// EVM environment configuration.
|
||||
@@ -203,6 +203,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
+ FromRecoveredTx<TxTy<Self::Primitives>>
|
||||
+ FromTxWithEncoded<TxTy<Self::Primitives>>,
|
||||
Precompiles = PrecompilesMap,
|
||||
Spec: Into<SpecId>,
|
||||
>,
|
||||
>;
|
||||
|
||||
|
||||
@@ -66,13 +66,17 @@ use tokio::sync::mpsc::{Sender, UnboundedReceiver};
|
||||
#[non_exhaustive]
|
||||
pub struct TestPoolBuilder;
|
||||
|
||||
impl<Node> PoolBuilder<Node> for TestPoolBuilder
|
||||
impl<Node, Evm: Send> PoolBuilder<Node, Evm> for TestPoolBuilder
|
||||
where
|
||||
Node: FullNodeTypes<Types: NodeTypes<Primitives: NodePrimitives<SignedTx = TransactionSigned>>>,
|
||||
{
|
||||
type Pool = TestPool;
|
||||
|
||||
async fn build_pool(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
|
||||
async fn build_pool(
|
||||
self,
|
||||
_ctx: &BuilderContext<Node>,
|
||||
_evm_config: Evm,
|
||||
) -> eyre::Result<Self::Pool> {
|
||||
Ok(testing_pool())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1631,7 +1631,7 @@ impl Discv4Service {
|
||||
.filter(|entry| entry.node.value.is_expired())
|
||||
.map(|n| n.node.value)
|
||||
.collect::<Vec<_>>();
|
||||
nodes.sort_by(|a, b| a.last_seen.cmp(&b.last_seen));
|
||||
nodes.sort_by_key(|a| a.last_seen);
|
||||
let to_ping = nodes.into_iter().map(|n| n.record).take(MAX_NODES_PING).collect::<Vec<_>>();
|
||||
for node in to_ping {
|
||||
self.try_ping(node, PingReason::RePing)
|
||||
|
||||
@@ -14,6 +14,7 @@ workspace = true
|
||||
[dependencies]
|
||||
# reth
|
||||
reth-chainspec.workspace = true
|
||||
reth-evm-ethereum = { workspace = true, optional = true }
|
||||
reth-fs-util.workspace = true
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-net-banlist.workspace = true
|
||||
@@ -136,6 +137,8 @@ test-utils = [
|
||||
"reth-primitives-traits/test-utils",
|
||||
"reth-provider/test-utils",
|
||||
"reth-ethereum-primitives/test-utils",
|
||||
"dep:reth-evm-ethereum",
|
||||
"reth-evm-ethereum?/test-utils",
|
||||
]
|
||||
|
||||
[[bench]]
|
||||
|
||||
@@ -131,6 +131,8 @@ pub struct TransactionsManagerMetrics {
|
||||
/// capacity. Note, this is not a limit to the number of inflight requests, but a health
|
||||
/// measure.
|
||||
pub(crate) capacity_pending_pool_imports: Counter,
|
||||
/// Total number of transactions ignored because pending pool imports are at capacity.
|
||||
pub(crate) skipped_transactions_pending_pool_imports_at_capacity: Counter,
|
||||
/// The time it took to prepare transactions for import. This is mostly sender recovery.
|
||||
pub(crate) pool_import_prepare_duration: Histogram,
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ use reth_eth_wire::{
|
||||
protocol::Protocol, DisconnectReason, EthNetworkPrimitives, HelloMessageWithProtocols,
|
||||
};
|
||||
use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
|
||||
use reth_evm_ethereum::EthEvmConfig;
|
||||
use reth_network_api::{
|
||||
events::{PeerEvent, SessionInfo},
|
||||
test_utils::{PeersHandle, PeersHandleProvider},
|
||||
@@ -182,17 +183,20 @@ where
|
||||
C: ChainSpecProvider<ChainSpec: EthereumHardforks>
|
||||
+ StateProviderFactory
|
||||
+ BlockReaderIdExt
|
||||
+ HeaderProvider
|
||||
+ HeaderProvider<Header = alloy_consensus::Header>
|
||||
+ Clone
|
||||
+ 'static,
|
||||
Pool: TransactionPool,
|
||||
{
|
||||
/// Installs an eth pool on each peer
|
||||
pub fn with_eth_pool(self) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore>> {
|
||||
pub fn with_eth_pool(
|
||||
self,
|
||||
) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
|
||||
self.map_pool(|peer| {
|
||||
let blob_store = InMemoryBlobStore::default();
|
||||
let pool = TransactionValidationTaskExecutor::eth(
|
||||
peer.client.clone(),
|
||||
EthEvmConfig::mainnet(),
|
||||
blob_store.clone(),
|
||||
TokioTaskExecutor::default(),
|
||||
);
|
||||
@@ -208,7 +212,7 @@ where
|
||||
pub fn with_eth_pool_config(
|
||||
self,
|
||||
tx_manager_config: TransactionsManagerConfig,
|
||||
) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore>> {
|
||||
) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
|
||||
self.with_eth_pool_config_and_policy(tx_manager_config, Default::default())
|
||||
}
|
||||
|
||||
@@ -217,11 +221,12 @@ where
|
||||
self,
|
||||
tx_manager_config: TransactionsManagerConfig,
|
||||
policy: TransactionPropagationKind,
|
||||
) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore>> {
|
||||
) -> Testnet<C, EthTransactionPool<C, InMemoryBlobStore, EthEvmConfig>> {
|
||||
self.map_pool(|peer| {
|
||||
let blob_store = InMemoryBlobStore::default();
|
||||
let pool = TransactionValidationTaskExecutor::eth(
|
||||
peer.client.clone(),
|
||||
EthEvmConfig::mainnet(),
|
||||
blob_store.clone(),
|
||||
TokioTaskExecutor::default(),
|
||||
);
|
||||
|
||||
@@ -188,13 +188,7 @@ impl<N: NetworkPrimitives> TransactionFetcher<N> {
|
||||
let TxFetchMetadata { fallback_peers, .. } =
|
||||
self.hashes_fetch_inflight_and_pending_fetch.peek(&hash)?;
|
||||
|
||||
for peer_id in fallback_peers.iter() {
|
||||
if self.is_idle(peer_id) {
|
||||
return Some(peer_id)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
fallback_peers.iter().find(|peer_id| self.is_idle(peer_id))
|
||||
}
|
||||
|
||||
/// Returns any idle peer for any hash pending fetch. If one is found, the corresponding
|
||||
|
||||
@@ -429,11 +429,22 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
|
||||
/// Returns `true` if [`TransactionsManager`] has capacity to request pending hashes. Returns
|
||||
/// `false` if [`TransactionsManager`] is operating close to full capacity.
|
||||
fn has_capacity_for_fetching_pending_hashes(&self) -> bool {
|
||||
self.pending_pool_imports_info
|
||||
.has_capacity(self.pending_pool_imports_info.max_pending_pool_imports) &&
|
||||
self.has_capacity_for_pending_pool_imports() &&
|
||||
self.transaction_fetcher.has_capacity_for_fetching_pending_hashes()
|
||||
}
|
||||
|
||||
/// Returns `true` if [`TransactionsManager`] has capacity for more pending pool imports.
|
||||
fn has_capacity_for_pending_pool_imports(&self) -> bool {
|
||||
self.remaining_pool_import_capacity() > 0
|
||||
}
|
||||
|
||||
/// Returns the remaining capacity for pending pool imports.
|
||||
fn remaining_pool_import_capacity(&self) -> usize {
|
||||
self.pending_pool_imports_info.max_pending_pool_imports.saturating_sub(
|
||||
self.pending_pool_imports_info.pending_pool_imports.load(Ordering::Relaxed),
|
||||
)
|
||||
}
|
||||
|
||||
fn report_peer_bad_transactions(&self, peer_id: PeerId) {
|
||||
self.report_peer(peer_id, ReputationChangeKind::BadTransactions);
|
||||
self.metrics.reported_bad_transactions.increment(1);
|
||||
@@ -1285,6 +1296,7 @@ where
|
||||
trace!(target: "net::tx", peer_id=format!("{peer_id:#}"), policy=?self.config.ingress_policy, "Ignoring full transactions from peer blocked by ingress policy");
|
||||
return;
|
||||
}
|
||||
|
||||
// ensure we didn't receive any blob transactions as these are disallowed to be
|
||||
// broadcasted in full
|
||||
|
||||
@@ -1335,7 +1347,13 @@ where
|
||||
return
|
||||
}
|
||||
|
||||
// Early return if we don't have capacity for any imports
|
||||
if !self.has_capacity_for_pending_pool_imports() {
|
||||
return
|
||||
}
|
||||
|
||||
let Some(peer) = self.peers.get_mut(&peer_id) else { return };
|
||||
let client_version = peer.client_version.clone();
|
||||
let mut transactions = transactions.0;
|
||||
|
||||
let start = Instant::now();
|
||||
@@ -1378,7 +1396,7 @@ where
|
||||
trace!(target: "net::tx",
|
||||
peer_id=format!("{peer_id:#}"),
|
||||
hash=%tx.tx_hash(),
|
||||
client_version=%peer.client_version,
|
||||
%client_version,
|
||||
"received a known bad transaction from peer"
|
||||
);
|
||||
has_bad_transactions = true;
|
||||
@@ -1387,6 +1405,18 @@ where
|
||||
true
|
||||
});
|
||||
|
||||
// Truncate to remaining capacity before recovery to avoid wasting CPU on transactions
|
||||
// that won't be imported anyway.
|
||||
let capacity = self.remaining_pool_import_capacity();
|
||||
if transactions.len() > capacity {
|
||||
let skipped = transactions.len() - capacity;
|
||||
transactions.truncate(capacity);
|
||||
self.metrics
|
||||
.skipped_transactions_pending_pool_imports_at_capacity
|
||||
.increment(skipped as u64);
|
||||
trace!(target: "net::tx", skipped, capacity, "Truncated transactions batch to capacity");
|
||||
}
|
||||
|
||||
let txs_len = transactions.len();
|
||||
|
||||
let new_txs = transactions
|
||||
@@ -1397,7 +1427,7 @@ where
|
||||
trace!(target: "net::tx",
|
||||
peer_id=format!("{peer_id:#}"),
|
||||
hash=%badtx.tx_hash(),
|
||||
client_version=%peer.client_version,
|
||||
client_version=%client_version,
|
||||
"failed ecrecovery for transaction"
|
||||
);
|
||||
None
|
||||
@@ -1448,7 +1478,7 @@ where
|
||||
self.metrics
|
||||
.occurrences_of_transaction_already_seen_by_peer
|
||||
.increment(num_already_seen_by_peer);
|
||||
trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=?peer.client_version, "Peer sent already seen transactions");
|
||||
trace!(target: "net::tx", num_txs=%num_already_seen_by_peer, ?peer_id, client=%client_version, "Peer sent already seen transactions");
|
||||
}
|
||||
|
||||
if has_bad_transactions {
|
||||
|
||||
@@ -20,6 +20,7 @@ use reth_network_p2p::{
|
||||
};
|
||||
use reth_network_peers::{mainnet_nodes, NodeRecord, TrustedPeer};
|
||||
use reth_network_types::peers::config::PeerBackoffDurations;
|
||||
use reth_provider::test_utils::MockEthProvider;
|
||||
use reth_storage_api::noop::NoopProvider;
|
||||
use reth_tracing::init_test_tracing;
|
||||
use reth_transaction_pool::test_utils::testing_pool;
|
||||
@@ -655,7 +656,8 @@ async fn new_random_peer(
|
||||
async fn test_connect_many() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let net = Testnet::create_with(5, NoopProvider::default()).await;
|
||||
let provider = MockEthProvider::default().with_genesis_block();
|
||||
let net = Testnet::create_with(5, provider).await;
|
||||
|
||||
// install request handlers
|
||||
let net = net.with_eth_pool();
|
||||
|
||||
@@ -22,7 +22,7 @@ use tokio::join;
|
||||
async fn test_tx_gossip() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let provider = MockEthProvider::default();
|
||||
let provider = MockEthProvider::default().with_genesis_block();
|
||||
let net = Testnet::create_with(2, provider.clone()).await;
|
||||
|
||||
// install request handlers
|
||||
@@ -61,7 +61,7 @@ async fn test_tx_gossip() {
|
||||
async fn test_tx_propagation_policy_trusted_only() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let provider = MockEthProvider::default();
|
||||
let provider = MockEthProvider::default().with_genesis_block();
|
||||
|
||||
let policy = TransactionPropagationKind::Trusted;
|
||||
let net = Testnet::create_with(2, provider.clone()).await;
|
||||
@@ -129,7 +129,7 @@ async fn test_tx_propagation_policy_trusted_only() {
|
||||
async fn test_tx_ingress_policy_trusted_only() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let provider = MockEthProvider::default();
|
||||
let provider = MockEthProvider::default().with_genesis_block();
|
||||
|
||||
let tx_manager_config = TransactionsManagerConfig {
|
||||
ingress_policy: TransactionIngressPolicy::Trusted,
|
||||
@@ -195,7 +195,7 @@ async fn test_tx_ingress_policy_trusted_only() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_4844_tx_gossip_penalization() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let provider = MockEthProvider::default();
|
||||
let provider = MockEthProvider::default().with_genesis_block();
|
||||
let net = Testnet::create_with(2, provider.clone()).await;
|
||||
|
||||
// install request handlers
|
||||
@@ -246,7 +246,7 @@ async fn test_4844_tx_gossip_penalization() {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_sending_invalid_transactions() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let provider = MockEthProvider::default();
|
||||
let provider = MockEthProvider::default().with_genesis_block();
|
||||
let net = Testnet::create_with(2, provider.clone()).await;
|
||||
// install request handlers
|
||||
let net = net.with_eth_pool();
|
||||
|
||||
@@ -571,8 +571,8 @@ where
|
||||
debug!(target: "downloaders", %err, ?this.start_hash, "Body range download failed");
|
||||
}
|
||||
}
|
||||
if this.bodies.is_empty() {
|
||||
// received bad response, re-request headers
|
||||
if this.request.bodies.is_none() && !this.is_bodies_complete() {
|
||||
// no pending bodies request (e.g., request error), retry remaining bodies
|
||||
// TODO: convert this into two futures, one which is a headers range
|
||||
// future, and one which is a bodies range future.
|
||||
//
|
||||
@@ -751,8 +751,12 @@ mod tests {
|
||||
use reth_ethereum_primitives::BlockBody;
|
||||
|
||||
use super::*;
|
||||
use crate::test_utils::TestFullBlockClient;
|
||||
use std::ops::Range;
|
||||
use crate::{error::RequestError, test_utils::TestFullBlockClient};
|
||||
use std::{
|
||||
ops::Range,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
};
|
||||
use tokio::time::{timeout, Duration};
|
||||
|
||||
#[tokio::test]
|
||||
async fn download_single_full_block() {
|
||||
@@ -800,6 +804,65 @@ mod tests {
|
||||
(sealed_header, body)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct FailingBodiesClient {
|
||||
inner: TestFullBlockClient,
|
||||
fail_on: usize,
|
||||
body_requests: Arc<AtomicUsize>,
|
||||
}
|
||||
|
||||
impl FailingBodiesClient {
|
||||
fn new(inner: TestFullBlockClient, fail_on: usize) -> Self {
|
||||
Self { inner, fail_on, body_requests: Arc::new(AtomicUsize::new(0)) }
|
||||
}
|
||||
}
|
||||
|
||||
impl DownloadClient for FailingBodiesClient {
|
||||
fn report_bad_message(&self, peer_id: PeerId) {
|
||||
self.inner.report_bad_message(peer_id);
|
||||
}
|
||||
|
||||
fn num_connected_peers(&self) -> usize {
|
||||
self.inner.num_connected_peers()
|
||||
}
|
||||
}
|
||||
|
||||
impl HeadersClient for FailingBodiesClient {
|
||||
type Header = <TestFullBlockClient as HeadersClient>::Header;
|
||||
type Output = <TestFullBlockClient as HeadersClient>::Output;
|
||||
|
||||
fn get_headers_with_priority(
|
||||
&self,
|
||||
request: HeadersRequest,
|
||||
priority: Priority,
|
||||
) -> Self::Output {
|
||||
self.inner.get_headers_with_priority(request, priority)
|
||||
}
|
||||
}
|
||||
|
||||
impl BodiesClient for FailingBodiesClient {
|
||||
type Body = <TestFullBlockClient as BodiesClient>::Body;
|
||||
type Output = <TestFullBlockClient as BodiesClient>::Output;
|
||||
|
||||
fn get_block_bodies_with_priority_and_range_hint(
|
||||
&self,
|
||||
hashes: Vec<B256>,
|
||||
priority: Priority,
|
||||
range_hint: Option<RangeInclusive<u64>>,
|
||||
) -> Self::Output {
|
||||
let attempt = self.body_requests.fetch_add(1, Ordering::SeqCst);
|
||||
if attempt == self.fail_on {
|
||||
return futures::future::ready(Err(RequestError::Timeout))
|
||||
}
|
||||
|
||||
self.inner.get_block_bodies_with_priority_and_range_hint(hashes, priority, range_hint)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockClient for FailingBodiesClient {
|
||||
type Block = reth_ethereum_primitives::Block;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn download_full_block_range() {
|
||||
let client = TestFullBlockClient::default();
|
||||
@@ -837,6 +900,25 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn download_full_block_range_retries_after_body_error() {
|
||||
let mut client = TestFullBlockClient::default();
|
||||
client.set_soft_limit(2);
|
||||
let (header, _) = insert_headers_into_client(&client, 0..3);
|
||||
|
||||
let client = FailingBodiesClient::new(client, 1);
|
||||
let body_requests = Arc::clone(&client.body_requests);
|
||||
let client = FullBlockClient::test_client(client);
|
||||
|
||||
let received =
|
||||
timeout(Duration::from_secs(1), client.get_full_block_range(header.hash(), 3))
|
||||
.await
|
||||
.expect("body request retry should complete");
|
||||
|
||||
assert_eq!(received.len(), 3);
|
||||
assert_eq!(body_requests.load(Ordering::SeqCst), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn download_full_block_range_with_invalid_header() {
|
||||
let client = TestFullBlockClient::default();
|
||||
|
||||
@@ -62,12 +62,12 @@ impl<Node, PoolB, PayloadB, NetworkB, ExecB, ConsB>
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder: evm_builder,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
} = self;
|
||||
ComponentsBuilder {
|
||||
executor_builder: evm_builder,
|
||||
executor_builder,
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
@@ -149,15 +149,12 @@ where
|
||||
pub fn pool<PB>(
|
||||
self,
|
||||
pool_builder: PB,
|
||||
) -> ComponentsBuilder<Node, PB, PayloadB, NetworkB, ExecB, ConsB>
|
||||
where
|
||||
PB: PoolBuilder<Node>,
|
||||
{
|
||||
) -> ComponentsBuilder<Node, PB, PayloadB, NetworkB, ExecB, ConsB> {
|
||||
let Self {
|
||||
pool_builder: _,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder: evm_builder,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
} = self;
|
||||
@@ -165,7 +162,7 @@ where
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder: evm_builder,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
}
|
||||
@@ -185,72 +182,6 @@ where
|
||||
_marker: self._marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node, PoolB, PayloadB, NetworkB, ExecB, ConsB>
|
||||
ComponentsBuilder<Node, PoolB, PayloadB, NetworkB, ExecB, ConsB>
|
||||
where
|
||||
Node: FullNodeTypes,
|
||||
PoolB: PoolBuilder<Node>,
|
||||
{
|
||||
/// Configures the network builder.
|
||||
///
|
||||
/// This accepts a [`NetworkBuilder`] instance that will be used to create the node's network
|
||||
/// stack.
|
||||
pub fn network<NB>(
|
||||
self,
|
||||
network_builder: NB,
|
||||
) -> ComponentsBuilder<Node, PoolB, PayloadB, NB, ExecB, ConsB>
|
||||
where
|
||||
NB: NetworkBuilder<Node, PoolB::Pool>,
|
||||
{
|
||||
let Self {
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder: _,
|
||||
executor_builder: evm_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
} = self;
|
||||
ComponentsBuilder {
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder: evm_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the payload builder.
|
||||
///
|
||||
/// This accepts a [`PayloadServiceBuilder`] instance that will be used to create the node's
|
||||
/// payload builder service.
|
||||
pub fn payload<PB>(
|
||||
self,
|
||||
payload_builder: PB,
|
||||
) -> ComponentsBuilder<Node, PoolB, PB, NetworkB, ExecB, ConsB>
|
||||
where
|
||||
ExecB: ExecutorBuilder<Node>,
|
||||
PB: PayloadServiceBuilder<Node, PoolB::Pool, ExecB::EVM>,
|
||||
{
|
||||
let Self {
|
||||
pool_builder,
|
||||
payload_builder: _,
|
||||
network_builder,
|
||||
executor_builder: evm_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
} = self;
|
||||
ComponentsBuilder {
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder: evm_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the executor builder.
|
||||
///
|
||||
@@ -298,7 +229,72 @@ where
|
||||
network_builder,
|
||||
executor_builder,
|
||||
consensus_builder: _,
|
||||
_marker,
|
||||
} = self;
|
||||
ComponentsBuilder {
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node, PoolB, PayloadB, NetworkB, ExecB, ConsB>
|
||||
ComponentsBuilder<Node, PoolB, PayloadB, NetworkB, ExecB, ConsB>
|
||||
where
|
||||
Node: FullNodeTypes,
|
||||
ExecB: ExecutorBuilder<Node>,
|
||||
PoolB: PoolBuilder<Node, ExecB::EVM>,
|
||||
{
|
||||
/// Configures the network builder.
|
||||
///
|
||||
/// This accepts a [`NetworkBuilder`] instance that will be used to create the node's network
|
||||
/// stack.
|
||||
pub fn network<NB>(
|
||||
self,
|
||||
network_builder: NB,
|
||||
) -> ComponentsBuilder<Node, PoolB, PayloadB, NB, ExecB, ConsB>
|
||||
where
|
||||
NB: NetworkBuilder<Node, PoolB::Pool>,
|
||||
{
|
||||
let Self {
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder: _,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
} = self;
|
||||
ComponentsBuilder {
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the payload builder.
|
||||
///
|
||||
/// This accepts a [`PayloadServiceBuilder`] instance that will be used to create the node's
|
||||
/// payload builder service.
|
||||
pub fn payload<PB>(
|
||||
self,
|
||||
payload_builder: PB,
|
||||
) -> ComponentsBuilder<Node, PoolB, PB, NetworkB, ExecB, ConsB>
|
||||
where
|
||||
PB: PayloadServiceBuilder<Node, PoolB::Pool, ExecB::EVM>,
|
||||
{
|
||||
let Self {
|
||||
pool_builder,
|
||||
payload_builder: _,
|
||||
network_builder,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
} = self;
|
||||
ComponentsBuilder {
|
||||
@@ -358,7 +354,7 @@ impl<Node, PoolB, PayloadB, NetworkB, ExecB, ConsB> NodeComponentsBuilder<Node>
|
||||
for ComponentsBuilder<Node, PoolB, PayloadB, NetworkB, ExecB, ConsB>
|
||||
where
|
||||
Node: FullNodeTypes,
|
||||
PoolB: PoolBuilder<Node, Pool: TransactionPool>,
|
||||
PoolB: PoolBuilder<Node, ExecB::EVM, Pool: TransactionPool>,
|
||||
NetworkB: NetworkBuilder<
|
||||
Node,
|
||||
PoolB::Pool,
|
||||
@@ -384,13 +380,13 @@ where
|
||||
pool_builder,
|
||||
payload_builder,
|
||||
network_builder,
|
||||
executor_builder: evm_builder,
|
||||
executor_builder,
|
||||
consensus_builder,
|
||||
_marker,
|
||||
} = self;
|
||||
|
||||
let evm_config = evm_builder.build_evm(context).await?;
|
||||
let pool = pool_builder.build_pool(context).await?;
|
||||
let evm_config = executor_builder.build_evm(context).await?;
|
||||
let pool = pool_builder.build_pool(context, evm_config.clone()).await?;
|
||||
let network = network_builder.build_network(context, pool.clone()).await?;
|
||||
let payload_builder_handle = payload_builder
|
||||
.spawn_payload_builder_service(context, pool.clone(), evm_config.clone())
|
||||
@@ -471,14 +467,19 @@ where
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NoopTransactionPoolBuilder<Tx = EthPooledTransaction>(PhantomData<Tx>);
|
||||
|
||||
impl<N, Tx> PoolBuilder<N> for NoopTransactionPoolBuilder<Tx>
|
||||
impl<N, Tx, Evm> PoolBuilder<N, Evm> for NoopTransactionPoolBuilder<Tx>
|
||||
where
|
||||
N: FullNodeTypes,
|
||||
Tx: EthPoolTransaction<Consensus = TxTy<N::Types>> + Unpin,
|
||||
Evm: Send,
|
||||
{
|
||||
type Pool = NoopTransactionPool<Tx>;
|
||||
|
||||
async fn build_pool(self, _ctx: &BuilderContext<N>) -> eyre::Result<Self::Pool> {
|
||||
async fn build_pool(
|
||||
self,
|
||||
_ctx: &BuilderContext<N>,
|
||||
_evm_config: Evm,
|
||||
) -> eyre::Result<Self::Pool> {
|
||||
Ok(NoopTransactionPool::<Tx>::new())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::{BuilderContext, FullNodeTypes};
|
||||
use alloy_primitives::Address;
|
||||
use reth_chain_state::CanonStateSubscriptions;
|
||||
use reth_chainspec::EthereumHardforks;
|
||||
use reth_node_api::{NodeTypes, TxTy};
|
||||
use reth_node_api::{BlockTy, NodeTypes, TxTy};
|
||||
use reth_transaction_pool::{
|
||||
blobstore::DiskFileBlobStore, BlobStore, CoinbaseTipOrdering, PoolConfig, PoolTransaction,
|
||||
SubPoolLimit, TransactionPool, TransactionValidationTaskExecutor, TransactionValidator,
|
||||
@@ -12,7 +12,7 @@ use reth_transaction_pool::{
|
||||
use std::{collections::HashSet, future::Future};
|
||||
|
||||
/// A type that knows how to build the transaction pool.
|
||||
pub trait PoolBuilder<Node: FullNodeTypes>: Send {
|
||||
pub trait PoolBuilder<Node: FullNodeTypes, Evm>: Send {
|
||||
/// The transaction pool to build.
|
||||
type Pool: TransactionPool<Transaction: PoolTransaction<Consensus = TxTy<Node::Types>>>
|
||||
+ Unpin
|
||||
@@ -22,16 +22,17 @@ pub trait PoolBuilder<Node: FullNodeTypes>: Send {
|
||||
fn build_pool(
|
||||
self,
|
||||
ctx: &BuilderContext<Node>,
|
||||
evm_config: Evm,
|
||||
) -> impl Future<Output = eyre::Result<Self::Pool>> + Send;
|
||||
}
|
||||
|
||||
impl<Node, F, Fut, Pool> PoolBuilder<Node> for F
|
||||
impl<Node, F, Fut, Pool, Evm> PoolBuilder<Node, Evm> for F
|
||||
where
|
||||
Node: FullNodeTypes,
|
||||
Pool: TransactionPool<Transaction: PoolTransaction<Consensus = TxTy<Node::Types>>>
|
||||
+ Unpin
|
||||
+ 'static,
|
||||
F: FnOnce(&BuilderContext<Node>) -> Fut + Send,
|
||||
F: FnOnce(&BuilderContext<Node>, Evm) -> Fut + Send,
|
||||
Fut: Future<Output = eyre::Result<Pool>> + Send,
|
||||
{
|
||||
type Pool = Pool;
|
||||
@@ -39,8 +40,9 @@ where
|
||||
fn build_pool(
|
||||
self,
|
||||
ctx: &BuilderContext<Node>,
|
||||
evm_config: Evm,
|
||||
) -> impl Future<Output = eyre::Result<Self::Pool>> {
|
||||
self(ctx)
|
||||
self(ctx, evm_config)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +131,7 @@ impl<'a, Node: FullNodeTypes, V> TxPoolBuilder<'a, Node, V> {
|
||||
impl<'a, Node, V> TxPoolBuilder<'a, Node, TransactionValidationTaskExecutor<V>>
|
||||
where
|
||||
Node: FullNodeTypes<Types: NodeTypes<ChainSpec: EthereumHardforks>>,
|
||||
V: TransactionValidator + 'static,
|
||||
V: TransactionValidator<Block = BlockTy<Node::Types>> + 'static,
|
||||
V::Transaction:
|
||||
PoolTransaction<Consensus = TxTy<Node::Types>> + reth_transaction_pool::EthPoolTransaction,
|
||||
{
|
||||
@@ -248,7 +250,7 @@ fn spawn_pool_maintenance_task<Node, Pool>(
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
Node: FullNodeTypes<Types: NodeTypes<ChainSpec: EthereumHardforks>>,
|
||||
Pool: reth_transaction_pool::TransactionPoolExt + Clone + 'static,
|
||||
Pool: reth_transaction_pool::TransactionPoolExt<Block = BlockTy<Node::Types>> + Clone + 'static,
|
||||
Pool::Transaction: PoolTransaction<Consensus = TxTy<Node::Types>>,
|
||||
{
|
||||
let chain_events = ctx.provider().canonical_state_stream();
|
||||
@@ -280,7 +282,7 @@ pub fn spawn_maintenance_tasks<Node, Pool>(
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
Node: FullNodeTypes<Types: NodeTypes<ChainSpec: EthereumHardforks>>,
|
||||
Pool: reth_transaction_pool::TransactionPoolExt + Clone + 'static,
|
||||
Pool: reth_transaction_pool::TransactionPoolExt<Block = BlockTy<Node::Types>> + Clone + 'static,
|
||||
Pool::Transaction: PoolTransaction<Consensus = TxTy<Node::Types>>,
|
||||
{
|
||||
spawn_local_backup_task(ctx, pool.clone())?;
|
||||
|
||||
@@ -4,10 +4,13 @@ use alloy_consensus::transaction::Either;
|
||||
use alloy_provider::network::AnyNetwork;
|
||||
use jsonrpsee::core::{DeserializeOwned, Serialize};
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider};
|
||||
use reth_consensus_debug_client::{
|
||||
BlockProvider, DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider,
|
||||
};
|
||||
use reth_engine_local::LocalMiner;
|
||||
use reth_node_api::{
|
||||
BlockTy, FullNodeComponents, HeaderTy, PayloadAttrTy, PayloadAttributesBuilder, PayloadTypes,
|
||||
BlockTy, FullNodeComponents, FullNodeTypes, HeaderTy, PayloadAttrTy, PayloadAttributesBuilder,
|
||||
PayloadTypes,
|
||||
};
|
||||
use std::{
|
||||
future::{Future, IntoFuture},
|
||||
@@ -109,9 +112,16 @@ impl<L> DebugNodeLauncher<L> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for the default debug block provider. We use etherscan provider to satisfy the
|
||||
/// bounds.
|
||||
pub type DefaultDebugBlockProvider<N> = EtherscanBlockProvider<
|
||||
<<N as FullNodeTypes>::Types as DebugNode<N>>::RpcBlock,
|
||||
BlockTy<<N as FullNodeTypes>::Types>,
|
||||
>;
|
||||
|
||||
/// Future for the [`DebugNodeLauncher`].
|
||||
#[expect(missing_debug_implementations, clippy::type_complexity)]
|
||||
pub struct DebugNodeLauncherFuture<L, Target, N>
|
||||
pub struct DebugNodeLauncherFuture<L, Target, N, B = DefaultDebugBlockProvider<N>>
|
||||
where
|
||||
N: FullNodeComponents<Types: DebugNode<N>>,
|
||||
{
|
||||
@@ -121,14 +131,17 @@ where
|
||||
Option<Box<dyn PayloadAttributesBuilder<PayloadAttrTy<N::Types>, HeaderTy<N::Types>>>>,
|
||||
map_attributes:
|
||||
Option<Box<dyn Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync>>,
|
||||
debug_block_provider: Option<B>,
|
||||
}
|
||||
|
||||
impl<L, Target, N, AddOns> DebugNodeLauncherFuture<L, Target, N>
|
||||
impl<L, Target, N, AddOns, B> DebugNodeLauncherFuture<L, Target, N, B>
|
||||
where
|
||||
N: FullNodeComponents<Types: DebugNode<N>>,
|
||||
AddOns: RethRpcAddOns<N>,
|
||||
L: LaunchNode<Target, Node = NodeHandle<N, AddOns>>,
|
||||
B: BlockProvider<Block = BlockTy<N::Types>> + Clone,
|
||||
{
|
||||
/// Sets a custom payload attributes builder for local mining in dev mode.
|
||||
pub fn with_payload_attributes_builder(
|
||||
self,
|
||||
builder: impl PayloadAttributesBuilder<PayloadAttrTy<N::Types>, HeaderTy<N::Types>>,
|
||||
@@ -138,9 +151,11 @@ where
|
||||
target: self.target,
|
||||
local_payload_attributes_builder: Some(Box::new(builder)),
|
||||
map_attributes: None,
|
||||
debug_block_provider: self.debug_block_provider,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a function to map payload attributes before building.
|
||||
pub fn map_debug_payload_attributes(
|
||||
self,
|
||||
f: impl Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync + 'static,
|
||||
@@ -150,16 +165,58 @@ where
|
||||
target: self.target,
|
||||
local_payload_attributes_builder: None,
|
||||
map_attributes: Some(Box::new(f)),
|
||||
debug_block_provider: self.debug_block_provider,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets a custom block provider for the debug consensus client.
|
||||
///
|
||||
/// When set, this provider will be used instead of creating an `EtherscanBlockProvider`
|
||||
/// or `RpcBlockProvider` from CLI arguments.
|
||||
pub fn with_debug_block_provider<B2>(
|
||||
self,
|
||||
provider: B2,
|
||||
) -> DebugNodeLauncherFuture<L, Target, N, B2>
|
||||
where
|
||||
B2: BlockProvider<Block = BlockTy<N::Types>> + Clone,
|
||||
{
|
||||
DebugNodeLauncherFuture {
|
||||
inner: self.inner,
|
||||
target: self.target,
|
||||
local_payload_attributes_builder: self.local_payload_attributes_builder,
|
||||
map_attributes: self.map_attributes,
|
||||
debug_block_provider: Some(provider),
|
||||
}
|
||||
}
|
||||
|
||||
async fn launch_node(self) -> eyre::Result<NodeHandle<N, AddOns>> {
|
||||
let Self { inner, target, local_payload_attributes_builder, map_attributes } = self;
|
||||
let Self {
|
||||
inner,
|
||||
target,
|
||||
local_payload_attributes_builder,
|
||||
map_attributes,
|
||||
debug_block_provider,
|
||||
} = self;
|
||||
|
||||
let handle = inner.launch_node(target).await?;
|
||||
|
||||
let config = &handle.node.config;
|
||||
if let Some(url) = config.debug.rpc_consensus_url.clone() {
|
||||
|
||||
if let Some(provider) = debug_block_provider {
|
||||
info!(target: "reth::cli", "Using custom debug block provider");
|
||||
|
||||
let rpc_consensus_client = DebugConsensusClient::new(
|
||||
handle.node.add_ons_handle.beacon_engine_handle.clone(),
|
||||
Arc::new(provider),
|
||||
);
|
||||
|
||||
handle
|
||||
.node
|
||||
.task_executor
|
||||
.spawn_critical("custom debug block provider consensus client", async move {
|
||||
rpc_consensus_client.run().await
|
||||
});
|
||||
} else if let Some(url) = config.debug.rpc_consensus_url.clone() {
|
||||
info!(target: "reth::cli", "Using RPC consensus client: {}", url);
|
||||
|
||||
let block_provider =
|
||||
@@ -180,14 +237,11 @@ where
|
||||
handle.node.task_executor.spawn_critical("rpc-ws consensus client", async move {
|
||||
rpc_consensus_client.run().await
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(maybe_custom_etherscan_url) = config.debug.etherscan.clone() {
|
||||
} else if let Some(maybe_custom_etherscan_url) = config.debug.etherscan.clone() {
|
||||
info!(target: "reth::cli", "Using etherscan as consensus client");
|
||||
|
||||
let chain = config.chain.chain();
|
||||
let etherscan_url = maybe_custom_etherscan_url.map(Ok).unwrap_or_else(|| {
|
||||
// If URL isn't provided, use default Etherscan URL for the chain if it is known
|
||||
chain
|
||||
.etherscan_urls()
|
||||
.map(|urls| urls.0.to_string())
|
||||
@@ -252,12 +306,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, Target, N, AddOns> IntoFuture for DebugNodeLauncherFuture<L, Target, N>
|
||||
impl<L, Target, N, AddOns, B> IntoFuture for DebugNodeLauncherFuture<L, Target, N, B>
|
||||
where
|
||||
Target: Send + 'static,
|
||||
N: FullNodeComponents<Types: DebugNode<N>>,
|
||||
AddOns: RethRpcAddOns<N> + 'static,
|
||||
L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
|
||||
B: BlockProvider<Block = BlockTy<N::Types>> + Clone + 'static,
|
||||
{
|
||||
type Output = eyre::Result<NodeHandle<N, AddOns>>;
|
||||
type IntoFuture = Pin<Box<dyn Future<Output = eyre::Result<NodeHandle<N, AddOns>>> + Send>>;
|
||||
@@ -273,6 +328,7 @@ where
|
||||
N: FullNodeComponents<Types: DebugNode<N>>,
|
||||
AddOns: RethRpcAddOns<N> + 'static,
|
||||
L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
|
||||
DefaultDebugBlockProvider<N>: BlockProvider<Block = BlockTy<N::Types>> + Clone,
|
||||
{
|
||||
type Node = NodeHandle<N, AddOns>;
|
||||
type Future = DebugNodeLauncherFuture<L, Target, N>;
|
||||
@@ -283,6 +339,7 @@ where
|
||||
target,
|
||||
local_payload_attributes_builder: None,
|
||||
map_attributes: None,
|
||||
debug_block_provider: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ pub use builder::{add_ons::AddOns, *};
|
||||
|
||||
mod launch;
|
||||
pub use launch::{
|
||||
debug::{DebugNode, DebugNodeLauncher},
|
||||
debug::{DebugNode, DebugNodeLauncher, DebugNodeLauncherFuture, DefaultDebugBlockProvider},
|
||||
engine::EngineNodeLauncher,
|
||||
*,
|
||||
};
|
||||
|
||||
@@ -22,9 +22,8 @@ pub struct DefaultEngineValues {
|
||||
legacy_state_root_task_enabled: bool,
|
||||
state_cache_disabled: bool,
|
||||
prewarming_disabled: bool,
|
||||
parallel_sparse_trie_disabled: bool,
|
||||
state_provider_metrics: bool,
|
||||
cross_block_cache_size: u64,
|
||||
cross_block_cache_size: usize,
|
||||
state_root_task_compare_updates: bool,
|
||||
accept_execution_requests_hash: bool,
|
||||
multiproof_chunking_enabled: bool,
|
||||
@@ -81,12 +80,6 @@ impl DefaultEngineValues {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to disable parallel sparse trie by default
|
||||
pub const fn with_parallel_sparse_trie_disabled(mut self, v: bool) -> Self {
|
||||
self.parallel_sparse_trie_disabled = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to enable state provider metrics by default
|
||||
pub const fn with_state_provider_metrics(mut self, v: bool) -> Self {
|
||||
self.state_provider_metrics = v;
|
||||
@@ -94,7 +87,7 @@ impl DefaultEngineValues {
|
||||
}
|
||||
|
||||
/// Set the default cross-block cache size in MB
|
||||
pub const fn with_cross_block_cache_size(mut self, v: u64) -> Self {
|
||||
pub const fn with_cross_block_cache_size(mut self, v: usize) -> Self {
|
||||
self.cross_block_cache_size = v;
|
||||
self
|
||||
}
|
||||
@@ -189,7 +182,6 @@ impl Default for DefaultEngineValues {
|
||||
legacy_state_root_task_enabled: false,
|
||||
state_cache_disabled: false,
|
||||
prewarming_disabled: false,
|
||||
parallel_sparse_trie_disabled: false,
|
||||
state_provider_metrics: false,
|
||||
cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB,
|
||||
state_root_task_compare_updates: false,
|
||||
@@ -244,14 +236,14 @@ pub struct EngineArgs {
|
||||
#[arg(long = "engine.disable-prewarming", alias = "engine.disable-caching-and-prewarming", default_value_t = DefaultEngineValues::get_global().prewarming_disabled)]
|
||||
pub prewarming_disabled: bool,
|
||||
|
||||
/// CAUTION: This CLI flag has no effect anymore, use --engine.disable-parallel-sparse-trie
|
||||
/// if you want to disable usage of the `ParallelSparseTrie`.
|
||||
/// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
|
||||
#[deprecated]
|
||||
#[arg(long = "engine.parallel-sparse-trie", default_value = "true", hide = true)]
|
||||
pub parallel_sparse_trie_enabled: bool,
|
||||
|
||||
/// Disable the parallel sparse trie in the engine.
|
||||
#[arg(long = "engine.disable-parallel-sparse-trie", default_value_t = DefaultEngineValues::get_global().parallel_sparse_trie_disabled)]
|
||||
/// CAUTION: This CLI flag has no effect anymore. The parallel sparse trie is always enabled.
|
||||
#[deprecated]
|
||||
#[arg(long = "engine.disable-parallel-sparse-trie", default_value = "false", hide = true)]
|
||||
pub parallel_sparse_trie_disabled: bool,
|
||||
|
||||
/// Enable state provider latency metrics. This allows the engine to collect and report stats
|
||||
@@ -262,7 +254,7 @@ pub struct EngineArgs {
|
||||
|
||||
/// Configure the size of cross-block cache in megabytes
|
||||
#[arg(long = "engine.cross-block-cache-size", default_value_t = DefaultEngineValues::get_global().cross_block_cache_size)]
|
||||
pub cross_block_cache_size: u64,
|
||||
pub cross_block_cache_size: usize,
|
||||
|
||||
/// Enable comparing trie updates from the state root task to the trie updates from the regular
|
||||
/// state root calculation.
|
||||
@@ -343,7 +335,6 @@ impl Default for EngineArgs {
|
||||
legacy_state_root_task_enabled,
|
||||
state_cache_disabled,
|
||||
prewarming_disabled,
|
||||
parallel_sparse_trie_disabled,
|
||||
state_provider_metrics,
|
||||
cross_block_cache_size,
|
||||
state_root_task_compare_updates,
|
||||
@@ -369,7 +360,7 @@ impl Default for EngineArgs {
|
||||
state_cache_disabled,
|
||||
prewarming_disabled,
|
||||
parallel_sparse_trie_enabled: true,
|
||||
parallel_sparse_trie_disabled,
|
||||
parallel_sparse_trie_disabled: false,
|
||||
state_provider_metrics,
|
||||
cross_block_cache_size,
|
||||
accept_execution_requests_hash,
|
||||
@@ -398,7 +389,6 @@ impl EngineArgs {
|
||||
.with_legacy_state_root(self.legacy_state_root_task_enabled)
|
||||
.without_state_cache(self.state_cache_disabled)
|
||||
.without_prewarming(self.prewarming_disabled)
|
||||
.with_disable_parallel_sparse_trie(self.parallel_sparse_trie_disabled)
|
||||
.with_state_provider_metrics(self.state_provider_metrics)
|
||||
.with_always_compare_trie_updates(self.state_root_task_compare_updates)
|
||||
.with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024)
|
||||
@@ -457,7 +447,7 @@ mod tests {
|
||||
state_cache_disabled: true,
|
||||
prewarming_disabled: true,
|
||||
parallel_sparse_trie_enabled: true,
|
||||
parallel_sparse_trie_disabled: true,
|
||||
parallel_sparse_trie_disabled: false,
|
||||
state_provider_metrics: true,
|
||||
cross_block_cache_size: 256,
|
||||
state_root_task_compare_updates: true,
|
||||
@@ -485,7 +475,6 @@ mod tests {
|
||||
"--engine.legacy-state-root",
|
||||
"--engine.disable-state-cache",
|
||||
"--engine.disable-prewarming",
|
||||
"--engine.disable-parallel-sparse-trie",
|
||||
"--engine.state-provider-metrics",
|
||||
"--engine.cross-block-cache-size",
|
||||
"256",
|
||||
|
||||
@@ -1025,6 +1025,7 @@ mod tests {
|
||||
max_receipts: 2000,
|
||||
max_headers: 1000,
|
||||
max_concurrent_db_requests: 512,
|
||||
max_cached_tx_hashes: 30_000,
|
||||
},
|
||||
gas_price_oracle: GasPriceOracleArgs {
|
||||
blocks: 20,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use clap::Args;
|
||||
use reth_rpc_server_types::constants::cache::{
|
||||
DEFAULT_BLOCK_CACHE_MAX_LEN, DEFAULT_CONCURRENT_DB_REQUESTS, DEFAULT_HEADER_CACHE_MAX_LEN,
|
||||
DEFAULT_RECEIPT_CACHE_MAX_LEN,
|
||||
DEFAULT_MAX_CACHED_TX_HASHES, DEFAULT_RECEIPT_CACHE_MAX_LEN,
|
||||
};
|
||||
|
||||
/// Parameters to configure RPC state cache.
|
||||
@@ -36,6 +36,13 @@ pub struct RpcStateCacheArgs {
|
||||
default_value_t = DEFAULT_CONCURRENT_DB_REQUESTS,
|
||||
)]
|
||||
pub max_concurrent_db_requests: usize,
|
||||
|
||||
/// Maximum number of transaction hashes to cache for transaction lookups.
|
||||
#[arg(
|
||||
long = "rpc-cache.max-cached-tx-hashes",
|
||||
default_value_t = DEFAULT_MAX_CACHED_TX_HASHES,
|
||||
)]
|
||||
pub max_cached_tx_hashes: u32,
|
||||
}
|
||||
|
||||
impl RpcStateCacheArgs {
|
||||
@@ -54,6 +61,7 @@ impl Default for RpcStateCacheArgs {
|
||||
max_receipts: DEFAULT_RECEIPT_CACHE_MAX_LEN,
|
||||
max_headers: DEFAULT_HEADER_CACHE_MAX_LEN,
|
||||
max_concurrent_db_requests: DEFAULT_CONCURRENT_DB_REQUESTS,
|
||||
max_cached_tx_hashes: DEFAULT_MAX_CACHED_TX_HASHES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use clap::Args;
|
||||
use reth_config::config::{BlocksPerFileConfig, StaticFilesConfig};
|
||||
use reth_storage_api::StorageSettings;
|
||||
|
||||
/// Blocks per static file when running in `--minimal` node.
|
||||
///
|
||||
@@ -40,6 +41,10 @@ pub struct StaticFilesArgs {
|
||||
#[arg(long = "static-files.blocks-per-file.account-change-sets")]
|
||||
pub blocks_per_file_account_change_sets: Option<u64>,
|
||||
|
||||
/// Number of blocks per file for the storage changesets segment.
|
||||
#[arg(long = "static-files.blocks-per-file.storage-change-sets")]
|
||||
pub blocks_per_file_storage_change_sets: Option<u64>,
|
||||
|
||||
/// Store receipts in static files instead of the database.
|
||||
///
|
||||
/// When enabled, receipts will be written to static files on disk instead of the database.
|
||||
@@ -68,6 +73,16 @@ pub struct StaticFilesArgs {
|
||||
/// the node has been initialized, changing this flag requires re-syncing from scratch.
|
||||
#[arg(long = "static-files.account-change-sets", default_value_t = default_static_file_flag(), action = clap::ArgAction::Set)]
|
||||
pub account_changesets: bool,
|
||||
|
||||
/// Store storage changesets in static files.
|
||||
///
|
||||
/// When enabled, storage changesets will be written to static files on disk instead of the
|
||||
/// database.
|
||||
///
|
||||
/// Note: This setting can only be configured at genesis initialization. Once
|
||||
/// the node has been initialized, changing this flag requires re-syncing from scratch.
|
||||
#[arg(long = "static-files.storage-change-sets", default_value_t = default_static_file_flag(), action = clap::ArgAction::Set)]
|
||||
pub storage_changesets: bool,
|
||||
}
|
||||
|
||||
impl StaticFilesArgs {
|
||||
@@ -98,9 +113,25 @@ impl StaticFilesArgs {
|
||||
account_change_sets: self
|
||||
.blocks_per_file_account_change_sets
|
||||
.or(config.blocks_per_file.account_change_sets),
|
||||
storage_change_sets: self
|
||||
.blocks_per_file_storage_change_sets
|
||||
.or(config.blocks_per_file.storage_change_sets),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the static files arguments into [`StorageSettings`].
|
||||
pub const fn to_settings(&self) -> StorageSettings {
|
||||
#[cfg(feature = "edge")]
|
||||
let base = StorageSettings::edge();
|
||||
#[cfg(not(feature = "edge"))]
|
||||
let base = StorageSettings::legacy();
|
||||
|
||||
base.with_receipts_in_static_files(self.receipts)
|
||||
.with_transaction_senders_in_static_files(self.transaction_senders)
|
||||
.with_account_changesets_in_static_files(self.account_changesets)
|
||||
.with_storage_changesets_in_static_files(self.storage_changesets)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StaticFilesArgs {
|
||||
@@ -111,9 +142,11 @@ impl Default for StaticFilesArgs {
|
||||
blocks_per_file_receipts: None,
|
||||
blocks_per_file_transaction_senders: None,
|
||||
blocks_per_file_account_change_sets: None,
|
||||
blocks_per_file_storage_change_sets: None,
|
||||
receipts: default_static_file_flag(),
|
||||
transaction_senders: default_static_file_flag(),
|
||||
account_changesets: default_static_file_flag(),
|
||||
storage_changesets: default_static_file_flag(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ pub use reth_engine_primitives::{
|
||||
};
|
||||
|
||||
/// Default size of cross-block cache in megabytes.
|
||||
pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: u64 = 4 * 1024;
|
||||
pub const DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB: usize = 4 * 1024;
|
||||
|
||||
/// This includes all necessary configuration to launch the node.
|
||||
/// The individual configuration options can be overwritten before launching the node.
|
||||
@@ -363,6 +363,7 @@ impl<ChainSpec> NodeConfig<ChainSpec> {
|
||||
.with_receipts_in_static_files(self.static_files.receipts)
|
||||
.with_transaction_senders_in_static_files(self.static_files.transaction_senders)
|
||||
.with_account_changesets_in_static_files(self.static_files.account_changesets)
|
||||
.with_storage_changesets_in_static_files(self.static_files.storage_changesets)
|
||||
.with_transaction_hash_numbers_in_rocksdb(self.rocksdb.all || self.rocksdb.tx_hash)
|
||||
.with_storages_history_in_rocksdb(self.rocksdb.all || self.rocksdb.storages_history)
|
||||
.with_account_history_in_rocksdb(self.rocksdb.all || self.rocksdb.account_history)
|
||||
|
||||
@@ -257,6 +257,11 @@ fn describe_rocksdb_metrics() {
|
||||
Unit::Bytes,
|
||||
"The size of memtables for a RocksDB table"
|
||||
);
|
||||
describe_gauge!(
|
||||
"rocksdb.wal_size",
|
||||
Unit::Bytes,
|
||||
"The total size of WAL (Write-Ahead Log) files. Important: this is not included in table_size or sst_size metrics"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "jemalloc", unix))]
|
||||
|
||||
@@ -17,9 +17,9 @@ impl OpReceiptBuilder for OpRethReceiptBuilder {
|
||||
|
||||
fn build_receipt<'a, E: Evm>(
|
||||
&self,
|
||||
ctx: ReceiptBuilderCtx<'a, OpTransactionSigned, E>,
|
||||
) -> Result<Self::Receipt, ReceiptBuilderCtx<'a, OpTransactionSigned, E>> {
|
||||
match ctx.tx.tx_type() {
|
||||
ctx: ReceiptBuilderCtx<'a, OpTxType, E>,
|
||||
) -> Result<Self::Receipt, ReceiptBuilderCtx<'a, OpTxType, E>> {
|
||||
match ctx.tx_type {
|
||||
OpTxType::Deposit => Err(ctx),
|
||||
ty => {
|
||||
let receipt = Receipt {
|
||||
|
||||
@@ -62,6 +62,7 @@ where
|
||||
type ExecutionPayloadEnvelopeV3 = OpExecutionPayloadEnvelopeV3;
|
||||
type ExecutionPayloadEnvelopeV4 = OpExecutionPayloadEnvelopeV4;
|
||||
type ExecutionPayloadEnvelopeV5 = OpExecutionPayloadEnvelopeV4;
|
||||
type ExecutionPayloadEnvelopeV6 = OpExecutionPayloadEnvelopeV4;
|
||||
}
|
||||
|
||||
/// Validator for Optimism engine API.
|
||||
|
||||
@@ -165,6 +165,7 @@ impl OpNode {
|
||||
self.args;
|
||||
ComponentsBuilder::default()
|
||||
.node_types::<Node>()
|
||||
.executor(OpExecutorBuilder::default())
|
||||
.pool(
|
||||
OpPoolBuilder::default()
|
||||
.with_enable_tx_conditional(self.args.enable_tx_conditional)
|
||||
@@ -173,7 +174,6 @@ impl OpNode {
|
||||
self.args.supervisor_safety_level,
|
||||
),
|
||||
)
|
||||
.executor(OpExecutorBuilder::default())
|
||||
.payload(BasicPayloadServiceBuilder::new(
|
||||
OpPayloadBuilder::new(compute_pending_block)
|
||||
.with_da_config(self.da_config.clone())
|
||||
@@ -957,14 +957,19 @@ impl<T> OpPoolBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Node, T> PoolBuilder<Node> for OpPoolBuilder<T>
|
||||
impl<Node, T, Evm> PoolBuilder<Node, Evm> for OpPoolBuilder<T>
|
||||
where
|
||||
Node: FullNodeTypes<Types: NodeTypes<ChainSpec: OpHardforks>>,
|
||||
T: EthPoolTransaction<Consensus = TxTy<Node::Types>> + OpPooledTx,
|
||||
Evm: ConfigureEvm<Primitives = PrimitivesTy<Node::Types>> + Clone + 'static,
|
||||
{
|
||||
type Pool = OpTransactionPool<Node::Provider, DiskFileBlobStore, T>;
|
||||
type Pool = OpTransactionPool<Node::Provider, DiskFileBlobStore, Evm, T>;
|
||||
|
||||
async fn build_pool(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
|
||||
async fn build_pool(
|
||||
self,
|
||||
ctx: &BuilderContext<Node>,
|
||||
evm_config: Evm,
|
||||
) -> eyre::Result<Self::Pool> {
|
||||
let Self { pool_config_overrides, .. } = self;
|
||||
|
||||
// supervisor used for interop
|
||||
@@ -982,27 +987,27 @@ where
|
||||
.await;
|
||||
|
||||
let blob_store = reth_node_builder::components::create_blob_store(ctx)?;
|
||||
let validator = TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone())
|
||||
.no_eip4844()
|
||||
.with_head_timestamp(ctx.head().timestamp)
|
||||
.with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes)
|
||||
.kzg_settings(ctx.kzg_settings()?)
|
||||
.set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap)
|
||||
.with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit)
|
||||
.with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee)
|
||||
.with_additional_tasks(
|
||||
pool_config_overrides
|
||||
.additional_validation_tasks
|
||||
.unwrap_or_else(|| ctx.config().txpool.additional_validation_tasks),
|
||||
)
|
||||
.build_with_tasks(ctx.task_executor().clone(), blob_store.clone())
|
||||
.map(|validator| {
|
||||
OpTransactionValidator::new(validator)
|
||||
// In --dev mode we can't require gas fees because we're unable to decode
|
||||
// the L1 block info
|
||||
.require_l1_data_gas_fee(!ctx.config().dev.dev)
|
||||
.with_supervisor(supervisor_client.clone())
|
||||
});
|
||||
let validator =
|
||||
TransactionValidationTaskExecutor::eth_builder(ctx.provider().clone(), evm_config)
|
||||
.no_eip4844()
|
||||
.with_max_tx_input_bytes(ctx.config().txpool.max_tx_input_bytes)
|
||||
.kzg_settings(ctx.kzg_settings()?)
|
||||
.set_tx_fee_cap(ctx.config().rpc.rpc_tx_fee_cap)
|
||||
.with_max_tx_gas_limit(ctx.config().txpool.max_tx_gas_limit)
|
||||
.with_minimum_priority_fee(ctx.config().txpool.minimum_priority_fee)
|
||||
.with_additional_tasks(
|
||||
pool_config_overrides
|
||||
.additional_validation_tasks
|
||||
.unwrap_or_else(|| ctx.config().txpool.additional_validation_tasks),
|
||||
)
|
||||
.build_with_tasks(ctx.task_executor().clone(), blob_store.clone())
|
||||
.map(|validator| {
|
||||
OpTransactionValidator::new(validator)
|
||||
// In --dev mode we can't require gas fees because we're unable to decode
|
||||
// the L1 block info
|
||||
.require_l1_data_gas_fee(!ctx.config().dev.dev)
|
||||
.with_supervisor(supervisor_client.clone())
|
||||
});
|
||||
|
||||
let final_pool_config = pool_config_overrides.apply(ctx.pool_config());
|
||||
|
||||
|
||||
@@ -52,9 +52,9 @@
|
||||
//! ComponentsBuilder::default()
|
||||
//! .node_types::<RethFullAdapter<_, OpNode>>()
|
||||
//! .noop_pool::<OpPooledTransaction>()
|
||||
//! .noop_network::<OpNetworkPrimitives>()
|
||||
//! .noop_consensus()
|
||||
//! .executor(OpExecutorBuilder::default())
|
||||
//! .noop_consensus()
|
||||
//! .noop_network::<OpNetworkPrimitives>()
|
||||
//! .noop_payload(),
|
||||
//! Box::new(()) as Box<dyn OnComponentInitializedHook<_>>,
|
||||
//! )
|
||||
|
||||
@@ -155,6 +155,10 @@ impl IsTyped2718 for OpTransactionSigned {
|
||||
}
|
||||
|
||||
impl SignedTransaction for OpTransactionSigned {
|
||||
fn is_system_tx(&self) -> bool {
|
||||
self.is_deposit()
|
||||
}
|
||||
|
||||
fn recalculate_hash(&self) -> B256 {
|
||||
keccak256(self.encoded_2718())
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ alloy-serde.workspace = true
|
||||
|
||||
# reth
|
||||
reth-chainspec.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-chain-state.workspace = true
|
||||
reth-storage-api.workspace = true
|
||||
|
||||
@@ -24,8 +24,8 @@ pub mod estimated_da_size;
|
||||
use reth_transaction_pool::{CoinbaseTipOrdering, Pool, TransactionValidationTaskExecutor};
|
||||
|
||||
/// Type alias for default optimism transaction pool
|
||||
pub type OpTransactionPool<Client, S, T = OpPooledTransaction> = Pool<
|
||||
TransactionValidationTaskExecutor<OpTransactionValidator<Client, T>>,
|
||||
pub type OpTransactionPool<Client, S, Evm, T = OpPooledTransaction> = Pool<
|
||||
TransactionValidationTaskExecutor<OpTransactionValidator<Client, T, Evm>>,
|
||||
CoinbaseTipOrdering<T>,
|
||||
S,
|
||||
>;
|
||||
|
||||
@@ -316,7 +316,8 @@ mod tests {
|
||||
use alloy_primitives::{TxKind, U256};
|
||||
use op_alloy_consensus::TxDeposit;
|
||||
use reth_optimism_chainspec::OP_MAINNET;
|
||||
use reth_optimism_primitives::OpTransactionSigned;
|
||||
use reth_optimism_evm::OpEvmConfig;
|
||||
use reth_optimism_primitives::{OpPrimitives, OpTransactionSigned};
|
||||
use reth_provider::test_utils::MockEthProvider;
|
||||
use reth_transaction_pool::{
|
||||
blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder, TransactionOrigin,
|
||||
@@ -324,8 +325,11 @@ mod tests {
|
||||
};
|
||||
#[tokio::test]
|
||||
async fn validate_optimism_transaction() {
|
||||
let client = MockEthProvider::default().with_chain_spec(OP_MAINNET.clone());
|
||||
let validator = EthTransactionValidatorBuilder::new(client)
|
||||
let client = MockEthProvider::<OpPrimitives>::new()
|
||||
.with_chain_spec(OP_MAINNET.clone())
|
||||
.with_genesis_block();
|
||||
let evm_config = OpEvmConfig::optimism(OP_MAINNET.clone());
|
||||
let validator = EthTransactionValidatorBuilder::new(client, evm_config)
|
||||
.no_shanghai()
|
||||
.no_cancun()
|
||||
.build(InMemoryBlobStore::default());
|
||||
|
||||
@@ -3,10 +3,12 @@ use alloy_consensus::{BlockHeader, Transaction};
|
||||
use op_revm::L1BlockInfo;
|
||||
use parking_lot::RwLock;
|
||||
use reth_chainspec::ChainSpecProvider;
|
||||
use reth_evm::ConfigureEvm;
|
||||
use reth_optimism_evm::RethL1BlockInfo;
|
||||
use reth_optimism_forks::OpHardforks;
|
||||
use reth_primitives_traits::{
|
||||
transaction::error::InvalidTransactionError, Block, BlockBody, GotExpected, SealedBlock,
|
||||
transaction::error::InvalidTransactionError, Block, BlockBody, BlockTy, GotExpected,
|
||||
SealedBlock,
|
||||
};
|
||||
use reth_storage_api::{AccountInfoReader, BlockReaderIdExt, StateProviderFactory};
|
||||
use reth_transaction_pool::{
|
||||
@@ -39,9 +41,9 @@ impl OpL1BlockInfo {
|
||||
|
||||
/// Validator for Optimism transactions.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OpTransactionValidator<Client, Tx> {
|
||||
pub struct OpTransactionValidator<Client, Tx, Evm> {
|
||||
/// The type that performs the actual validation.
|
||||
inner: Arc<EthTransactionValidator<Client, Tx>>,
|
||||
inner: Arc<EthTransactionValidator<Client, Tx, Evm>>,
|
||||
/// Additional block info required for validation.
|
||||
block_info: Arc<OpL1BlockInfo>,
|
||||
/// If true, ensure that the transaction's sender has enough balance to cover the L1 gas fee
|
||||
@@ -54,7 +56,7 @@ pub struct OpTransactionValidator<Client, Tx> {
|
||||
fork_tracker: Arc<OpForkTracker>,
|
||||
}
|
||||
|
||||
impl<Client, Tx> OpTransactionValidator<Client, Tx> {
|
||||
impl<Client, Tx, Evm> OpTransactionValidator<Client, Tx, Evm> {
|
||||
/// Returns the configured chain spec
|
||||
pub fn chain_spec(&self) -> Arc<Client::ChainSpec>
|
||||
where
|
||||
@@ -86,14 +88,15 @@ impl<Client, Tx> OpTransactionValidator<Client, Tx> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client, Tx> OpTransactionValidator<Client, Tx>
|
||||
impl<Client, Tx, Evm> OpTransactionValidator<Client, Tx, Evm>
|
||||
where
|
||||
Client:
|
||||
ChainSpecProvider<ChainSpec: OpHardforks> + StateProviderFactory + BlockReaderIdExt + Sync,
|
||||
Tx: EthPoolTransaction + OpPooledTx,
|
||||
Evm: ConfigureEvm,
|
||||
{
|
||||
/// Create a new [`OpTransactionValidator`].
|
||||
pub fn new(inner: EthTransactionValidator<Client, Tx>) -> Self {
|
||||
pub fn new(inner: EthTransactionValidator<Client, Tx, Evm>) -> Self {
|
||||
let this = Self::with_block_info(inner, OpL1BlockInfo::default());
|
||||
if let Ok(Some(block)) =
|
||||
this.inner.client().block_by_number_or_tag(alloy_eips::BlockNumberOrTag::Latest)
|
||||
@@ -112,7 +115,7 @@ where
|
||||
|
||||
/// Create a new [`OpTransactionValidator`] with the given [`OpL1BlockInfo`].
|
||||
pub fn with_block_info(
|
||||
inner: EthTransactionValidator<Client, Tx>,
|
||||
inner: EthTransactionValidator<Client, Tx, Evm>,
|
||||
block_info: OpL1BlockInfo,
|
||||
) -> Self {
|
||||
Self {
|
||||
@@ -288,13 +291,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<Client, Tx> TransactionValidator for OpTransactionValidator<Client, Tx>
|
||||
impl<Client, Tx, Evm> TransactionValidator for OpTransactionValidator<Client, Tx, Evm>
|
||||
where
|
||||
Client:
|
||||
ChainSpecProvider<ChainSpec: OpHardforks> + StateProviderFactory + BlockReaderIdExt + Sync,
|
||||
Tx: EthPoolTransaction + OpPooledTx,
|
||||
Evm: ConfigureEvm,
|
||||
{
|
||||
type Transaction = Tx;
|
||||
type Block = BlockTy<Evm::Primitives>;
|
||||
|
||||
async fn validate_transaction(
|
||||
&self,
|
||||
@@ -325,10 +330,7 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
fn on_new_head_block<B>(&self, new_tip_block: &SealedBlock<B>)
|
||||
where
|
||||
B: Block,
|
||||
{
|
||||
fn on_new_head_block(&self, new_tip_block: &SealedBlock<Self::Block>) {
|
||||
self.inner.on_new_head_block(new_tip_block);
|
||||
self.update_l1_block_info(
|
||||
new_tip_block.header(),
|
||||
|
||||
@@ -798,12 +798,14 @@ mod rpc_compat {
|
||||
.zip(senders)
|
||||
.enumerate()
|
||||
.map(|(idx, (tx, sender))| {
|
||||
#[allow(clippy::needless_update)]
|
||||
let tx_info = TransactionInfo {
|
||||
hash: Some(*tx.tx_hash()),
|
||||
block_hash,
|
||||
block_number: Some(block_number),
|
||||
base_fee,
|
||||
index: Some(idx as u64),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
converter(Recovered::new_unchecked(tx, sender), tx_info)
|
||||
|
||||
@@ -48,6 +48,16 @@ pub trait SignedTransaction:
|
||||
+ TxHashRef
|
||||
+ IsTyped2718
|
||||
{
|
||||
/// Returns whether this is a system transaction.
|
||||
///
|
||||
/// System transactions are created at the protocol level rather than by users. They are
|
||||
/// typically used by L2s for special purposes (e.g., Optimism deposit transactions with type
|
||||
/// 126) and may have different validation rules or fee handling compared to standard
|
||||
/// user-initiated transactions.
|
||||
fn is_system_tx(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns whether this transaction type can be __broadcasted__ as full transaction over the
|
||||
/// network.
|
||||
///
|
||||
|
||||
@@ -17,6 +17,7 @@ reth-exex-types.workspace = true
|
||||
reth-db-api.workspace = true
|
||||
reth-errors.workspace = true
|
||||
reth-provider.workspace = true
|
||||
reth-storage-api.workspace = true
|
||||
reth-tokio-util.workspace = true
|
||||
reth-config.workspace = true
|
||||
reth-prune-types.workspace = true
|
||||
|
||||
@@ -10,6 +10,7 @@ use reth_provider::{
|
||||
StageCheckpointReader, StaticFileProviderFactory, StorageSettingsCache,
|
||||
};
|
||||
use reth_prune_types::PruneModes;
|
||||
use reth_storage_api::{ChangeSetReader, StorageChangeSetReader};
|
||||
use std::time::Duration;
|
||||
use tokio::sync::watch;
|
||||
|
||||
@@ -82,6 +83,8 @@ impl PrunerBuilder {
|
||||
+ ChainStateBlockReader
|
||||
+ StorageSettingsCache
|
||||
+ StageCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader
|
||||
+ StaticFileProviderFactory<
|
||||
Primitives: NodePrimitives<SignedTx: Value, Receipt: Value, BlockHeader: Value>,
|
||||
>,
|
||||
@@ -116,7 +119,9 @@ impl PrunerBuilder {
|
||||
+ PruneCheckpointWriter
|
||||
+ PruneCheckpointReader
|
||||
+ StorageSettingsCache
|
||||
+ StageCheckpointReader,
|
||||
+ StageCheckpointReader
|
||||
+ ChangeSetReader
|
||||
+ StorageChangeSetReader,
|
||||
{
|
||||
let segments = SegmentSet::<Provider>::from_components(static_file_provider, self.segments);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user