mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
134 Commits
yk/worker-
...
performanc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c04d1abe1 | ||
|
|
4231f4b688 | ||
|
|
0b607113dc | ||
|
|
be4dc53b92 | ||
|
|
4afb555d06 | ||
|
|
ab2ef99458 | ||
|
|
bfd4b79245 | ||
|
|
49057b1c0c | ||
|
|
b6772370d7 | ||
|
|
d72935628a | ||
|
|
ad63b135d6 | ||
|
|
90651ae8e8 | ||
|
|
bbd51862d4 | ||
|
|
08a16a5bde | ||
|
|
f2c39db7a2 | ||
|
|
ae9e84d6e3 | ||
|
|
c51da593d1 | ||
|
|
0e08f9f56c | ||
|
|
7eef092110 | ||
|
|
40e8241bf5 | ||
|
|
dd9ff731e4 | ||
|
|
83f9d1837f | ||
|
|
68911e617b | ||
|
|
36ba6db029 | ||
|
|
fec4432d82 | ||
|
|
179da26305 | ||
|
|
b5e7a694d2 | ||
|
|
9489667814 | ||
|
|
004877ba59 | ||
|
|
a9e36923e1 | ||
|
|
74a3816611 | ||
|
|
5576d4547f | ||
|
|
21216e2f24 | ||
|
|
42c1e1afe1 | ||
|
|
5f7e87fa2a | ||
|
|
1b417dacc4 | ||
|
|
bb952be5b5 | ||
|
|
f927eec880 | ||
|
|
9c61f5568c | ||
|
|
662c0486a1 | ||
|
|
997848c2a1 | ||
|
|
155bdecf3b | ||
|
|
679234f105 | ||
|
|
419c7b489b | ||
|
|
06dac07b5f | ||
|
|
5621132b8b | ||
|
|
3380eb69c8 | ||
|
|
0366497ada | ||
|
|
cd71f3d5a4 | ||
|
|
64909d33e6 | ||
|
|
3c9ad31344 | ||
|
|
f3e14fd061 | ||
|
|
daf6b88dc6 | ||
|
|
d2d58f9a0e | ||
|
|
ace4e515b5 | ||
|
|
134164954b | ||
|
|
2775dd1f23 | ||
|
|
ac0f9687bd | ||
|
|
a9c21a395d | ||
|
|
df7ad9ae45 | ||
|
|
5903e42a98 | ||
|
|
3c41b99599 | ||
|
|
d70d80fff1 | ||
|
|
ed3a8a03d5 | ||
|
|
bfcd46d01d | ||
|
|
194d545fae | ||
|
|
97243ec1f4 | ||
|
|
93c1b0f52f | ||
|
|
474c09095f | ||
|
|
24c298133f | ||
|
|
da27336a1e | ||
|
|
2e567d6658 | ||
|
|
28e7c8a7cb | ||
|
|
a2a5e03cb8 | ||
|
|
6073aa5b4a | ||
|
|
e90cfedf3d | ||
|
|
8b27ca6fa2 | ||
|
|
1752d6fb99 | ||
|
|
ac891a780b | ||
|
|
036626b8a7 | ||
|
|
68f0c9812f | ||
|
|
c9920c9690 | ||
|
|
af82606ff4 | ||
|
|
38331a362e | ||
|
|
e8dae2ae7d | ||
|
|
ce5f90175b | ||
|
|
8c361c87c2 | ||
|
|
4fbbb1fe54 | ||
|
|
b7d8815104 | ||
|
|
b91cd8f451 | ||
|
|
09aee4e35a | ||
|
|
505a384b10 | ||
|
|
6e00b99b67 | ||
|
|
1d389cfe7a | ||
|
|
2e62387469 | ||
|
|
31133255fe | ||
|
|
a6b9472d1c | ||
|
|
6636d2a2ad | ||
|
|
ab6854d159 | ||
|
|
5a274fc939 | ||
|
|
c9431b224b | ||
|
|
8cbfd91db0 | ||
|
|
43f9942ba7 | ||
|
|
06adc3ee0c | ||
|
|
fbf6be4cf2 | ||
|
|
21d61d40d1 | ||
|
|
cf7d709358 | ||
|
|
e9355caba5 | ||
|
|
fdd9d5bb40 | ||
|
|
9eeba7e6b3 | ||
|
|
0085acc868 | ||
|
|
c697147f90 | ||
|
|
7388d6636d | ||
|
|
0b859c0735 | ||
|
|
a8e0606fa7 | ||
|
|
969689d9b6 | ||
|
|
ad2081493a | ||
|
|
abfb6d3965 | ||
|
|
0f0eb7a531 | ||
|
|
4f1e486b4f | ||
|
|
05307d088c | ||
|
|
245cca7ce2 | ||
|
|
28d6996fc4 | ||
|
|
0eaffdf489 | ||
|
|
9c141cac4b | ||
|
|
fc6ab35c5c | ||
|
|
f88bf4e427 | ||
|
|
3d330caf36 | ||
|
|
5a43e77771 | ||
|
|
5b3c479ed5 | ||
|
|
dc06b47abe | ||
|
|
e9cd7cc003 | ||
|
|
f633efc969 | ||
|
|
2f55b1c30f |
@@ -12,7 +12,7 @@ workflows:
|
||||
# Check that `A` activates the features of `B`.
|
||||
"propagate-feature",
|
||||
# These are the features to check:
|
||||
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat,min-error-logs,min-warn-logs,min-info-logs,min-debug-logs,min-trace-logs,otlp,js-tracer,portable",
|
||||
"--features=std,op,dev,asm-keccak,jemalloc,jemalloc-prof,tracy-allocator,serde-bincode-compat,serde,test-utils,arbitrary,bench,alloy-compat,min-error-logs,min-warn-logs,min-info-logs,min-debug-logs,min-trace-logs,otlp,js-tracer,portable,keccak-cache-global",
|
||||
# Do not try to add a new section to `[features]` of `A` only because `B` exposes that feature. There are edge-cases where this is still needed, but we can add them manually.
|
||||
"--left-side-feature-missing=ignore",
|
||||
# Ignore the case that `A` it outside of the workspace. Otherwise it will report errors in external dependencies that we have no influence on.
|
||||
|
||||
7
.github/actionlint.yaml
vendored
Normal file
7
.github/actionlint.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
self-hosted-runner:
|
||||
labels:
|
||||
- depot-ubuntu-latest
|
||||
- depot-ubuntu-latest-2
|
||||
- depot-ubuntu-latest-4
|
||||
- depot-ubuntu-latest-8
|
||||
- depot-ubuntu-latest-16
|
||||
2
.github/assets/hive/run_simulator.sh
vendored
2
.github/assets/hive/run_simulator.sh
vendored
@@ -7,7 +7,7 @@ sim="${1}"
|
||||
limit="${2}"
|
||||
|
||||
run_hive() {
|
||||
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 8 --client reth 2>&1 | tee /tmp/log || true
|
||||
hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 16 --client reth 2>&1 | tee /tmp/log || true
|
||||
}
|
||||
|
||||
check_log() {
|
||||
|
||||
4
.github/workflows/bench.yml
vendored
4
.github/workflows/bench.yml
vendored
@@ -11,17 +11,19 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
BASELINE: base
|
||||
SEED: reth
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
name: bench
|
||||
jobs:
|
||||
codspeed:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: true
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
7
.github/workflows/book.yml
vendored
7
.github/workflows/book.yml
vendored
@@ -10,9 +10,12 @@ on:
|
||||
types: [opened, reopened, synchronize, closed]
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-8
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -33,6 +36,8 @@ jobs:
|
||||
- name: Install Rust nightly
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: Build docs
|
||||
run: cd docs/vocs && bash scripts/build-cargo-docs.sh
|
||||
|
||||
|
||||
4
.github/workflows/compact.yml
vendored
4
.github/workflows/compact.yml
vendored
@@ -13,11 +13,12 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
name: compact-codec
|
||||
jobs:
|
||||
compact-codec:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
bin:
|
||||
@@ -26,6 +27,7 @@ jobs:
|
||||
steps:
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
4
.github/workflows/e2e.yml
vendored
4
.github/workflows/e2e.yml
vendored
@@ -11,6 +11,7 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SEED: rustethereumethereumrust
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -19,13 +20,14 @@ concurrency:
|
||||
jobs:
|
||||
test:
|
||||
name: e2e-testsuite
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
|
||||
12
.github/workflows/hive.yml
vendored
12
.github/workflows/hive.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
prepare-hive:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-16
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout hive tests
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
- name: Restore hive assets cache
|
||||
id: cache-hive
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ./hive_assets
|
||||
key: hive-assets-${{ steps.hive-commit.outputs.hash }}-${{ hashFiles('.github/assets/hive/build_simulators.sh') }}
|
||||
@@ -67,7 +67,7 @@ jobs:
|
||||
chmod +x hive
|
||||
|
||||
- name: Upload hive assets
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: hive_assets
|
||||
path: ./hive_assets
|
||||
@@ -178,7 +178,7 @@ jobs:
|
||||
- prepare-reth
|
||||
- prepare-hive
|
||||
name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-16
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
@@ -187,13 +187,13 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download hive assets
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: hive_assets
|
||||
path: /tmp
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
|
||||
5
.github/workflows/integration.yml
vendored
5
.github/workflows/integration.yml
vendored
@@ -14,6 +14,7 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SEED: rustethereumethereumrust
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -23,7 +24,7 @@ jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.network }}
|
||||
if: github.event_name != 'schedule'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
@@ -37,6 +38,7 @@ jobs:
|
||||
- name: Install Geth
|
||||
run: .github/assets/install_geth.sh
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -74,6 +76,7 @@ jobs:
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
4
.github/workflows/kurtosis-op.yml
vendored
4
.github/workflows/kurtosis-op.yml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
name: run kurtosis
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
needs:
|
||||
- prepare-reth
|
||||
steps:
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
|
||||
4
.github/workflows/kurtosis.yml
vendored
4
.github/workflows/kurtosis.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
name: run kurtosis
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
needs:
|
||||
- prepare-reth
|
||||
steps:
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
|
||||
51
.github/workflows/lint.yml
vendored
51
.github/workflows/lint.yml
vendored
@@ -8,11 +8,12 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
clippy-binaries:
|
||||
name: clippy binaries / ${{ matrix.type }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -26,6 +27,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@clippy
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -40,7 +42,7 @@ jobs:
|
||||
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -48,6 +50,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -56,7 +59,7 @@ jobs:
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
wasm:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -65,6 +68,7 @@ jobs:
|
||||
with:
|
||||
target: wasm32-wasip1
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -75,7 +79,7 @@ jobs:
|
||||
.github/assets/check_wasm.sh
|
||||
|
||||
riscv:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -84,6 +88,7 @@ jobs:
|
||||
with:
|
||||
target: riscv32imac-unknown-none-elf
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -93,17 +98,18 @@ jobs:
|
||||
|
||||
crate-checks:
|
||||
name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }})
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
strategy:
|
||||
matrix:
|
||||
partition: [1, 2]
|
||||
total_partitions: [2]
|
||||
partition: [1, 2, 3]
|
||||
total_partitions: [3]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -111,7 +117,7 @@ jobs:
|
||||
|
||||
msrv:
|
||||
name: MSRV
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -124,6 +130,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.88" # MSRV
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -133,12 +140,13 @@ jobs:
|
||||
|
||||
docs:
|
||||
name: docs
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -150,7 +158,7 @@ jobs:
|
||||
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
@@ -158,17 +166,19 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Run fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
udeps:
|
||||
name: udeps
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -177,12 +187,13 @@ jobs:
|
||||
|
||||
book:
|
||||
name: book
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -232,31 +243,39 @@ jobs:
|
||||
- name: Ensure no arbitrary or proptest dependency on default build
|
||||
run: cargo tree --package reth -e=features,no-dev | grep -Eq "arbitrary|proptest" && exit 1 || exit 0
|
||||
|
||||
# Checks that selected rates can compile with power set of features
|
||||
# Checks that selected crates can compile with power set of features
|
||||
features:
|
||||
name: features
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: cargo install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
- run: make check-features
|
||||
- run: |
|
||||
cargo hack check \
|
||||
--package reth-codecs \
|
||||
--package reth-primitives-traits \
|
||||
--package reth-primitives \
|
||||
--feature-powerset \
|
||||
--depth 2
|
||||
env:
|
||||
RUSTFLAGS: -D warnings
|
||||
|
||||
# Check crates correctly propagate features
|
||||
feature-propagation:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: taiki-e/cache-cargo-install-action@v2
|
||||
with:
|
||||
|
||||
4
.github/workflows/prepare-reth.yml
vendored
4
.github/workflows/prepare-reth.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
prepare-reth:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: mkdir artifacts
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
|
||||
- name: Upload reth image
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: artifacts
|
||||
path: ./artifacts
|
||||
|
||||
2
.github/workflows/release-dist.yml
vendored
2
.github/workflows/release-dist.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Update Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@v6
|
||||
uses: dawidd6/action-homebrew-bump-formula@v7
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW }}
|
||||
no_fork: true
|
||||
|
||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -22,6 +22,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
DOCKER_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/reth
|
||||
DOCKER_OP_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/op-reth
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
dry-run:
|
||||
@@ -51,6 +52,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Verify crate version matches tag
|
||||
# Check that the Cargo version starts with the tag,
|
||||
# so that Cargo version 1.4.8 can be matched against both v1.4.8 and v1.4.8-rc.1
|
||||
@@ -104,6 +106,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: ${{ matrix.configs.target }}
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install cross main
|
||||
id: cross_main
|
||||
run: |
|
||||
@@ -141,14 +144,14 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
|
||||
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz
|
||||
|
||||
- name: Upload signature
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
|
||||
path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc
|
||||
@@ -170,7 +173,7 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
- name: Generate full changelog
|
||||
id: changelog
|
||||
run: |
|
||||
|
||||
6
.github/workflows/reproducible-build.yml
vendored
6
.github/workflows/reproducible-build.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
echo "Binaries SHA256 on ${{ matrix.machine }}: $(cat checksum.sha256)"
|
||||
|
||||
- name: Upload the hash
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: checksum-${{ matrix.machine }}
|
||||
path: |
|
||||
@@ -55,12 +55,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts from machine-1
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: checksum-machine-1
|
||||
path: machine-1/
|
||||
- name: Download artifacts from machine-2
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: checksum-machine-2
|
||||
path: machine-2/
|
||||
|
||||
4
.github/workflows/stage.yml
vendored
4
.github/workflows/stage.yml
vendored
@@ -12,6 +12,7 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
FROM_BLOCK: 0
|
||||
TO_BLOCK: 50000
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -22,7 +23,7 @@ jobs:
|
||||
name: stage-run-test
|
||||
# Only run stage commands test in merge groups
|
||||
if: github.event_name == 'merge_group'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -31,6 +32,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
4
.github/workflows/sync-era.yml
vendored
4
.github/workflows/sync-era.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -17,7 +18,7 @@ concurrency:
|
||||
jobs:
|
||||
sync:
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -41,6 +42,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
4
.github/workflows/sync.yml
vendored
4
.github/workflows/sync.yml
vendored
@@ -9,6 +9,7 @@ on:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -17,7 +18,7 @@ concurrency:
|
||||
jobs:
|
||||
sync:
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -41,6 +42,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
10
.github/workflows/unit.yml
vendored
10
.github/workflows/unit.yml
vendored
@@ -11,6 +11,7 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
SEED: rustethereumethereumrust
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@@ -19,7 +20,7 @@ concurrency:
|
||||
jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }})
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
@@ -46,6 +47,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -64,7 +66,7 @@ jobs:
|
||||
|
||||
state:
|
||||
name: Ethereum state tests
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -91,6 +93,7 @@ jobs:
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -98,7 +101,7 @@ jobs:
|
||||
|
||||
doc:
|
||||
name: doc tests
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: depot-ubuntu-latest
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
@@ -106,6 +109,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
2
.github/workflows/update-superchain.yml
vendored
2
.github/workflows/update-superchain.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
./fetch_superchain_config.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
commit-message: "chore: update superchain config"
|
||||
title: "chore: update superchain config"
|
||||
|
||||
9
.github/workflows/windows.yml
vendored
9
.github/workflows/windows.yml
vendored
@@ -9,9 +9,12 @@ on:
|
||||
branches: [main]
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
check-reth:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
@@ -21,6 +24,7 @@ jobs:
|
||||
with:
|
||||
target: x86_64-pc-windows-gnu
|
||||
- uses: taiki-e/install-action@cross
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
@@ -30,7 +34,7 @@ jobs:
|
||||
run: cargo check --target x86_64-pc-windows-gnu
|
||||
|
||||
check-op-reth:
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: depot-ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
|
||||
steps:
|
||||
@@ -40,6 +44,7 @@ jobs:
|
||||
with:
|
||||
target: x86_64-pc-windows-gnu
|
||||
- uses: taiki-e/install-action@cross
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
154
Cargo.lock
generated
154
Cargo.lock
generated
@@ -97,9 +97,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "alloy-chains"
|
||||
version = "0.2.21"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9ebac8ff9c2f07667e1803dc777304337e160ce5153335beb45e8ec0751808"
|
||||
checksum = "35d744058a9daa51a8cf22a3009607498fcf82d3cf4c5444dd8056cdf651f471"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
@@ -238,6 +238,18 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-eip7928"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "926b2c0d34e641cf8b17bf54ce50fda16715b9f68ad878fa6128bae410c6f890"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rlp",
|
||||
"borsh",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alloy-eips"
|
||||
version = "1.1.3"
|
||||
@@ -266,9 +278,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-evm"
|
||||
version = "0.24.2"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01be36ba6f5e6e62563b369e03ca529eac46aea50677f84655084b4750816574"
|
||||
checksum = "e6ccc4c702c840148af1ce784cc5c6ed9274a020ef32417c5b1dbeab8c317673"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -317,9 +329,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-json-abi"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f"
|
||||
checksum = "6bfca3dbbcb7498f0f60e67aff2ad6aff57032e22eb2fd03189854be11a22c03"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-sol-type-parser",
|
||||
@@ -383,9 +395,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-op-evm"
|
||||
version = "0.24.2"
|
||||
version = "0.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "231262d7e06000f3fb642d32d38ca75e09e78e04977c10be0a07a5ee2c869cfd"
|
||||
checksum = "0f640da852f93ddaa3b9a602b7ca41d80e0023f77a67b68aaaf511c32f1fe0ce"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -414,9 +426,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-primitives"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28"
|
||||
checksum = "5c850e6ccbd34b8a463a1e934ffc8fc00e1efc5e5489f2ad82d7797949f3bd4e"
|
||||
dependencies = [
|
||||
"alloy-rlp",
|
||||
"arbitrary",
|
||||
@@ -435,6 +447,7 @@ dependencies = [
|
||||
"proptest",
|
||||
"proptest-derive 0.6.0",
|
||||
"rand 0.9.2",
|
||||
"rapidhash",
|
||||
"ruint",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
@@ -769,9 +782,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-macro"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312"
|
||||
checksum = "b2218e3aeb3ee665d117fdf188db0d5acfdc3f7b7502c827421cb78f26a2aec0"
|
||||
dependencies = [
|
||||
"alloy-sol-macro-expander",
|
||||
"alloy-sol-macro-input",
|
||||
@@ -783,9 +796,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-macro-expander"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028"
|
||||
checksum = "b231cb8cc48e66dd1c6e11a1402f3ac86c3667cbc13a6969a0ac030ba7bb8c88"
|
||||
dependencies = [
|
||||
"alloy-sol-macro-input",
|
||||
"const-hex",
|
||||
@@ -801,9 +814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-macro-input"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c"
|
||||
checksum = "49a522d79929c1bf0152b07567a38f7eaed3ab149e53e7528afa78ff11994668"
|
||||
dependencies = [
|
||||
"const-hex",
|
||||
"dunce",
|
||||
@@ -817,9 +830,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-type-parser"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9"
|
||||
checksum = "0475c459859c8d9428af6ff3736614655a57efda8cc435a3b8b4796fa5ac1dd0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow",
|
||||
@@ -827,9 +840,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloy-sol-types"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c"
|
||||
checksum = "35287d9d821d5f26011bcd8d9101340898f761c9933cf50fca689bb7ed62fdeb"
|
||||
dependencies = [
|
||||
"alloy-json-abi",
|
||||
"alloy-primitives",
|
||||
@@ -1369,9 +1382,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "async-compression"
|
||||
version = "0.4.34"
|
||||
version = "0.4.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e86f6d3dc9dc4352edeea6b8e499e13e3f5dc3b964d7ca5fd411415a3498473"
|
||||
checksum = "98ec5f6c2f8bc326c994cb9e241cc257ddaba9afa8555a43cffbb5dd86efaa37"
|
||||
dependencies = [
|
||||
"compression-codecs",
|
||||
"compression-core",
|
||||
@@ -1529,9 +1542,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.8.0"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
|
||||
checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a"
|
||||
|
||||
[[package]]
|
||||
name = "bech32"
|
||||
@@ -2377,9 +2390,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "compression-codecs"
|
||||
version = "0.4.33"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "302266479cb963552d11bd042013a58ef1adc56768016c8b82b4199488f2d4ad"
|
||||
checksum = "b0f7ac3e5b97fdce45e8922fb05cae2c37f7bbd63d30dd94821dacfd8f3f2bf2"
|
||||
dependencies = [
|
||||
"brotli",
|
||||
"compression-core",
|
||||
@@ -3671,6 +3684,17 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-custom-rpc-middleware"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"jsonrpsee",
|
||||
"reth-ethereum",
|
||||
"tower",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "example-db-access"
|
||||
version = "0.0.0"
|
||||
@@ -3982,9 +4006,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.7"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2152dbcb980c05735e2a651d96011320a949eb31a0c8b38b72645ce97dec676"
|
||||
checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
@@ -5451,9 +5475,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libp2p-identity"
|
||||
version = "0.2.12"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774"
|
||||
checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3"
|
||||
dependencies = [
|
||||
"asn1_der",
|
||||
"bs58",
|
||||
@@ -6188,9 +6212,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy"
|
||||
version = "0.22.4"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3b13412d297c1f9341f678b763750b120a73ffe998fa54a94d6eda98449e7ca"
|
||||
checksum = "e9b8fee21003dd4f076563de9b9d26f8c97840157ef78593cd7f262c5ca99848"
|
||||
dependencies = [
|
||||
"op-alloy-consensus",
|
||||
"op-alloy-network",
|
||||
@@ -6201,9 +6225,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-consensus"
|
||||
version = "0.22.4"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "726da827358a547be9f1e37c2a756b9e3729cb0350f43408164794b370cad8ae"
|
||||
checksum = "736381a95471d23e267263cfcee9e1d96d30b9754a94a2819148f83379de8a86"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -6227,9 +6251,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc"
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-network"
|
||||
version = "0.22.4"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f63f27e65be273ec8fcb0b6af0fd850b550979465ab93423705ceb3dfddbd2ab"
|
||||
checksum = "4034183dca6bff6632e7c24c92e75ff5f0eabb58144edb4d8241814851334d47"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-network",
|
||||
@@ -6243,9 +6267,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-provider"
|
||||
version = "0.22.4"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a71456699aa256dc20119736422ad9a44da8b9585036117afb936778122093b9"
|
||||
checksum = "6753d90efbaa8ea8bcb89c1737408ca85fa60d7adb875049d3f382c063666f86"
|
||||
dependencies = [
|
||||
"alloy-network",
|
||||
"alloy-primitives",
|
||||
@@ -6258,9 +6282,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-rpc-jsonrpsee"
|
||||
version = "0.22.4"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ef9114426b16172254555aad34a8ea96c01895e40da92f5d12ea680a1baeaa7"
|
||||
checksum = "c1c820ef9c802ebc732281a940bfb6ac2345af4d9fff041cbb64b4b546676686"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"jsonrpsee",
|
||||
@@ -6268,9 +6292,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-rpc-types"
|
||||
version = "0.22.4"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562dd4462562c41f9fdc4d860858c40e14a25df7f983ae82047f15f08fce4d19"
|
||||
checksum = "ddd87c6b9e5b6eee8d6b76f41b04368dca0e9f38d83338e5b00e730c282098a4"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -6288,9 +6312,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "op-alloy-rpc-types-engine"
|
||||
version = "0.22.4"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8f24b8cb66e4b33e6c9e508bf46b8ecafc92eadd0b93fedd306c0accb477657"
|
||||
checksum = "77727699310a18cdeed32da3928c709e2704043b6584ed416397d5da65694efc"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eips",
|
||||
@@ -6304,6 +6328,7 @@ dependencies = [
|
||||
"ethereum_ssz_derive",
|
||||
"op-alloy-consensus",
|
||||
"serde",
|
||||
"sha2",
|
||||
"snap",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
@@ -7185,6 +7210,16 @@ dependencies = [
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rapidhash"
|
||||
version = "4.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8e65c75143ce5d47c55b510297eeb1182f3c739b6043c537670e9fc18612dae"
|
||||
dependencies = [
|
||||
"rand 0.9.2",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
@@ -7339,9 +7374,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.24"
|
||||
version = "0.12.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f"
|
||||
checksum = "b6eff9328d40131d43bd911d42d79eb6a47312002a4daefc9e37f17e74a7701a"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
@@ -8196,6 +8231,7 @@ name = "reth-engine-tree"
|
||||
version = "1.9.3"
|
||||
dependencies = [
|
||||
"alloy-consensus",
|
||||
"alloy-eip7928",
|
||||
"alloy-eips",
|
||||
"alloy-evm",
|
||||
"alloy-primitives",
|
||||
@@ -8614,6 +8650,7 @@ dependencies = [
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"metrics",
|
||||
"rayon",
|
||||
"reth-ethereum-forks",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-execution-errors",
|
||||
@@ -8919,6 +8956,7 @@ dependencies = [
|
||||
"pin-project",
|
||||
"rand 0.8.5",
|
||||
"rand 0.9.2",
|
||||
"rayon",
|
||||
"reth-chainspec",
|
||||
"reth-consensus",
|
||||
"reth-discv4",
|
||||
@@ -9220,6 +9258,7 @@ dependencies = [
|
||||
"alloy-sol-types",
|
||||
"eyre",
|
||||
"futures",
|
||||
"jsonrpsee-core",
|
||||
"rand 0.9.2",
|
||||
"reth-chainspec",
|
||||
"reth-db",
|
||||
@@ -9255,6 +9294,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"similar-asserts",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -10145,6 +10185,7 @@ dependencies = [
|
||||
"reth-db-api",
|
||||
"reth-engine-primitives",
|
||||
"reth-errors",
|
||||
"reth-ethereum-engine-primitives",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-evm",
|
||||
"reth-evm-ethereum",
|
||||
@@ -10207,6 +10248,9 @@ dependencies = [
|
||||
"reth-network-peers",
|
||||
"reth-rpc-eth-api",
|
||||
"reth-trie-common",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10271,6 +10315,7 @@ dependencies = [
|
||||
"reth-rpc-server-types",
|
||||
"reth-storage-api",
|
||||
"reth-tasks",
|
||||
"reth-tokio-util",
|
||||
"reth-tracing",
|
||||
"reth-transaction-pool",
|
||||
"serde",
|
||||
@@ -10862,6 +10907,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"proptest",
|
||||
"proptest-arbitrary-interop",
|
||||
"rand 0.9.2",
|
||||
"reth-ethereum-primitives",
|
||||
"reth-execution-errors",
|
||||
"reth-metrics",
|
||||
@@ -11164,9 +11210,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "revm-inspectors"
|
||||
version = "0.33.1"
|
||||
version = "0.33.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c93974333e7acc4b2dc024b10def99707f7375a4d53db7a7f8351722d25673f"
|
||||
checksum = "01def7351cd9af844150b8e88980bcd11304f33ce23c3d7c25f2a8dab87c1345"
|
||||
dependencies = [
|
||||
"alloy-primitives",
|
||||
"alloy-rpc-types-eth",
|
||||
@@ -12048,9 +12094,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
@@ -12305,9 +12351,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn-solidity"
|
||||
version = "1.4.1"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3"
|
||||
checksum = "60ceeb7c95a4536de0c0e1649bd98d1a72a4bb9590b1f3e45a8a0bfdb7c188c0"
|
||||
dependencies = [
|
||||
"paste",
|
||||
"proc-macro2",
|
||||
@@ -12872,9 +12918,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.7"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456"
|
||||
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||
dependencies = [
|
||||
"async-compression",
|
||||
"base64 0.22.1",
|
||||
|
||||
26
Cargo.toml
26
Cargo.toml
@@ -153,6 +153,7 @@ members = [
|
||||
"examples/custom-node-components/",
|
||||
"examples/custom-payload-builder/",
|
||||
"examples/custom-rlpx-subprotocol",
|
||||
"examples/custom-rpc-middleware",
|
||||
"examples/custom-node",
|
||||
"examples/db-access",
|
||||
"examples/engine-api-access",
|
||||
@@ -375,11 +376,11 @@ reth-era-utils = { path = "crates/era-utils" }
|
||||
reth-errors = { path = "crates/errors" }
|
||||
reth-eth-wire = { path = "crates/net/eth-wire" }
|
||||
reth-eth-wire-types = { path = "crates/net/eth-wire-types" }
|
||||
reth-ethereum-payload-builder = { path = "crates/ethereum/payload" }
|
||||
reth-ethereum-cli = { path = "crates/ethereum/cli", default-features = false }
|
||||
reth-ethereum-consensus = { path = "crates/ethereum/consensus", default-features = false }
|
||||
reth-ethereum-engine-primitives = { path = "crates/ethereum/engine-primitives", default-features = false }
|
||||
reth-ethereum-forks = { path = "crates/ethereum/hardforks", default-features = false }
|
||||
reth-ethereum-payload-builder = { path = "crates/ethereum/payload" }
|
||||
reth-ethereum-primitives = { path = "crates/ethereum/primitives", default-features = false }
|
||||
reth-ethereum = { path = "crates/ethereum/reth" }
|
||||
reth-etl = { path = "crates/etl" }
|
||||
@@ -480,17 +481,18 @@ revm-primitives = { version = "21.0.2", default-features = false }
|
||||
revm-interpreter = { version = "31.1.0", default-features = false }
|
||||
revm-database-interface = { version = "8.0.5", default-features = false }
|
||||
op-revm = { version = "14.1.0", default-features = false }
|
||||
revm-inspectors = "0.33.1"
|
||||
revm-inspectors = "0.33.2"
|
||||
|
||||
# eth
|
||||
alloy-chains = { version = "0.2.5", default-features = false }
|
||||
alloy-dyn-abi = "1.4.1"
|
||||
alloy-eip2124 = { version = "0.2.0", default-features = false }
|
||||
alloy-evm = { version = "0.24.1", default-features = false }
|
||||
alloy-primitives = { version = "1.4.1", default-features = false, features = ["map-foldhash"] }
|
||||
alloy-eip7928 = { version = "0.1.0" }
|
||||
alloy-evm = { version = "0.25.1", 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.4.1"
|
||||
alloy-sol-types = { version = "1.4.1", default-features = false }
|
||||
alloy-sol-macro = "1.5.0"
|
||||
alloy-sol-types = { version = "1.5.0", default-features = false }
|
||||
alloy-trie = { version = "0.9.1", default-features = false }
|
||||
|
||||
alloy-hardforks = "0.4.5"
|
||||
@@ -524,13 +526,13 @@ alloy-transport-ipc = { version = "1.1.3", default-features = false }
|
||||
alloy-transport-ws = { version = "1.1.3", default-features = false }
|
||||
|
||||
# op
|
||||
alloy-op-evm = { version = "0.24.1", default-features = false }
|
||||
alloy-op-evm = { version = "0.25.0", default-features = false }
|
||||
alloy-op-hardforks = "0.4.4"
|
||||
op-alloy-rpc-types = { version = "0.22.4", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.22.4", default-features = false }
|
||||
op-alloy-network = { version = "0.22.4", default-features = false }
|
||||
op-alloy-consensus = { version = "0.22.4", default-features = false }
|
||||
op-alloy-rpc-jsonrpsee = { version = "0.22.4", default-features = false }
|
||||
op-alloy-rpc-types = { version = "0.23.1", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.23.1", default-features = false }
|
||||
op-alloy-network = { version = "0.23.1", default-features = false }
|
||||
op-alloy-consensus = { version = "0.23.1", default-features = false }
|
||||
op-alloy-rpc-jsonrpsee = { version = "0.23.1", default-features = false }
|
||||
op-alloy-flz = { version = "0.13.1", default-features = false }
|
||||
|
||||
# misc
|
||||
|
||||
7
Makefile
7
Makefile
@@ -521,10 +521,3 @@ pr:
|
||||
make update-book-cli && \
|
||||
cargo docs --document-private-items && \
|
||||
make test
|
||||
|
||||
check-features:
|
||||
cargo hack check \
|
||||
--package reth-codecs \
|
||||
--package reth-primitives-traits \
|
||||
--package reth-primitives \
|
||||
--feature-powerset
|
||||
|
||||
@@ -506,8 +506,8 @@ async fn run_warmup_phase(
|
||||
// Build additional args with conditional --debug.startup-sync-state-idle flag
|
||||
let additional_args = args.build_additional_args("warmup", args.baseline_args.as_ref());
|
||||
|
||||
// Start reth node for warmup
|
||||
let mut node_process =
|
||||
// Start reth node for warmup (command is not stored for warmup phase)
|
||||
let (mut node_process, _warmup_command) =
|
||||
node_manager.start_node(&binary_path, warmup_ref, "warmup", &additional_args).await?;
|
||||
|
||||
// Wait for node to be ready and get its current tip
|
||||
@@ -607,8 +607,8 @@ async fn run_benchmark_workflow(
|
||||
// Build additional args with conditional --debug.startup-sync-state-idle flag
|
||||
let additional_args = args.build_additional_args(ref_type, base_args_str);
|
||||
|
||||
// Start reth node
|
||||
let mut node_process =
|
||||
// Start reth node and capture the command for reporting
|
||||
let (mut node_process, reth_command) =
|
||||
node_manager.start_node(&binary_path, git_ref, ref_type, &additional_args).await?;
|
||||
|
||||
// Wait for node to be ready and get its current tip (wherever it is)
|
||||
@@ -645,8 +645,9 @@ async fn run_benchmark_workflow(
|
||||
// Store results for comparison
|
||||
comparison_generator.add_ref_results(ref_type, &output_dir)?;
|
||||
|
||||
// Set the benchmark run timestamps
|
||||
// Set the benchmark run timestamps and reth command
|
||||
comparison_generator.set_ref_timestamps(ref_type, benchmark_start, benchmark_end)?;
|
||||
comparison_generator.set_ref_command(ref_type, reth_command)?;
|
||||
|
||||
info!("Completed {} reference benchmark", ref_type);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ pub(crate) struct ComparisonGenerator {
|
||||
feature_ref_name: String,
|
||||
baseline_results: Option<BenchmarkResults>,
|
||||
feature_results: Option<BenchmarkResults>,
|
||||
baseline_command: Option<String>,
|
||||
feature_command: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents the results from a single benchmark run
|
||||
@@ -57,7 +59,6 @@ pub(crate) struct TotalGasRow {
|
||||
/// - `mean_new_payload_latency_ms`: arithmetic mean latency across blocks.
|
||||
/// - `median_new_payload_latency_ms`: p50 latency across blocks.
|
||||
/// - `p90_new_payload_latency_ms` / `p99_new_payload_latency_ms`: tail latencies across blocks.
|
||||
/// - `std_dev_new_payload_latency_ms`: standard deviation of latency across blocks.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub(crate) struct BenchmarkSummary {
|
||||
pub total_blocks: u64,
|
||||
@@ -67,7 +68,6 @@ pub(crate) struct BenchmarkSummary {
|
||||
pub median_new_payload_latency_ms: f64,
|
||||
pub p90_new_payload_latency_ms: f64,
|
||||
pub p99_new_payload_latency_ms: f64,
|
||||
pub std_dev_new_payload_latency_ms: f64,
|
||||
pub gas_per_second: f64,
|
||||
pub blocks_per_second: f64,
|
||||
pub min_block_number: u64,
|
||||
@@ -91,6 +91,7 @@ pub(crate) struct RefInfo {
|
||||
pub summary: BenchmarkSummary,
|
||||
pub start_timestamp: Option<DateTime<Utc>>,
|
||||
pub end_timestamp: Option<DateTime<Utc>>,
|
||||
pub reth_command: Option<String>,
|
||||
}
|
||||
|
||||
/// Summary of the comparison between references.
|
||||
@@ -98,7 +99,6 @@ pub(crate) struct RefInfo {
|
||||
/// Percent deltas are `(feature - baseline) / baseline * 100`:
|
||||
/// - `new_payload_latency_p50_change_percent` / p90 / p99: percent changes of the respective
|
||||
/// per-block percentiles.
|
||||
/// - `std_dev_change_percent`: percent change in standard deviation of newPayload latency.
|
||||
/// - `per_block_latency_change_mean_percent` / `per_block_latency_change_median_percent` are the
|
||||
/// mean and median of per-block percent deltas (feature vs baseline), capturing block-level
|
||||
/// drift.
|
||||
@@ -117,7 +117,6 @@ pub(crate) struct ComparisonSummary {
|
||||
pub new_payload_latency_p50_change_percent: f64,
|
||||
pub new_payload_latency_p90_change_percent: f64,
|
||||
pub new_payload_latency_p99_change_percent: f64,
|
||||
pub std_dev_change_percent: f64,
|
||||
pub gas_per_second_change_percent: f64,
|
||||
pub blocks_per_second_change_percent: f64,
|
||||
}
|
||||
@@ -146,6 +145,8 @@ impl ComparisonGenerator {
|
||||
feature_ref_name: args.feature_ref.clone(),
|
||||
baseline_results: None,
|
||||
feature_results: None,
|
||||
baseline_command: None,
|
||||
feature_command: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,6 +211,21 @@ impl ComparisonGenerator {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the reth command for a reference
|
||||
pub(crate) fn set_ref_command(&mut self, ref_type: &str, command: String) -> Result<()> {
|
||||
match ref_type {
|
||||
"baseline" => {
|
||||
self.baseline_command = Some(command);
|
||||
}
|
||||
"feature" => {
|
||||
self.feature_command = Some(command);
|
||||
}
|
||||
_ => return Err(eyre!("Unknown reference type: {}", ref_type)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate the final comparison report
|
||||
pub(crate) async fn generate_comparison_report(&self) -> Result<()> {
|
||||
info!("Generating comparison report...");
|
||||
@@ -234,12 +250,14 @@ impl ComparisonGenerator {
|
||||
summary: baseline.summary.clone(),
|
||||
start_timestamp: baseline.start_timestamp,
|
||||
end_timestamp: baseline.end_timestamp,
|
||||
reth_command: self.baseline_command.clone(),
|
||||
},
|
||||
feature: RefInfo {
|
||||
ref_name: feature.ref_name.clone(),
|
||||
summary: feature.summary.clone(),
|
||||
start_timestamp: feature.start_timestamp,
|
||||
end_timestamp: feature.end_timestamp,
|
||||
reth_command: self.feature_command.clone(),
|
||||
},
|
||||
comparison_summary,
|
||||
per_block_comparisons,
|
||||
@@ -339,9 +357,6 @@ impl ComparisonGenerator {
|
||||
let mean_new_payload_latency_ms: f64 =
|
||||
latencies_ms.iter().sum::<f64>() / total_blocks as f64;
|
||||
|
||||
let std_dev_new_payload_latency_ms =
|
||||
calculate_std_dev(&latencies_ms, mean_new_payload_latency_ms);
|
||||
|
||||
let mut sorted_latencies_ms = latencies_ms;
|
||||
sorted_latencies_ms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
|
||||
let median_new_payload_latency_ms = percentile(&sorted_latencies_ms, 0.5);
|
||||
@@ -372,7 +387,6 @@ impl ComparisonGenerator {
|
||||
median_new_payload_latency_ms,
|
||||
p90_new_payload_latency_ms,
|
||||
p99_new_payload_latency_ms,
|
||||
std_dev_new_payload_latency_ms,
|
||||
gas_per_second,
|
||||
blocks_per_second,
|
||||
min_block_number,
|
||||
@@ -440,10 +454,6 @@ impl ComparisonGenerator {
|
||||
baseline.p99_new_payload_latency_ms,
|
||||
feature.p99_new_payload_latency_ms,
|
||||
),
|
||||
std_dev_change_percent: calc_percent_change(
|
||||
baseline.std_dev_new_payload_latency_ms,
|
||||
feature.std_dev_new_payload_latency_ms,
|
||||
),
|
||||
gas_per_second_change_percent: calc_percent_change(
|
||||
baseline.gas_per_second,
|
||||
feature.gas_per_second,
|
||||
@@ -574,7 +584,6 @@ impl ComparisonGenerator {
|
||||
" NewPayload Latency p99: {:+.2}%",
|
||||
summary.new_payload_latency_p99_change_percent
|
||||
);
|
||||
println!(" NewPayload Latency std dev: {:+.2}%", summary.std_dev_change_percent);
|
||||
println!(
|
||||
" Gas/Second: {:+.2}%",
|
||||
summary.gas_per_second_change_percent
|
||||
@@ -597,12 +606,11 @@ impl ComparisonGenerator {
|
||||
);
|
||||
println!(" NewPayload latency (ms):");
|
||||
println!(
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}, std dev: {:.2}",
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
|
||||
baseline.mean_new_payload_latency_ms,
|
||||
baseline.median_new_payload_latency_ms,
|
||||
baseline.p90_new_payload_latency_ms,
|
||||
baseline.p99_new_payload_latency_ms,
|
||||
baseline.std_dev_new_payload_latency_ms
|
||||
baseline.p99_new_payload_latency_ms
|
||||
);
|
||||
if let (Some(start), Some(end)) =
|
||||
(&report.baseline.start_timestamp, &report.baseline.end_timestamp)
|
||||
@@ -613,6 +621,9 @@ impl ComparisonGenerator {
|
||||
end.format("%Y-%m-%d %H:%M:%S UTC")
|
||||
);
|
||||
}
|
||||
if let Some(ref cmd) = report.baseline.reth_command {
|
||||
println!(" Command: {}", cmd);
|
||||
}
|
||||
println!();
|
||||
|
||||
println!("Feature Summary:");
|
||||
@@ -627,12 +638,11 @@ impl ComparisonGenerator {
|
||||
);
|
||||
println!(" NewPayload latency (ms):");
|
||||
println!(
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}, std dev: {:.2}",
|
||||
" mean: {:.2}, p50: {:.2}, p90: {:.2}, p99: {:.2}",
|
||||
feature.mean_new_payload_latency_ms,
|
||||
feature.median_new_payload_latency_ms,
|
||||
feature.p90_new_payload_latency_ms,
|
||||
feature.p99_new_payload_latency_ms,
|
||||
feature.std_dev_new_payload_latency_ms
|
||||
feature.p99_new_payload_latency_ms
|
||||
);
|
||||
if let (Some(start), Some(end)) =
|
||||
(&report.feature.start_timestamp, &report.feature.end_timestamp)
|
||||
@@ -643,6 +653,9 @@ impl ComparisonGenerator {
|
||||
end.format("%Y-%m-%d %H:%M:%S UTC")
|
||||
);
|
||||
}
|
||||
if let Some(ref cmd) = report.feature.reth_command {
|
||||
println!(" Command: {}", cmd);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,19 +240,24 @@ impl NodeManager {
|
||||
}
|
||||
|
||||
/// Start a reth node using the specified binary path and return the process handle
|
||||
/// along with the formatted reth command string for reporting.
|
||||
pub(crate) async fn start_node(
|
||||
&mut self,
|
||||
binary_path: &std::path::Path,
|
||||
_git_ref: &str,
|
||||
ref_type: &str,
|
||||
additional_args: &[String],
|
||||
) -> Result<tokio::process::Child> {
|
||||
) -> Result<(tokio::process::Child, String)> {
|
||||
// Store the binary path for later use (e.g., in unwind_to_block)
|
||||
self.binary_path = Some(binary_path.to_path_buf());
|
||||
|
||||
let binary_path_str = binary_path.to_string_lossy();
|
||||
let (reth_args, _) = self.build_reth_args(&binary_path_str, additional_args, ref_type);
|
||||
|
||||
// Format the reth command string for reporting
|
||||
let reth_command = shlex::try_join(reth_args.iter().map(|s| s.as_str()))
|
||||
.wrap_err("Failed to format reth command string")?;
|
||||
|
||||
// Log additional arguments if any
|
||||
if !self.additional_reth_args.is_empty() {
|
||||
info!("Using common additional reth arguments: {:?}", self.additional_reth_args);
|
||||
@@ -346,7 +351,7 @@ impl NodeManager {
|
||||
// Give the node a moment to start up
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
|
||||
Ok(child)
|
||||
Ok((child, reth_command))
|
||||
}
|
||||
|
||||
/// Wait for the node to be ready and return its current tip
|
||||
|
||||
@@ -80,7 +80,7 @@ RUSTFLAGS="-C target-cpu=native" cargo build --profile profiling --no-default-fe
|
||||
### Run the Benchmark:
|
||||
First, start the reth node. Here is an example that runs `reth` compiled with the `profiling` profile, runs `samply`, and configures `reth` to run with metrics enabled:
|
||||
```bash
|
||||
samply record -p 3001 target/profiling/reth node --metrics localhost:9001 --authrpc.jwt-secret <jwt_file_path>
|
||||
samply record -p 3001 target/profiling/reth node --metrics localhost:9001 --authrpc.jwtsecret <jwt_file_path>
|
||||
```
|
||||
|
||||
```bash
|
||||
@@ -143,5 +143,5 @@ To reproduce the benchmark, first re-set the node to the block that the benchmar
|
||||
- **RPC Configuration**: The RPC endpoints should be accessible and configured correctly, specifically the RPC endpoint must support `eth_getBlockByNumber` and support fetching full transactions. The benchmark will make one RPC query per block as fast as possible, so ensure the RPC endpoint does not rate limit or block requests after a certain volume.
|
||||
- **Reproducibility**: Ensure that the node is at the same state before attempting to retry a benchmark. The `new-payload-fcu` command specifically will commit to the database, so the node must be rolled back using `reth stage unwind` to reproducibly retry benchmarks.
|
||||
- **Profiling tools**: If you are collecting CPU profiles, tools like [`samply`](https://github.com/mstange/samply) and [`perf`](https://perf.wiki.kernel.org/index.php/Main_Page) can be useful for analyzing node performance.
|
||||
- **Benchmark Data**: `reth-bench` additionally contains a `--benchmark.output` flag, which will output gas used benchmarks across the benchmark range in CSV format. This may be useful for further data analysis.
|
||||
- **Benchmark Data**: `reth-bench` additionally contains a `--output` flag, which will output gas used benchmarks across the benchmark range in CSV format. This may be useful for further data analysis.
|
||||
- **Platform Information**: To ensure accurate and reproducible benchmarking, document the platform details, including hardware specifications, OS version, and any other relevant information before publishing any benchmarks.
|
||||
|
||||
@@ -81,7 +81,7 @@ backon.workspace = true
|
||||
tempfile.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer"]
|
||||
default = ["jemalloc", "otlp", "reth-revm/portable", "js-tracer", "keccak-cache-global"]
|
||||
|
||||
otlp = [
|
||||
"reth-ethereum-cli/otlp",
|
||||
@@ -102,7 +102,9 @@ asm-keccak = [
|
||||
"reth-ethereum-cli/asm-keccak",
|
||||
"reth-node-ethereum/asm-keccak",
|
||||
]
|
||||
|
||||
keccak-cache-global = [
|
||||
"reth-node-ethereum/keccak-cache-global",
|
||||
]
|
||||
jemalloc = [
|
||||
"reth-cli-util/jemalloc",
|
||||
"reth-node-core/jemalloc",
|
||||
|
||||
@@ -18,7 +18,7 @@ use reth_primitives_traits::{
|
||||
};
|
||||
use reth_storage_api::StateProviderBox;
|
||||
use reth_trie::{updates::TrieUpdatesSorted, HashedPostStateSorted, TrieInputSorted};
|
||||
use std::{collections::BTreeMap, sync::Arc, time::Instant};
|
||||
use std::{collections::BTreeMap, ops::Deref, sync::Arc, time::Instant};
|
||||
use tokio::sync::{broadcast, watch};
|
||||
|
||||
/// Size of the broadcast channel used to notify canonical state events.
|
||||
@@ -634,6 +634,8 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
/// We assume that the `Receipts` in the executed block `ExecutionOutcome`
|
||||
/// has only one element corresponding to the executed block associated to
|
||||
/// the state.
|
||||
///
|
||||
/// This clones the vector of receipts. To avoid it, use [`Self::executed_block_receipts_ref`].
|
||||
pub fn executed_block_receipts(&self) -> Vec<N::Receipt> {
|
||||
let receipts = self.receipts();
|
||||
|
||||
@@ -646,6 +648,22 @@ impl<N: NodePrimitives> BlockState<N> {
|
||||
receipts.first().cloned().unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns a slice of `Receipt` of executed block that determines the state.
|
||||
/// We assume that the `Receipts` in the executed block `ExecutionOutcome`
|
||||
/// has only one element corresponding to the executed block associated to
|
||||
/// the state.
|
||||
pub fn executed_block_receipts_ref(&self) -> &[N::Receipt] {
|
||||
let receipts = self.receipts();
|
||||
|
||||
debug_assert!(
|
||||
receipts.len() <= 1,
|
||||
"Expected at most one block's worth of receipts, found {}",
|
||||
receipts.len()
|
||||
);
|
||||
|
||||
receipts.first().map(|receipts| receipts.deref()).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Returns a vector of __parent__ `BlockStates`.
|
||||
///
|
||||
/// The block state order in the output vector is newest to oldest (highest to lowest):
|
||||
|
||||
@@ -23,7 +23,7 @@ use reth_node_core::{
|
||||
dirs::{ChainPath, DataDirPath},
|
||||
};
|
||||
use reth_provider::{
|
||||
providers::{BlockchainProvider, NodeTypesForProvider, StaticFileProvider},
|
||||
providers::{BlockchainProvider, NodeTypesForProvider, RocksDBProvider, StaticFileProvider},
|
||||
ProviderFactory, StaticFileProviderFactory,
|
||||
};
|
||||
use reth_stages::{sets::DefaultStages, Pipeline, PipelineTarget};
|
||||
@@ -75,10 +75,12 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain());
|
||||
let db_path = data_dir.db();
|
||||
let sf_path = data_dir.static_files();
|
||||
let rocksdb_path = data_dir.rocksdb();
|
||||
|
||||
if access.is_read_write() {
|
||||
reth_fs_util::create_dir_all(&db_path)?;
|
||||
reth_fs_util::create_dir_all(&sf_path)?;
|
||||
reth_fs_util::create_dir_all(&rocksdb_path)?;
|
||||
}
|
||||
|
||||
let config_path = self.config.clone().unwrap_or_else(|| data_dir.config());
|
||||
@@ -108,8 +110,14 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
StaticFileProvider::read_only(sf_path, false)?,
|
||||
),
|
||||
};
|
||||
// TransactionDB only support read-write mode
|
||||
let rocksdb_provider = RocksDBProvider::builder(data_dir.rocksdb())
|
||||
.with_default_tables()
|
||||
.with_database_log_level(self.db.log_level)
|
||||
.build()?;
|
||||
|
||||
let provider_factory = self.create_provider_factory(&config, db, sfp, access)?;
|
||||
let provider_factory =
|
||||
self.create_provider_factory(&config, db, sfp, rocksdb_provider, access)?;
|
||||
if access.is_read_write() {
|
||||
debug!(target: "reth::cli", chain=%self.chain.chain(), genesis=?self.chain.genesis_hash(), "Initializing genesis");
|
||||
init_genesis_with_settings(&provider_factory, self.static_files.to_settings())?;
|
||||
@@ -128,6 +136,7 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
config: &Config,
|
||||
db: Arc<DatabaseEnv>,
|
||||
static_file_provider: StaticFileProvider<N::Primitives>,
|
||||
rocksdb_provider: RocksDBProvider,
|
||||
access: AccessRights,
|
||||
) -> eyre::Result<ProviderFactory<NodeTypesWithDBAdapter<N, Arc<DatabaseEnv>>>>
|
||||
where
|
||||
@@ -138,6 +147,7 @@ impl<C: ChainSpecParser> EnvironmentArgs<C> {
|
||||
db,
|
||||
self.chain.clone(),
|
||||
static_file_provider,
|
||||
rocksdb_provider,
|
||||
)?
|
||||
.with_prune_modes(prune_modes.clone());
|
||||
|
||||
|
||||
@@ -8,12 +8,17 @@ use reth_db::{
|
||||
RawDupSort,
|
||||
};
|
||||
use reth_db_api::{
|
||||
table::{Decompress, DupSort, Table},
|
||||
tables, RawKey, RawTable, Receipts, TableViewer, Transactions,
|
||||
cursor::{DbCursorRO, DbDupCursorRO},
|
||||
database::Database,
|
||||
table::{Compress, Decompress, DupSort, Table},
|
||||
tables,
|
||||
transaction::DbTx,
|
||||
RawKey, RawTable, Receipts, TableViewer, Transactions,
|
||||
};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_api::{HeaderTy, ReceiptTy, TxTy};
|
||||
use reth_node_builder::NodeTypesWithDB;
|
||||
use reth_primitives_traits::ValueWithSubKey;
|
||||
use reth_provider::{providers::ProviderNodeTypes, StaticFileProviderFactory};
|
||||
use reth_static_file_types::StaticFileSegment;
|
||||
use tracing::error;
|
||||
@@ -39,6 +44,14 @@ enum Subcommand {
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
subkey: Option<String>,
|
||||
|
||||
/// Optional end key for range query (exclusive upper bound)
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
end_key: Option<String>,
|
||||
|
||||
/// Optional end subkey for range query (exclusive upper bound)
|
||||
#[arg(value_parser = maybe_json_value_parser)]
|
||||
end_subkey: Option<String>,
|
||||
|
||||
/// Output bytes instead of human-readable decoded value
|
||||
#[arg(long)]
|
||||
raw: bool,
|
||||
@@ -61,8 +74,8 @@ impl Command {
|
||||
/// Execute `db get` command
|
||||
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
match self.subcommand {
|
||||
Subcommand::Mdbx { table, key, subkey, raw } => {
|
||||
table.view(&GetValueViewer { tool, key, subkey, raw })?
|
||||
Subcommand::Mdbx { table, key, subkey, end_key, end_subkey, raw } => {
|
||||
table.view(&GetValueViewer { tool, key, subkey, end_key, end_subkey, raw })?
|
||||
}
|
||||
Subcommand::StaticFile { segment, key, raw } => {
|
||||
let (key, mask): (u64, _) = match segment {
|
||||
@@ -154,6 +167,8 @@ struct GetValueViewer<'a, N: NodeTypesWithDB> {
|
||||
tool: &'a DbTool<N>,
|
||||
key: String,
|
||||
subkey: Option<String>,
|
||||
end_key: Option<String>,
|
||||
end_subkey: Option<String>,
|
||||
raw: bool,
|
||||
}
|
||||
|
||||
@@ -163,53 +178,158 @@ impl<N: ProviderNodeTypes> TableViewer<()> for GetValueViewer<'_, N> {
|
||||
fn view<T: Table>(&self) -> Result<(), Self::Error> {
|
||||
let key = table_key::<T>(&self.key)?;
|
||||
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get::<RawTable<T>>(RawKey::from(key))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
|
||||
};
|
||||
// A non-dupsort table cannot have subkeys. The `subkey` arg becomes the `end_key`. First we
|
||||
// check that `end_key` and `end_subkey` weren't previously given, as that wouldn't be
|
||||
// valid.
|
||||
if self.end_key.is_some() || self.end_subkey.is_some() {
|
||||
return Err(eyre::eyre!("Only END_KEY can be given for non-DUPSORT tables"));
|
||||
}
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
}
|
||||
};
|
||||
let end_key = self.subkey.clone();
|
||||
|
||||
// Check if we're doing a range query
|
||||
if let Some(ref end_key_str) = end_key {
|
||||
let end_key = table_key::<T>(end_key_str)?;
|
||||
|
||||
// Use walk_range to iterate over the range
|
||||
self.tool.provider_factory.db_ref().view(|tx| {
|
||||
let mut cursor = tx.cursor_read::<T>()?;
|
||||
let walker = cursor.walk_range(key..end_key)?;
|
||||
|
||||
for result in walker {
|
||||
let (k, v) = result?;
|
||||
let json_val = if self.raw {
|
||||
let raw_key = RawKey::from(k);
|
||||
serde_json::json!({
|
||||
"key": hex::encode_prefixed(raw_key.raw_key()),
|
||||
"val": hex::encode_prefixed(v.compress().as_ref()),
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"key": &k,
|
||||
"val": &v,
|
||||
})
|
||||
};
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&json_val)?);
|
||||
}
|
||||
|
||||
Ok::<_, eyre::Report>(())
|
||||
})??;
|
||||
} else {
|
||||
// Single key lookup
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get::<RawTable<T>>(RawKey::from(key))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
self.tool.get::<T>(key)?.as_ref().map(serde_json::to_string_pretty).transpose()?
|
||||
};
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table key.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error> {
|
||||
fn view_dupsort<T: DupSort>(&self) -> Result<(), Self::Error>
|
||||
where
|
||||
T::Value: reth_primitives_traits::ValueWithSubKey<SubKey = T::SubKey>,
|
||||
{
|
||||
// get a key for given table
|
||||
let key = table_key::<T>(&self.key)?;
|
||||
|
||||
// process dupsort table
|
||||
let subkey = table_subkey::<T>(self.subkey.as_deref())?;
|
||||
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
self.tool
|
||||
.get_dup::<T>(key, subkey)?
|
||||
// Check if we're doing a range query
|
||||
if let Some(ref end_key_str) = self.end_key {
|
||||
let end_key = table_key::<T>(end_key_str)?;
|
||||
let start_subkey = table_subkey::<T>(Some(
|
||||
self.subkey.as_ref().expect("must have been given if end_key is given").as_str(),
|
||||
))?;
|
||||
let end_subkey_parsed = self
|
||||
.end_subkey
|
||||
.as_ref()
|
||||
.map(serde_json::to_string_pretty)
|
||||
.transpose()?
|
||||
};
|
||||
.map(|s| table_subkey::<T>(Some(s.as_str())))
|
||||
.transpose()?;
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table subkey.");
|
||||
}
|
||||
};
|
||||
self.tool.provider_factory.db_ref().view(|tx| {
|
||||
let mut cursor = tx.cursor_dup_read::<T>()?;
|
||||
|
||||
// Seek to the starting key. If there is actually a key at the starting key then
|
||||
// seek to the subkey within it.
|
||||
if let Some((decoded_key, _)) = cursor.seek(key.clone())? &&
|
||||
decoded_key == key
|
||||
{
|
||||
cursor.seek_by_key_subkey(key.clone(), start_subkey.clone())?;
|
||||
}
|
||||
|
||||
// Get the current position to start iteration
|
||||
let mut current = cursor.current()?;
|
||||
|
||||
while let Some((decoded_key, decoded_value)) = current {
|
||||
// Extract the subkey using the ValueWithSubKey trait
|
||||
let decoded_subkey = decoded_value.get_subkey();
|
||||
|
||||
// Check if we've reached the end (exclusive)
|
||||
if (&decoded_key, Some(&decoded_subkey)) >=
|
||||
(&end_key, end_subkey_parsed.as_ref())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Output the entry with both key and subkey
|
||||
let json_val = if self.raw {
|
||||
let raw_key = RawKey::from(decoded_key.clone());
|
||||
serde_json::json!({
|
||||
"key": hex::encode_prefixed(raw_key.raw_key()),
|
||||
"val": hex::encode_prefixed(decoded_value.compress().as_ref()),
|
||||
})
|
||||
} else {
|
||||
serde_json::json!({
|
||||
"key": &decoded_key,
|
||||
"val": &decoded_value,
|
||||
})
|
||||
};
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&json_val)?);
|
||||
|
||||
// Move to next entry
|
||||
current = cursor.next()?;
|
||||
}
|
||||
|
||||
Ok::<_, eyre::Report>(())
|
||||
})??;
|
||||
} else {
|
||||
// Single key/subkey lookup
|
||||
let subkey = table_subkey::<T>(self.subkey.as_deref())?;
|
||||
|
||||
let content = if self.raw {
|
||||
self.tool
|
||||
.get_dup::<RawDupSort<T>>(RawKey::from(key), RawKey::from(subkey))?
|
||||
.map(|content| hex::encode_prefixed(content.raw_value()))
|
||||
} else {
|
||||
self.tool
|
||||
.get_dup::<T>(key, subkey)?
|
||||
.as_ref()
|
||||
.map(serde_json::to_string_pretty)
|
||||
.transpose()?
|
||||
};
|
||||
|
||||
match content {
|
||||
Some(content) => {
|
||||
println!("{content}");
|
||||
}
|
||||
None => {
|
||||
error!(target: "reth::cli", "No content for the given table subkey.");
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs};
|
||||
use clap::{Parser, Subcommand};
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_cli_runner::CliContext;
|
||||
use reth_db::version::{get_db_version, DatabaseVersionError, DB_VERSION};
|
||||
use reth_db_common::DbTool;
|
||||
use std::{
|
||||
@@ -79,7 +80,10 @@ macro_rules! db_exec {
|
||||
|
||||
impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> Command<C> {
|
||||
/// Execute `db` command
|
||||
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(self) -> eyre::Result<()> {
|
||||
pub async fn execute<N: CliNodeTypes<ChainSpec = C::ChainSpec>>(
|
||||
self,
|
||||
ctx: CliContext,
|
||||
) -> eyre::Result<()> {
|
||||
let data_dir = self.env.datadir.clone().resolve_datadir(self.env.chain.chain());
|
||||
let db_path = data_dir.db();
|
||||
let static_files_path = data_dir.static_files();
|
||||
@@ -158,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)?;
|
||||
command.execute(&tool, ctx.task_executor.clone())?;
|
||||
});
|
||||
}
|
||||
Subcommands::StaticFileHeader(command) => {
|
||||
|
||||
@@ -18,6 +18,7 @@ use reth_node_metrics::{
|
||||
};
|
||||
use reth_provider::{providers::ProviderNodeTypes, ChainSpecProvider, StageCheckpointReader};
|
||||
use reth_stages::StageId;
|
||||
use reth_tasks::TaskExecutor;
|
||||
use reth_trie::{
|
||||
verify::{Output, Verifier},
|
||||
Nibbles,
|
||||
@@ -48,52 +49,37 @@ pub struct Command {
|
||||
|
||||
impl Command {
|
||||
/// Execute `db repair-trie` command
|
||||
pub fn execute<N: ProviderNodeTypes>(self, tool: &DbTool<N>) -> eyre::Result<()> {
|
||||
pub fn execute<N: ProviderNodeTypes>(
|
||||
self,
|
||||
tool: &DbTool<N>,
|
||||
task_executor: TaskExecutor,
|
||||
) -> eyre::Result<()> {
|
||||
// Set up metrics server if requested
|
||||
let _metrics_handle = if let Some(listen_addr) = self.metrics {
|
||||
// Spawn an OS thread with a single-threaded tokio runtime for the metrics server
|
||||
let chain_name = tool.provider_factory.chain_spec().chain().to_string();
|
||||
let executor = task_executor.clone();
|
||||
|
||||
let handle = std::thread::Builder::new().name("metrics-server".to_string()).spawn(
|
||||
move || {
|
||||
// Create a single-threaded tokio runtime
|
||||
let runtime = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("Failed to create tokio runtime for metrics server");
|
||||
let handle = task_executor.spawn_critical("metrics server", async move {
|
||||
let config = MetricServerConfig::new(
|
||||
listen_addr,
|
||||
VersionInfo {
|
||||
version: version_metadata().cargo_pkg_version.as_ref(),
|
||||
build_timestamp: version_metadata().vergen_build_timestamp.as_ref(),
|
||||
cargo_features: version_metadata().vergen_cargo_features.as_ref(),
|
||||
git_sha: version_metadata().vergen_git_sha.as_ref(),
|
||||
target_triple: version_metadata().vergen_cargo_target_triple.as_ref(),
|
||||
build_profile: version_metadata().build_profile_name.as_ref(),
|
||||
},
|
||||
ChainSpecInfo { name: chain_name },
|
||||
executor,
|
||||
Hooks::builder().build(),
|
||||
);
|
||||
|
||||
let handle = runtime.handle().clone();
|
||||
runtime.block_on(async move {
|
||||
let task_manager = reth_tasks::TaskManager::new(handle.clone());
|
||||
let task_executor = task_manager.executor();
|
||||
|
||||
let config = MetricServerConfig::new(
|
||||
listen_addr,
|
||||
VersionInfo {
|
||||
version: version_metadata().cargo_pkg_version.as_ref(),
|
||||
build_timestamp: version_metadata().vergen_build_timestamp.as_ref(),
|
||||
cargo_features: version_metadata().vergen_cargo_features.as_ref(),
|
||||
git_sha: version_metadata().vergen_git_sha.as_ref(),
|
||||
target_triple: version_metadata()
|
||||
.vergen_cargo_target_triple
|
||||
.as_ref(),
|
||||
build_profile: version_metadata().build_profile_name.as_ref(),
|
||||
},
|
||||
ChainSpecInfo { name: chain_name },
|
||||
task_executor,
|
||||
Hooks::builder().build(),
|
||||
);
|
||||
|
||||
// Spawn the metrics server
|
||||
if let Err(e) = MetricServer::new(config).serve().await {
|
||||
tracing::error!("Metrics server error: {}", e);
|
||||
}
|
||||
|
||||
// Block forever to keep the runtime alive
|
||||
std::future::pending::<()>().await
|
||||
});
|
||||
},
|
||||
)?;
|
||||
// Spawn the metrics server
|
||||
if let Err(e) = MetricServer::new(config).serve().await {
|
||||
tracing::error!("Metrics server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Some(handle)
|
||||
} else {
|
||||
|
||||
@@ -92,6 +92,8 @@ impl Command {
|
||||
receipts_in_static_files: _,
|
||||
transaction_senders_in_static_files: _,
|
||||
storages_history_in_rocksdb: _,
|
||||
transaction_hash_numbers_in_rocksdb: _,
|
||||
account_history_in_rocksdb: _,
|
||||
} = settings.unwrap_or_else(StorageSettings::legacy);
|
||||
|
||||
// Update the setting based on the key
|
||||
|
||||
@@ -110,7 +110,7 @@ impl<C: ChainSpecParser<ChainSpec: EthChainSpec + EthereumHardforks>> InitStateC
|
||||
static_file_provider.commit()?;
|
||||
} else if last_block_number > 0 && last_block_number < header.number() {
|
||||
return Err(eyre::eyre!(
|
||||
"Data directory should be empty when calling init-state with --without-evm-history."
|
||||
"Data directory should be empty when calling init-state with --without-evm."
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ impl<C: ChainSpecParser> DownloadArgs<C> {
|
||||
|
||||
let net = NetworkConfigBuilder::<N::NetworkPrimitives>::new(p2p_secret_key)
|
||||
.peer_config(config.peers_config_with_basic_nodes_from_file(None))
|
||||
.external_ip_resolver(self.network.nat)
|
||||
.external_ip_resolver(self.network.nat.clone())
|
||||
.network_id(self.network.network_id)
|
||||
.boot_nodes(boot_nodes.clone())
|
||||
.apply(|builder| {
|
||||
|
||||
@@ -9,7 +9,7 @@ use reth_evm::ConfigureEvm;
|
||||
use reth_node_builder::NodeTypesWithDB;
|
||||
use reth_node_core::dirs::{ChainPath, DataDirPath};
|
||||
use reth_provider::{
|
||||
providers::{ProviderNodeTypes, StaticFileProvider},
|
||||
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
|
||||
DatabaseProviderFactory, ProviderFactory,
|
||||
};
|
||||
use reth_stages::{stages::ExecutionStage, Stage, StageCheckpoint, UnwindInput};
|
||||
@@ -42,6 +42,7 @@ where
|
||||
Arc::new(output_db),
|
||||
db_tool.chain(),
|
||||
StaticFileProvider::read_write(output_datadir.static_files())?,
|
||||
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
|
||||
)?,
|
||||
to,
|
||||
from,
|
||||
|
||||
@@ -6,7 +6,7 @@ use reth_db_api::{database::Database, table::TableImporter, tables};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_core::dirs::{ChainPath, DataDirPath};
|
||||
use reth_provider::{
|
||||
providers::{ProviderNodeTypes, StaticFileProvider},
|
||||
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
|
||||
DatabaseProviderFactory, ProviderFactory,
|
||||
};
|
||||
use reth_stages::{stages::AccountHashingStage, Stage, StageCheckpoint, UnwindInput};
|
||||
@@ -39,6 +39,7 @@ pub(crate) async fn dump_hashing_account_stage<N: ProviderNodeTypes<DB = Arc<Dat
|
||||
Arc::new(output_db),
|
||||
db_tool.chain(),
|
||||
StaticFileProvider::read_write(output_datadir.static_files())?,
|
||||
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
|
||||
)?,
|
||||
to,
|
||||
from,
|
||||
|
||||
@@ -5,7 +5,7 @@ use reth_db_api::{database::Database, table::TableImporter, tables};
|
||||
use reth_db_common::DbTool;
|
||||
use reth_node_core::dirs::{ChainPath, DataDirPath};
|
||||
use reth_provider::{
|
||||
providers::{ProviderNodeTypes, StaticFileProvider},
|
||||
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
|
||||
DatabaseProviderFactory, ProviderFactory,
|
||||
};
|
||||
use reth_stages::{stages::StorageHashingStage, Stage, StageCheckpoint, UnwindInput};
|
||||
@@ -29,6 +29,7 @@ pub(crate) async fn dump_hashing_storage_stage<N: ProviderNodeTypes<DB = Arc<Dat
|
||||
Arc::new(output_db),
|
||||
db_tool.chain(),
|
||||
StaticFileProvider::read_write(output_datadir.static_files())?,
|
||||
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
|
||||
)?,
|
||||
to,
|
||||
from,
|
||||
|
||||
@@ -12,7 +12,7 @@ use reth_evm::ConfigureEvm;
|
||||
use reth_exex::ExExManagerHandle;
|
||||
use reth_node_core::dirs::{ChainPath, DataDirPath};
|
||||
use reth_provider::{
|
||||
providers::{ProviderNodeTypes, StaticFileProvider},
|
||||
providers::{ProviderNodeTypes, RocksDBProvider, StaticFileProvider},
|
||||
DatabaseProviderFactory, ProviderFactory,
|
||||
};
|
||||
use reth_stages::{
|
||||
@@ -62,6 +62,7 @@ where
|
||||
Arc::new(output_db),
|
||||
db_tool.chain(),
|
||||
StaticFileProvider::read_write(output_datadir.static_files())?,
|
||||
RocksDBProvider::builder(output_datadir.rocksdb()).build()?,
|
||||
)?,
|
||||
to,
|
||||
from,
|
||||
|
||||
@@ -97,6 +97,57 @@ impl CliRunner {
|
||||
command_res
|
||||
}
|
||||
|
||||
/// Executes a command in a blocking context with access to `CliContext`.
|
||||
///
|
||||
/// See [`Runtime::spawn_blocking`](tokio::runtime::Runtime::spawn_blocking).
|
||||
pub fn run_blocking_command_until_exit<F, E>(
|
||||
self,
|
||||
command: impl FnOnce(CliContext) -> F + Send + 'static,
|
||||
) -> Result<(), E>
|
||||
where
|
||||
F: Future<Output = Result<(), E>> + Send + 'static,
|
||||
E: Send + Sync + From<std::io::Error> + From<reth_tasks::PanickedTaskError> + 'static,
|
||||
{
|
||||
let AsyncCliRunner { context, mut task_manager, tokio_runtime } =
|
||||
AsyncCliRunner::new(self.tokio_runtime);
|
||||
|
||||
// Spawn the command on the blocking thread pool
|
||||
let handle = tokio_runtime.handle().clone();
|
||||
let command_handle =
|
||||
tokio_runtime.handle().spawn_blocking(move || handle.block_on(command(context)));
|
||||
|
||||
// Wait for the command to complete or ctrl-c
|
||||
let command_res = tokio_runtime.block_on(run_to_completion_or_panic(
|
||||
&mut task_manager,
|
||||
run_until_ctrl_c(
|
||||
async move { command_handle.await.expect("Failed to join blocking task") },
|
||||
),
|
||||
));
|
||||
|
||||
if command_res.is_err() {
|
||||
error!(target: "reth::cli", "shutting down due to error");
|
||||
} else {
|
||||
debug!(target: "reth::cli", "shutting down gracefully");
|
||||
task_manager.graceful_shutdown_with_timeout(Duration::from_secs(5));
|
||||
}
|
||||
|
||||
// Shutdown the runtime on a separate thread
|
||||
let (tx, rx) = mpsc::channel();
|
||||
std::thread::Builder::new()
|
||||
.name("tokio-runtime-shutdown".to_string())
|
||||
.spawn(move || {
|
||||
drop(tokio_runtime);
|
||||
let _ = tx.send(());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let _ = rx.recv_timeout(Duration::from_secs(5)).inspect_err(|err| {
|
||||
debug!(target: "reth::cli", %err, "tokio runtime shutdown timed out");
|
||||
});
|
||||
|
||||
command_res
|
||||
}
|
||||
|
||||
/// Executes a regular future until completion or until external signal received.
|
||||
pub fn run_until_ctrl_c<F, E>(self, fut: F) -> Result<(), E>
|
||||
where
|
||||
|
||||
@@ -531,8 +531,12 @@ impl PruneConfig {
|
||||
self.segments.receipts.is_some() || !self.segments.receipts_log_filter.is_empty()
|
||||
}
|
||||
|
||||
/// Merges another `PruneConfig` into this one, taking values from the other config if and only
|
||||
/// if the corresponding value in this config is not set.
|
||||
/// Merges values from `other` into `self`.
|
||||
/// - `Option<PruneMode>` fields: set from `other` only if `self` is `None`.
|
||||
/// - `block_interval`: set from `other` only if `self.block_interval ==
|
||||
/// DEFAULT_BLOCK_INTERVAL`.
|
||||
/// - `merkle_changesets`: always set from `other`.
|
||||
/// - `receipts_log_filter`: set from `other` only if `self` is empty and `other` is non-empty.
|
||||
pub fn merge(&mut self, other: Self) {
|
||||
let Self {
|
||||
block_interval,
|
||||
@@ -561,7 +565,7 @@ impl PruneConfig {
|
||||
self.segments.account_history = self.segments.account_history.or(account_history);
|
||||
self.segments.storage_history = self.segments.storage_history.or(storage_history);
|
||||
self.segments.bodies_history = self.segments.bodies_history.or(bodies_history);
|
||||
// Merkle changesets is not optional, so we just replace it if provided
|
||||
// Merkle changesets is not optional; always take the value from `other`
|
||||
self.segments.merkle_changesets = merkle_changesets;
|
||||
|
||||
if self.segments.receipts_log_filter.0.is_empty() && !receipts_log_filter.0.is_empty() {
|
||||
|
||||
@@ -279,20 +279,28 @@ pub fn validate_against_parent_hash_number<H: BlockHeader>(
|
||||
header: &H,
|
||||
parent: &SealedHeader<H>,
|
||||
) -> Result<(), ConsensusError> {
|
||||
// Parent number is consistent.
|
||||
if parent.number() + 1 != header.number() {
|
||||
return Err(ConsensusError::ParentBlockNumberMismatch {
|
||||
parent_block_number: parent.number(),
|
||||
block_number: header.number(),
|
||||
})
|
||||
}
|
||||
|
||||
if parent.hash() != header.parent_hash() {
|
||||
return Err(ConsensusError::ParentHashMismatch(
|
||||
GotExpected { got: header.parent_hash(), expected: parent.hash() }.into(),
|
||||
))
|
||||
}
|
||||
|
||||
let Some(parent_number) = parent.number().checked_add(1) else {
|
||||
// parent block already reached the maximum
|
||||
return Err(ConsensusError::ParentBlockNumberMismatch {
|
||||
parent_block_number: parent.number(),
|
||||
block_number: u64::MAX,
|
||||
})
|
||||
};
|
||||
|
||||
// Parent number is consistent.
|
||||
if parent_number != header.number() {
|
||||
return Err(ConsensusError::ParentBlockNumberMismatch {
|
||||
parent_block_number: parent.number(),
|
||||
block_number: header.number(),
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -327,7 +335,7 @@ pub fn validate_against_parent_eip1559_base_fee<ChainSpec: EthChainSpec + Ethere
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the timestamp against the parent to make sure it is in the past.
|
||||
/// Validates that the block timestamp is greater than the parent block timestamp.
|
||||
#[inline]
|
||||
pub fn validate_against_parent_timestamp<H: BlockHeader>(
|
||||
header: &H,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::BlockProvider;
|
||||
use alloy_provider::{Network, Provider, ProviderBuilder};
|
||||
use alloy_provider::{ConnectionConfig, Network, Provider, ProviderBuilder, WebSocketConfig};
|
||||
use alloy_transport::TransportResult;
|
||||
use futures::{Stream, StreamExt};
|
||||
use reth_node_api::Block;
|
||||
@@ -25,7 +25,19 @@ impl<N: Network, PrimitiveBlock> RpcBlockProvider<N, PrimitiveBlock> {
|
||||
convert: impl Fn(N::BlockResponse) -> PrimitiveBlock + Send + Sync + 'static,
|
||||
) -> eyre::Result<Self> {
|
||||
Ok(Self {
|
||||
provider: Arc::new(ProviderBuilder::default().connect(rpc_url).await?),
|
||||
provider: Arc::new(
|
||||
ProviderBuilder::default()
|
||||
.connect_with_config(
|
||||
rpc_url,
|
||||
ConnectionConfig::default().with_max_retries(u32::MAX).with_ws_config(
|
||||
WebSocketConfig::default()
|
||||
// allow larger messages/frames for big blocks
|
||||
.max_frame_size(Some(128 * 1024 * 1024))
|
||||
.max_message_size(Some(128 * 1024 * 1024)),
|
||||
),
|
||||
)
|
||||
.await?,
|
||||
),
|
||||
url: rpc_url.to_string(),
|
||||
convert: Arc::new(convert),
|
||||
})
|
||||
|
||||
@@ -110,6 +110,7 @@ pub async fn setup_engine_with_chain_import(
|
||||
// Create database path and static files path
|
||||
let db_path = datadir.join("db");
|
||||
let static_files_path = datadir.join("static_files");
|
||||
let rocksdb_dir_path = datadir.join("rocksdb");
|
||||
|
||||
// Initialize the database using init_db (same as CLI import command)
|
||||
// Use the same database arguments as the node will use
|
||||
@@ -125,6 +126,7 @@ pub async fn setup_engine_with_chain_import(
|
||||
db.clone(),
|
||||
chain_spec.clone(),
|
||||
reth_provider::providers::StaticFileProvider::read_write(static_files_path.clone())?,
|
||||
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path).build().unwrap(),
|
||||
)?;
|
||||
|
||||
// Initialize genesis if needed
|
||||
@@ -311,6 +313,7 @@ mod tests {
|
||||
std::fs::create_dir_all(&datadir).unwrap();
|
||||
let db_path = datadir.join("db");
|
||||
let static_files_path = datadir.join("static_files");
|
||||
let rocksdb_dir_path = datadir.join("rocksdb");
|
||||
|
||||
// Import the chain
|
||||
{
|
||||
@@ -324,6 +327,9 @@ mod tests {
|
||||
chain_spec.clone(),
|
||||
reth_provider::providers::StaticFileProvider::read_write(static_files_path.clone())
|
||||
.unwrap(),
|
||||
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path.clone())
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.expect("failed to create provider factory");
|
||||
|
||||
@@ -385,6 +391,9 @@ mod tests {
|
||||
chain_spec.clone(),
|
||||
reth_provider::providers::StaticFileProvider::read_only(static_files_path, false)
|
||||
.unwrap(),
|
||||
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path)
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
.expect("failed to create provider factory");
|
||||
|
||||
@@ -472,11 +481,15 @@ mod tests {
|
||||
// Create static files path
|
||||
let static_files_path = datadir.join("static_files");
|
||||
|
||||
// Create rocksdb path
|
||||
let rocksdb_dir_path = datadir.join("rocksdb");
|
||||
|
||||
// Create a provider factory
|
||||
let provider_factory: ProviderFactory<MockNodeTypesWithDB> = ProviderFactory::new(
|
||||
db.clone(),
|
||||
chain_spec.clone(),
|
||||
reth_provider::providers::StaticFileProvider::read_write(static_files_path).unwrap(),
|
||||
reth_provider::providers::RocksDBProvider::builder(rocksdb_dir_path).build().unwrap(),
|
||||
)
|
||||
.expect("failed to create provider factory");
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
//! Engine tree configuration.
|
||||
|
||||
use alloy_eips::merge::EPOCH_SLOTS;
|
||||
|
||||
/// Triggers persistence when the number of canonical blocks in memory exceeds this threshold.
|
||||
pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2;
|
||||
|
||||
@@ -40,7 +42,7 @@ pub const DEFAULT_RESERVED_CPU_CORES: usize = 1;
|
||||
/// Default maximum concurrency for prewarm task.
|
||||
pub const DEFAULT_PREWARM_MAX_CONCURRENCY: usize = 16;
|
||||
|
||||
const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = 256;
|
||||
const DEFAULT_BLOCK_BUFFER_LIMIT: u32 = EPOCH_SLOTS as u32 * 2;
|
||||
const DEFAULT_MAX_INVALID_HEADER_CACHE_LENGTH: u32 = 256;
|
||||
const DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE: usize = 4;
|
||||
const DEFAULT_CROSS_BLOCK_CACHE_SIZE: u64 = 4 * 1024 * 1024 * 1024;
|
||||
@@ -89,6 +91,8 @@ pub struct TreeConfig {
|
||||
/// Whether to always compare trie updates from the state root task to the trie updates from
|
||||
/// the regular state root calculation.
|
||||
always_compare_trie_updates: bool,
|
||||
/// Whether to disable state cache.
|
||||
disable_state_cache: bool,
|
||||
/// Whether to disable parallel prewarming.
|
||||
disable_prewarming: bool,
|
||||
/// Whether to disable the parallel sparse trie state root algorithm.
|
||||
@@ -97,7 +101,7 @@ pub struct TreeConfig {
|
||||
state_provider_metrics: bool,
|
||||
/// Cross-block cache size in bytes.
|
||||
cross_block_cache_size: u64,
|
||||
/// Whether the host has enough parallelism to run state root in parallel.
|
||||
/// Whether the host has enough parallelism to run state root task.
|
||||
has_enough_parallelism: bool,
|
||||
/// Whether multiproof task should chunk proof targets.
|
||||
multiproof_chunking_enabled: bool,
|
||||
@@ -143,6 +147,7 @@ impl Default for TreeConfig {
|
||||
max_execute_block_batch_size: DEFAULT_MAX_EXECUTE_BLOCK_BATCH_SIZE,
|
||||
legacy_state_root: false,
|
||||
always_compare_trie_updates: false,
|
||||
disable_state_cache: false,
|
||||
disable_prewarming: false,
|
||||
disable_parallel_sparse_trie: false,
|
||||
state_provider_metrics: false,
|
||||
@@ -173,6 +178,7 @@ impl TreeConfig {
|
||||
max_execute_block_batch_size: usize,
|
||||
legacy_state_root: bool,
|
||||
always_compare_trie_updates: bool,
|
||||
disable_state_cache: bool,
|
||||
disable_prewarming: bool,
|
||||
disable_parallel_sparse_trie: bool,
|
||||
state_provider_metrics: bool,
|
||||
@@ -197,6 +203,7 @@ impl TreeConfig {
|
||||
max_execute_block_batch_size,
|
||||
legacy_state_root,
|
||||
always_compare_trie_updates,
|
||||
disable_state_cache,
|
||||
disable_prewarming,
|
||||
disable_parallel_sparse_trie,
|
||||
state_provider_metrics,
|
||||
@@ -271,7 +278,12 @@ impl TreeConfig {
|
||||
self.disable_parallel_sparse_trie
|
||||
}
|
||||
|
||||
/// Returns whether or not parallel prewarming should be used.
|
||||
/// Returns whether or not state cache is disabled.
|
||||
pub const fn disable_state_cache(&self) -> bool {
|
||||
self.disable_state_cache
|
||||
}
|
||||
|
||||
/// Returns whether or not parallel prewarming is disabled.
|
||||
pub const fn disable_prewarming(&self) -> bool {
|
||||
self.disable_prewarming
|
||||
}
|
||||
@@ -363,6 +375,12 @@ impl TreeConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Setter for whether to disable state cache.
|
||||
pub const fn without_state_cache(mut self, disable_state_cache: bool) -> Self {
|
||||
self.disable_state_cache = disable_state_cache;
|
||||
self
|
||||
}
|
||||
|
||||
/// Setter for whether to disable parallel prewarming.
|
||||
pub const fn without_prewarming(mut self, disable_prewarming: bool) -> Self {
|
||||
self.disable_prewarming = disable_prewarming;
|
||||
@@ -385,17 +403,12 @@ impl TreeConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Setter for whether or not the host has enough parallelism to run state root in parallel.
|
||||
/// Setter for has enough parallelism.
|
||||
pub const fn with_has_enough_parallelism(mut self, has_enough_parallelism: bool) -> Self {
|
||||
self.has_enough_parallelism = has_enough_parallelism;
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether or not the host has enough parallelism to run state root in parallel.
|
||||
pub const fn has_enough_parallelism(&self) -> bool {
|
||||
self.has_enough_parallelism
|
||||
}
|
||||
|
||||
/// Setter for state provider metrics.
|
||||
pub const fn with_state_provider_metrics(mut self, state_provider_metrics: bool) -> Self {
|
||||
self.state_provider_metrics = state_provider_metrics;
|
||||
|
||||
@@ -22,7 +22,8 @@ use reth_trie_common::HashedPostState;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
// Re-export [`ExecutionPayload`] moved to `reth_payload_primitives`
|
||||
pub use reth_evm::{ConfigureEngineEvm, ExecutableTxIterator};
|
||||
#[cfg(feature = "std")]
|
||||
pub use reth_evm::{ConfigureEngineEvm, ExecutableTxIterator, ExecutableTxTuple};
|
||||
pub use reth_payload_primitives::ExecutionPayload;
|
||||
|
||||
mod error;
|
||||
|
||||
@@ -16,7 +16,7 @@ reth-chain-state.workspace = true
|
||||
reth-chainspec = { workspace = true, optional = true }
|
||||
reth-consensus.workspace = true
|
||||
reth-db.workspace = true
|
||||
reth-engine-primitives.workspace = true
|
||||
reth-engine-primitives = { workspace = true, features = ["std"] }
|
||||
reth-errors.workspace = true
|
||||
reth-execution-types.workspace = true
|
||||
reth-evm = { workspace = true, features = ["metrics"] }
|
||||
@@ -39,6 +39,7 @@ reth-trie.workspace = true
|
||||
alloy-evm.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
alloy-eips.workspace = true
|
||||
alloy-eip7928.workspace = true
|
||||
alloy-primitives.workspace = true
|
||||
alloy-rlp.workspace = true
|
||||
alloy-rpc-types-engine.workspace = true
|
||||
|
||||
@@ -230,17 +230,18 @@ fn bench_state_root(c: &mut Criterion) {
|
||||
let mut handle = payload_processor.spawn(
|
||||
Default::default(),
|
||||
(
|
||||
core::iter::empty::<
|
||||
Vec::<
|
||||
Result<
|
||||
Recovered<TransactionSigned>,
|
||||
core::convert::Infallible,
|
||||
>,
|
||||
>(),
|
||||
>::new(),
|
||||
std::convert::identity,
|
||||
),
|
||||
StateProviderBuilder::new(provider.clone(), genesis_hash, None),
|
||||
OverlayStateProviderFactory::new(provider),
|
||||
&TreeConfig::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
let mut state_hook = handle.state_hook();
|
||||
|
||||
@@ -22,7 +22,7 @@ const NANOS_PER_SEC: u32 = 1_000_000_000;
|
||||
|
||||
/// An atomic version of [`Duration`], using an [`AtomicU64`] to store the total nanoseconds in the
|
||||
/// duration.
|
||||
#[derive(Default)]
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct AtomicDuration {
|
||||
/// The nanoseconds part of the duration
|
||||
///
|
||||
@@ -59,7 +59,8 @@ impl AtomicDuration {
|
||||
}
|
||||
|
||||
/// A wrapper of a state provider and latency metrics.
|
||||
pub(crate) struct InstrumentedStateProvider<S> {
|
||||
#[derive(Debug)]
|
||||
pub struct InstrumentedStateProvider<S> {
|
||||
/// The state provider
|
||||
state_provider: S,
|
||||
|
||||
@@ -80,11 +81,12 @@ impl<S> InstrumentedStateProvider<S>
|
||||
where
|
||||
S: StateProvider,
|
||||
{
|
||||
/// Creates a new [`InstrumentedStateProvider`] from a state provider
|
||||
pub(crate) fn from_state_provider(state_provider: S) -> Self {
|
||||
/// Creates a new [`InstrumentedStateProvider`] from a state provider with the provided label
|
||||
/// for metrics.
|
||||
pub fn from_state_provider(state_provider: S, source: &'static str) -> Self {
|
||||
Self {
|
||||
state_provider,
|
||||
metrics: StateProviderMetrics::default(),
|
||||
metrics: StateProviderMetrics::new_with_labels(&[("source", source)]),
|
||||
total_storage_fetch_latency: AtomicDuration::zero(),
|
||||
total_code_fetch_latency: AtomicDuration::zero(),
|
||||
total_account_fetch_latency: AtomicDuration::zero(),
|
||||
@@ -134,6 +136,12 @@ impl<S> InstrumentedStateProvider<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Drop for InstrumentedStateProvider<S> {
|
||||
fn drop(&mut self) {
|
||||
self.record_total_latency();
|
||||
}
|
||||
}
|
||||
|
||||
/// Metrics for the instrumented state provider
|
||||
#[derive(Metrics, Clone)]
|
||||
#[metrics(scope = "sync.state_provider")]
|
||||
|
||||
@@ -63,7 +63,7 @@ impl EngineApiMetrics {
|
||||
pub(crate) fn execute_metered<E, DB>(
|
||||
&self,
|
||||
executor: E,
|
||||
transactions: impl Iterator<Item = Result<impl ExecutableTx<E>, BlockExecutionError>>,
|
||||
mut transactions: impl Iterator<Item = Result<impl ExecutableTx<E>, BlockExecutionError>>,
|
||||
state_hook: Box<dyn OnStateHook>,
|
||||
) -> Result<(BlockExecutionOutput<E::Receipt>, Vec<Address>), BlockExecutionError>
|
||||
where
|
||||
@@ -79,27 +79,42 @@ impl EngineApiMetrics {
|
||||
let mut executor = executor.with_state_hook(Some(Box::new(wrapper)));
|
||||
|
||||
let f = || {
|
||||
let start = Instant::now();
|
||||
debug_span!(target: "engine::tree", "pre execution")
|
||||
.entered()
|
||||
.in_scope(|| executor.apply_pre_execution_changes())?;
|
||||
self.executor.pre_execution_histogram.record(start.elapsed());
|
||||
|
||||
let exec_span = debug_span!(target: "engine::tree", "execution").entered();
|
||||
for tx in transactions {
|
||||
loop {
|
||||
let start = Instant::now();
|
||||
let Some(tx) = transactions.next() else { break };
|
||||
self.executor.transaction_wait_histogram.record(start.elapsed());
|
||||
|
||||
let tx = tx?;
|
||||
senders.push(*tx.signer());
|
||||
|
||||
let span =
|
||||
debug_span!(target: "engine::tree", "execute tx", tx_hash=?tx.tx().tx_hash());
|
||||
let enter = span.entered();
|
||||
trace!(target: "engine::tree", "Executing transaction");
|
||||
senders.push(*tx.signer());
|
||||
let start = Instant::now();
|
||||
let gas_used = executor.execute_transaction(tx)?;
|
||||
self.executor.transaction_execution_histogram.record(start.elapsed());
|
||||
|
||||
// record the tx gas used
|
||||
enter.record("gas_used", gas_used);
|
||||
}
|
||||
drop(exec_span);
|
||||
debug_span!(target: "engine::tree", "finish")
|
||||
|
||||
let start = Instant::now();
|
||||
let result = debug_span!(target: "engine::tree", "finish")
|
||||
.entered()
|
||||
.in_scope(|| executor.finish())
|
||||
.map(|(evm, result)| (evm.into_db(), result))
|
||||
.map(|(evm, result)| (evm.into_db(), result));
|
||||
self.executor.post_execution_histogram.record(start.elapsed());
|
||||
|
||||
result
|
||||
};
|
||||
|
||||
// Use metered to execute and track timing/gas metrics
|
||||
|
||||
@@ -54,7 +54,7 @@ use tracing::*;
|
||||
mod block_buffer;
|
||||
mod cached_state;
|
||||
pub mod error;
|
||||
mod instrumented_state;
|
||||
pub mod instrumented_state;
|
||||
mod invalid_headers;
|
||||
mod metrics;
|
||||
mod payload_processor;
|
||||
|
||||
318
crates/engine/tree/src/tree/payload_processor/bal.rs
Normal file
318
crates/engine/tree/src/tree/payload_processor/bal.rs
Normal file
@@ -0,0 +1,318 @@
|
||||
//! BAL (Block Access List, EIP-7928) related functionality.
|
||||
|
||||
use alloy_consensus::constants::KECCAK_EMPTY;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_primitives::{keccak256, U256};
|
||||
use reth_primitives_traits::Account;
|
||||
use reth_provider::{AccountReader, ProviderError};
|
||||
use reth_trie::{HashedPostState, HashedStorage};
|
||||
|
||||
/// Converts a Block Access List into a [`HashedPostState`] by extracting the final state
|
||||
/// of modified accounts and storage slots.
|
||||
pub fn bal_to_hashed_post_state<P>(
|
||||
bal: &BlockAccessList,
|
||||
provider: &P,
|
||||
) -> Result<HashedPostState, ProviderError>
|
||||
where
|
||||
P: AccountReader,
|
||||
{
|
||||
let mut hashed_state = HashedPostState::with_capacity(bal.len());
|
||||
|
||||
for account_changes in bal {
|
||||
let address = account_changes.address;
|
||||
let hashed_address = keccak256(address);
|
||||
|
||||
// Get the latest balance (last balance change if any)
|
||||
let balance = account_changes.balance_changes.last().map(|change| change.post_balance);
|
||||
|
||||
// Get the latest nonce (last nonce change if any)
|
||||
let nonce = account_changes.nonce_changes.last().map(|change| change.new_nonce);
|
||||
|
||||
// Get the latest code (last code change if any)
|
||||
let code_hash = if let Some(code_change) = account_changes.code_changes.last() {
|
||||
if code_change.new_code.is_empty() {
|
||||
Some(Some(KECCAK_EMPTY))
|
||||
} else {
|
||||
Some(Some(keccak256(&code_change.new_code)))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Only fetch account from provider if we're missing any field
|
||||
let existing_account = if balance.is_none() || nonce.is_none() || code_hash.is_none() {
|
||||
provider.basic_account(&address)?
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Build the final account state
|
||||
let account = Account {
|
||||
balance: balance.unwrap_or_else(|| {
|
||||
existing_account.as_ref().map(|acc| acc.balance).unwrap_or(U256::ZERO)
|
||||
}),
|
||||
nonce: nonce
|
||||
.unwrap_or_else(|| existing_account.as_ref().map(|acc| acc.nonce).unwrap_or(0)),
|
||||
bytecode_hash: code_hash.unwrap_or_else(|| {
|
||||
existing_account.as_ref().and_then(|acc| acc.bytecode_hash).or(Some(KECCAK_EMPTY))
|
||||
}),
|
||||
};
|
||||
|
||||
hashed_state.accounts.insert(hashed_address, Some(account));
|
||||
|
||||
// Process storage changes
|
||||
if !account_changes.storage_changes.is_empty() {
|
||||
let mut storage_map = HashedStorage::new(false);
|
||||
|
||||
for slot_changes in &account_changes.storage_changes {
|
||||
let hashed_slot = keccak256(slot_changes.slot);
|
||||
|
||||
// Get the last change for this slot
|
||||
if let Some(last_change) = slot_changes.changes.last() {
|
||||
storage_map
|
||||
.storage
|
||||
.insert(hashed_slot, U256::from_be_bytes(last_change.new_value.0));
|
||||
}
|
||||
}
|
||||
|
||||
if !storage_map.storage.is_empty() {
|
||||
hashed_state.storages.insert(hashed_address, storage_map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(hashed_state)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_eip7928::{
|
||||
AccountChanges, BalanceChange, CodeChange, NonceChange, SlotChanges, StorageChange,
|
||||
};
|
||||
use alloy_primitives::{Address, Bytes, StorageKey, B256};
|
||||
use reth_revm::test_utils::StateProviderTest;
|
||||
|
||||
#[test]
|
||||
fn test_bal_to_hashed_post_state_basic() {
|
||||
let provider = StateProviderTest::default();
|
||||
|
||||
let address = Address::random();
|
||||
let account_changes = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![],
|
||||
storage_reads: vec![],
|
||||
balance_changes: vec![BalanceChange::new(0, U256::from(100))],
|
||||
nonce_changes: vec![NonceChange::new(0, 1)],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
let bal = vec![account_changes];
|
||||
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
|
||||
|
||||
assert_eq!(result.accounts.len(), 1);
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
let account_opt = result.accounts.get(&hashed_address).unwrap();
|
||||
assert!(account_opt.is_some());
|
||||
|
||||
let account = account_opt.as_ref().unwrap();
|
||||
assert_eq!(account.balance, U256::from(100));
|
||||
assert_eq!(account.nonce, 1);
|
||||
assert_eq!(account.bytecode_hash, Some(KECCAK_EMPTY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bal_with_storage_changes() {
|
||||
let provider = StateProviderTest::default();
|
||||
|
||||
let address = Address::random();
|
||||
let slot = StorageKey::random();
|
||||
let value = B256::random();
|
||||
|
||||
let slot_changes = SlotChanges { slot, changes: vec![StorageChange::new(0, value)] };
|
||||
|
||||
let account_changes = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![slot_changes],
|
||||
storage_reads: vec![],
|
||||
balance_changes: vec![BalanceChange::new(0, U256::from(500))],
|
||||
nonce_changes: vec![NonceChange::new(0, 2)],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
let bal = vec![account_changes];
|
||||
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
assert!(result.storages.contains_key(&hashed_address));
|
||||
|
||||
let storage = result.storages.get(&hashed_address).unwrap();
|
||||
let hashed_slot = keccak256(slot);
|
||||
|
||||
let stored_value = storage.storage.get(&hashed_slot).unwrap();
|
||||
assert_eq!(*stored_value, U256::from_be_bytes(value.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bal_with_code_change() {
|
||||
let provider = StateProviderTest::default();
|
||||
|
||||
let address = Address::random();
|
||||
let code = Bytes::from(vec![0x60, 0x80, 0x60, 0x40]); // Some bytecode
|
||||
|
||||
let account_changes = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![],
|
||||
storage_reads: vec![],
|
||||
balance_changes: vec![BalanceChange::new(0, U256::from(1000))],
|
||||
nonce_changes: vec![NonceChange::new(0, 1)],
|
||||
code_changes: vec![CodeChange::new(0, code.clone())],
|
||||
};
|
||||
|
||||
let bal = vec![account_changes];
|
||||
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
let account_opt = result.accounts.get(&hashed_address).unwrap();
|
||||
let account = account_opt.as_ref().unwrap();
|
||||
|
||||
let expected_code_hash = keccak256(&code);
|
||||
assert_eq!(account.bytecode_hash, Some(expected_code_hash));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bal_with_empty_code() {
|
||||
let provider = StateProviderTest::default();
|
||||
|
||||
let address = Address::random();
|
||||
let empty_code = Bytes::default();
|
||||
|
||||
let account_changes = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![],
|
||||
storage_reads: vec![],
|
||||
balance_changes: vec![BalanceChange::new(0, U256::from(1000))],
|
||||
nonce_changes: vec![NonceChange::new(0, 1)],
|
||||
code_changes: vec![CodeChange::new(0, empty_code)],
|
||||
};
|
||||
|
||||
let bal = vec![account_changes];
|
||||
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
let account_opt = result.accounts.get(&hashed_address).unwrap();
|
||||
let account = account_opt.as_ref().unwrap();
|
||||
|
||||
assert_eq!(account.bytecode_hash, Some(KECCAK_EMPTY));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bal_multiple_changes_takes_last() {
|
||||
let provider = StateProviderTest::default();
|
||||
|
||||
let address = Address::random();
|
||||
|
||||
// Multiple balance changes - should take the last one
|
||||
let account_changes = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![],
|
||||
storage_reads: vec![],
|
||||
balance_changes: vec![
|
||||
BalanceChange::new(0, U256::from(100)),
|
||||
BalanceChange::new(1, U256::from(200)),
|
||||
BalanceChange::new(2, U256::from(300)),
|
||||
],
|
||||
nonce_changes: vec![
|
||||
NonceChange::new(0, 1),
|
||||
NonceChange::new(1, 2),
|
||||
NonceChange::new(2, 3),
|
||||
],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
let bal = vec![account_changes];
|
||||
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
let account_opt = result.accounts.get(&hashed_address).unwrap();
|
||||
let account = account_opt.as_ref().unwrap();
|
||||
|
||||
// Should have the last values
|
||||
assert_eq!(account.balance, U256::from(300));
|
||||
assert_eq!(account.nonce, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bal_uses_provider_for_missing_fields() {
|
||||
let mut provider = StateProviderTest::default();
|
||||
|
||||
let address = Address::random();
|
||||
let code_hash = B256::random();
|
||||
let existing_account =
|
||||
Account { balance: U256::from(999), nonce: 42, bytecode_hash: Some(code_hash) };
|
||||
provider.insert_account(address, existing_account, None, Default::default());
|
||||
|
||||
// Only change balance, nonce and code should come from provider
|
||||
let account_changes = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![],
|
||||
storage_reads: vec![],
|
||||
balance_changes: vec![BalanceChange::new(0, U256::from(1500))],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
let bal = vec![account_changes];
|
||||
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
let account_opt = result.accounts.get(&hashed_address).unwrap();
|
||||
let account = account_opt.as_ref().unwrap();
|
||||
|
||||
// Balance should be updated
|
||||
assert_eq!(account.balance, U256::from(1500));
|
||||
// Nonce and bytecode_hash should come from provider
|
||||
assert_eq!(account.nonce, 42);
|
||||
assert_eq!(account.bytecode_hash, Some(code_hash));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bal_multiple_storage_changes_per_slot() {
|
||||
let provider = StateProviderTest::default();
|
||||
|
||||
let address = Address::random();
|
||||
let slot = StorageKey::random();
|
||||
|
||||
// Multiple changes to the same slot - should take the last one
|
||||
let slot_changes = SlotChanges {
|
||||
slot,
|
||||
changes: vec![
|
||||
StorageChange::new(0, B256::from(U256::from(100).to_be_bytes::<32>())),
|
||||
StorageChange::new(1, B256::from(U256::from(200).to_be_bytes::<32>())),
|
||||
StorageChange::new(2, B256::from(U256::from(300).to_be_bytes::<32>())),
|
||||
],
|
||||
};
|
||||
|
||||
let account_changes = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![slot_changes],
|
||||
storage_reads: vec![],
|
||||
balance_changes: vec![BalanceChange::new(0, U256::from(100))],
|
||||
nonce_changes: vec![NonceChange::new(0, 1)],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
let bal = vec![account_changes];
|
||||
let result = bal_to_hashed_post_state(&bal, &provider).unwrap();
|
||||
|
||||
let hashed_address = keccak256(address);
|
||||
let storage = result.storages.get(&hashed_address).unwrap();
|
||||
let hashed_slot = keccak256(slot);
|
||||
|
||||
let stored_value = storage.storage.get(&hashed_slot).unwrap();
|
||||
|
||||
// Should have the last value
|
||||
assert_eq!(*stored_value, U256::from(300));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ use crate::tree::{
|
||||
sparse_trie::SparseTrieTask,
|
||||
StateProviderBuilder, TreeConfig,
|
||||
};
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::eip1898::BlockWithParent;
|
||||
use alloy_evm::{block::StateChangeSource, ToTxEnv};
|
||||
use alloy_primitives::B256;
|
||||
@@ -21,7 +22,7 @@ use executor::WorkloadExecutor;
|
||||
use multiproof::{SparseTrieUpdate, *};
|
||||
use parking_lot::RwLock;
|
||||
use prewarm::PrewarmMetrics;
|
||||
use rayon::iter::{ParallelBridge, ParallelIterator};
|
||||
use rayon::prelude::*;
|
||||
use reth_engine_primitives::ExecutableTxIterator;
|
||||
use reth_evm::{
|
||||
execute::{ExecutableTxFor, WithTxEnv},
|
||||
@@ -49,8 +50,9 @@ use std::{
|
||||
},
|
||||
time::Instant,
|
||||
};
|
||||
use tracing::{debug, debug_span, instrument, warn, Span};
|
||||
use tracing::{debug, debug_span, error, instrument, warn, Span};
|
||||
|
||||
pub mod bal;
|
||||
mod configured_sparse_trie;
|
||||
pub mod executor;
|
||||
pub mod multiproof;
|
||||
@@ -106,6 +108,8 @@ where
|
||||
cross_block_cache_size: u64,
|
||||
/// Whether transactions should not be executed on prewarming task.
|
||||
disable_transaction_prewarming: bool,
|
||||
/// Whether state cache should be disable
|
||||
disable_state_cache: bool,
|
||||
/// Determines how to configure the evm for execution.
|
||||
evm_config: Evm,
|
||||
/// Whether precompile cache should be disabled.
|
||||
@@ -149,6 +153,7 @@ where
|
||||
cross_block_cache_size: config.cross_block_cache_size(),
|
||||
disable_transaction_prewarming: config.disable_prewarming(),
|
||||
evm_config,
|
||||
disable_state_cache: config.disable_state_cache(),
|
||||
precompile_cache_disabled: config.precompile_cache_disabled(),
|
||||
precompile_cache_map,
|
||||
sparse_state_trie: Arc::default(),
|
||||
@@ -209,6 +214,7 @@ where
|
||||
provider_builder: StateProviderBuilder<N, P>,
|
||||
multiproof_provider_factory: F,
|
||||
config: &TreeConfig,
|
||||
bal: Option<Arc<BlockAccessList>>,
|
||||
) -> PayloadHandle<WithTxEnv<TxEnvFor<Evm>, I::Tx>, I::Error>
|
||||
where
|
||||
P: BlockReader + StateProviderFactory + StateReader + Clone + 'static,
|
||||
@@ -249,19 +255,45 @@ where
|
||||
// wire the multiproof task to the prewarm task
|
||||
let to_multi_proof = Some(multi_proof_task.state_root_message_sender());
|
||||
|
||||
let prewarm_handle = self.spawn_caching_with(
|
||||
env,
|
||||
prewarm_rx,
|
||||
transaction_count_hint,
|
||||
provider_builder,
|
||||
to_multi_proof.clone(),
|
||||
);
|
||||
// Handle BAL-based optimization if available
|
||||
let prewarm_handle = if let Some(bal) = bal {
|
||||
// When BAL is present, skip spawning prewarm tasks entirely and send BAL to multiproof
|
||||
debug!(target: "engine::tree::payload_processor", "BAL present, skipping prewarm tasks");
|
||||
|
||||
// Send BAL message immediately to MultiProofTask
|
||||
if let Some(ref sender) = to_multi_proof &&
|
||||
let Err(err) = sender.send(MultiProofMessage::BlockAccessList(bal))
|
||||
{
|
||||
// In this case state root validation will simply fail
|
||||
error!(target: "engine::tree::payload_processor", ?err, "Failed to send BAL to MultiProofTask");
|
||||
}
|
||||
|
||||
// Spawn minimal cache-only task without prewarming
|
||||
self.spawn_caching_with(
|
||||
env,
|
||||
prewarm_rx,
|
||||
transaction_count_hint,
|
||||
provider_builder.clone(),
|
||||
None, // Don't send proof targets when BAL is present
|
||||
)
|
||||
} else {
|
||||
// Normal path: spawn with full prewarming
|
||||
self.spawn_caching_with(
|
||||
env,
|
||||
prewarm_rx,
|
||||
transaction_count_hint,
|
||||
provider_builder.clone(),
|
||||
to_multi_proof.clone(),
|
||||
)
|
||||
};
|
||||
|
||||
// spawn multi-proof task
|
||||
let parent_span = span.clone();
|
||||
self.executor.spawn_blocking(move || {
|
||||
let _enter = parent_span.entered();
|
||||
multi_proof_task.run();
|
||||
// Build a state provider for the multiproof task
|
||||
let provider = provider_builder.build().expect("failed to build provider");
|
||||
multi_proof_task.run(provider);
|
||||
});
|
||||
|
||||
// wire the sparse trie to the state root response receiver
|
||||
@@ -315,36 +347,32 @@ where
|
||||
usize,
|
||||
) {
|
||||
let (transactions, convert) = transactions.into();
|
||||
let transactions = transactions.into_iter();
|
||||
// Get the transaction count for prewarming task
|
||||
// Use upper bound if available (more accurate), otherwise use lower bound
|
||||
let (lower, upper) = transactions.size_hint();
|
||||
let transaction_count_hint = upper.unwrap_or(lower);
|
||||
let transactions = transactions.into_par_iter();
|
||||
let transaction_count_hint = transactions.len();
|
||||
|
||||
// Spawn a task that iterates through all transactions in parallel and sends them to the
|
||||
// main task.
|
||||
let (tx, rx) = mpsc::channel();
|
||||
let (ooo_tx, ooo_rx) = mpsc::channel();
|
||||
let (prewarm_tx, prewarm_rx) = mpsc::channel();
|
||||
let (execute_tx, execute_rx) = mpsc::channel();
|
||||
|
||||
// Spawn a task that `convert`s all transactions in parallel and sends them out-of-order.
|
||||
self.executor.spawn_blocking(move || {
|
||||
transactions.enumerate().par_bridge().for_each_with(tx, |sender, (idx, tx)| {
|
||||
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 _ = sender.send((idx, tx));
|
||||
// Only send Ok(_) variants to prewarming task.
|
||||
if let Ok(tx) = &tx {
|
||||
let _ = prewarm_tx.send(tx.clone());
|
||||
}
|
||||
let _ = ooo_tx.send((idx, tx));
|
||||
});
|
||||
});
|
||||
|
||||
// Spawn a task that processes out-of-order transactions from the task above and sends them
|
||||
// to prewarming and execution tasks.
|
||||
let (prewarm_tx, prewarm_rx) = mpsc::channel();
|
||||
let (execute_tx, execute_rx) = mpsc::channel();
|
||||
// to the execution task in order.
|
||||
self.executor.spawn_blocking(move || {
|
||||
let mut next_for_execution = 0;
|
||||
let mut queue = BTreeMap::new();
|
||||
while let Ok((idx, tx)) = rx.recv() {
|
||||
// only send Ok(_) variants to prewarming task
|
||||
if let Ok(tx) = &tx {
|
||||
let _ = prewarm_tx.send(tx.clone());
|
||||
}
|
||||
|
||||
while let Ok((idx, tx)) = ooo_rx.recv() {
|
||||
if next_for_execution == idx {
|
||||
let _ = execute_tx.send(tx);
|
||||
next_for_execution += 1;
|
||||
@@ -382,9 +410,15 @@ where
|
||||
transactions = mpsc::channel().1;
|
||||
}
|
||||
|
||||
let saved_cache = self.cache_for(env.parent_hash);
|
||||
let cache = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
let (saved_cache, cache, cache_metrics) = if self.disable_state_cache {
|
||||
(None, None, None)
|
||||
} else {
|
||||
let saved_cache = self.cache_for(env.parent_hash);
|
||||
let cache = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
(Some(saved_cache), Some(cache), Some(cache_metrics))
|
||||
};
|
||||
|
||||
// configure prewarming
|
||||
let prewarm_ctx = PrewarmContext {
|
||||
env,
|
||||
@@ -590,18 +624,18 @@ impl<Tx, Err> PayloadHandle<Tx, Err> {
|
||||
|
||||
move |source: StateChangeSource, state: &EvmState| {
|
||||
if let Some(sender) = &to_multi_proof {
|
||||
let _ = sender.send(MultiProofMessage::StateUpdate(source, state.clone()));
|
||||
let _ = sender.send(MultiProofMessage::StateUpdate(source.into(), state.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a clone of the caches used by prewarming
|
||||
pub(super) fn caches(&self) -> StateExecutionCache {
|
||||
pub(super) fn caches(&self) -> Option<StateExecutionCache> {
|
||||
self.prewarm_handle.cache.clone()
|
||||
}
|
||||
|
||||
/// Returns a clone of the cache metrics used by prewarming
|
||||
pub(super) fn cache_metrics(&self) -> CachedStateMetrics {
|
||||
pub(super) fn cache_metrics(&self) -> Option<CachedStateMetrics> {
|
||||
self.prewarm_handle.cache_metrics.clone()
|
||||
}
|
||||
|
||||
@@ -631,9 +665,9 @@ impl<Tx, Err> PayloadHandle<Tx, Err> {
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CacheTaskHandle {
|
||||
/// The shared cache the task operates with.
|
||||
cache: StateExecutionCache,
|
||||
cache: Option<StateExecutionCache>,
|
||||
/// Metrics for the caches
|
||||
cache_metrics: CachedStateMetrics,
|
||||
cache_metrics: Option<CachedStateMetrics>,
|
||||
/// Channel to the spawned prewarm task if any
|
||||
to_prewarm_task: Option<std::sync::mpsc::Sender<PrewarmTaskEvent>>,
|
||||
}
|
||||
@@ -1048,19 +1082,17 @@ mod tests {
|
||||
|
||||
let provider_factory = BlockchainProvider::new(factory).unwrap();
|
||||
|
||||
let mut handle =
|
||||
payload_processor.spawn(
|
||||
Default::default(),
|
||||
(
|
||||
core::iter::empty::<
|
||||
Result<Recovered<TransactionSigned>, core::convert::Infallible>,
|
||||
>(),
|
||||
std::convert::identity,
|
||||
),
|
||||
StateProviderBuilder::new(provider_factory.clone(), genesis_hash, None),
|
||||
OverlayStateProviderFactory::new(provider_factory),
|
||||
&TreeConfig::default(),
|
||||
);
|
||||
let mut handle = payload_processor.spawn(
|
||||
Default::default(),
|
||||
(
|
||||
Vec::<Result<Recovered<TransactionSigned>, core::convert::Infallible>>::new(),
|
||||
std::convert::identity,
|
||||
),
|
||||
StateProviderBuilder::new(provider_factory.clone(), genesis_hash, None),
|
||||
OverlayStateProviderFactory::new(provider_factory),
|
||||
&TreeConfig::default(),
|
||||
None, // No BAL for test
|
||||
);
|
||||
|
||||
let mut state_hook = handle.state_hook();
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -29,7 +29,7 @@ use metrics::{Counter, Gauge, Histogram};
|
||||
use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, SpecFor};
|
||||
use reth_metrics::Metrics;
|
||||
use reth_primitives_traits::NodePrimitives;
|
||||
use reth_provider::{BlockReader, StateProviderFactory, StateReader};
|
||||
use reth_provider::{BlockReader, StateProviderBox, StateProviderFactory, StateReader};
|
||||
use reth_revm::{database::StateProviderDatabase, db::BundleState, state::EvmState};
|
||||
use reth_trie::MultiProofTargets;
|
||||
use std::{
|
||||
@@ -255,31 +255,35 @@ where
|
||||
self;
|
||||
let hash = env.hash;
|
||||
|
||||
debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
|
||||
// Perform all cache operations atomically under the lock
|
||||
execution_cache.update_with_guard(|cached| {
|
||||
// consumes the `SavedCache` held by the prewarming task, which releases its usage guard
|
||||
let (caches, cache_metrics) = saved_cache.split();
|
||||
let new_cache = SavedCache::new(hash, caches, cache_metrics);
|
||||
if let Some(saved_cache) = saved_cache {
|
||||
debug!(target: "engine::caching", parent_hash=?hash, "Updating execution cache");
|
||||
// Perform all cache operations atomically under the lock
|
||||
execution_cache.update_with_guard(|cached| {
|
||||
// consumes the `SavedCache` held by the prewarming task, which releases its usage
|
||||
// guard
|
||||
let (caches, cache_metrics) = saved_cache.split();
|
||||
let new_cache = SavedCache::new(hash, caches, cache_metrics);
|
||||
|
||||
// Insert state into cache while holding the lock
|
||||
if new_cache.cache().insert_state(&state).is_err() {
|
||||
// Clear the cache on error to prevent having a polluted cache
|
||||
*cached = None;
|
||||
debug!(target: "engine::caching", "cleared execution cache on update error");
|
||||
return;
|
||||
}
|
||||
// Insert state into cache while holding the lock
|
||||
if new_cache.cache().insert_state(&state).is_err() {
|
||||
// Clear the cache on error to prevent having a polluted cache
|
||||
*cached = None;
|
||||
debug!(target: "engine::caching", "cleared execution cache on update error");
|
||||
return;
|
||||
}
|
||||
|
||||
new_cache.update_metrics();
|
||||
new_cache.update_metrics();
|
||||
|
||||
// Replace the shared cache with the new one; the previous cache (if any) is dropped.
|
||||
*cached = Some(new_cache);
|
||||
});
|
||||
// Replace the shared cache with the new one; the previous cache (if any) is
|
||||
// dropped.
|
||||
*cached = Some(new_cache);
|
||||
});
|
||||
|
||||
let elapsed = start.elapsed();
|
||||
debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
|
||||
let elapsed = start.elapsed();
|
||||
debug!(target: "engine::caching", parent_hash=?hash, elapsed=?elapsed, "Updated execution cache");
|
||||
|
||||
metrics.cache_saving_duration.set(elapsed.as_secs_f64());
|
||||
metrics.cache_saving_duration.set(elapsed.as_secs_f64());
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes the task.
|
||||
@@ -356,7 +360,7 @@ where
|
||||
{
|
||||
pub(super) env: ExecutionEnv<Evm>,
|
||||
pub(super) evm_config: Evm,
|
||||
pub(super) saved_cache: SavedCache,
|
||||
pub(super) saved_cache: Option<SavedCache>,
|
||||
/// Provider to obtain the state
|
||||
pub(super) provider: StateProviderBuilder<N, P>,
|
||||
pub(super) metrics: PrewarmMetrics,
|
||||
@@ -400,10 +404,13 @@ where
|
||||
};
|
||||
|
||||
// Use the caches to create a new provider with caching
|
||||
let caches = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
let state_provider =
|
||||
CachedStateProvider::new_with_caches(state_provider, caches, cache_metrics);
|
||||
let state_provider: StateProviderBox = if let Some(saved_cache) = saved_cache {
|
||||
let caches = saved_cache.cache().clone();
|
||||
let cache_metrics = saved_cache.metrics().clone();
|
||||
Box::new(CachedStateProvider::new_with_caches(state_provider, caches, cache_metrics))
|
||||
} else {
|
||||
state_provider
|
||||
};
|
||||
|
||||
let state_provider = StateProviderDatabase::new(state_provider);
|
||||
|
||||
|
||||
@@ -11,9 +11,11 @@ use crate::tree::{
|
||||
StateProviderDatabase, TreeConfig,
|
||||
};
|
||||
use alloy_consensus::transaction::Either;
|
||||
use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::{eip1898::BlockWithParent, NumHash};
|
||||
use alloy_evm::Evm;
|
||||
use alloy_primitives::B256;
|
||||
use rayon::prelude::*;
|
||||
use reth_chain_state::{CanonicalInMemoryState, DeferredTrieData, ExecutedBlock};
|
||||
use reth_consensus::{ConsensusError, FullConsensus};
|
||||
use reth_engine_primitives::{
|
||||
@@ -220,7 +222,7 @@ where
|
||||
.map_err(NewPayloadError::other)?
|
||||
.into();
|
||||
|
||||
let iter = Either::Left(iter.into_iter().map(Either::Left));
|
||||
let iter = Either::Left(iter.into_par_iter().map(Either::Left));
|
||||
let convert = move |tx| {
|
||||
let Either::Left(tx) = tx else { unreachable!() };
|
||||
convert(tx).map(Either::Left).map_err(Either::Left)
|
||||
@@ -230,8 +232,9 @@ where
|
||||
Ok((iter, Box::new(convert) as Box<dyn Fn(_) -> _ + Send + Sync + 'static>))
|
||||
}
|
||||
BlockOrPayload::Block(block) => {
|
||||
let iter =
|
||||
Either::Right(block.body().clone_transactions().into_iter().map(Either::Right));
|
||||
let iter = Either::Right(
|
||||
block.body().clone_transactions().into_par_iter().map(Either::Right),
|
||||
);
|
||||
let convert = move |tx: Either<_, N::SignedTx>| {
|
||||
let Either::Right(tx) = tx else { unreachable!() };
|
||||
tx.try_into_recovered().map(Either::Right).map_err(Either::Right)
|
||||
@@ -368,7 +371,7 @@ where
|
||||
)
|
||||
.into())
|
||||
};
|
||||
let state_provider = ensure_ok!(provider_builder.build());
|
||||
let mut state_provider = ensure_ok!(provider_builder.build());
|
||||
drop(_enter);
|
||||
|
||||
// fetch parent block
|
||||
@@ -399,6 +402,14 @@ where
|
||||
// use prewarming background task
|
||||
let txs = self.tx_iterator_for(&input)?;
|
||||
|
||||
// Extract the BAL, if valid and available
|
||||
let block_access_list = ensure_ok!(input
|
||||
.block_access_list()
|
||||
.transpose()
|
||||
// Eventually gets converted to a `InsertBlockErrorKind::Other`
|
||||
.map_err(Box::<dyn std::error::Error + Send + Sync>::from))
|
||||
.map(Arc::new);
|
||||
|
||||
// Spawn the appropriate processor based on strategy
|
||||
let mut handle = ensure_ok!(self.spawn_payload_processor(
|
||||
env.clone(),
|
||||
@@ -407,22 +418,24 @@ where
|
||||
parent_hash,
|
||||
ctx.state(),
|
||||
strategy,
|
||||
block_access_list,
|
||||
));
|
||||
|
||||
// Use cached state provider before executing, used in execution after prewarming threads
|
||||
// complete
|
||||
let state_provider = CachedStateProvider::new_with_caches(
|
||||
state_provider,
|
||||
handle.caches(),
|
||||
handle.cache_metrics(),
|
||||
);
|
||||
if let Some((caches, cache_metrics)) = handle.caches().zip(handle.cache_metrics()) {
|
||||
state_provider = Box::new(CachedStateProvider::new_with_caches(
|
||||
state_provider,
|
||||
caches,
|
||||
cache_metrics,
|
||||
));
|
||||
};
|
||||
|
||||
// Execute the block and handle any execution errors
|
||||
let (output, senders) = match if self.config.state_provider_metrics() {
|
||||
let state_provider = InstrumentedStateProvider::from_state_provider(&state_provider);
|
||||
let result = self.execute_block(&state_provider, env, &input, &mut handle);
|
||||
state_provider.record_total_latency();
|
||||
result
|
||||
let state_provider =
|
||||
InstrumentedStateProvider::from_state_provider(&state_provider, "engine");
|
||||
self.execute_block(&state_provider, env, &input, &mut handle)
|
||||
} else {
|
||||
self.execute_block(&state_provider, env, &input, &mut handle)
|
||||
} {
|
||||
@@ -775,6 +788,7 @@ where
|
||||
parent_hash: B256,
|
||||
state: &EngineApiTreeState<N>,
|
||||
strategy: StateRootStrategy,
|
||||
block_access_list: Option<Arc<BlockAccessList>>,
|
||||
) -> Result<
|
||||
PayloadHandle<
|
||||
impl ExecutableTxFor<Evm> + use<N, P, Evm, V, T>,
|
||||
@@ -804,12 +818,14 @@ where
|
||||
.record(trie_input_start.elapsed().as_secs_f64());
|
||||
|
||||
let spawn_start = Instant::now();
|
||||
|
||||
let handle = self.payload_processor.spawn(
|
||||
env,
|
||||
txs,
|
||||
provider_builder,
|
||||
multiproof_provider_factory,
|
||||
&self.config,
|
||||
block_access_list,
|
||||
);
|
||||
|
||||
// record prewarming initialization duration
|
||||
@@ -872,7 +888,7 @@ where
|
||||
/// Note: Use state root task only if prefix sets are empty, otherwise proof generation is
|
||||
/// too expensive because it requires walking all paths in every proof.
|
||||
const fn plan_state_root_computation(&self) -> StateRootStrategy {
|
||||
if self.config.state_root_fallback() || !self.config.has_enough_parallelism() {
|
||||
if self.config.state_root_fallback() {
|
||||
StateRootStrategy::Synchronous
|
||||
} else if self.config.use_state_root_task() {
|
||||
StateRootStrategy::StateRootTask
|
||||
@@ -1243,4 +1259,10 @@ impl<T: PayloadTypes> BlockOrPayload<T> {
|
||||
Self::Block(_) => "block",
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the block access list if available.
|
||||
pub const fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>> {
|
||||
// TODO decode and return `BlockAccessList`
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ reth-primitives-traits.workspace = true
|
||||
reth-errors.workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
reth-fs-util.workspace = true
|
||||
reth-engine-primitives.workspace = true
|
||||
reth-engine-primitives = { workspace = true, features = ["std"] }
|
||||
reth-engine-tree.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-revm.workspace = true
|
||||
|
||||
@@ -150,6 +150,12 @@ where
|
||||
let era1_id = Era1Id::new(&config.network, start_block, block_count as u32)
|
||||
.with_hash(historical_root);
|
||||
|
||||
let era1_id = if config.max_blocks_per_file == MAX_BLOCKS_PER_ERA1 as u64 {
|
||||
era1_id
|
||||
} else {
|
||||
era1_id.with_era_count()
|
||||
};
|
||||
|
||||
debug!("Final file name {}", era1_id.to_file_name());
|
||||
let file_path = config.dir.join(era1_id.to_file_name());
|
||||
let file = std::fs::File::create(&file_path)?;
|
||||
|
||||
@@ -252,7 +252,7 @@ where
|
||||
|
||||
/// Extracts block headers and bodies from `iter` and appends them using `writer` and `provider`.
|
||||
///
|
||||
/// Adds on to `total_difficulty` and collects hash to height using `hash_collector`.
|
||||
/// Collects hash to height using `hash_collector`.
|
||||
///
|
||||
/// Skips all blocks below the [`start_bound`] of `block_numbers` and stops when reaching past the
|
||||
/// [`end_bound`] or the end of the file.
|
||||
|
||||
@@ -24,7 +24,7 @@ fn test_export_with_genesis_only() {
|
||||
assert!(file_path.exists(), "Exported file should exist on disk");
|
||||
let file_name = file_path.file_name().unwrap().to_str().unwrap();
|
||||
assert!(
|
||||
file_name.starts_with("mainnet-00000-00001-"),
|
||||
file_name.starts_with("mainnet-00000-"),
|
||||
"File should have correct prefix with era format"
|
||||
);
|
||||
assert!(file_name.ends_with(".era1"), "File should have correct extension");
|
||||
|
||||
@@ -30,8 +30,11 @@ pub trait EraFileFormat: Sized {
|
||||
|
||||
/// Era file identifiers
|
||||
pub trait EraFileId: Clone {
|
||||
/// Convert to standardized file name
|
||||
fn to_file_name(&self) -> String;
|
||||
/// File type for this identifier
|
||||
const FILE_TYPE: EraFileType;
|
||||
|
||||
/// Number of items, slots for `era`, blocks for `era1`, per era
|
||||
const ITEMS_PER_ERA: u64;
|
||||
|
||||
/// Get the network name
|
||||
fn network_name(&self) -> &str;
|
||||
@@ -41,6 +44,43 @@ pub trait EraFileId: Clone {
|
||||
|
||||
/// Get the count of items
|
||||
fn count(&self) -> u32;
|
||||
|
||||
/// Get the optional hash identifier
|
||||
fn hash(&self) -> Option<[u8; 4]>;
|
||||
|
||||
/// Whether to include era count in filename
|
||||
fn include_era_count(&self) -> bool;
|
||||
|
||||
/// Calculate era number
|
||||
fn era_number(&self) -> u64 {
|
||||
self.start_number() / Self::ITEMS_PER_ERA
|
||||
}
|
||||
|
||||
/// Calculate the number of eras spanned per file.
|
||||
///
|
||||
/// If the user can decide how many slots/blocks per era file there are, we need to calculate
|
||||
/// it. Most of the time it should be 1, but it can never be more than 2 eras per file
|
||||
/// as there is a maximum of 8192 slots/blocks per era file.
|
||||
fn era_count(&self) -> u64 {
|
||||
if self.count() == 0 {
|
||||
return 0;
|
||||
}
|
||||
let first_era = self.era_number();
|
||||
let last_number = self.start_number() + self.count() as u64 - 1;
|
||||
let last_era = last_number / Self::ITEMS_PER_ERA;
|
||||
last_era - first_era + 1
|
||||
}
|
||||
|
||||
/// Convert to standardized file name.
|
||||
fn to_file_name(&self) -> String {
|
||||
Self::FILE_TYPE.format_filename(
|
||||
self.network_name(),
|
||||
self.era_number(),
|
||||
self.hash(),
|
||||
self.include_era_count(),
|
||||
self.era_count(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`StreamReader`] for reading era-format files
|
||||
@@ -154,6 +194,37 @@ impl EraFileType {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate era file name.
|
||||
///
|
||||
/// Standard format: `<config-name>-<era-number>-<short-historical-root>.<ext>`
|
||||
/// See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md#file-name>
|
||||
///
|
||||
/// With era count (for custom exports):
|
||||
/// `<config-name>-<era-number>-<era-count>-<short-historical-root>.<ext>`
|
||||
pub fn format_filename(
|
||||
&self,
|
||||
network_name: &str,
|
||||
era_number: u64,
|
||||
hash: Option<[u8; 4]>,
|
||||
include_era_count: bool,
|
||||
era_count: u64,
|
||||
) -> String {
|
||||
let hash = format_hash(hash);
|
||||
|
||||
if include_era_count {
|
||||
format!(
|
||||
"{}-{:05}-{:05}-{}{}",
|
||||
network_name,
|
||||
era_number,
|
||||
era_count,
|
||||
hash,
|
||||
self.extension()
|
||||
)
|
||||
} else {
|
||||
format!("{}-{:05}-{}{}", network_name, era_number, hash, self.extension())
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect file type from URL
|
||||
/// By default, it assumes `Era` type
|
||||
pub fn from_url(url: &str) -> Self {
|
||||
@@ -164,3 +235,11 @@ impl EraFileType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format hash as hex string, or placeholder if none
|
||||
pub fn format_hash(hash: Option<[u8; 4]>) -> String {
|
||||
match hash {
|
||||
Some(h) => format!("{:02x}{:02x}{:02x}{:02x}", h[0], h[1], h[2], h[3]),
|
||||
None => "00000000".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md>
|
||||
|
||||
use crate::{
|
||||
common::file_ops::EraFileId,
|
||||
common::file_ops::{EraFileId, EraFileType},
|
||||
e2s::types::{Entry, IndexEntry, SLOT_INDEX},
|
||||
era::types::consensus::{CompressedBeaconState, CompressedSignedBeaconBlock},
|
||||
};
|
||||
@@ -163,12 +163,22 @@ pub struct EraId {
|
||||
/// Optional hash identifier for this file
|
||||
/// First 4 bytes of the last historical root in the last state in the era file
|
||||
pub hash: Option<[u8; 4]>,
|
||||
|
||||
/// Whether to include era count in filename
|
||||
/// It is used for custom exports when we don't use the max number of items per file
|
||||
include_era_count: bool,
|
||||
}
|
||||
|
||||
impl EraId {
|
||||
/// Create a new [`EraId`]
|
||||
pub fn new(network_name: impl Into<String>, start_slot: u64, slot_count: u32) -> Self {
|
||||
Self { network_name: network_name.into(), start_slot, slot_count, hash: None }
|
||||
Self {
|
||||
network_name: network_name.into(),
|
||||
start_slot,
|
||||
slot_count,
|
||||
hash: None,
|
||||
include_era_count: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a hash identifier to [`EraId`]
|
||||
@@ -177,32 +187,18 @@ impl EraId {
|
||||
self
|
||||
}
|
||||
|
||||
/// Calculate which era number the file starts at
|
||||
pub const fn era_number(&self) -> u64 {
|
||||
self.start_slot / SLOTS_PER_HISTORICAL_ROOT
|
||||
}
|
||||
|
||||
// Helper function to calculate the number of eras per era1 file,
|
||||
// If the user can decide how many blocks per era1 file there are, we need to calculate it.
|
||||
// Most of the time it should be 1, but it can never be more than 2 eras per file
|
||||
// as there is a maximum of 8192 blocks per era1 file.
|
||||
const fn calculate_era_count(&self) -> u64 {
|
||||
if self.slot_count == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let first_era = self.era_number();
|
||||
|
||||
// Calculate the actual last slot number in the range
|
||||
let last_slot = self.start_slot + self.slot_count as u64 - 1;
|
||||
// Find which era the last block belongs to
|
||||
let last_era = last_slot / SLOTS_PER_HISTORICAL_ROOT;
|
||||
// Count how many eras we span
|
||||
last_era - first_era + 1
|
||||
/// Include era count in filename, for custom slot-per-file exports
|
||||
pub const fn with_era_count(mut self) -> Self {
|
||||
self.include_era_count = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl EraFileId for EraId {
|
||||
const FILE_TYPE: EraFileType = EraFileType::Era;
|
||||
|
||||
const ITEMS_PER_ERA: u64 = SLOTS_PER_HISTORICAL_ROOT;
|
||||
|
||||
fn network_name(&self) -> &str {
|
||||
&self.network_name
|
||||
}
|
||||
@@ -214,24 +210,13 @@ impl EraFileId for EraId {
|
||||
fn count(&self) -> u32 {
|
||||
self.slot_count
|
||||
}
|
||||
/// Convert to file name following the era file naming:
|
||||
/// `<config-name>-<era-number>-<era-count>-<short-historical-root>.era`
|
||||
/// <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md#file-name>
|
||||
/// See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md>
|
||||
fn to_file_name(&self) -> String {
|
||||
let era_number = self.era_number();
|
||||
let era_count = self.calculate_era_count();
|
||||
|
||||
if let Some(hash) = self.hash {
|
||||
format!(
|
||||
"{}-{:05}-{:05}-{:02x}{:02x}{:02x}{:02x}.era",
|
||||
self.network_name, era_number, era_count, hash[0], hash[1], hash[2], hash[3]
|
||||
)
|
||||
} else {
|
||||
// era spec format with placeholder hash when no hash available
|
||||
// Format: `<config-name>-<era-number>-<era-count>-00000000.era`
|
||||
format!("{}-{:05}-{:05}-00000000.era", self.network_name, era_number, era_count)
|
||||
}
|
||||
fn hash(&self) -> Option<[u8; 4]> {
|
||||
self.hash
|
||||
}
|
||||
|
||||
fn include_era_count(&self) -> bool {
|
||||
self.include_era_count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,4 +384,40 @@ mod tests {
|
||||
let parsed_offset = index.offsets[0];
|
||||
assert_eq!(parsed_offset, -1024);
|
||||
}
|
||||
|
||||
#[test_case::test_case(
|
||||
EraId::new("mainnet", 0, 8192).with_hash([0x4b, 0x36, 0x3d, 0xb9]),
|
||||
"mainnet-00000-4b363db9.era";
|
||||
"Mainnet era 0"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
EraId::new("mainnet", 8192, 8192).with_hash([0x40, 0xcf, 0x2f, 0x3c]),
|
||||
"mainnet-00001-40cf2f3c.era";
|
||||
"Mainnet era 1"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
EraId::new("mainnet", 0, 8192),
|
||||
"mainnet-00000-00000000.era";
|
||||
"Without hash"
|
||||
)]
|
||||
fn test_era_id_file_naming(id: EraId, expected_file_name: &str) {
|
||||
let actual_file_name = id.to_file_name();
|
||||
assert_eq!(actual_file_name, expected_file_name);
|
||||
}
|
||||
|
||||
// File naming with era-count, for custom exports
|
||||
#[test_case::test_case(
|
||||
EraId::new("mainnet", 0, 8192).with_hash([0x4b, 0x36, 0x3d, 0xb9]).with_era_count(),
|
||||
"mainnet-00000-00001-4b363db9.era";
|
||||
"Mainnet era 0 with count"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
EraId::new("mainnet", 8000, 500).with_hash([0xab, 0xcd, 0xef, 0x12]).with_era_count(),
|
||||
"mainnet-00000-00002-abcdef12.era";
|
||||
"Spanning two eras with count"
|
||||
)]
|
||||
fn test_era_id_file_naming_with_era_count(id: EraId, expected_file_name: &str) {
|
||||
let actual_file_name = id.to_file_name();
|
||||
assert_eq!(actual_file_name, expected_file_name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era1.md>
|
||||
|
||||
use crate::{
|
||||
common::file_ops::EraFileId,
|
||||
common::file_ops::{EraFileId, EraFileType},
|
||||
e2s::types::{Entry, IndexEntry},
|
||||
era1::types::execution::{Accumulator, BlockTuple, MAX_BLOCKS_PER_ERA1},
|
||||
};
|
||||
@@ -105,6 +105,10 @@ pub struct Era1Id {
|
||||
/// Optional hash identifier for this file
|
||||
/// First 4 bytes of the last historical root in the last state in the era file
|
||||
pub hash: Option<[u8; 4]>,
|
||||
|
||||
/// Whether to include era count in filename
|
||||
/// It is used for custom exports when we don't use the max number of items per file
|
||||
pub include_era_count: bool,
|
||||
}
|
||||
|
||||
impl Era1Id {
|
||||
@@ -114,7 +118,13 @@ impl Era1Id {
|
||||
start_block: BlockNumber,
|
||||
block_count: u32,
|
||||
) -> Self {
|
||||
Self { network_name: network_name.into(), start_block, block_count, hash: None }
|
||||
Self {
|
||||
network_name: network_name.into(),
|
||||
start_block,
|
||||
block_count,
|
||||
hash: None,
|
||||
include_era_count: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a hash identifier to [`Era1Id`]
|
||||
@@ -123,21 +133,17 @@ impl Era1Id {
|
||||
self
|
||||
}
|
||||
|
||||
// Helper function to calculate the number of eras per era1 file,
|
||||
// If the user can decide how many blocks per era1 file there are, we need to calculate it.
|
||||
// Most of the time it should be 1, but it can never be more than 2 eras per file
|
||||
// as there is a maximum of 8192 blocks per era1 file.
|
||||
const fn calculate_era_count(&self, first_era: u64) -> u64 {
|
||||
// Calculate the actual last block number in the range
|
||||
let last_block = self.start_block + self.block_count as u64 - 1;
|
||||
// Find which era the last block belongs to
|
||||
let last_era = last_block / MAX_BLOCKS_PER_ERA1 as u64;
|
||||
// Count how many eras we span
|
||||
last_era - first_era + 1
|
||||
/// Include era count in filename, for custom block-per-file exports
|
||||
pub const fn with_era_count(mut self) -> Self {
|
||||
self.include_era_count = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl EraFileId for Era1Id {
|
||||
const FILE_TYPE: EraFileType = EraFileType::Era1;
|
||||
|
||||
const ITEMS_PER_ERA: u64 = MAX_BLOCKS_PER_ERA1 as u64;
|
||||
fn network_name(&self) -> &str {
|
||||
&self.network_name
|
||||
}
|
||||
@@ -149,24 +155,13 @@ impl EraFileId for Era1Id {
|
||||
fn count(&self) -> u32 {
|
||||
self.block_count
|
||||
}
|
||||
/// Convert to file name following the era file naming:
|
||||
/// `<config-name>-<era-number>-<era-count>-<short-historical-root>.era(1)`
|
||||
/// <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era.md#file-name>
|
||||
/// See also <https://github.com/eth-clients/e2store-format-specs/blob/main/formats/era1.md>
|
||||
fn to_file_name(&self) -> String {
|
||||
// Find which era the first block belongs to
|
||||
let era_number = self.start_block / MAX_BLOCKS_PER_ERA1 as u64;
|
||||
let era_count = self.calculate_era_count(era_number);
|
||||
if let Some(hash) = self.hash {
|
||||
format!(
|
||||
"{}-{:05}-{:05}-{:02x}{:02x}{:02x}{:02x}.era1",
|
||||
self.network_name, era_number, era_count, hash[0], hash[1], hash[2], hash[3]
|
||||
)
|
||||
} else {
|
||||
// era spec format with placeholder hash when no hash available
|
||||
// Format: `<config-name>-<era-number>-<era-count>-00000000.era1`
|
||||
format!("{}-{:05}-{:05}-00000000.era1", self.network_name, era_number, era_count)
|
||||
}
|
||||
|
||||
fn hash(&self) -> Option<[u8; 4]> {
|
||||
self.hash
|
||||
}
|
||||
|
||||
fn include_era_count(&self) -> bool {
|
||||
self.include_era_count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,35 +309,51 @@ mod tests {
|
||||
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("mainnet", 0, 8192).with_hash([0x5e, 0xc1, 0xff, 0xb8]),
|
||||
"mainnet-00000-00001-5ec1ffb8.era1";
|
||||
"mainnet-00000-5ec1ffb8.era1";
|
||||
"Mainnet era 0"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("mainnet", 8192, 8192).with_hash([0x5e, 0xcb, 0x9b, 0xf9]),
|
||||
"mainnet-00001-00001-5ecb9bf9.era1";
|
||||
"mainnet-00001-5ecb9bf9.era1";
|
||||
"Mainnet era 1"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("sepolia", 0, 8192).with_hash([0x90, 0x91, 0x84, 0x72]),
|
||||
"sepolia-00000-00001-90918472.era1";
|
||||
"sepolia-00000-90918472.era1";
|
||||
"Sepolia era 0"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("sepolia", 155648, 8192).with_hash([0xfa, 0x77, 0x00, 0x19]),
|
||||
"sepolia-00019-00001-fa770019.era1";
|
||||
"sepolia-00019-fa770019.era1";
|
||||
"Sepolia era 19"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("mainnet", 1000, 100),
|
||||
"mainnet-00000-00001-00000000.era1";
|
||||
"mainnet-00000-00000000.era1";
|
||||
"ID without hash"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("sepolia", 101130240, 8192).with_hash([0xab, 0xcd, 0xef, 0x12]),
|
||||
"sepolia-12345-00001-abcdef12.era1";
|
||||
"sepolia-12345-abcdef12.era1";
|
||||
"Large block number era 12345"
|
||||
)]
|
||||
fn test_era1id_file_naming(id: Era1Id, expected_file_name: &str) {
|
||||
fn test_era1_id_file_naming(id: Era1Id, expected_file_name: &str) {
|
||||
let actual_file_name = id.to_file_name();
|
||||
assert_eq!(actual_file_name, expected_file_name);
|
||||
}
|
||||
|
||||
// File naming with era-count, for custom exports
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("mainnet", 0, 8192).with_hash([0x5e, 0xc1, 0xff, 0xb8]).with_era_count(),
|
||||
"mainnet-00000-00001-5ec1ffb8.era1";
|
||||
"Mainnet era 0 with count"
|
||||
)]
|
||||
#[test_case::test_case(
|
||||
Era1Id::new("mainnet", 8000, 500).with_hash([0xab, 0xcd, 0xef, 0x12]).with_era_count(),
|
||||
"mainnet-00000-00002-abcdef12.era1";
|
||||
"Spanning two eras with count"
|
||||
)]
|
||||
fn test_era1_id_file_naming_with_era_count(id: Era1Id, expected_file_name: &str) {
|
||||
let actual_file_name = id.to_file_name();
|
||||
assert_eq!(actual_file_name, expected_file_name);
|
||||
}
|
||||
|
||||
@@ -154,7 +154,9 @@ where
|
||||
Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()),
|
||||
Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::Db(command) => {
|
||||
runner.run_blocking_command_until_exit(|ctx| command.execute::<N>(ctx))
|
||||
}
|
||||
Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::<N>()),
|
||||
Commands::Stage(command) => {
|
||||
runner.run_command_until_exit(|ctx| command.execute::<N, _>(ctx, components))
|
||||
|
||||
@@ -5,7 +5,6 @@ use alloy_consensus::{
|
||||
};
|
||||
use alloy_eips::merge::BEACON_NONCE;
|
||||
use alloy_evm::{block::BlockExecutorFactory, eth::EthBlockExecutionCtx};
|
||||
use alloy_primitives::Bytes;
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_evm::execute::{BlockAssembler, BlockAssemblerInput, BlockExecutionError};
|
||||
use reth_execution_types::BlockExecutionResult;
|
||||
@@ -17,14 +16,12 @@ use revm::context::Block as _;
|
||||
pub struct EthBlockAssembler<ChainSpec = reth_chainspec::ChainSpec> {
|
||||
/// The chainspec.
|
||||
pub chain_spec: Arc<ChainSpec>,
|
||||
/// Extra data to use for the blocks.
|
||||
pub extra_data: Bytes,
|
||||
}
|
||||
|
||||
impl<ChainSpec> EthBlockAssembler<ChainSpec> {
|
||||
/// Creates a new [`EthBlockAssembler`].
|
||||
pub fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||
Self { chain_spec, extra_data: Default::default() }
|
||||
pub const fn new(chain_spec: Arc<ChainSpec>) -> Self {
|
||||
Self { chain_spec }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +107,7 @@ where
|
||||
gas_limit: evm_env.block_env.gas_limit(),
|
||||
difficulty: evm_env.block_env.difficulty(),
|
||||
gas_used: *gas_used,
|
||||
extra_data: self.extra_data.clone(),
|
||||
extra_data: ctx.extra_data,
|
||||
parent_beacon_block_root: ctx.parent_beacon_block_root,
|
||||
blob_gas_used: block_blob_gas_used,
|
||||
excess_blob_gas,
|
||||
|
||||
@@ -19,32 +19,37 @@ extern crate alloc;
|
||||
|
||||
use alloc::{borrow::Cow, sync::Arc};
|
||||
use alloy_consensus::Header;
|
||||
use alloy_eips::Decodable2718;
|
||||
pub use alloy_evm::EthEvm;
|
||||
use alloy_evm::{
|
||||
eth::{EthBlockExecutionCtx, EthBlockExecutorFactory},
|
||||
EthEvmFactory, FromRecoveredTx, FromTxWithEncoded,
|
||||
};
|
||||
use alloy_primitives::{Bytes, U256};
|
||||
use alloy_rpc_types_engine::ExecutionData;
|
||||
use core::{convert::Infallible, fmt::Debug};
|
||||
use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks, MAINNET};
|
||||
use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET};
|
||||
use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned};
|
||||
use reth_evm::{
|
||||
eth::NextEvmEnvAttributes, precompiles::PrecompilesMap, ConfigureEngineEvm, ConfigureEvm,
|
||||
EvmEnv, EvmEnvFor, EvmFactory, ExecutableTxIterator, ExecutionCtxFor, NextBlockEnvAttributes,
|
||||
TransactionEnv,
|
||||
eth::NextEvmEnvAttributes, precompiles::PrecompilesMap, ConfigureEvm, EvmEnv, EvmFactory,
|
||||
NextBlockEnvAttributes, TransactionEnv,
|
||||
};
|
||||
use reth_primitives_traits::{
|
||||
constants::MAX_TX_GAS_LIMIT_OSAKA, SealedBlock, SealedHeader, SignedTransaction, TxTy,
|
||||
};
|
||||
use reth_storage_errors::any::AnyError;
|
||||
use revm::{
|
||||
context::{BlockEnv, CfgEnv},
|
||||
context_interface::block::BlobExcessGasAndPrice,
|
||||
primitives::hardfork::SpecId,
|
||||
use reth_primitives_traits::{SealedBlock, SealedHeader};
|
||||
use revm::{context::BlockEnv, primitives::hardfork::SpecId};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
use reth_evm::{ConfigureEngineEvm, ExecutableTxIterator};
|
||||
#[allow(unused_imports)]
|
||||
use {
|
||||
alloy_eips::Decodable2718,
|
||||
alloy_primitives::{Bytes, U256},
|
||||
alloy_rpc_types_engine::ExecutionData,
|
||||
reth_chainspec::EthereumHardforks,
|
||||
reth_evm::{EvmEnvFor, ExecutionCtxFor},
|
||||
reth_primitives_traits::{constants::MAX_TX_GAS_LIMIT_OSAKA, SignedTransaction, TxTy},
|
||||
reth_storage_errors::any::AnyError,
|
||||
revm::context::CfgEnv,
|
||||
revm::context_interface::block::BlobExcessGasAndPrice,
|
||||
};
|
||||
|
||||
pub use alloy_evm::EthEvm;
|
||||
|
||||
mod config;
|
||||
use alloy_evm::eth::spec::EthExecutorSpec;
|
||||
pub use config::{revm_spec, revm_spec_by_timestamp_and_block_number};
|
||||
@@ -116,12 +121,6 @@ impl<ChainSpec, EvmFactory> EthEvmConfig<ChainSpec, EvmFactory> {
|
||||
pub const fn chain_spec(&self) -> &Arc<ChainSpec> {
|
||||
self.executor_factory.spec()
|
||||
}
|
||||
|
||||
/// Sets the extra data for the block assembler.
|
||||
pub fn with_extra_data(mut self, extra_data: Bytes) -> Self {
|
||||
self.block_assembler.extra_data = extra_data;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<ChainSpec, EvmF> ConfigureEvm for EthEvmConfig<ChainSpec, EvmF>
|
||||
@@ -193,6 +192,7 @@ where
|
||||
parent_beacon_block_root: block.header().parent_beacon_block_root,
|
||||
ommers: &block.body().ommers,
|
||||
withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
|
||||
extra_data: block.header().extra_data.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -206,10 +206,12 @@ where
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root,
|
||||
ommers: &[],
|
||||
withdrawals: attributes.withdrawals.map(Cow::Owned),
|
||||
extra_data: attributes.extra_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl<ChainSpec, EvmF> ConfigureEngineEvm<ExecutionData> for EthEvmConfig<ChainSpec, EvmF>
|
||||
where
|
||||
ChainSpec: EthExecutorSpec + EthChainSpec<Header = Header> + Hardforks + 'static,
|
||||
@@ -282,6 +284,7 @@ where
|
||||
parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(),
|
||||
ommers: &[],
|
||||
withdrawals: payload.payload.withdrawals().map(|w| Cow::Owned(w.clone().into())),
|
||||
extra_data: payload.payload.as_v1().extra_data.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -289,7 +292,7 @@ where
|
||||
&self,
|
||||
payload: &ExecutionData,
|
||||
) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
|
||||
let txs = payload.payload.transactions().clone().into_iter();
|
||||
let txs = payload.payload.transactions().clone();
|
||||
let convert = |tx: Bytes| {
|
||||
let tx =
|
||||
TxTy::<Self::Primitives>::decode_2718_exact(tx.as_ref()).map_err(AnyError::new)?;
|
||||
|
||||
@@ -24,7 +24,7 @@ reth-provider.workspace = true
|
||||
reth-transaction-pool.workspace = true
|
||||
reth-network.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-evm-ethereum.workspace = true
|
||||
reth-evm-ethereum = { workspace = true, features = ["std"] }
|
||||
reth-rpc.workspace = true
|
||||
reth-rpc-api.workspace = true
|
||||
reth-rpc-eth-api.workspace = true
|
||||
@@ -35,7 +35,7 @@ reth-chainspec.workspace = true
|
||||
reth-revm = { workspace = true, features = ["std"] }
|
||||
reth-rpc-eth-types.workspace = true
|
||||
reth-engine-local.workspace = true
|
||||
reth-engine-primitives.workspace = true
|
||||
reth-engine-primitives = { workspace = true, features = ["std"] }
|
||||
reth-payload-primitives.workspace = true
|
||||
|
||||
# ethereum
|
||||
@@ -61,6 +61,8 @@ reth-node-core.workspace = true
|
||||
reth-e2e-test-utils.workspace = true
|
||||
reth-tasks.workspace = true
|
||||
reth-testing-utils.workspace = true
|
||||
tempfile.workspace = true
|
||||
jsonrpsee-core.workspace = true
|
||||
|
||||
alloy-primitives.workspace = true
|
||||
alloy-provider.workspace = true
|
||||
@@ -86,6 +88,9 @@ asm-keccak = [
|
||||
"reth-node-core/asm-keccak",
|
||||
"revm/asm-keccak",
|
||||
]
|
||||
keccak-cache-global = [
|
||||
"alloy-primitives/keccak-cache-global",
|
||||
]
|
||||
js-tracer = [
|
||||
"reth-node-builder/js-tracer",
|
||||
"reth-rpc/js-tracer",
|
||||
|
||||
@@ -32,15 +32,15 @@ use reth_node_builder::{
|
||||
EngineValidatorBuilder, EthApiBuilder, EthApiCtx, Identity, PayloadValidatorBuilder,
|
||||
RethRpcAddOns, RpcAddOns, RpcHandle,
|
||||
},
|
||||
BuilderContext, DebugNode, Node, NodeAdapter, PayloadBuilderConfig,
|
||||
BuilderContext, DebugNode, Node, NodeAdapter,
|
||||
};
|
||||
use reth_payload_primitives::PayloadTypes;
|
||||
use reth_provider::{providers::ProviderFactoryBuilder, EthStorage};
|
||||
use reth_rpc::{
|
||||
eth::core::{EthApiFor, EthRpcConverterFor},
|
||||
ValidationApi,
|
||||
TestingApi, ValidationApi,
|
||||
};
|
||||
use reth_rpc_api::servers::BlockSubmissionValidationApiServer;
|
||||
use reth_rpc_api::servers::{BlockSubmissionValidationApiServer, TestingApiServer};
|
||||
use reth_rpc_builder::{config::RethRpcServerConfig, middleware::RethRpcMiddleware};
|
||||
use reth_rpc_eth_api::{
|
||||
helpers::{
|
||||
@@ -118,13 +118,14 @@ impl EthereumNode {
|
||||
/// use reth_chainspec::ChainSpecBuilder;
|
||||
/// use reth_db::open_db_read_only;
|
||||
/// use reth_node_ethereum::EthereumNode;
|
||||
/// use reth_provider::providers::StaticFileProvider;
|
||||
/// use reth_provider::providers::{RocksDBProvider, StaticFileProvider};
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// let factory = EthereumNode::provider_factory_builder()
|
||||
/// .db(Arc::new(open_db_read_only("db", Default::default()).unwrap()))
|
||||
/// .chainspec(ChainSpecBuilder::mainnet().build().into())
|
||||
/// .static_file(StaticFileProvider::read_only("db/static_files", false).unwrap())
|
||||
/// .rocksdb_provider(RocksDBProvider::builder("db/rocksdb").build().unwrap())
|
||||
/// .build_provider_factory();
|
||||
/// ```
|
||||
pub fn provider_factory_builder() -> ProviderFactoryBuilder<Self> {
|
||||
@@ -313,6 +314,17 @@ where
|
||||
.modules
|
||||
.merge_if_module_configured(RethRpcModule::Eth, eth_config.into_rpc())?;
|
||||
|
||||
// testing_buildBlockV1: only wire when the hidden testing module is explicitly
|
||||
// requested on any transport. Default stays disabled to honor security guidance.
|
||||
let testing_api = TestingApi::new(
|
||||
container.registry.eth_api().clone(),
|
||||
container.registry.evm_config().clone(),
|
||||
)
|
||||
.into_rpc();
|
||||
container
|
||||
.modules
|
||||
.merge_if_module_configured(RethRpcModule::Testing, testing_api)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
@@ -426,9 +438,7 @@ where
|
||||
type EVM = EthEvmConfig<Types::ChainSpec>;
|
||||
|
||||
async fn build_evm(self, ctx: &BuilderContext<Node>) -> eyre::Result<Self::EVM> {
|
||||
let evm_config = EthEvmConfig::new(ctx.chain_spec())
|
||||
.with_extra_data(ctx.payload_builder_config().extra_data_bytes());
|
||||
Ok(evm_config)
|
||||
Ok(EthEvmConfig::new(ctx.chain_spec()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ where
|
||||
evm_config,
|
||||
EthereumBuilderConfig::new()
|
||||
.with_gas_limit(gas_limit)
|
||||
.with_max_blobs_per_block(conf.max_blobs_per_block()),
|
||||
.with_max_blobs_per_block(conf.max_blobs_per_block())
|
||||
.with_extra_data(conf.extra_data_bytes()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,6 @@
|
||||
|
||||
mod builder;
|
||||
mod exex;
|
||||
mod testing;
|
||||
|
||||
const fn main() {}
|
||||
|
||||
85
crates/ethereum/node/tests/it/testing.rs
Normal file
85
crates/ethereum/node/tests/it/testing.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
//! E2E tests for the testing RPC namespace.
|
||||
|
||||
use alloy_primitives::{Address, B256};
|
||||
use alloy_rpc_types_engine::ExecutionPayloadEnvelopeV4;
|
||||
use jsonrpsee_core::client::ClientT;
|
||||
use reth_db::test_utils::create_test_rw_db;
|
||||
use reth_ethereum_engine_primitives::EthPayloadAttributes;
|
||||
use reth_node_builder::{NodeBuilder, NodeConfig};
|
||||
use reth_node_core::{
|
||||
args::DatadirArgs,
|
||||
dirs::{DataDirPath, MaybePlatformPath},
|
||||
};
|
||||
use reth_node_ethereum::{node::EthereumAddOns, EthereumNode};
|
||||
use reth_rpc_api::TestingBuildBlockRequestV1;
|
||||
use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection};
|
||||
use reth_tasks::TaskManager;
|
||||
use std::str::FromStr;
|
||||
use tempfile::tempdir;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn testing_rpc_build_block_works() -> eyre::Result<()> {
|
||||
let tasks = TaskManager::current();
|
||||
let mut rpc_args = reth_node_core::args::RpcServerArgs::default().with_http();
|
||||
rpc_args.http_api = Some(RpcModuleSelection::from_iter([RethRpcModule::Testing]));
|
||||
let tempdir = tempdir().expect("temp datadir");
|
||||
let datadir_args = DatadirArgs {
|
||||
datadir: MaybePlatformPath::<DataDirPath>::from_str(tempdir.path().to_str().unwrap())
|
||||
.expect("valid datadir"),
|
||||
static_files_path: Some(tempdir.path().join("static")),
|
||||
rocksdb_path: Some(tempdir.path().join("rocksdb")),
|
||||
};
|
||||
let config = NodeConfig::test().with_datadir_args(datadir_args).with_rpc(rpc_args);
|
||||
let db = create_test_rw_db();
|
||||
|
||||
let (tx, rx): (
|
||||
oneshot::Sender<eyre::Result<ExecutionPayloadEnvelopeV4>>,
|
||||
oneshot::Receiver<eyre::Result<ExecutionPayloadEnvelopeV4>>,
|
||||
) = oneshot::channel();
|
||||
|
||||
let builder = NodeBuilder::new(config)
|
||||
.with_database(db)
|
||||
.with_launch_context(tasks.executor())
|
||||
.with_types::<EthereumNode>()
|
||||
.with_components(EthereumNode::components())
|
||||
.with_add_ons(EthereumAddOns::default())
|
||||
.on_rpc_started(move |ctx, handles| {
|
||||
let Some(client) = handles.rpc.http_client() else { return Ok(()) };
|
||||
|
||||
let chain = ctx.config().chain.clone();
|
||||
let parent_block_hash = chain.genesis_hash();
|
||||
let payload_attributes = EthPayloadAttributes {
|
||||
timestamp: chain.genesis().timestamp + 1,
|
||||
prev_randao: B256::ZERO,
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: None,
|
||||
parent_beacon_block_root: None,
|
||||
};
|
||||
|
||||
let request = TestingBuildBlockRequestV1 {
|
||||
parent_block_hash,
|
||||
payload_attributes,
|
||||
transactions: vec![],
|
||||
extra_data: None,
|
||||
};
|
||||
|
||||
tokio::spawn(async move {
|
||||
let res: eyre::Result<ExecutionPayloadEnvelopeV4> =
|
||||
client.request("testing_buildBlockV1", [request]).await.map_err(Into::into);
|
||||
let _ = tx.send(res);
|
||||
});
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
// Launch the node with the default engine launcher.
|
||||
let launcher = builder.engine_api_launcher();
|
||||
let _node = builder.launch_with(launcher).await?;
|
||||
|
||||
// Wait for the testing RPC call to return.
|
||||
let res = rx.await.expect("testing_buildBlockV1 response");
|
||||
assert!(res.is_ok(), "testing_buildBlockV1 failed: {:?}", res.err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -24,7 +24,7 @@ reth-payload-builder-primitives.workspace = true
|
||||
reth-payload-primitives.workspace = true
|
||||
reth-basic-payload-builder.workspace = true
|
||||
reth-evm.workspace = true
|
||||
reth-evm-ethereum.workspace = true
|
||||
reth-evm-ethereum = { workspace = true, features = ["std"] }
|
||||
reth-errors.workspace = true
|
||||
reth-chainspec.workspace = true
|
||||
reth-payload-validator.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use alloy_eips::eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M;
|
||||
use alloy_primitives::Bytes;
|
||||
use reth_primitives_traits::constants::GAS_LIMIT_BOUND_DIVISOR;
|
||||
|
||||
/// Settings for the Ethereum builder.
|
||||
@@ -13,6 +14,8 @@ pub struct EthereumBuilderConfig {
|
||||
///
|
||||
/// If `None`, defaults to the protocol maximum.
|
||||
pub max_blobs_per_block: Option<u64>,
|
||||
/// Extra data for built blocks.
|
||||
pub extra_data: Bytes,
|
||||
}
|
||||
|
||||
impl Default for EthereumBuilderConfig {
|
||||
@@ -28,6 +31,7 @@ impl EthereumBuilderConfig {
|
||||
desired_gas_limit: ETHEREUM_BLOCK_GAS_LIMIT_30M,
|
||||
await_payload_on_missing: true,
|
||||
max_blobs_per_block: None,
|
||||
extra_data: Bytes::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +53,12 @@ impl EthereumBuilderConfig {
|
||||
self.max_blobs_per_block = max_blobs_per_block;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the extra data for built blocks.
|
||||
pub fn with_extra_data(mut self, extra_data: Bytes) -> Self {
|
||||
self.extra_data = extra_data;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl EthereumBuilderConfig {
|
||||
|
||||
@@ -168,6 +168,7 @@ where
|
||||
gas_limit: builder_config.gas_limit(parent_header.gas_limit),
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root(),
|
||||
withdrawals: Some(attributes.withdrawals().clone()),
|
||||
extra_data: builder_config.extra_data,
|
||||
},
|
||||
)
|
||||
.map_err(PayloadBuilderError::other)?;
|
||||
|
||||
@@ -79,7 +79,9 @@ arbitrary = [
|
||||
"alloy-rpc-types-engine?/arbitrary",
|
||||
"reth-codecs?/arbitrary",
|
||||
]
|
||||
|
||||
keccak-cache-global = [
|
||||
"reth-node-ethereum?/keccak-cache-global",
|
||||
]
|
||||
test-utils = [
|
||||
"reth-chainspec/test-utils",
|
||||
"reth-consensus?/test-utils",
|
||||
|
||||
@@ -32,6 +32,7 @@ auto_impl.workspace = true
|
||||
derive_more.workspace = true
|
||||
futures-util.workspace = true
|
||||
metrics = { workspace = true, optional = true }
|
||||
rayon = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
reth-ethereum-primitives.workspace = true
|
||||
@@ -40,6 +41,7 @@ reth-ethereum-forks.workspace = true
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"dep:rayon",
|
||||
"reth-primitives-traits/std",
|
||||
"alloy-eips/std",
|
||||
"alloy-primitives/std",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{execute::ExecutableTxFor, ConfigureEvm, EvmEnvFor, ExecutionCtxFor};
|
||||
use rayon::prelude::*;
|
||||
|
||||
/// [`ConfigureEvm`] extension providing methods for executing payloads.
|
||||
pub trait ConfigureEngineEvm<ExecutionData>: ConfigureEvm {
|
||||
@@ -21,7 +22,7 @@ pub trait ConfigureEngineEvm<ExecutionData>: ConfigureEvm {
|
||||
/// A helper trait representing a pair of a "raw" transactions iterator and a closure that can be
|
||||
/// used to convert them to an executable transaction. This tuple is used in the engine to
|
||||
/// parallelize heavy work like decoding or recovery.
|
||||
pub trait ExecutableTxTuple: Into<(Self::Iter, Self::Convert)> + Send + 'static {
|
||||
pub trait ExecutableTxTuple: Into<(Self::IntoIter, Self::Convert)> + Send + 'static {
|
||||
/// Raw transaction that can be converted to an [`ExecutableTxTuple::Tx`]
|
||||
///
|
||||
/// This can be any type that can be converted to an [`ExecutableTxTuple::Tx`]. For example,
|
||||
@@ -32,8 +33,10 @@ pub trait ExecutableTxTuple: Into<(Self::Iter, Self::Convert)> + Send + 'static
|
||||
/// Errors that may occur while recovering or decoding transactions.
|
||||
type Error: core::error::Error + Send + Sync + 'static;
|
||||
|
||||
/// Iterator over [`ExecutableTxTuple::Tx`]
|
||||
type Iter: Iterator<Item = Self::RawTx> + Send + 'static;
|
||||
/// Iterator over [`ExecutableTxTuple::Tx`].
|
||||
type IntoIter: IntoParallelIterator<Item = Self::RawTx, Iter: IndexedParallelIterator>
|
||||
+ Send
|
||||
+ 'static;
|
||||
/// Closure that can be used to convert a [`ExecutableTxTuple::RawTx`] to a
|
||||
/// [`ExecutableTxTuple::Tx`]. This might involve heavy work like decoding or recovery
|
||||
/// and will be parallelized in the engine.
|
||||
@@ -45,14 +48,14 @@ where
|
||||
RawTx: Send + Sync + 'static,
|
||||
Tx: Clone + Send + Sync + 'static,
|
||||
Err: core::error::Error + Send + Sync + 'static,
|
||||
I: Iterator<Item = RawTx> + Send + 'static,
|
||||
I: IntoParallelIterator<Item = RawTx, Iter: IndexedParallelIterator> + Send + 'static,
|
||||
F: Fn(RawTx) -> Result<Tx, Err> + Send + Sync + 'static,
|
||||
{
|
||||
type RawTx = RawTx;
|
||||
type Tx = Tx;
|
||||
type Error = Err;
|
||||
|
||||
type Iter = I;
|
||||
type IntoIter = I;
|
||||
type Convert = F;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ use alloy_evm::{
|
||||
block::{BlockExecutorFactory, BlockExecutorFor},
|
||||
precompiles::PrecompilesMap,
|
||||
};
|
||||
use alloy_primitives::{Address, B256};
|
||||
use alloy_primitives::{Address, Bytes, B256};
|
||||
use core::{error::Error, fmt::Debug};
|
||||
use execute::{BasicBlockExecutor, BlockAssembler, BlockBuilder};
|
||||
use reth_execution_errors::BlockExecutionError;
|
||||
@@ -44,8 +44,10 @@ pub mod execute;
|
||||
mod aliases;
|
||||
pub use aliases::*;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod engine;
|
||||
pub use engine::{ConfigureEngineEvm, ExecutableTxIterator};
|
||||
#[cfg(feature = "std")]
|
||||
pub use engine::{ConfigureEngineEvm, ExecutableTxIterator, ExecutableTxTuple};
|
||||
|
||||
#[cfg(feature = "metrics")]
|
||||
pub mod metrics;
|
||||
@@ -501,6 +503,8 @@ pub struct NextBlockEnvAttributes {
|
||||
pub parent_beacon_block_root: Option<B256>,
|
||||
/// Withdrawals
|
||||
pub withdrawals: Option<Withdrawals>,
|
||||
/// Optional extra data.
|
||||
pub extra_data: Bytes,
|
||||
}
|
||||
|
||||
/// Abstraction over transaction environment.
|
||||
|
||||
@@ -17,6 +17,14 @@ pub struct ExecutorMetrics {
|
||||
/// The Histogram for amount of gas used.
|
||||
pub gas_used_histogram: Histogram,
|
||||
|
||||
/// The Histogram for amount of time taken to execute the pre-execution changes.
|
||||
pub pre_execution_histogram: Histogram,
|
||||
/// The Histogram for amount of time taken to wait for one transaction to be available.
|
||||
pub transaction_wait_histogram: Histogram,
|
||||
/// The Histogram for amount of time taken to execute one transaction.
|
||||
pub transaction_execution_histogram: Histogram,
|
||||
/// The Histogram for amount of time taken to execute the post-execution changes.
|
||||
pub post_execution_histogram: Histogram,
|
||||
/// The Histogram for amount of time taken to execute blocks.
|
||||
pub execution_histogram: Histogram,
|
||||
/// The total amount of time it took to execute the latest block.
|
||||
|
||||
@@ -20,7 +20,9 @@ use futures_util::FutureExt;
|
||||
use reth_chainspec::{ChainSpec, MAINNET};
|
||||
use reth_consensus::test_utils::TestConsensus;
|
||||
use reth_db::{
|
||||
test_utils::{create_test_rw_db, create_test_static_files_dir, TempDatabase},
|
||||
test_utils::{
|
||||
create_test_rocksdb_dir, create_test_rw_db, create_test_static_files_dir, TempDatabase,
|
||||
},
|
||||
DatabaseEnv,
|
||||
};
|
||||
use reth_db_common::init::init_genesis;
|
||||
@@ -50,7 +52,7 @@ use reth_node_ethereum::{
|
||||
use reth_payload_builder::noop::NoopPayloadBuilderService;
|
||||
use reth_primitives_traits::{Block as _, RecoveredBlock};
|
||||
use reth_provider::{
|
||||
providers::{BlockchainProvider, StaticFileProvider},
|
||||
providers::{BlockchainProvider, RocksDBProvider, StaticFileProvider},
|
||||
BlockReader, EthStorage, ProviderFactory,
|
||||
};
|
||||
use reth_tasks::TaskManager;
|
||||
@@ -239,11 +241,13 @@ pub async fn test_exex_context_with_chain_spec(
|
||||
let consensus = Arc::new(TestConsensus::default());
|
||||
|
||||
let (static_dir, _) = create_test_static_files_dir();
|
||||
let (rocksdb_dir, _) = create_test_rocksdb_dir();
|
||||
let db = create_test_rw_db();
|
||||
let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<TestNode, _>>::new(
|
||||
db,
|
||||
chain_spec.clone(),
|
||||
StaticFileProvider::read_write(static_dir.keep()).expect("static file provider"),
|
||||
RocksDBProvider::builder(rocksdb_dir.keep()).build().unwrap(),
|
||||
)?;
|
||||
|
||||
let genesis_hash = init_genesis(&provider_factory)?;
|
||||
|
||||
@@ -95,17 +95,33 @@ pub(super) mod serde_bincode_compat {
|
||||
/// notification: ExExNotification<N>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This enum mirrors [`super::ExExNotification`] but uses borrowed [`Chain`] types
|
||||
/// instead of `Arc<Chain>` for bincode compatibility.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[expect(missing_docs)]
|
||||
#[serde(bound = "")]
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
pub enum ExExNotification<'a, N>
|
||||
where
|
||||
N: NodePrimitives,
|
||||
{
|
||||
ChainCommitted { new: Chain<'a, N> },
|
||||
ChainReorged { old: Chain<'a, N>, new: Chain<'a, N> },
|
||||
ChainReverted { old: Chain<'a, N> },
|
||||
/// Chain got committed without a reorg, and only the new chain is returned.
|
||||
ChainCommitted {
|
||||
/// The new chain after commit.
|
||||
new: Chain<'a, N>,
|
||||
},
|
||||
/// Chain got reorged, and both the old and the new chains are returned.
|
||||
ChainReorged {
|
||||
/// The old chain before reorg.
|
||||
old: Chain<'a, N>,
|
||||
/// The new chain after reorg.
|
||||
new: Chain<'a, N>,
|
||||
},
|
||||
/// Chain got reverted, and only the old chain is returned.
|
||||
ChainReverted {
|
||||
/// The old chain before reversion.
|
||||
old: Chain<'a, N>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a, N> From<&'a super::ExExNotification<N>> for ExExNotification<'a, N>
|
||||
|
||||
@@ -24,7 +24,7 @@ pub struct Discv4Config {
|
||||
/// The number of allowed consecutive failures for `FindNode` requests. Default: 5.
|
||||
pub max_find_node_failures: u8,
|
||||
/// The interval to use when checking for expired nodes that need to be re-pinged. Default:
|
||||
/// 10min.
|
||||
/// 10 seconds.
|
||||
pub ping_interval: Duration,
|
||||
/// The duration of we consider a ping timed out.
|
||||
pub ping_expiration: Duration,
|
||||
@@ -93,7 +93,7 @@ impl Discv4Config {
|
||||
/// Returns the corresponding [`ResolveNatInterval`], if a [`NatResolver`] and an interval was
|
||||
/// configured
|
||||
pub fn resolve_external_ip_interval(&self) -> Option<ResolveNatInterval> {
|
||||
let resolver = self.external_ip_resolver?;
|
||||
let resolver = self.external_ip_resolver.clone()?;
|
||||
let interval = self.resolve_external_ip_interval?;
|
||||
Some(ResolveNatInterval::interval_at(resolver, tokio::time::Instant::now(), interval))
|
||||
}
|
||||
@@ -275,10 +275,7 @@ impl Discv4ConfigBuilder {
|
||||
}
|
||||
|
||||
/// Configures if and how the external IP of the node should be resolved.
|
||||
pub const fn external_ip_resolver(
|
||||
&mut self,
|
||||
external_ip_resolver: Option<NatResolver>,
|
||||
) -> &mut Self {
|
||||
pub fn external_ip_resolver(&mut self, external_ip_resolver: Option<NatResolver>) -> &mut Self {
|
||||
self.config.external_ip_resolver = external_ip_resolver;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -625,10 +625,13 @@ impl Discv4Service {
|
||||
self.lookup_interval = tokio::time::interval(duration);
|
||||
}
|
||||
|
||||
/// Sets the external Ip to the configured external IP if [`NatResolver::ExternalIp`].
|
||||
/// Sets the external Ip to the configured external IP if [`NatResolver::ExternalIp`] or
|
||||
/// [`NatResolver::ExternalAddr`]. In the case of [`NatResolver::ExternalAddr`], it will return
|
||||
/// the first IP address found for the domain associated with the discv4 UDP port.
|
||||
fn resolve_external_ip(&mut self) {
|
||||
if let Some(r) = &self.resolve_external_ip_interval &&
|
||||
let Some(external_ip) = r.resolver().as_external_ip()
|
||||
let Some(external_ip) =
|
||||
r.resolver().clone().as_external_ip(self.local_node_record.udp_port)
|
||||
{
|
||||
self.set_external_ip_addr(external_ip);
|
||||
}
|
||||
|
||||
@@ -1218,7 +1218,9 @@ impl ReverseHeadersDownloaderBuilder {
|
||||
next_request_block_number: 0,
|
||||
next_chain_tip_block_number: 0,
|
||||
lowest_validated_header: None,
|
||||
request_limit,
|
||||
// TODO(mattsse): tmp hotfix to prevent issues with syncing from besu which has an upper
|
||||
// limit of 512
|
||||
request_limit: request_limit.min(512),
|
||||
min_concurrent_requests,
|
||||
max_concurrent_requests,
|
||||
stream_batch_size,
|
||||
|
||||
@@ -169,7 +169,7 @@ impl NewPooledTransactionHashes {
|
||||
matches!(version, EthVersion::Eth67 | EthVersion::Eth66)
|
||||
}
|
||||
Self::Eth68(_) => {
|
||||
matches!(version, EthVersion::Eth68 | EthVersion::Eth69)
|
||||
matches!(version, EthVersion::Eth68 | EthVersion::Eth69 | EthVersion::Eth70)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,6 +100,16 @@ impl Capability {
|
||||
Self::eth(EthVersion::Eth68)
|
||||
}
|
||||
|
||||
/// Returns the [`EthVersion::Eth69`] capability.
|
||||
pub const fn eth_69() -> Self {
|
||||
Self::eth(EthVersion::Eth69)
|
||||
}
|
||||
|
||||
/// Returns the [`EthVersion::Eth70`] capability.
|
||||
pub const fn eth_70() -> Self {
|
||||
Self::eth(EthVersion::Eth70)
|
||||
}
|
||||
|
||||
/// Whether this is eth v66 protocol.
|
||||
#[inline]
|
||||
pub fn is_eth_v66(&self) -> bool {
|
||||
@@ -118,10 +128,26 @@ impl Capability {
|
||||
self.name == "eth" && self.version == 68
|
||||
}
|
||||
|
||||
/// Whether this is eth v69.
|
||||
#[inline]
|
||||
pub fn is_eth_v69(&self) -> bool {
|
||||
self.name == "eth" && self.version == 69
|
||||
}
|
||||
|
||||
/// Whether this is eth v70.
|
||||
#[inline]
|
||||
pub fn is_eth_v70(&self) -> bool {
|
||||
self.name == "eth" && self.version == 70
|
||||
}
|
||||
|
||||
/// Whether this is any eth version.
|
||||
#[inline]
|
||||
pub fn is_eth(&self) -> bool {
|
||||
self.is_eth_v66() || self.is_eth_v67() || self.is_eth_v68()
|
||||
self.is_eth_v66() ||
|
||||
self.is_eth_v67() ||
|
||||
self.is_eth_v68() ||
|
||||
self.is_eth_v69() ||
|
||||
self.is_eth_v70()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +167,7 @@ impl From<EthVersion> for Capability {
|
||||
#[cfg(any(test, feature = "arbitrary"))]
|
||||
impl<'a> arbitrary::Arbitrary<'a> for Capability {
|
||||
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
|
||||
let version = u.int_in_range(66..=69)?; // Valid eth protocol versions are 66-69
|
||||
let version = u.int_in_range(66..=70)?; // Valid eth protocol versions are 66-70
|
||||
// Only generate valid eth protocol name for now since it's the only supported protocol
|
||||
Ok(Self::new_static("eth", version))
|
||||
}
|
||||
@@ -155,6 +181,8 @@ pub struct Capabilities {
|
||||
eth_66: bool,
|
||||
eth_67: bool,
|
||||
eth_68: bool,
|
||||
eth_69: bool,
|
||||
eth_70: bool,
|
||||
}
|
||||
|
||||
impl Capabilities {
|
||||
@@ -164,6 +192,8 @@ impl Capabilities {
|
||||
eth_66: value.iter().any(Capability::is_eth_v66),
|
||||
eth_67: value.iter().any(Capability::is_eth_v67),
|
||||
eth_68: value.iter().any(Capability::is_eth_v68),
|
||||
eth_69: value.iter().any(Capability::is_eth_v69),
|
||||
eth_70: value.iter().any(Capability::is_eth_v70),
|
||||
inner: value,
|
||||
}
|
||||
}
|
||||
@@ -182,7 +212,7 @@ impl Capabilities {
|
||||
/// Whether the peer supports `eth` sub-protocol.
|
||||
#[inline]
|
||||
pub const fn supports_eth(&self) -> bool {
|
||||
self.eth_68 || self.eth_67 || self.eth_66
|
||||
self.eth_70 || self.eth_69 || self.eth_68 || self.eth_67 || self.eth_66
|
||||
}
|
||||
|
||||
/// Whether this peer supports eth v66 protocol.
|
||||
@@ -202,6 +232,18 @@ impl Capabilities {
|
||||
pub const fn supports_eth_v68(&self) -> bool {
|
||||
self.eth_68
|
||||
}
|
||||
|
||||
/// Whether this peer supports eth v69 protocol.
|
||||
#[inline]
|
||||
pub const fn supports_eth_v69(&self) -> bool {
|
||||
self.eth_69
|
||||
}
|
||||
|
||||
/// Whether this peer supports eth v70 protocol.
|
||||
#[inline]
|
||||
pub const fn supports_eth_v70(&self) -> bool {
|
||||
self.eth_70
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Capability>> for Capabilities {
|
||||
@@ -224,6 +266,8 @@ impl Decodable for Capabilities {
|
||||
eth_66: inner.iter().any(Capability::is_eth_v66),
|
||||
eth_67: inner.iter().any(Capability::is_eth_v67),
|
||||
eth_68: inner.iter().any(Capability::is_eth_v68),
|
||||
eth_69: inner.iter().any(Capability::is_eth_v69),
|
||||
eth_70: inner.iter().any(Capability::is_eth_v70),
|
||||
inner,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Implements Ethereum wire protocol for versions 66, 67, and 68.
|
||||
//! Implements Ethereum wire protocol for versions 66 through 70.
|
||||
//! Defines structs/enums for messages, request-response pairs, and broadcasts.
|
||||
//! Handles compatibility with [`EthVersion`].
|
||||
//!
|
||||
@@ -8,13 +8,13 @@
|
||||
|
||||
use super::{
|
||||
broadcast::NewBlockHashes, BlockBodies, BlockHeaders, GetBlockBodies, GetBlockHeaders,
|
||||
GetNodeData, GetPooledTransactions, GetReceipts, NewPooledTransactionHashes66,
|
||||
GetNodeData, GetPooledTransactions, GetReceipts, GetReceipts70, NewPooledTransactionHashes66,
|
||||
NewPooledTransactionHashes68, NodeData, PooledTransactions, Receipts, Status, StatusEth69,
|
||||
Transactions,
|
||||
};
|
||||
use crate::{
|
||||
status::StatusMessage, BlockRangeUpdate, EthNetworkPrimitives, EthVersion, NetworkPrimitives,
|
||||
RawCapabilityMessage, Receipts69, SharedTransactions,
|
||||
RawCapabilityMessage, Receipts69, Receipts70, SharedTransactions,
|
||||
};
|
||||
use alloc::{boxed::Box, string::String, sync::Arc};
|
||||
use alloy_primitives::{
|
||||
@@ -111,13 +111,29 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
}
|
||||
EthMessage::NodeData(RequestPair::decode(buf)?)
|
||||
}
|
||||
EthMessageID::GetReceipts => EthMessage::GetReceipts(RequestPair::decode(buf)?),
|
||||
EthMessageID::Receipts => {
|
||||
if version < EthVersion::Eth69 {
|
||||
EthMessage::Receipts(RequestPair::decode(buf)?)
|
||||
EthMessageID::GetReceipts => {
|
||||
if version >= EthVersion::Eth70 {
|
||||
EthMessage::GetReceipts70(RequestPair::decode(buf)?)
|
||||
} else {
|
||||
// with eth69, receipts no longer include the bloom
|
||||
EthMessage::Receipts69(RequestPair::decode(buf)?)
|
||||
EthMessage::GetReceipts(RequestPair::decode(buf)?)
|
||||
}
|
||||
}
|
||||
EthMessageID::Receipts => {
|
||||
match version {
|
||||
v if v >= EthVersion::Eth70 => {
|
||||
// eth/70 continues to omit bloom filters and adds the
|
||||
// `lastBlockIncomplete` flag, encoded as
|
||||
// `[request-id, lastBlockIncomplete, [[receipt₁, receipt₂], ...]]`.
|
||||
EthMessage::Receipts70(RequestPair::decode(buf)?)
|
||||
}
|
||||
EthVersion::Eth69 => {
|
||||
// with eth69, receipts no longer include the bloom
|
||||
EthMessage::Receipts69(RequestPair::decode(buf)?)
|
||||
}
|
||||
_ => {
|
||||
// before eth69 we need to decode the bloom as well
|
||||
EthMessage::Receipts(RequestPair::decode(buf)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
EthMessageID::BlockRangeUpdate => {
|
||||
@@ -205,6 +221,9 @@ impl<N: NetworkPrimitives> From<EthBroadcastMessage<N>> for ProtocolBroadcastMes
|
||||
///
|
||||
/// The `eth/69` announces the historical block range served by the node. Removes total difficulty
|
||||
/// information. And removes the Bloom field from receipts transferred over the protocol.
|
||||
///
|
||||
/// The `eth/70` (EIP-7975) keeps the eth/69 status format and introduces partial receipts.
|
||||
/// requests/responses.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
@@ -259,6 +278,12 @@ pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
NodeData(RequestPair<NodeData>),
|
||||
/// Represents a `GetReceipts` request-response pair.
|
||||
GetReceipts(RequestPair<GetReceipts>),
|
||||
/// Represents a `GetReceipts` request for eth/70.
|
||||
///
|
||||
/// Note: Unlike earlier protocol versions, the eth/70 encoding for
|
||||
/// `GetReceipts` in EIP-7975 inlines the request id. The type still wraps
|
||||
/// a [`RequestPair`], but with a custom inline encoding.
|
||||
GetReceipts70(RequestPair<GetReceipts70>),
|
||||
/// Represents a Receipts request-response pair.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
@@ -271,6 +296,16 @@ pub enum EthMessage<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
serde(bound = "N::Receipt: serde::Serialize + serde::de::DeserializeOwned")
|
||||
)]
|
||||
Receipts69(RequestPair<Receipts69<N::Receipt>>),
|
||||
/// Represents a Receipts request-response pair for eth/70.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
serde(bound = "N::Receipt: serde::Serialize + serde::de::DeserializeOwned")
|
||||
)]
|
||||
///
|
||||
/// Note: The eth/70 encoding for `Receipts` in EIP-7975 inlines the
|
||||
/// request id. The type still wraps a [`RequestPair`], but with a custom
|
||||
/// inline encoding.
|
||||
Receipts70(RequestPair<Receipts70<N::Receipt>>),
|
||||
/// Represents a `BlockRangeUpdate` message broadcast to the network.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
@@ -300,8 +335,8 @@ impl<N: NetworkPrimitives> EthMessage<N> {
|
||||
Self::PooledTransactions(_) => EthMessageID::PooledTransactions,
|
||||
Self::GetNodeData(_) => EthMessageID::GetNodeData,
|
||||
Self::NodeData(_) => EthMessageID::NodeData,
|
||||
Self::GetReceipts(_) => EthMessageID::GetReceipts,
|
||||
Self::Receipts(_) | Self::Receipts69(_) => EthMessageID::Receipts,
|
||||
Self::GetReceipts(_) | Self::GetReceipts70(_) => EthMessageID::GetReceipts,
|
||||
Self::Receipts(_) | Self::Receipts69(_) | Self::Receipts70(_) => EthMessageID::Receipts,
|
||||
Self::BlockRangeUpdate(_) => EthMessageID::BlockRangeUpdate,
|
||||
Self::Other(msg) => EthMessageID::Other(msg.id as u8),
|
||||
}
|
||||
@@ -314,6 +349,7 @@ impl<N: NetworkPrimitives> EthMessage<N> {
|
||||
Self::GetBlockBodies(_) |
|
||||
Self::GetBlockHeaders(_) |
|
||||
Self::GetReceipts(_) |
|
||||
Self::GetReceipts70(_) |
|
||||
Self::GetPooledTransactions(_) |
|
||||
Self::GetNodeData(_)
|
||||
)
|
||||
@@ -326,11 +362,40 @@ impl<N: NetworkPrimitives> EthMessage<N> {
|
||||
Self::PooledTransactions(_) |
|
||||
Self::Receipts(_) |
|
||||
Self::Receipts69(_) |
|
||||
Self::Receipts70(_) |
|
||||
Self::BlockHeaders(_) |
|
||||
Self::BlockBodies(_) |
|
||||
Self::NodeData(_)
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts the message types where applicable.
|
||||
///
|
||||
/// This handles up/downcasting where appropriate, for example for different receipt request
|
||||
/// types.
|
||||
pub fn map_versioned(self, version: EthVersion) -> Self {
|
||||
// For eth/70 peers we send `GetReceipts` using the new eth/70
|
||||
// encoding with `firstBlockReceiptIndex = 0`, while keeping the
|
||||
// user-facing `PeerRequest` API unchanged.
|
||||
if version >= EthVersion::Eth70 {
|
||||
return match self {
|
||||
Self::GetReceipts(pair) => {
|
||||
let RequestPair { request_id, message } = pair;
|
||||
let req = RequestPair {
|
||||
request_id,
|
||||
message: GetReceipts70 {
|
||||
first_block_receipt_index: 0,
|
||||
block_hashes: message.0,
|
||||
},
|
||||
};
|
||||
Self::GetReceipts70(req)
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
|
||||
@@ -351,8 +416,10 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
|
||||
Self::GetNodeData(request) => request.encode(out),
|
||||
Self::NodeData(data) => data.encode(out),
|
||||
Self::GetReceipts(request) => request.encode(out),
|
||||
Self::GetReceipts70(request) => request.encode(out),
|
||||
Self::Receipts(receipts) => receipts.encode(out),
|
||||
Self::Receipts69(receipt69) => receipt69.encode(out),
|
||||
Self::Receipts70(receipt70) => receipt70.encode(out),
|
||||
Self::BlockRangeUpdate(block_range_update) => block_range_update.encode(out),
|
||||
Self::Other(unknown) => out.put_slice(&unknown.payload),
|
||||
}
|
||||
@@ -374,8 +441,10 @@ impl<N: NetworkPrimitives> Encodable for EthMessage<N> {
|
||||
Self::GetNodeData(request) => request.length(),
|
||||
Self::NodeData(data) => data.length(),
|
||||
Self::GetReceipts(request) => request.length(),
|
||||
Self::GetReceipts70(request) => request.length(),
|
||||
Self::Receipts(receipts) => receipts.length(),
|
||||
Self::Receipts69(receipt69) => receipt69.length(),
|
||||
Self::Receipts70(receipt70) => receipt70.length(),
|
||||
Self::BlockRangeUpdate(block_range_update) => block_range_update.length(),
|
||||
Self::Other(unknown) => unknown.length(),
|
||||
}
|
||||
|
||||
@@ -17,6 +17,42 @@ pub struct GetReceipts(
|
||||
pub Vec<B256>,
|
||||
);
|
||||
|
||||
/// Eth/70 `GetReceipts` request payload that supports partial receipt queries.
|
||||
///
|
||||
/// When used with eth/70, the request id is carried by the surrounding
|
||||
/// [`crate::message::RequestPair`], and the on-wire shape is the flattened list
|
||||
/// `firstBlockReceiptIndex, [blockhash₁, ...]`.
|
||||
///
|
||||
/// See also [eip-7975](https://eips.ethereum.org/EIPS/eip-7975)
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
|
||||
pub struct GetReceipts70 {
|
||||
/// Index into the receipts of the first requested block hash.
|
||||
pub first_block_receipt_index: u64,
|
||||
/// The block hashes to request receipts for.
|
||||
pub block_hashes: Vec<B256>,
|
||||
}
|
||||
|
||||
impl alloy_rlp::Encodable for GetReceipts70 {
|
||||
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
|
||||
self.first_block_receipt_index.encode(out);
|
||||
self.block_hashes.encode(out);
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
self.first_block_receipt_index.length() + self.block_hashes.length()
|
||||
}
|
||||
}
|
||||
|
||||
impl alloy_rlp::Decodable for GetReceipts70 {
|
||||
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||
let first_block_receipt_index = u64::decode(buf)?;
|
||||
let block_hashes = Vec::<B256>::decode(buf)?;
|
||||
Ok(Self { first_block_receipt_index, block_hashes })
|
||||
}
|
||||
}
|
||||
|
||||
/// The response to [`GetReceipts`], containing receipt lists that correspond to each block
|
||||
/// requested.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Default)]
|
||||
@@ -58,7 +94,13 @@ pub struct Receipts69<T = Receipt>(pub Vec<Vec<T>>);
|
||||
impl<T: TxReceipt> Receipts69<T> {
|
||||
/// Encodes all receipts with the bloom filter.
|
||||
///
|
||||
/// Note: This is an expensive operation that recalculates the bloom for each receipt.
|
||||
/// Eth/69 omits bloom filters on the wire, while some internal callers
|
||||
/// (and legacy APIs) still operate on [`Receipts`] with
|
||||
/// [`ReceiptWithBloom`]. This helper reconstructs the bloom locally from
|
||||
/// each receipt's logs so the older API can be used on top of eth/69 data.
|
||||
///
|
||||
/// Note: This is an expensive operation that recalculates the bloom for
|
||||
/// every receipt.
|
||||
pub fn into_with_bloom(self) -> Receipts<T> {
|
||||
Receipts(
|
||||
self.0
|
||||
@@ -75,6 +117,68 @@ impl<T: TxReceipt> From<Receipts69<T>> for Receipts<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Eth/70 `Receipts` response payload.
|
||||
///
|
||||
/// This is used in conjunction with [`crate::message::RequestPair`] to encode the full wire
|
||||
/// message `[request-id, lastBlockIncomplete, [[receipt₁, receipt₂], ...]]`.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
|
||||
pub struct Receipts70<T = Receipt> {
|
||||
/// Whether the receipts list for the last block is incomplete.
|
||||
pub last_block_incomplete: bool,
|
||||
/// Receipts grouped by block.
|
||||
pub receipts: Vec<Vec<T>>,
|
||||
}
|
||||
|
||||
impl<T> alloy_rlp::Encodable for Receipts70<T>
|
||||
where
|
||||
T: alloy_rlp::Encodable,
|
||||
{
|
||||
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
|
||||
self.last_block_incomplete.encode(out);
|
||||
self.receipts.encode(out);
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
self.last_block_incomplete.length() + self.receipts.length()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> alloy_rlp::Decodable for Receipts70<T>
|
||||
where
|
||||
T: alloy_rlp::Decodable,
|
||||
{
|
||||
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
|
||||
let last_block_incomplete = bool::decode(buf)?;
|
||||
let receipts = Vec::<Vec<T>>::decode(buf)?;
|
||||
Ok(Self { last_block_incomplete, receipts })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxReceipt> Receipts70<T> {
|
||||
/// Encodes all receipts with the bloom filter.
|
||||
///
|
||||
/// Just like eth/69, eth/70 does not transmit bloom filters over the wire.
|
||||
/// When higher layers still expect the older bloom-bearing [`Receipts`]
|
||||
/// type, this helper converts the eth/70 payload into that shape by
|
||||
/// recomputing the bloom locally from the contained receipts.
|
||||
///
|
||||
/// Note: This is an expensive operation that recalculates the bloom for
|
||||
/// every receipt.
|
||||
pub fn into_with_bloom(self) -> Receipts<T> {
|
||||
// Reuse the eth/69 helper, since both variants carry the same
|
||||
// receipt list shape (only eth/70 adds request metadata).
|
||||
Receipts69(self.receipts).into_with_bloom()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TxReceipt> From<Receipts70<T>> for Receipts<T> {
|
||||
fn from(receipts: Receipts70<T>) -> Self {
|
||||
receipts.into_with_bloom()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -225,4 +329,70 @@ mod tests {
|
||||
let encoded = alloy_rlp::encode(&request);
|
||||
assert_eq!(encoded, data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_get_receipts70_inline_shape() {
|
||||
let req = RequestPair {
|
||||
request_id: 1111,
|
||||
message: GetReceipts70 {
|
||||
first_block_receipt_index: 0,
|
||||
block_hashes: vec![
|
||||
hex!("00000000000000000000000000000000000000000000000000000000deadc0de").into(),
|
||||
hex!("00000000000000000000000000000000000000000000000000000000feedbeef").into(),
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
let mut out = vec![];
|
||||
req.encode(&mut out);
|
||||
|
||||
let mut buf = out.as_slice();
|
||||
let header = alloy_rlp::Header::decode(&mut buf).unwrap();
|
||||
let payload_start = buf.len();
|
||||
let request_id = u64::decode(&mut buf).unwrap();
|
||||
let first_block_receipt_index = u64::decode(&mut buf).unwrap();
|
||||
let block_hashes = Vec::<B256>::decode(&mut buf).unwrap();
|
||||
|
||||
assert!(buf.is_empty(), "buffer not fully consumed");
|
||||
assert_eq!(request_id, 1111);
|
||||
assert_eq!(first_block_receipt_index, 0);
|
||||
assert_eq!(block_hashes.len(), 2);
|
||||
// ensure payload length matches header
|
||||
assert_eq!(payload_start - buf.len(), header.payload_length);
|
||||
|
||||
let mut buf = out.as_slice();
|
||||
let decoded = RequestPair::<GetReceipts70>::decode(&mut buf).unwrap();
|
||||
assert!(buf.is_empty(), "buffer not fully consumed on decode");
|
||||
assert_eq!(decoded, req);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_receipts70_inline_shape() {
|
||||
let payload: Receipts70<Receipt> =
|
||||
Receipts70 { last_block_incomplete: true, receipts: vec![vec![Receipt::default()]] };
|
||||
|
||||
let resp = RequestPair { request_id: 7, message: payload };
|
||||
|
||||
let mut out = vec![];
|
||||
resp.encode(&mut out);
|
||||
|
||||
let mut buf = out.as_slice();
|
||||
let header = alloy_rlp::Header::decode(&mut buf).unwrap();
|
||||
let payload_start = buf.len();
|
||||
let request_id = u64::decode(&mut buf).unwrap();
|
||||
let last_block_incomplete = bool::decode(&mut buf).unwrap();
|
||||
let receipts = Vec::<Vec<Receipt>>::decode(&mut buf).unwrap();
|
||||
|
||||
assert!(buf.is_empty(), "buffer not fully consumed");
|
||||
assert_eq!(payload_start - buf.len(), header.payload_length);
|
||||
assert_eq!(request_id, 7);
|
||||
assert!(last_block_incomplete);
|
||||
assert_eq!(receipts.len(), 1);
|
||||
assert_eq!(receipts[0].len(), 1);
|
||||
|
||||
let mut buf = out.as_slice();
|
||||
let decoded = RequestPair::<Receipts70>::decode(&mut buf).unwrap();
|
||||
assert!(buf.is_empty(), "buffer not fully consumed on decode");
|
||||
assert_eq!(decoded, resp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use reth_codecs_derive::add_arbitrary_tests;
|
||||
/// unsupported fields are stripped out.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Copy)]
|
||||
pub struct UnifiedStatus {
|
||||
/// The eth protocol version (e.g. eth/66 to eth/69).
|
||||
/// The eth protocol version (e.g. eth/66 to eth/70).
|
||||
pub version: EthVersion,
|
||||
/// The chain ID identifying the peer’s network.
|
||||
pub chain: Chain,
|
||||
@@ -157,7 +157,7 @@ impl StatusBuilder {
|
||||
self.status
|
||||
}
|
||||
|
||||
/// Sets the eth protocol version (e.g., eth/66, eth/69).
|
||||
/// Sets the eth protocol version (e.g., eth/66, eth/70).
|
||||
pub const fn version(mut self, version: EthVersion) -> Self {
|
||||
self.status.version = version;
|
||||
self
|
||||
@@ -378,8 +378,8 @@ impl Debug for StatusEth69 {
|
||||
}
|
||||
}
|
||||
|
||||
/// `StatusMessage` can store either the Legacy version (with TD) or the
|
||||
/// eth/69 version (omits TD).
|
||||
/// `StatusMessage` can store either the Legacy version (with TD), or the eth/69+/eth/70 version
|
||||
/// (omits TD, includes block range).
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum StatusMessage {
|
||||
@@ -546,6 +546,24 @@ mod tests {
|
||||
assert_eq!(unified_status, roundtripped_unified_status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_eth70() {
|
||||
let unified_status = UnifiedStatus::builder()
|
||||
.version(EthVersion::Eth70)
|
||||
.chain(Chain::mainnet())
|
||||
.genesis(MAINNET_GENESIS_HASH)
|
||||
.forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 })
|
||||
.blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d"))
|
||||
.total_difficulty(None)
|
||||
.earliest_block(Some(1))
|
||||
.latest_block(Some(2))
|
||||
.build();
|
||||
|
||||
let status_message = unified_status.into_message();
|
||||
let roundtripped_unified_status = UnifiedStatus::from_message(status_message);
|
||||
assert_eq!(unified_status, roundtripped_unified_status);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn encode_eth69_status_message() {
|
||||
let expected = hex!("f8544501a0d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3c684b715077d8083ed14f2840112a880a0feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d");
|
||||
|
||||
@@ -27,6 +27,8 @@ pub enum EthVersion {
|
||||
Eth68 = 68,
|
||||
/// The `eth` protocol version 69.
|
||||
Eth69 = 69,
|
||||
/// The `eth` protocol version 70.
|
||||
Eth70 = 70,
|
||||
}
|
||||
|
||||
impl EthVersion {
|
||||
@@ -55,6 +57,11 @@ impl EthVersion {
|
||||
pub const fn is_eth69(&self) -> bool {
|
||||
matches!(self, Self::Eth69)
|
||||
}
|
||||
|
||||
/// Returns true if the version is eth/70
|
||||
pub const fn is_eth70(&self) -> bool {
|
||||
matches!(self, Self::Eth70)
|
||||
}
|
||||
}
|
||||
|
||||
/// RLP encodes `EthVersion` as a single byte (66-69).
|
||||
@@ -96,6 +103,7 @@ impl TryFrom<&str> for EthVersion {
|
||||
"67" => Ok(Self::Eth67),
|
||||
"68" => Ok(Self::Eth68),
|
||||
"69" => Ok(Self::Eth69),
|
||||
"70" => Ok(Self::Eth70),
|
||||
_ => Err(ParseVersionError(s.to_string())),
|
||||
}
|
||||
}
|
||||
@@ -120,6 +128,7 @@ impl TryFrom<u8> for EthVersion {
|
||||
67 => Ok(Self::Eth67),
|
||||
68 => Ok(Self::Eth68),
|
||||
69 => Ok(Self::Eth69),
|
||||
70 => Ok(Self::Eth70),
|
||||
_ => Err(ParseVersionError(u.to_string())),
|
||||
}
|
||||
}
|
||||
@@ -149,6 +158,7 @@ impl From<EthVersion> for &'static str {
|
||||
EthVersion::Eth67 => "67",
|
||||
EthVersion::Eth68 => "68",
|
||||
EthVersion::Eth69 => "69",
|
||||
EthVersion::Eth70 => "70",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,7 +205,7 @@ impl Decodable for ProtocolVersion {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{EthVersion, ParseVersionError};
|
||||
use super::EthVersion;
|
||||
use alloy_rlp::{Decodable, Encodable, Error as RlpError};
|
||||
use bytes::BytesMut;
|
||||
|
||||
@@ -205,7 +215,7 @@ mod tests {
|
||||
assert_eq!(EthVersion::Eth67, EthVersion::try_from("67").unwrap());
|
||||
assert_eq!(EthVersion::Eth68, EthVersion::try_from("68").unwrap());
|
||||
assert_eq!(EthVersion::Eth69, EthVersion::try_from("69").unwrap());
|
||||
assert_eq!(Err(ParseVersionError("70".to_string())), EthVersion::try_from("70"));
|
||||
assert_eq!(EthVersion::Eth70, EthVersion::try_from("70").unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -214,12 +224,18 @@ mod tests {
|
||||
assert_eq!(EthVersion::Eth67, "67".parse().unwrap());
|
||||
assert_eq!(EthVersion::Eth68, "68".parse().unwrap());
|
||||
assert_eq!(EthVersion::Eth69, "69".parse().unwrap());
|
||||
assert_eq!(Err(ParseVersionError("70".to_string())), "70".parse::<EthVersion>());
|
||||
assert_eq!(EthVersion::Eth70, "70".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eth_version_rlp_encode() {
|
||||
let versions = [EthVersion::Eth66, EthVersion::Eth67, EthVersion::Eth68, EthVersion::Eth69];
|
||||
let versions = [
|
||||
EthVersion::Eth66,
|
||||
EthVersion::Eth67,
|
||||
EthVersion::Eth68,
|
||||
EthVersion::Eth69,
|
||||
EthVersion::Eth70,
|
||||
];
|
||||
|
||||
for version in versions {
|
||||
let mut encoded = BytesMut::new();
|
||||
@@ -236,7 +252,7 @@ mod tests {
|
||||
(67_u8, Ok(EthVersion::Eth67)),
|
||||
(68_u8, Ok(EthVersion::Eth68)),
|
||||
(69_u8, Ok(EthVersion::Eth69)),
|
||||
(70_u8, Err(RlpError::Custom("invalid eth version"))),
|
||||
(70_u8, Ok(EthVersion::Eth70)),
|
||||
(65_u8, Err(RlpError::Custom("invalid eth version"))),
|
||||
];
|
||||
|
||||
|
||||
@@ -418,6 +418,8 @@ mod tests {
|
||||
Capability::new_static("eth", 66),
|
||||
Capability::new_static("eth", 67),
|
||||
Capability::new_static("eth", 68),
|
||||
Capability::new_static("eth", 69),
|
||||
Capability::new_static("eth", 70),
|
||||
]
|
||||
.into();
|
||||
|
||||
@@ -425,6 +427,8 @@ mod tests {
|
||||
assert!(capabilities.supports_eth_v66());
|
||||
assert!(capabilities.supports_eth_v67());
|
||||
assert!(capabilities.supports_eth_v68());
|
||||
assert!(capabilities.supports_eth_v69());
|
||||
assert!(capabilities.supports_eth_v70());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -260,10 +260,11 @@ mod tests {
|
||||
|
||||
assert_eq!(hello_encoded.len(), hello.length());
|
||||
}
|
||||
//TODO: add test for eth70 here once we have fully support it
|
||||
|
||||
#[test]
|
||||
fn test_default_protocols_include_eth69() {
|
||||
// ensure that the default protocol list includes Eth69 as the latest version
|
||||
fn test_default_protocols_still_include_eth69() {
|
||||
// ensure that older eth/69 remains advertised for compatibility
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let id = pk2id(&secret_key.public_key(SECP256K1));
|
||||
let hello = HelloMessageWithProtocols::builder(id).build();
|
||||
|
||||
@@ -19,7 +19,7 @@ pub use net_if::{NetInterfaceError, DEFAULT_NET_IF_NAME};
|
||||
use std::{
|
||||
fmt,
|
||||
future::{poll_fn, Future},
|
||||
net::{AddrParseError, IpAddr},
|
||||
net::{AddrParseError, IpAddr, ToSocketAddrs},
|
||||
pin::Pin,
|
||||
str::FromStr,
|
||||
task::{Context, Poll},
|
||||
@@ -38,7 +38,7 @@ const EXTERNAL_IP_APIS: &[&str] =
|
||||
&["https://ipinfo.io/ip", "https://icanhazip.com", "https://ifconfig.me"];
|
||||
|
||||
/// All builtin resolvers.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, Hash)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(SerializeDisplay, DeserializeFromStr))]
|
||||
pub enum NatResolver {
|
||||
/// Resolve with any available resolver.
|
||||
@@ -50,6 +50,14 @@ pub enum NatResolver {
|
||||
PublicIp,
|
||||
/// Use the given [`IpAddr`]
|
||||
ExternalIp(IpAddr),
|
||||
/// Use the given domain name as the external address to expose to peers.
|
||||
/// This is behaving essentially the same as [`NatResolver::ExternalIp`], but supports domain
|
||||
/// names. Domain names are resolved to IP addresses using the OS's resolver. The first IP
|
||||
/// address found is used.
|
||||
/// This may be useful in docker bridge networks where containers are usually queried by DNS
|
||||
/// instead of direct IP addresses.
|
||||
/// Note: the domain shouldn't include a port number. Only the IP address is resolved.
|
||||
ExternalAddr(String),
|
||||
/// Resolve external IP via the network interface.
|
||||
NetIf,
|
||||
/// Resolve nothing
|
||||
@@ -62,10 +70,17 @@ impl NatResolver {
|
||||
external_addr_with(self).await
|
||||
}
|
||||
|
||||
/// Returns the external ip, if it is [`NatResolver::ExternalIp`]
|
||||
pub const fn as_external_ip(self) -> Option<IpAddr> {
|
||||
/// Returns the fixed ip, if it is [`NatResolver::ExternalIp`] or [`NatResolver::ExternalAddr`].
|
||||
///
|
||||
/// In the case of [`NatResolver::ExternalAddr`], it will return the first IP address found for
|
||||
/// the domain.
|
||||
pub fn as_external_ip(self, port: u16) -> Option<IpAddr> {
|
||||
match self {
|
||||
Self::ExternalIp(ip) => Some(ip),
|
||||
Self::ExternalAddr(domain) => format!("{domain}:{port}")
|
||||
.to_socket_addrs()
|
||||
.ok()
|
||||
.and_then(|mut addrs| addrs.next().map(|addr| addr.ip())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -78,6 +93,7 @@ impl fmt::Display for NatResolver {
|
||||
Self::Upnp => f.write_str("upnp"),
|
||||
Self::PublicIp => f.write_str("publicip"),
|
||||
Self::ExternalIp(ip) => write!(f, "extip:{ip}"),
|
||||
Self::ExternalAddr(domain) => write!(f, "extaddr:{domain}"),
|
||||
Self::NetIf => f.write_str("netif"),
|
||||
Self::None => f.write_str("none"),
|
||||
}
|
||||
@@ -106,12 +122,15 @@ impl FromStr for NatResolver {
|
||||
"publicip" | "public-ip" => Self::PublicIp,
|
||||
"netif" => Self::NetIf,
|
||||
s => {
|
||||
let Some(ip) = s.strip_prefix("extip:") else {
|
||||
if let Some(ip) = s.strip_prefix("extip:") {
|
||||
Self::ExternalIp(ip.parse()?)
|
||||
} else if let Some(domain) = s.strip_prefix("extaddr:") {
|
||||
Self::ExternalAddr(domain.to_string())
|
||||
} else {
|
||||
return Err(ParseNatResolverError::UnknownVariant(format!(
|
||||
"Unknown Nat Resolver: {s}"
|
||||
)))
|
||||
};
|
||||
Self::ExternalIp(ip.parse()?)
|
||||
)));
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(r)
|
||||
@@ -180,7 +199,7 @@ impl ResolveNatInterval {
|
||||
/// `None` if the attempt was unsuccessful.
|
||||
pub fn poll_tick(&mut self, cx: &mut Context<'_>) -> Poll<Option<IpAddr>> {
|
||||
if self.interval.poll_tick(cx).is_ready() {
|
||||
self.future = Some(Box::pin(self.resolver.external_addr()));
|
||||
self.future = Some(Box::pin(self.resolver.clone().external_addr()));
|
||||
}
|
||||
|
||||
if let Some(mut fut) = self.future.take() {
|
||||
@@ -212,6 +231,9 @@ pub async fn external_addr_with(resolver: NatResolver) -> Option<IpAddr> {
|
||||
);
|
||||
})
|
||||
.ok(),
|
||||
NatResolver::ExternalAddr(domain) => {
|
||||
domain.to_socket_addrs().ok().and_then(|mut addrs| addrs.next().map(|addr| addr.ip()))
|
||||
}
|
||||
NatResolver::None => None,
|
||||
}
|
||||
}
|
||||
@@ -245,7 +267,7 @@ async fn resolve_external_ip_url(url: &str) -> Option<IpAddr> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
@@ -267,6 +289,18 @@ mod tests {
|
||||
dbg!(ip);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_external_ip_test() {
|
||||
let resolver = NatResolver::ExternalAddr("localhost".to_string());
|
||||
let ip = resolver.as_external_ip(30303).expect("localhost should be resolvable");
|
||||
|
||||
if ip.is_ipv4() {
|
||||
assert_eq!(ip, IpAddr::V4(Ipv4Addr::LOCALHOST));
|
||||
} else {
|
||||
assert_eq!(ip, IpAddr::V6(Ipv6Addr::LOCALHOST));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str() {
|
||||
assert_eq!(NatResolver::Any, "any".parse().unwrap());
|
||||
@@ -275,6 +309,6 @@ mod tests {
|
||||
let ip = NatResolver::ExternalIp(IpAddr::V4(Ipv4Addr::UNSPECIFIED));
|
||||
let s = "extip:0.0.0.0";
|
||||
assert_eq!(ip, s.parse().unwrap());
|
||||
assert_eq!(ip.to_string().as_str(), s);
|
||||
assert_eq!(ip.to_string(), s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
use reth_eth_wire_types::{
|
||||
message::RequestPair, BlockBodies, BlockHeaders, Capabilities, DisconnectReason, EthMessage,
|
||||
EthNetworkPrimitives, EthVersion, GetBlockBodies, GetBlockHeaders, GetNodeData,
|
||||
GetPooledTransactions, GetReceipts, NetworkPrimitives, NodeData, PooledTransactions, Receipts,
|
||||
Receipts69, UnifiedStatus,
|
||||
GetPooledTransactions, GetReceipts, GetReceipts70, NetworkPrimitives, NodeData,
|
||||
PooledTransactions, Receipts, Receipts69, Receipts70, UnifiedStatus,
|
||||
};
|
||||
use reth_ethereum_forks::ForkId;
|
||||
use reth_network_p2p::error::{RequestError, RequestResult};
|
||||
@@ -238,6 +238,15 @@ pub enum PeerRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
/// The channel to send the response for receipts.
|
||||
response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
|
||||
},
|
||||
/// Requests receipts from the peer using eth/70 (supports `firstBlockReceiptIndex`).
|
||||
///
|
||||
/// The response should be sent through the channel.
|
||||
GetReceipts70 {
|
||||
/// The request for receipts.
|
||||
request: GetReceipts70,
|
||||
/// The channel to send the response for receipts.
|
||||
response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
|
||||
},
|
||||
}
|
||||
|
||||
// === impl PeerRequest ===
|
||||
@@ -257,6 +266,7 @@ impl<N: NetworkPrimitives> PeerRequest<N> {
|
||||
Self::GetNodeData { response, .. } => response.send(Err(err)).ok(),
|
||||
Self::GetReceipts { response, .. } => response.send(Err(err)).ok(),
|
||||
Self::GetReceipts69 { response, .. } => response.send(Err(err)).ok(),
|
||||
Self::GetReceipts70 { response, .. } => response.send(Err(err)).ok(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -281,6 +291,9 @@ impl<N: NetworkPrimitives> PeerRequest<N> {
|
||||
Self::GetReceipts { request, .. } | Self::GetReceipts69 { request, .. } => {
|
||||
EthMessage::GetReceipts(RequestPair { request_id, message: request.clone() })
|
||||
}
|
||||
Self::GetReceipts70 { request, .. } => {
|
||||
EthMessage::GetReceipts70(RequestPair { request_id, message: request.clone() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,7 @@ tracing.workspace = true
|
||||
rustc-hash.workspace = true
|
||||
thiserror.workspace = true
|
||||
parking_lot.workspace = true
|
||||
rayon.workspace = true
|
||||
rand.workspace = true
|
||||
rand_08.workspace = true
|
||||
secp256k1 = { workspace = true, features = ["global-context", "std", "recovery"] }
|
||||
|
||||
@@ -433,7 +433,7 @@ impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
|
||||
pub fn external_ip_resolver(mut self, resolver: NatResolver) -> Self {
|
||||
self.discovery_v4_builder
|
||||
.get_or_insert_with(Discv4Config::builder)
|
||||
.external_ip_resolver(Some(resolver));
|
||||
.external_ip_resolver(Some(resolver.clone()));
|
||||
self.nat = Some(resolver);
|
||||
self
|
||||
}
|
||||
@@ -484,7 +484,7 @@ impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
|
||||
}
|
||||
|
||||
// Disable nat
|
||||
pub const fn disable_nat(mut self) -> Self {
|
||||
pub fn disable_nat(mut self) -> Self {
|
||||
self.nat = None;
|
||||
self
|
||||
}
|
||||
@@ -579,7 +579,7 @@ impl<N: NetworkPrimitives> NetworkConfigBuilder<N> {
|
||||
}
|
||||
|
||||
/// Sets the NAT resolver for external IP.
|
||||
pub const fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
|
||||
pub fn add_nat(mut self, nat: Option<NatResolver>) -> Self {
|
||||
self.nat = nat;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ use alloy_rlp::Encodable;
|
||||
use futures::StreamExt;
|
||||
use reth_eth_wire::{
|
||||
BlockBodies, BlockHeaders, EthNetworkPrimitives, GetBlockBodies, GetBlockHeaders, GetNodeData,
|
||||
GetReceipts, HeadersDirection, NetworkPrimitives, NodeData, Receipts, Receipts69,
|
||||
GetReceipts, GetReceipts70, HeadersDirection, NetworkPrimitives, NodeData, Receipts,
|
||||
Receipts69, Receipts70,
|
||||
};
|
||||
use reth_network_api::test_utils::PeersHandle;
|
||||
use reth_network_p2p::error::RequestResult;
|
||||
@@ -217,6 +218,69 @@ where
|
||||
let _ = response.send(Ok(Receipts69(receipts)));
|
||||
}
|
||||
|
||||
/// Handles partial responses for [`GetReceipts70`] queries.
|
||||
///
|
||||
/// This will adhere to the soft limit but allow filling the last vec partially.
|
||||
fn on_receipts70_request(
|
||||
&self,
|
||||
_peer_id: PeerId,
|
||||
request: GetReceipts70,
|
||||
response: oneshot::Sender<RequestResult<Receipts70<C::Receipt>>>,
|
||||
) {
|
||||
self.metrics.eth_receipts_requests_received_total.increment(1);
|
||||
|
||||
let GetReceipts70 { first_block_receipt_index, block_hashes } = request;
|
||||
|
||||
let mut receipts = Vec::new();
|
||||
let mut total_bytes = 0usize;
|
||||
let mut last_block_incomplete = false;
|
||||
|
||||
for (idx, hash) in block_hashes.into_iter().enumerate() {
|
||||
if idx >= MAX_RECEIPTS_SERVE {
|
||||
break
|
||||
}
|
||||
|
||||
let Some(mut block_receipts) =
|
||||
self.client.receipts_by_block(BlockHashOrNumber::Hash(hash)).unwrap_or_default()
|
||||
else {
|
||||
break
|
||||
};
|
||||
|
||||
if idx == 0 && first_block_receipt_index > 0 {
|
||||
let skip = first_block_receipt_index as usize;
|
||||
if skip >= block_receipts.len() {
|
||||
block_receipts.clear();
|
||||
} else {
|
||||
block_receipts.drain(0..skip);
|
||||
}
|
||||
}
|
||||
|
||||
let block_size = block_receipts.length();
|
||||
|
||||
if total_bytes + block_size <= SOFT_RESPONSE_LIMIT {
|
||||
total_bytes += block_size;
|
||||
receipts.push(block_receipts);
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut partial_block = Vec::new();
|
||||
for receipt in block_receipts {
|
||||
let receipt_size = receipt.length();
|
||||
if total_bytes + receipt_size > SOFT_RESPONSE_LIMIT {
|
||||
break;
|
||||
}
|
||||
total_bytes += receipt_size;
|
||||
partial_block.push(receipt);
|
||||
}
|
||||
|
||||
receipts.push(partial_block);
|
||||
last_block_incomplete = true;
|
||||
break;
|
||||
}
|
||||
|
||||
let _ = response.send(Ok(Receipts70 { last_block_incomplete, receipts }));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_receipts_response<T, F>(&self, request: GetReceipts, transform_fn: F) -> Vec<Vec<T>>
|
||||
where
|
||||
@@ -285,6 +349,9 @@ where
|
||||
IncomingEthRequest::GetReceipts69 { peer_id, request, response } => {
|
||||
this.on_receipts69_request(peer_id, request, response)
|
||||
}
|
||||
IncomingEthRequest::GetReceipts70 { peer_id, request, response } => {
|
||||
this.on_receipts70_request(peer_id, request, response)
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -359,4 +426,15 @@ pub enum IncomingEthRequest<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
/// The channel sender for the response containing Receipts69.
|
||||
response: oneshot::Sender<RequestResult<Receipts69<N::Receipt>>>,
|
||||
},
|
||||
/// Request Receipts from the peer using eth/70.
|
||||
///
|
||||
/// The response should be sent through the channel.
|
||||
GetReceipts70 {
|
||||
/// The ID of the peer to request receipts from.
|
||||
peer_id: PeerId,
|
||||
/// The specific receipts requested including the `firstBlockReceiptIndex`.
|
||||
request: GetReceipts70,
|
||||
/// The channel sender for the response containing Receipts70.
|
||||
response: oneshot::Sender<RequestResult<Receipts70<N::Receipt>>>,
|
||||
},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user