mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
65 Commits
devnet4
...
ejh/hive-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c408bcd1dc | ||
|
|
9b50bb7c8d | ||
|
|
a66d49c190 | ||
|
|
ab2252c33d | ||
|
|
8dbb015770 | ||
|
|
96be836679 | ||
|
|
ab5f2db594 | ||
|
|
6a633a42f0 | ||
|
|
08fd55d8c9 | ||
|
|
fab95c1f3a | ||
|
|
e4191ccea8 | ||
|
|
080ff004e3 | ||
|
|
18599f1732 | ||
|
|
9fd35e2917 | ||
|
|
c1a5e20b50 | ||
|
|
0ff16ea053 | ||
|
|
3541bd7f65 | ||
|
|
a3aec0c662 | ||
|
|
0dfdaca3f0 | ||
|
|
c535a7fb5b | ||
|
|
bf6270b8a3 | ||
|
|
1e78685a6c | ||
|
|
1f1e320643 | ||
|
|
74ea20400e | ||
|
|
ffff5fbce2 | ||
|
|
f514892b41 | ||
|
|
d0ad4b0e18 | ||
|
|
cb6ed16485 | ||
|
|
a88eef91f4 | ||
|
|
f7e7afd51f | ||
|
|
102764285b | ||
|
|
4679c86003 | ||
|
|
7671838c61 | ||
|
|
8f4461c060 | ||
|
|
0119f3c612 | ||
|
|
094aaef5a1 | ||
|
|
32d03ff4d7 | ||
|
|
3368ce6485 | ||
|
|
9bc2388871 | ||
|
|
1728fa97c0 | ||
|
|
868248ec54 | ||
|
|
16ab4b8518 | ||
|
|
ce74466b93 | ||
|
|
992fc30ff5 | ||
|
|
3ece6b6047 | ||
|
|
dec9f93ad1 | ||
|
|
03484f76ec | ||
|
|
b870f04509 | ||
|
|
5277e59cc4 | ||
|
|
6271c2702f | ||
|
|
ce15ab9f55 | ||
|
|
0b1ec2dc89 | ||
|
|
5862c72880 | ||
|
|
8a7655ca5d | ||
|
|
cd20adc1d4 | ||
|
|
f0fe45d6bf | ||
|
|
00422207f4 | ||
|
|
28a94829e9 | ||
|
|
e081249f65 | ||
|
|
99fedf01f8 | ||
|
|
179e1bfc34 | ||
|
|
3adb5b9e58 | ||
|
|
57d7c98f66 | ||
|
|
5d9a43f2d4 | ||
|
|
defd0e8e5c |
2
.github/scripts/hive/Dockerfile
vendored
2
.github/scripts/hive/Dockerfile
vendored
@@ -3,7 +3,7 @@
|
||||
#
|
||||
# We'll use cargo-chef to speed up the build
|
||||
#
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef
|
||||
FROM lukemathwalker/cargo-chef:latest-rust-trixie AS chef
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
|
||||
7
.github/scripts/hive/build_simulators.sh
vendored
7
.github/scripts/hive/build_simulators.sh
vendored
@@ -9,10 +9,13 @@ go build .
|
||||
|
||||
./hive -client reth # first builds and caches the client
|
||||
|
||||
# Run each hive command in the background for each simulator and wait
|
||||
# Run each hive command in the background for each simulator and wait
|
||||
echo "Building images"
|
||||
# TODO: test code has been moved from https://github.com/ethereum/execution-spec-tests to https://github.com/ethereum/execution-specs we need to pin eels branch with `--sim.buildarg branch=<release-branch-name>` once we have the fusaka release tagged on the new repo
|
||||
./hive -client reth --sim "ethereum/eels" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz -sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/eels" \
|
||||
--sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/bal@v5.1.0/fixtures_bal.tar.gz \
|
||||
--sim.buildarg branch=err-map-3 \
|
||||
--sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/engine" -sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "devp2p" -sim.timelimit 1s || true &
|
||||
./hive -client reth --sim "ethereum/rpc-compat" -sim.timelimit 1s || true &
|
||||
|
||||
88
.github/scripts/hive/expected_failures.yaml
vendored
88
.github/scripts/hive/expected_failures.yaml
vendored
@@ -18,30 +18,18 @@ rpc-compat:
|
||||
|
||||
# no fix due to https://github.com/paradigmxyz/reth/issues/8732
|
||||
engine-withdrawals:
|
||||
- Withdrawals Fork On Genesis (Paris) (reth)
|
||||
- Withdrawals Fork on Block 1 (Paris) (reth)
|
||||
- Withdrawals Fork on Block 2 (Paris) (reth)
|
||||
- Withdrawals Fork on Block 3 (Paris) (reth)
|
||||
- Withdraw to a single account (Paris) (reth)
|
||||
- Withdraw to two accounts (Paris) (reth)
|
||||
- Withdraw many accounts (Paris) (reth)
|
||||
- Withdraw zero amount (Paris) (reth)
|
||||
- Empty Withdrawals (Paris) (reth)
|
||||
- Corrupted Block Hash Payload (INVALID) (Paris) (reth)
|
||||
- Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org (Paris) (reth)
|
||||
|
||||
engine-api: [ ]
|
||||
engine-api: []
|
||||
|
||||
# no fix due to https://github.com/paradigmxyz/reth/issues/8732
|
||||
engine-cancun:
|
||||
- Invalid PayloadAttributes, Missing BeaconRoot, Syncing=True (Cancun) (reth)
|
||||
# the test fails with older versions of the code for which it passed before, probably related to changes
|
||||
# in hive or its dependencies
|
||||
- Blob Transaction Ordering, Multiple Clients (Cancun) (reth)
|
||||
|
||||
sync: [ ]
|
||||
sync: []
|
||||
|
||||
engine-auth: [ ]
|
||||
engine-auth: []
|
||||
|
||||
# EIP-7610 related tests (Revert creation in case of non-empty storage):
|
||||
#
|
||||
@@ -49,7 +37,7 @@ engine-auth: [ ]
|
||||
# The test artificially creates an empty account with storage, then tests EIP-7610's behavior.
|
||||
# On mainnet, ~25 such accounts exist as contract addresses (derived from keccak(prefix, caller,
|
||||
# nonce/salt), not from public keys). No private key exists for contract addresses. To trigger
|
||||
# this with EIP-7702, you'd need to recover a private key from one of the already deployed contract addresses - mathematically impossible.
|
||||
# this with EIP-7702, you'd need to recover a private key from one of the already deployed contract addresses - mathematically impossible.
|
||||
#
|
||||
# tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_*
|
||||
# Requires hash collision on create2 address to target already deployed accounts with storage.
|
||||
@@ -59,6 +47,10 @@ engine-auth: [ ]
|
||||
#
|
||||
# System contract tests (already fixed and deployed):
|
||||
#
|
||||
# tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout and test_invalid_log_length
|
||||
# System contract is already fixed and deployed; tests cover scenarios where contract is
|
||||
# malformed which can't happen retroactively. No point in adding checks.
|
||||
#
|
||||
# tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment
|
||||
# tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment
|
||||
# Post-fork system contract deployment tests. Should fix for spec compliance but not realistic
|
||||
@@ -67,8 +59,44 @@ eels/consume-engine:
|
||||
- tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage[fork_Prague-blockchain_test_engine-zero_nonce]-reth
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
|
||||
- tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_index_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Prague-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
|
||||
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-nonzero_balance]-reth
|
||||
- tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test_engine-deploy_after_fork-zero_balance]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_False]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Prague-blockchain_test_engine-slice_bytes_True]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_index_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Osaka-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_False]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Osaka-blockchain_test_engine-slice_bytes_True]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_index_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_index_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Amsterdam-blockchain_test_engine-slice_bytes_True]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_log_length[fork_Amsterdam-blockchain_test_engine-slice_bytes_False]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_pubkey_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_pubkey_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_withdrawal_credentials_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_withdrawal_credentials_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_amount_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_amount_offset-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_signature_size-value_zero]-reth
|
||||
- tests/prague/eip6110_deposits/test_modified_contract.py::test_invalid_layout[fork_Amsterdam-blockchain_test_engine-log_argument_signature_offset-value_zero]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Osaka-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Paris-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
@@ -119,6 +147,20 @@ eels/consume-engine:
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Amsterdam-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
|
||||
# Blob limit tests:
|
||||
#
|
||||
@@ -213,3 +255,17 @@ eels/consume-rlp:
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Amsterdam-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
|
||||
2
.github/workflows/docker-test.yml
vendored
2
.github/workflows/docker-test.yml
vendored
@@ -79,4 +79,4 @@ jobs:
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ./artifacts
|
||||
path: ./artifacts
|
||||
75
.github/workflows/hive.yml
vendored
75
.github/workflows/hive.yml
vendored
@@ -5,7 +5,10 @@ name: hive
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
- cron: "0 */6 * * *"
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
@@ -15,30 +18,34 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-reth-stable:
|
||||
uses: ./.github/workflows/docker-test.yml
|
||||
prepare-reth-stable:
|
||||
uses: ./.github/workflows/prepare-reth.yml
|
||||
with:
|
||||
hive_target: hive-stable
|
||||
image_tag: ghcr.io/paradigmxyz/reth:latest
|
||||
binary_name: reth
|
||||
cargo_features: "asm-keccak"
|
||||
artifact_name: "reth-stable"
|
||||
secrets: inherit
|
||||
|
||||
build-reth-edge:
|
||||
uses: ./.github/workflows/docker-test.yml
|
||||
prepare-reth-edge:
|
||||
uses: ./.github/workflows/prepare-reth.yml
|
||||
with:
|
||||
hive_target: hive-edge
|
||||
image_tag: ghcr.io/paradigmxyz/reth:latest
|
||||
binary_name: reth
|
||||
cargo_features: "asm-keccak edge"
|
||||
artifact_name: "reth-edge"
|
||||
secrets: inherit
|
||||
|
||||
prepare-hive:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
runs-on:
|
||||
group: Reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
repository: Soubhik-10/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
|
||||
- name: Get hive commit hash
|
||||
@@ -124,27 +131,29 @@ jobs:
|
||||
# eth_ rpc methods
|
||||
- sim: ethereum/rpc-compat
|
||||
include:
|
||||
- eth_blockNumber
|
||||
# - eth_blockNumber
|
||||
- eth_call
|
||||
- eth_chainId
|
||||
- eth_createAccessList
|
||||
- eth_estimateGas
|
||||
- eth_feeHistory
|
||||
- eth_getBalance
|
||||
- eth_getBlockBy
|
||||
- eth_getBlockTransactionCountBy
|
||||
- eth_getCode
|
||||
- eth_getProof
|
||||
- eth_getStorage
|
||||
- eth_getTransactionBy
|
||||
- eth_getTransactionCount
|
||||
- eth_getTransactionReceipt
|
||||
- eth_sendRawTransaction
|
||||
- eth_syncing
|
||||
# debug_ rpc methods
|
||||
- debug_
|
||||
# - eth_chainId
|
||||
# - eth_createAccessList
|
||||
# - eth_estimateGas
|
||||
# - eth_feeHistory
|
||||
# - eth_getBalance
|
||||
# - eth_getBlockBy
|
||||
# - eth_getBlockTransactionCountBy
|
||||
# - eth_getCode
|
||||
# - eth_getProof
|
||||
# - eth_getStorage
|
||||
# - eth_getTransactionBy
|
||||
# - eth_getTransactionCount
|
||||
# - eth_getTransactionReceipt
|
||||
# - eth_sendRawTransaction
|
||||
# - eth_syncing
|
||||
# # debug_ rpc methods
|
||||
# - debug_
|
||||
|
||||
# consume-engine
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/amsterdam.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
limit: .*tests/osaka.*
|
||||
- sim: ethereum/eels/consume-engine
|
||||
@@ -165,6 +174,8 @@ jobs:
|
||||
limit: .*tests/paris.*
|
||||
|
||||
# consume-rlp
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/amsterdam.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/osaka.*
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
@@ -184,8 +195,8 @@ jobs:
|
||||
- sim: ethereum/eels/consume-rlp
|
||||
limit: .*tests/paris.*
|
||||
needs:
|
||||
- build-reth-stable
|
||||
- build-reth-edge
|
||||
- prepare-reth-stable
|
||||
- prepare-reth-edge
|
||||
- prepare-hive
|
||||
name: ${{ matrix.storage }} / ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }}
|
||||
# Use larger runners for eels tests to avoid OOM runner crashes
|
||||
@@ -220,7 +231,7 @@ jobs:
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
repository: Soubhik-10/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
|
||||
|
||||
61
.github/workflows/prepare-reth.yml
vendored
Normal file
61
.github/workflows/prepare-reth.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Prepare Reth Image
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_tag:
|
||||
required: true
|
||||
type: string
|
||||
description: "Docker image tag to use"
|
||||
binary_name:
|
||||
required: false
|
||||
type: string
|
||||
default: "reth"
|
||||
description: "Binary name to build (reth or op-reth)"
|
||||
cargo_features:
|
||||
required: false
|
||||
type: string
|
||||
default: "asm-keccak"
|
||||
description: "Cargo features to enable"
|
||||
cargo_package:
|
||||
required: false
|
||||
type: string
|
||||
description: "Optional cargo package path"
|
||||
artifact_name:
|
||||
required: false
|
||||
type: string
|
||||
default: "artifacts"
|
||||
description: "Name for the uploaded artifact"
|
||||
|
||||
jobs:
|
||||
prepare-reth:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: depot-ubuntu-latest-4
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: mkdir artifacts
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and export reth image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: .github/scripts/hive/Dockerfile
|
||||
tags: ${{ inputs.image_tag }}
|
||||
outputs: type=docker,dest=./artifacts/reth_image.tar
|
||||
build-args: |
|
||||
CARGO_BIN=${{ inputs.binary_name }}
|
||||
MANIFEST_PATH=${{ inputs.cargo_package }}
|
||||
FEATURES=${{ inputs.cargo_features }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Upload reth image
|
||||
id: upload
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ./artifacts
|
||||
854
Cargo.lock
generated
854
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
79
Cargo.toml
79
Cargo.toml
@@ -1,5 +1,5 @@
|
||||
[workspace.package]
|
||||
version = "1.11.1"
|
||||
version = "1.11.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.93"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -138,7 +138,6 @@ members = [
|
||||
"examples/exex-subscription",
|
||||
"examples/exex-test",
|
||||
"examples/full-contract-state",
|
||||
"examples/migrate-trie-to-packed",
|
||||
"examples/manual-p2p/",
|
||||
"examples/network-txpool/",
|
||||
"examples/network/",
|
||||
@@ -447,14 +446,14 @@ op-revm = { version = "15.0.0", default-features = false }
|
||||
revm-inspectors = "0.34.2"
|
||||
|
||||
# eth
|
||||
alloy-dyn-abi = "1.5.6"
|
||||
alloy-primitives = { version = "1.5.6", default-features = false, features = ["map-foldhash"] }
|
||||
alloy-sol-types = { version = "1.5.6", default-features = false }
|
||||
alloy-dyn-abi = "1.5.0"
|
||||
alloy-primitives = { version = "1.5.0", default-features = false, features = ["map-foldhash"] }
|
||||
alloy-sol-types = { version = "1.5.0", default-features = false }
|
||||
|
||||
alloy-chains = { version = "0.2.5", default-features = false }
|
||||
alloy-eip2124 = { version = "0.2.0", default-features = false }
|
||||
alloy-eip7928 = { version = "0.3.0", default-features = false }
|
||||
alloy-evm = { version = "0.28.0", default-features = false }
|
||||
alloy-eip7928 = { version = "0.3.0", default-features = false, features = ["rlp"] }
|
||||
alloy-evm = { version = "0.27.2", default-features = false }
|
||||
alloy-rlp = { version = "0.3.13", default-features = false, features = ["core-net"] }
|
||||
alloy-trie = { version = "0.9.4", default-features = false }
|
||||
|
||||
@@ -489,7 +488,6 @@ alloy-transport-ipc = { version = "1.7.3", default-features = false }
|
||||
alloy-transport-ws = { version = "1.7.3", default-features = false }
|
||||
|
||||
# op
|
||||
alloy-op-evm = { version = "0.27.2", default-features = false }
|
||||
alloy-op-hardforks = "0.4.4"
|
||||
op-alloy-rpc-types = { version = "0.23.1", default-features = false }
|
||||
op-alloy-rpc-types-engine = { version = "0.23.1", default-features = false }
|
||||
@@ -533,19 +531,20 @@ quanta = "0.12"
|
||||
paste = "1.0"
|
||||
rand = "0.9"
|
||||
rayon = "1.7"
|
||||
thread-priority = "3.0.0"
|
||||
rustc-hash = { version = "2.0", default-features = false }
|
||||
schnellru = "0.2"
|
||||
serde = { version = "1.0", default-features = false }
|
||||
serde_json = { version = "1.0", default-features = false, features = ["alloc"] }
|
||||
serde_with = { version = "3", default-features = false, features = ["macros"] }
|
||||
sha2 = { version = "0.10", default-features = false }
|
||||
shellexpand = "3.0.0"
|
||||
shlex = "1.3"
|
||||
smallvec = "1"
|
||||
strum = { version = "0.27", default-features = false }
|
||||
strum_macros = "0.27"
|
||||
syn = "2.0"
|
||||
thiserror = { version = "2.0.0", default-features = false }
|
||||
thread-priority = "3.0.0"
|
||||
tar = "0.4.44"
|
||||
tracing = { version = "0.1.0", default-features = false, features = ["attributes"] }
|
||||
tracing-appender = "0.2"
|
||||
@@ -761,3 +760,65 @@ ipnet = "2.11"
|
||||
|
||||
# alloy-evm = { git = "https://github.com/alloy-rs/evm", rev = "072c248" }
|
||||
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm", rev = "072c248" }
|
||||
|
||||
# =============================================================================
|
||||
# BAL devnet2 patches (EIP-7778, EIP-7928 block access lists)
|
||||
# =============================================================================
|
||||
|
||||
# revm staging patches
|
||||
revm = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-bytecode = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-database = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-database-interface = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-state = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-interpreter = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-precompile = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-context = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-context-interface = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-handler = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-inspector = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
op-revm = { git = "https://github.com/bluealloy/revm", branch = "main" }
|
||||
revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", branch = "staging" }
|
||||
|
||||
# alloy-evm bal-devnet2 patches
|
||||
alloy-evm = { git = "https://github.com/alloy-rs/evm", branch = "bal-devnet2" }
|
||||
|
||||
# alloy bal-devnet2 patches
|
||||
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-consensus-any = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-contract = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-eips = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-genesis = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-network = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-provider = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-admin = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-anvil = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-beacon = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-debug = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-any = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-mev = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-trace = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-rpc-types-txpool = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-serde = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-signer = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
alloy-tx-macros = { git = "https://github.com/alloy-rs/alloy", branch = "bal-devnet2" }
|
||||
|
||||
# op-alloy bal-devnet2 patches
|
||||
op-alloy-consensus = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
op-alloy-network = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
op-alloy-rpc-types = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
op-alloy-rpc-types-engine = { git = "https://github.com/alloy-rs/op-alloy", branch = "bal-devnet2" }
|
||||
|
||||
2
Makefile
2
Makefile
@@ -29,7 +29,7 @@ EF_TESTS_URL := https://github.com/ethereum/tests/archive/refs/tags/$(EF_TESTS_T
|
||||
EF_TESTS_DIR := ./testing/ef-tests/ethereum-tests
|
||||
|
||||
# The release tag of https://github.com/ethereum/execution-spec-tests to use for EEST tests
|
||||
EEST_TESTS_TAG := v4.5.0
|
||||
EEST_TESTS_TAG := bal@v5.0.0
|
||||
EEST_TESTS_URL := https://github.com/ethereum/execution-spec-tests/releases/download/$(EEST_TESTS_TAG)/fixtures_stable.tar.gz
|
||||
EEST_TESTS_DIR := ./testing/ef-tests/execution-spec-tests
|
||||
|
||||
|
||||
@@ -773,6 +773,7 @@ impl Command {
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
},
|
||||
transactions: transactions.to_vec(),
|
||||
extra_data: None,
|
||||
|
||||
@@ -76,7 +76,7 @@ use alloy_primitives::{Address, B256};
|
||||
use alloy_provider::{ext::EngineApi, network::AnyNetwork, RootProvider};
|
||||
use alloy_rpc_types_engine::{
|
||||
CancunPayloadFields, ExecutionPayload, ExecutionPayloadSidecar, ForkchoiceState,
|
||||
PayloadAttributes, PayloadId,
|
||||
PayloadAttributes, PayloadId, PraguePayloadFields,
|
||||
};
|
||||
use eyre::OptionExt;
|
||||
use reth_chainspec::{ChainSpec, EthereumHardforks};
|
||||
@@ -138,6 +138,7 @@ pub(crate) fn prepare_payload_request(
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: shanghai_active.then(Vec::new),
|
||||
parent_beacon_block_root: cancun_active.then_some(B256::ZERO),
|
||||
slot_number: None,
|
||||
},
|
||||
forkchoice_state: ForkchoiceState {
|
||||
head_block_hash: parent_hash,
|
||||
@@ -230,14 +231,33 @@ pub(crate) async fn get_payload_with_sidecar(
|
||||
}
|
||||
4 => {
|
||||
let envelope = provider.get_payload_v4(payload_id).await?;
|
||||
Ok(envelope.into_payload_and_sidecar(
|
||||
parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V4")?,
|
||||
let versioned_hashes = versioned_hashes_from_commitments(
|
||||
&envelope.envelope_inner.blobs_bundle.commitments,
|
||||
);
|
||||
let cancun_fields = CancunPayloadFields {
|
||||
parent_beacon_block_root: parent_beacon_block_root
|
||||
.ok_or_eyre("parent_beacon_block_root required for V4")?,
|
||||
versioned_hashes,
|
||||
};
|
||||
let prague_fields = PraguePayloadFields::new(envelope.execution_requests);
|
||||
Ok((
|
||||
ExecutionPayload::V3(envelope.envelope_inner.execution_payload),
|
||||
ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
|
||||
))
|
||||
}
|
||||
5 => {
|
||||
let envelope = provider.get_payload_v5(payload_id).await?;
|
||||
Ok(envelope.into_payload_and_sidecar(
|
||||
parent_beacon_block_root.ok_or_eyre("parent_beacon_block_root required for V5")?,
|
||||
let versioned_hashes =
|
||||
versioned_hashes_from_commitments(&envelope.blobs_bundle.commitments);
|
||||
let cancun_fields = CancunPayloadFields {
|
||||
parent_beacon_block_root: parent_beacon_block_root
|
||||
.ok_or_eyre("parent_beacon_block_root required for V5")?,
|
||||
versioned_hashes,
|
||||
};
|
||||
let prague_fields = PraguePayloadFields::new(envelope.execution_requests);
|
||||
Ok((
|
||||
ExecutionPayload::V3(envelope.execution_payload),
|
||||
ExecutionPayloadSidecar::v4(cancun_fields, prague_fields),
|
||||
))
|
||||
}
|
||||
_ => panic!("This tool does not support getPayload versions past v5"),
|
||||
|
||||
@@ -160,12 +160,11 @@ impl PersistenceSubscription {
|
||||
/// to pings.
|
||||
pub(crate) async fn setup_persistence_subscription(
|
||||
ws_url: Url,
|
||||
persistence_timeout: Duration,
|
||||
_persistence_timeout: Duration,
|
||||
) -> eyre::Result<PersistenceSubscription> {
|
||||
info!(target: "reth-bench", "Connecting to WebSocket at {} for persistence subscription", ws_url);
|
||||
|
||||
let ws_connect =
|
||||
WsConnect::new(ws_url.to_string()).with_keepalive_interval(persistence_timeout);
|
||||
let ws_connect = WsConnect::new(ws_url.to_string());
|
||||
let client = RpcClient::connect_pubsub(ws_connect)
|
||||
.await
|
||||
.wrap_err("Failed to connect to WebSocket RPC endpoint")?;
|
||||
|
||||
@@ -240,6 +240,7 @@ impl Command {
|
||||
ExecutionPayload::V1(p) => config.apply_to_payload_v1(p),
|
||||
ExecutionPayload::V2(p) => config.apply_to_payload_v2(p),
|
||||
ExecutionPayload::V3(p) => config.apply_to_payload_v3(p),
|
||||
ExecutionPayload::V4(p) => config.apply_to_payload_v3(&mut p.payload_inner),
|
||||
};
|
||||
|
||||
let skip_recalc = self.skip_hash_recalc || config.should_skip_hash_recalc();
|
||||
@@ -254,6 +255,9 @@ impl Command {
|
||||
ExecutionPayload::V1(p) => p.block_hash,
|
||||
ExecutionPayload::V2(p) => p.payload_inner.block_hash,
|
||||
ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash,
|
||||
ExecutionPayload::V4(p) => {
|
||||
p.payload_inner.payload_inner.payload_inner.block_hash
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -262,6 +266,9 @@ impl Command {
|
||||
ExecutionPayload::V1(p) => p.block_hash = new_hash,
|
||||
ExecutionPayload::V2(p) => p.payload_inner.block_hash = new_hash,
|
||||
ExecutionPayload::V3(p) => p.payload_inner.payload_inner.block_hash = new_hash,
|
||||
ExecutionPayload::V4(p) => {
|
||||
p.payload_inner.payload_inner.payload_inner.block_hash = new_hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -214,6 +214,20 @@ pub(crate) fn payload_to_new_payload(
|
||||
let execution_data = ExecutionData { payload: payload.clone(), sidecar: sidecar.clone() };
|
||||
|
||||
let (version, params) = match payload {
|
||||
ExecutionPayload::V4(payload) => {
|
||||
let cancun = sidecar.cancun().unwrap();
|
||||
let prague = sidecar.prague().unwrap();
|
||||
let requests = prague.requests.requests_hash();
|
||||
(
|
||||
EngineApiMessageVersion::V5,
|
||||
serde_json::to_value((
|
||||
payload,
|
||||
cancun.versioned_hashes.clone(),
|
||||
cancun.parent_beacon_block_root,
|
||||
requests,
|
||||
))?,
|
||||
)
|
||||
}
|
||||
ExecutionPayload::V3(payload) => {
|
||||
let cancun = sidecar.cancun().unwrap();
|
||||
|
||||
@@ -371,9 +385,12 @@ pub(crate) async fn call_forkchoice_updated<N, P: EngineApiValidWaitExt<N>>(
|
||||
forkchoice_state: ForkchoiceState,
|
||||
payload_attributes: Option<PayloadAttributes>,
|
||||
) -> TransportResult<ForkchoiceUpdated> {
|
||||
// FCU V3 is used for both Cancun and Prague (there is no FCU V4)
|
||||
// FCU V3 is used for Cancun, Prague, and Amsterdam (there is no FCU V4-V6)
|
||||
match message_version {
|
||||
EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => {
|
||||
EngineApiMessageVersion::V3 |
|
||||
EngineApiMessageVersion::V4 |
|
||||
EngineApiMessageVersion::V5 |
|
||||
EngineApiMessageVersion::V6 => {
|
||||
provider.fork_choice_updated_v3_wait(forkchoice_state, payload_attributes).await
|
||||
}
|
||||
EngineApiMessageVersion::V2 => {
|
||||
|
||||
@@ -70,7 +70,7 @@ aquamarine.workspace = true
|
||||
clap = { workspace = true, features = ["derive", "env"] }
|
||||
|
||||
[dev-dependencies]
|
||||
alloy-node-bindings = "1.6.3"
|
||||
alloy-node-bindings = "1.5.2"
|
||||
alloy-provider = { workspace = true, features = ["reqwest"] }
|
||||
alloy-rpc-types-eth.workspace = true
|
||||
backon.workspace = true
|
||||
|
||||
@@ -772,6 +772,7 @@ impl<N: NodePrimitives> Default for ExecutedBlock<N> {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
state: Default::default(),
|
||||
}),
|
||||
|
||||
@@ -212,6 +212,7 @@ impl<N: NodePrimitives> TestBlockBuilder<N> {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
state: BundleState::default(),
|
||||
}),
|
||||
|
||||
@@ -28,7 +28,7 @@ use alloy_consensus::{
|
||||
};
|
||||
use alloy_eips::{
|
||||
eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams,
|
||||
eip7892::BlobScheduleBlobParams,
|
||||
eip7892::BlobScheduleBlobParams, eip7928::EMPTY_BLOCK_ACCESS_LIST_HASH,
|
||||
};
|
||||
use alloy_genesis::{ChainConfig, Genesis};
|
||||
use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256};
|
||||
@@ -76,6 +76,18 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
|
||||
.active_at_timestamp(genesis.timestamp)
|
||||
.then_some(EMPTY_REQUESTS_HASH);
|
||||
|
||||
// If Amsterdam is activated at genesis we set block access list hash to empty hash.
|
||||
let block_access_list_hash = hardforks
|
||||
.fork(EthereumHardfork::Amsterdam)
|
||||
.active_at_timestamp(genesis.timestamp)
|
||||
.then_some(EMPTY_BLOCK_ACCESS_LIST_HASH);
|
||||
|
||||
// If Amsterdam is activated at genesis we set slot number to 0.
|
||||
let slot_number = hardforks
|
||||
.fork(EthereumHardfork::Amsterdam)
|
||||
.active_at_timestamp(genesis.timestamp)
|
||||
.then_some(0);
|
||||
|
||||
Header {
|
||||
number: genesis.number.unwrap_or_default(),
|
||||
parent_hash: genesis.parent_hash.unwrap_or_default(),
|
||||
@@ -93,6 +105,8 @@ pub fn make_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> Hea
|
||||
blob_gas_used,
|
||||
excess_blob_gas,
|
||||
requests_hash,
|
||||
block_access_list_hash,
|
||||
slot_number,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -298,6 +312,7 @@ pub fn create_chain_config(
|
||||
cancun_time: timestamp(EthereumHardfork::Cancun),
|
||||
prague_time: timestamp(EthereumHardfork::Prague),
|
||||
osaka_time: timestamp(EthereumHardfork::Osaka),
|
||||
amsterdam_time: timestamp(EthereumHardfork::Amsterdam),
|
||||
bpo1_time: timestamp(EthereumHardfork::Bpo1),
|
||||
bpo2_time: timestamp(EthereumHardfork::Bpo2),
|
||||
bpo3_time: timestamp(EthereumHardfork::Bpo3),
|
||||
@@ -880,6 +895,7 @@ impl From<Genesis> for ChainSpec {
|
||||
(EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time),
|
||||
(EthereumHardfork::Prague.boxed(), genesis.config.prague_time),
|
||||
(EthereumHardfork::Osaka.boxed(), genesis.config.osaka_time),
|
||||
(EthereumHardfork::Amsterdam.boxed(), genesis.config.amsterdam_time),
|
||||
(EthereumHardfork::Bpo1.boxed(), genesis.config.bpo1_time),
|
||||
(EthereumHardfork::Bpo2.boxed(), genesis.config.bpo2_time),
|
||||
(EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time),
|
||||
@@ -1191,6 +1207,19 @@ impl ChainSpecBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Amsterdam at genesis.
|
||||
pub fn amsterdam_activated(mut self) -> Self {
|
||||
self = self.osaka_activated();
|
||||
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(0));
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Amsterdam at the given timestamp.
|
||||
pub fn with_amsterdam_at(mut self, timestamp: u64) -> Self {
|
||||
self.hardforks.insert(EthereumHardfork::Amsterdam, ForkCondition::Timestamp(timestamp));
|
||||
self
|
||||
}
|
||||
|
||||
/// Build the resulting [`ChainSpec`].
|
||||
///
|
||||
/// # Panics
|
||||
|
||||
@@ -87,6 +87,27 @@ pub fn validate_cancun_gas<B: Block>(block: &SealedBlock<B>) -> Result<(), Conse
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate that Amsterdam header fields are present in the block.
|
||||
///
|
||||
/// This checks that the `block_access_list_hash` and `slot_number` are set in the header,
|
||||
/// as required post-Amsterdam.
|
||||
///
|
||||
/// See [EIP-7928]: Block-level Access Lists
|
||||
/// See [EIP-7778]: Slot Number in Block Header
|
||||
///
|
||||
/// [EIP-7928]: https://eips.ethereum.org/EIPS/eip-7928
|
||||
/// [EIP-7778]: https://eips.ethereum.org/EIPS/eip-7778
|
||||
#[inline]
|
||||
pub fn validate_amsterdam_header_fields<H: BlockHeader>(header: &H) -> Result<(), ConsensusError> {
|
||||
if header.block_access_list_hash().is_none() {
|
||||
return Err(ConsensusError::BlockAccessListHashMissing);
|
||||
}
|
||||
if header.slot_number().is_none() {
|
||||
return Err(ConsensusError::SlotNumberMissing);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensures the block response data matches the header.
|
||||
///
|
||||
/// This ensures the body response items match the header's hashes:
|
||||
|
||||
@@ -331,6 +331,26 @@ pub enum ConsensusError {
|
||||
#[error("unexpected parent beacon block root")]
|
||||
ParentBeaconBlockRootUnexpected,
|
||||
|
||||
/// Error when the block access list hash is missing.
|
||||
#[error("missing block access list hash")]
|
||||
BlockAccessListHashMissing,
|
||||
|
||||
/// Error when an unexpected block access list hash is encountered.
|
||||
#[error("unexpected block access list hash")]
|
||||
BlockAccessListHashUnexpected,
|
||||
|
||||
/// Error when the block access list hash doesn't match the expected value.
|
||||
#[error("block access list hash mismatch: {0}")]
|
||||
BlockAccessListHashMismatch(GotExpectedBoxed<B256>),
|
||||
|
||||
/// Error when the slot number is missing.
|
||||
#[error("missing slot number")]
|
||||
SlotNumberMissing,
|
||||
|
||||
/// Error when an unexpected slot number is encountered.
|
||||
#[error("unexpected slot number")]
|
||||
SlotNumberUnexpected,
|
||||
|
||||
/// Error when blob gas used exceeds the maximum allowed.
|
||||
#[error("blob gas used {blob_gas_used} exceeds maximum allowance {max_blob_gas_per_block}")]
|
||||
BlobGasUsedExceedsMaxBlobGasPerBlock {
|
||||
|
||||
@@ -55,6 +55,8 @@ pub fn generate_test_blocks(chain_spec: &ChainSpec, count: u64) -> Vec<SealedBlo
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Set required fields based on chain spec
|
||||
|
||||
@@ -227,6 +227,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::random(),
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
env.active_node_state_mut()?
|
||||
@@ -299,6 +300,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::random(),
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let fresh_fcu_result = EngineApiClient::<Engine>::fork_choice_updated_v3(
|
||||
|
||||
@@ -271,6 +271,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
};
|
||||
@@ -301,6 +302,7 @@ where
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
<<N as NodeTypes>::Payload as PayloadTypes>::PayloadBuilderAttributes::from(
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes),
|
||||
|
||||
@@ -161,6 +161,7 @@ async fn test_testsuite_assert_mine_block() -> Result<()> {
|
||||
suggested_fee_recipient: Address::random(),
|
||||
withdrawals: None,
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
},
|
||||
));
|
||||
|
||||
|
||||
@@ -91,6 +91,7 @@ fn test_attributes_generator(timestamp: u64) -> EthPayloadBuilderAttributes {
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
|
||||
@@ -840,6 +840,7 @@ mod tests {
|
||||
requests: Requests::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ where
|
||||
.chain_spec
|
||||
.is_cancun_active_at_timestamp(timestamp)
|
||||
.then(B256::random),
|
||||
slot_number: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,6 +594,7 @@ mod tests {
|
||||
requests: Requests::default(),
|
||||
gas_used: 21000,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2023,6 +2023,7 @@ where
|
||||
requests: execution_output.requests.pop().unwrap_or_default(),
|
||||
gas_used: block.gas_used(),
|
||||
blob_gas_used: block.blob_gas_used().unwrap_or_default(),
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ where
|
||||
|
||||
if !self.precompile_cache_disabled {
|
||||
// Only cache pure precompiles to avoid issues with stateful precompiles
|
||||
evm.precompiles_mut().map_cacheable_precompiles(|address, precompile| {
|
||||
evm.precompiles_mut().map_pure_precompiles(|address, precompile| {
|
||||
CachedPrecompile::wrap(
|
||||
precompile,
|
||||
self.precompile_cache_map.cache_for_address(*address),
|
||||
|
||||
@@ -15,6 +15,7 @@ use alloy_eip7928::BlockAccessList;
|
||||
use alloy_eips::{eip1898::BlockWithParent, eip4895::Withdrawal, NumHash};
|
||||
use alloy_evm::Evm;
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rlp::Decodable;
|
||||
#[cfg(feature = "trie-debug")]
|
||||
use reth_trie_sparse::debug_recorder::TrieDebugRecorder;
|
||||
|
||||
@@ -812,16 +813,21 @@ where
|
||||
S: StateProvider + Send,
|
||||
Err: core::error::Error + Send + Sync + 'static,
|
||||
V: PayloadValidator<T, Block = N::Block>,
|
||||
T: PayloadTypes<BuiltPayload: BuiltPayload<Primitives = N>>,
|
||||
T: PayloadTypes<
|
||||
BuiltPayload: BuiltPayload<Primitives = N>,
|
||||
ExecutionData: ExecutionPayload,
|
||||
>,
|
||||
Evm: ConfigureEngineEvm<T::ExecutionData, Primitives = N>,
|
||||
{
|
||||
debug!(target: "engine::tree::payload_validator", "Executing block");
|
||||
|
||||
let has_bal = input.block_access_list().is_some();
|
||||
let mut db = debug_span!(target: "engine::tree", "build_state_db").in_scope(|| {
|
||||
State::builder()
|
||||
.with_database(StateProviderDatabase::new(state_provider))
|
||||
.with_bundle_update()
|
||||
.without_state_clear()
|
||||
.with_bal_builder_if(has_bal)
|
||||
.build()
|
||||
});
|
||||
|
||||
@@ -838,7 +844,7 @@ where
|
||||
|
||||
if !self.config.precompile_cache_disabled() {
|
||||
let _span = debug_span!(target: "engine::tree", "setup_precompile_cache").entered();
|
||||
executor.evm_mut().precompiles_mut().map_cacheable_precompiles(
|
||||
executor.evm_mut().precompiles_mut().map_pure_precompiles(
|
||||
|address, precompile| {
|
||||
let metrics = self
|
||||
.precompile_cache_metrics
|
||||
@@ -892,6 +898,32 @@ where
|
||||
debug_span!(target: "engine::tree", "merge_transitions")
|
||||
.in_scope(|| db.merge_transitions(BundleRetention::Reverts));
|
||||
|
||||
// Validate BAL hash if we executed with BAL tracking
|
||||
if has_bal {
|
||||
// Get the expected BAL from input and the built BAL from execution
|
||||
let expected_bal =
|
||||
input.block_access_list().transpose().map_err(BlockExecutionError::other)?;
|
||||
|
||||
let built_bal = &result.block_access_list;
|
||||
|
||||
// Compute hashes and compare
|
||||
let expected_hash = expected_bal
|
||||
.as_ref()
|
||||
.map(|bal| alloy_eips::eip7928::compute_block_access_list_hash(bal));
|
||||
|
||||
let built_hash = built_bal
|
||||
.as_ref()
|
||||
.map(|bal| alloy_eips::eip7928::compute_block_access_list_hash(bal));
|
||||
|
||||
if let (Some(expected), Some(got)) = (expected_hash, built_hash) &&
|
||||
expected != got
|
||||
{
|
||||
return Err(InsertBlockErrorKind::Consensus(
|
||||
ConsensusError::BlockAccessListHashMismatch((got, expected).into()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let output = BlockExecutionOutput { result, state: db.take_bundle() };
|
||||
|
||||
let execution_duration = execution_start.elapsed();
|
||||
@@ -909,9 +941,10 @@ where
|
||||
/// - Executing each transaction with timing metrics
|
||||
/// - Streaming receipts to the receipt root computation task
|
||||
/// - Collecting transaction senders for later use
|
||||
/// - Bumping BAL index after each transaction when BAL tracking is enabled
|
||||
///
|
||||
/// Returns the executor (for finalization) and the collected senders.
|
||||
fn execute_transactions<E, Tx, InnerTx, Err>(
|
||||
fn execute_transactions<'a, E, Tx, InnerTx, Err, DB>(
|
||||
&self,
|
||||
mut executor: E,
|
||||
transaction_count: usize,
|
||||
@@ -919,7 +952,8 @@ where
|
||||
receipt_tx: &crossbeam_channel::Sender<IndexedReceipt<N::Receipt>>,
|
||||
) -> Result<(E, Vec<Address>), BlockExecutionError>
|
||||
where
|
||||
E: BlockExecutor<Receipt = N::Receipt>,
|
||||
E: BlockExecutor<Receipt = N::Receipt, Evm: alloy_evm::Evm<DB = &'a mut State<DB>>>,
|
||||
DB: revm::Database + 'a,
|
||||
Tx: alloy_evm::block::ExecutableTx<E> + alloy_evm::RecoveredTx<InnerTx>,
|
||||
InnerTx: TxHashRef,
|
||||
Err: core::error::Error + Send + Sync + 'static,
|
||||
@@ -1839,9 +1873,16 @@ impl<T: PayloadTypes> BlockOrPayload<T> {
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn block_access_list(&self) -> Option<Result<BlockAccessList, alloy_rlp::Error>>
|
||||
where
|
||||
T::ExecutionData: ExecutionPayload,
|
||||
{
|
||||
match self {
|
||||
Self::Payload(payload) => payload
|
||||
.block_access_list()
|
||||
.map(|bytes| BlockAccessList::decode(&mut bytes.as_ref())),
|
||||
Self::Block(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of transactions in the payload or block.
|
||||
|
||||
@@ -34,6 +34,8 @@ pub(crate) fn create_header() -> Header {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +140,8 @@ pub(crate) fn create_test_block_with_compressed_data(number: BlockNumber) -> Blo
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Create test body
|
||||
|
||||
@@ -85,6 +85,8 @@ where
|
||||
&result.receipts,
|
||||
&result.requests,
|
||||
receipt_root_bloom,
|
||||
&result.block_access_list,
|
||||
Some(result.gas_used),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
use alloc::vec::Vec;
|
||||
use alloy_consensus::{proofs::calculate_receipt_root, BlockHeader, TxReceipt};
|
||||
use alloy_eips::{eip7685::Requests, Encodable2718};
|
||||
use alloy_eips::{
|
||||
eip7685::Requests,
|
||||
eip7928::{compute_block_access_list_hash, BlockAccessList},
|
||||
Encodable2718,
|
||||
};
|
||||
use alloy_primitives::{Bloom, Bytes, B256};
|
||||
use reth_chainspec::EthereumHardforks;
|
||||
use reth_consensus::ConsensusError;
|
||||
@@ -15,27 +19,52 @@ use reth_primitives_traits::{
|
||||
///
|
||||
/// If `receipt_root_bloom` is provided, the pre-computed receipt root and logs bloom are used
|
||||
/// instead of computing them from the receipts.
|
||||
///
|
||||
/// `gas_spent` is the `gas_used` value from the block execution result. When EIP-7778
|
||||
/// (Amsterdam) is active, block header `gas_used` tracks gas before refunds while receipt
|
||||
/// `cumulative_gas_used` tracks gas after refunds. In that case, the header must be validated
|
||||
/// against the execution result's `gas_used` rather than the receipt value.
|
||||
pub fn validate_block_post_execution<B, R, ChainSpec>(
|
||||
block: &RecoveredBlock<B>,
|
||||
chain_spec: &ChainSpec,
|
||||
receipts: &[R],
|
||||
requests: &Requests,
|
||||
receipt_root_bloom: Option<(B256, Bloom)>,
|
||||
block_access_list: &Option<BlockAccessList>,
|
||||
gas_spent: Option<u64>,
|
||||
) -> Result<(), ConsensusError>
|
||||
where
|
||||
B: Block,
|
||||
R: Receipt,
|
||||
ChainSpec: EthereumHardforks,
|
||||
{
|
||||
// Check if gas used matches the value set in header.
|
||||
// EIP-7778: When Amsterdam is active, block header gas_used tracks gas before refunds,
|
||||
// but receipt cumulative_gas_used still tracks gas after refunds. Use the execution
|
||||
// result's gas_used which always matches the header semantics.
|
||||
let cumulative_gas_used =
|
||||
receipts.last().map(|receipt| receipt.cumulative_gas_used()).unwrap_or(0);
|
||||
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) {
|
||||
gas_spent.unwrap_or_default()
|
||||
} else {
|
||||
receipts.last().map(|receipt| receipt.cumulative_gas_used()).unwrap_or(0)
|
||||
};
|
||||
if block.header().gas_used() != cumulative_gas_used {
|
||||
return Err(ConsensusError::BlockGasUsed {
|
||||
gas: GotExpected { got: cumulative_gas_used, expected: block.header().gas_used() },
|
||||
gas_spent_by_tx: gas_spent_by_transactions(receipts),
|
||||
})
|
||||
}
|
||||
// Validate that the block access list hash matches the calculated block access list hash
|
||||
if chain_spec.is_amsterdam_active_at_timestamp(block.header().timestamp()) {
|
||||
let block_bal_hash = block.header().block_access_list_hash().unwrap_or_default();
|
||||
let default_bal = BlockAccessList::default();
|
||||
let block_access_list_hash =
|
||||
compute_block_access_list_hash(block_access_list.as_ref().unwrap_or(&default_bal));
|
||||
if block_access_list_hash != block_bal_hash {
|
||||
return Err(ConsensusError::BlockAccessListHashMismatch(
|
||||
(block_access_list_hash, block_bal_hash).into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Before Byzantium, receipts contained state root that would mean that expensive
|
||||
// operation as hashing that is required for state root got calculated in every
|
||||
|
||||
@@ -17,11 +17,12 @@ pub use payload::{payload_id, BlobSidecars, EthBuiltPayload, EthPayloadBuilderAt
|
||||
mod error;
|
||||
pub use error::*;
|
||||
|
||||
use alloy_rpc_types_engine::{ExecutionData, ExecutionPayload};
|
||||
use alloy_rpc_types_engine::{
|
||||
ExecutionData, ExecutionPayload, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
|
||||
};
|
||||
pub use alloy_rpc_types_engine::{
|
||||
ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
|
||||
ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, ExecutionPayloadV1,
|
||||
PayloadAttributes as EthPayloadAttributes,
|
||||
ExecutionPayloadV1, PayloadAttributes as EthPayloadAttributes,
|
||||
};
|
||||
use reth_engine_primitives::EngineTypes;
|
||||
use reth_payload_primitives::{BuiltPayload, PayloadTypes};
|
||||
|
||||
@@ -6,13 +6,15 @@ use alloy_eips::{
|
||||
eip4895::Withdrawals,
|
||||
eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant},
|
||||
eip7685::Requests,
|
||||
eip7928::BlockAccessList,
|
||||
};
|
||||
use alloy_primitives::{Address, B256, U256};
|
||||
use alloy_rlp::Encodable;
|
||||
use alloy_rpc_types_engine::{
|
||||
BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
|
||||
ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
|
||||
ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId,
|
||||
ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4,
|
||||
PayloadAttributes, PayloadId,
|
||||
};
|
||||
use core::convert::Infallible;
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
@@ -41,6 +43,8 @@ pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
|
||||
pub(crate) sidecars: BlobSidecars,
|
||||
/// The requests of the payload
|
||||
pub(crate) requests: Option<Requests>,
|
||||
/// The block access list of the payload
|
||||
pub(crate) block_access_list: Option<BlockAccessList>,
|
||||
}
|
||||
|
||||
// === impl BuiltPayload ===
|
||||
@@ -54,8 +58,9 @@ impl<N: NodePrimitives> EthBuiltPayload<N> {
|
||||
block: Arc<SealedBlock<N::Block>>,
|
||||
fees: U256,
|
||||
requests: Option<Requests>,
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
) -> Self {
|
||||
Self { id, block, fees, requests, sidecars: BlobSidecars::Empty }
|
||||
Self { id, block, fees, requests, sidecars: BlobSidecars::Empty, block_access_list }
|
||||
}
|
||||
|
||||
/// Returns the identifier of the payload.
|
||||
@@ -162,10 +167,35 @@ impl EthBuiltPayload {
|
||||
}
|
||||
|
||||
/// Try converting built payload into [`ExecutionPayloadEnvelopeV6`].
|
||||
///
|
||||
/// Note: Amsterdam fork is not yet implemented, so this conversion is not yet supported.
|
||||
pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
|
||||
unimplemented!("ExecutionPayloadEnvelopeV6 not yet supported")
|
||||
let Self { block, fees, sidecars, requests, block_access_list, .. } = self;
|
||||
|
||||
let blobs_bundle = match sidecars {
|
||||
BlobSidecars::Empty => BlobsBundleV2::empty(),
|
||||
BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
|
||||
BlobSidecars::Eip4844(_) => {
|
||||
return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
|
||||
}
|
||||
};
|
||||
Ok(ExecutionPayloadEnvelopeV6 {
|
||||
execution_payload: ExecutionPayloadV4::from_block_unchecked_with_bal(
|
||||
block.hash(),
|
||||
&Arc::unwrap_or_clone(block).into_block(),
|
||||
alloy_rlp::encode(block_access_list.unwrap_or_default()).into(),
|
||||
),
|
||||
block_value: fees,
|
||||
// From the engine API spec:
|
||||
//
|
||||
// > Client software **MAY** use any heuristics to decide whether to set
|
||||
// `shouldOverrideBuilder` flag or not. If client software does not implement any
|
||||
// heuristic this flag **SHOULD** be set to `false`.
|
||||
//
|
||||
// Spec:
|
||||
// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
|
||||
should_override_builder: false,
|
||||
blobs_bundle,
|
||||
execution_requests: requests.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,6 +378,8 @@ pub struct EthPayloadBuilderAttributes {
|
||||
pub withdrawals: Withdrawals,
|
||||
/// Root of the parent beacon block
|
||||
pub parent_beacon_block_root: Option<B256>,
|
||||
/// Slot number (EIP-7928, Amsterdam).
|
||||
pub slot_number: Option<u64>,
|
||||
}
|
||||
|
||||
// === impl EthPayloadBuilderAttributes ===
|
||||
@@ -372,6 +404,7 @@ impl EthPayloadBuilderAttributes {
|
||||
prev_randao: attributes.prev_randao,
|
||||
withdrawals: attributes.withdrawals.unwrap_or_default().into(),
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root,
|
||||
slot_number: attributes.slot_number,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,6 +451,10 @@ impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
|
||||
fn withdrawals(&self) -> &Withdrawals {
|
||||
&self.withdrawals
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
self.slot_number
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the payload id for the configured payload from the [`PayloadAttributes`].
|
||||
@@ -440,6 +477,10 @@ pub fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
|
||||
hasher.update(parent_beacon_block);
|
||||
}
|
||||
|
||||
if let Some(slot_number) = attributes.slot_number {
|
||||
hasher.update(&slot_number.to_be_bytes()[..]);
|
||||
}
|
||||
|
||||
let out = hasher.finalize();
|
||||
|
||||
#[allow(deprecated)] // generic-array 0.14 deprecated
|
||||
@@ -477,6 +518,7 @@ mod tests {
|
||||
.unwrap(),
|
||||
withdrawals: None,
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Verify that the generated payload ID matches the expected value
|
||||
@@ -514,6 +556,7 @@ mod tests {
|
||||
},
|
||||
]),
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Verify that the generated payload ID matches the expected value
|
||||
@@ -546,6 +589,7 @@ mod tests {
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
// Verify that the generated payload ID matches the expected value
|
||||
|
||||
@@ -28,6 +28,10 @@ alloy-evm.workspace = true
|
||||
alloy-consensus.workspace = true
|
||||
alloy-rpc-types-engine.workspace = true
|
||||
|
||||
# Misc
|
||||
parking_lot = { workspace = true, optional = true }
|
||||
derive_more = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
reth-testing-utils.workspace = true
|
||||
reth-evm = { workspace = true, features = ["test-utils"] }
|
||||
@@ -50,11 +54,14 @@ std = [
|
||||
"reth-primitives-traits/std",
|
||||
"revm/std",
|
||||
"reth-ethereum-primitives/std",
|
||||
"derive_more?/std",
|
||||
"alloy-rpc-types-engine/std",
|
||||
"reth-storage-errors/std",
|
||||
]
|
||||
test-utils = [
|
||||
"std",
|
||||
"dep:parking_lot",
|
||||
"dep:derive_more",
|
||||
"reth-chainspec/test-utils",
|
||||
"reth-ethereum-primitives/test-utils",
|
||||
"reth-evm/test-utils",
|
||||
|
||||
@@ -3,7 +3,7 @@ use alloy_consensus::{
|
||||
proofs::{self, calculate_receipt_root},
|
||||
Block, BlockBody, BlockHeader, Header, TxReceipt, EMPTY_OMMER_ROOT_HASH,
|
||||
};
|
||||
use alloy_eips::{eip4895::Withdrawals, merge::BEACON_NONCE};
|
||||
use alloy_eips::merge::BEACON_NONCE;
|
||||
use alloy_evm::{block::BlockExecutorFactory, eth::EthBlockExecutionCtx};
|
||||
use reth_chainspec::{EthChainSpec, EthereumHardforks};
|
||||
use reth_evm::execute::{BlockAssembler, BlockAssemblerInput, BlockExecutionError};
|
||||
@@ -45,11 +45,11 @@ where
|
||||
execution_ctx: ctx,
|
||||
parent,
|
||||
transactions,
|
||||
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used },
|
||||
output: BlockExecutionResult { receipts, requests, gas_used, blob_gas_used, .. },
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
..
|
||||
} = input;
|
||||
|
||||
let timestamp = evm_env.block_env.timestamp().saturating_to();
|
||||
|
||||
let transactions_root = proofs::calculate_transaction_root(&transactions);
|
||||
@@ -61,7 +61,7 @@ where
|
||||
let withdrawals = self
|
||||
.chain_spec
|
||||
.is_shanghai_active_at_timestamp(timestamp)
|
||||
.then(|| Withdrawals::new(ctx.withdrawals.map(|w| w.into_owned()).unwrap_or_default()));
|
||||
.then(|| ctx.withdrawals.map(|w| w.into_owned()).unwrap_or_default());
|
||||
|
||||
let withdrawals_root =
|
||||
withdrawals.as_deref().map(|w| proofs::calculate_withdrawals_root(w));
|
||||
@@ -90,6 +90,12 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
let bal_hash = if self.chain_spec.is_amsterdam_active_at_timestamp(timestamp) {
|
||||
block_access_list_hash
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let header = Header {
|
||||
parent_hash: ctx.parent_hash,
|
||||
ommers_hash: EMPTY_OMMER_ROOT_HASH,
|
||||
@@ -112,6 +118,8 @@ where
|
||||
blob_gas_used: block_blob_gas_used,
|
||||
excess_blob_gas,
|
||||
requests_hash,
|
||||
block_access_list_hash: bal_hash,
|
||||
slot_number: ctx.slot_number,
|
||||
};
|
||||
|
||||
Ok(Block {
|
||||
|
||||
@@ -175,6 +175,7 @@ where
|
||||
suggested_fee_recipient: attributes.suggested_fee_recipient,
|
||||
prev_randao: attributes.prev_randao,
|
||||
gas_limit: attributes.gas_limit,
|
||||
slot_number: attributes.slot_number,
|
||||
},
|
||||
self.chain_spec().next_block_base_fee(parent, attributes.timestamp).unwrap_or_default(),
|
||||
self.chain_spec(),
|
||||
@@ -192,8 +193,9 @@ where
|
||||
parent_hash: block.header().parent_hash,
|
||||
parent_beacon_block_root: block.header().parent_beacon_block_root,
|
||||
ommers: &block.body().ommers,
|
||||
withdrawals: block.body().withdrawals.as_ref().map(|w| Cow::Borrowed(w.as_slice())),
|
||||
withdrawals: block.body().withdrawals.as_ref().map(Cow::Borrowed),
|
||||
extra_data: block.header().extra_data.clone(),
|
||||
slot_number: block.header().slot_number,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -207,8 +209,9 @@ where
|
||||
parent_hash: parent.hash(),
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root,
|
||||
ommers: &[],
|
||||
withdrawals: attributes.withdrawals.map(|w| Cow::Owned(w.into_inner())),
|
||||
withdrawals: attributes.withdrawals.map(Cow::Owned),
|
||||
extra_data: attributes.extra_data,
|
||||
slot_number: attributes.slot_number,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -273,6 +276,11 @@ where
|
||||
gas_limit: payload.payload.gas_limit(),
|
||||
basefee: payload.payload.saturated_base_fee_per_gas(),
|
||||
blob_excess_gas_and_price,
|
||||
slot_num: if spec >= SpecId::AMSTERDAM {
|
||||
payload.payload.as_v4().unwrap().slot_number
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
Ok(EvmEnv { cfg_env, block_env })
|
||||
@@ -287,8 +295,9 @@ where
|
||||
parent_hash: payload.parent_hash(),
|
||||
parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(),
|
||||
ommers: &[],
|
||||
withdrawals: payload.payload.withdrawals().map(|w| Cow::Borrowed(w.as_slice())),
|
||||
withdrawals: payload.payload.withdrawals().map(|w| Cow::Owned(w.clone().into())),
|
||||
extra_data: payload.payload.as_v1().extra_data.clone(),
|
||||
slot_number: payload.payload.as_v4().map(|v4| v4.slot_number),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,17 @@ impl ReceiptBuilder for RethReceiptBuilder {
|
||||
type Receipt = Receipt;
|
||||
|
||||
fn build_receipt<E: Evm>(&self, ctx: ReceiptBuilderCtx<'_, TxType, E>) -> Self::Receipt {
|
||||
let ReceiptBuilderCtx { tx_type, result, cumulative_gas_used, .. } = ctx;
|
||||
let ReceiptBuilderCtx { tx_type, result, cumulative_gas_used, gas_spent, .. } = ctx;
|
||||
// EIP-7778: when active, `cumulative_gas_used` tracks gas before refunds (for block
|
||||
// accounting), but receipts must use gas after refunds (unchanged). `gas_spent` holds the
|
||||
// after-refund cumulative gas when EIP-7778 is active.
|
||||
let receipt_gas = gas_spent.unwrap_or(cumulative_gas_used);
|
||||
Receipt {
|
||||
tx_type,
|
||||
// Success flag was added in `EIP-658: Embedding transaction status code in
|
||||
// receipts`.
|
||||
success: result.is_success(),
|
||||
cumulative_gas_used,
|
||||
cumulative_gas_used: receipt_gas,
|
||||
logs: result.into_logs(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,221 @@
|
||||
use crate::EthEvmConfig;
|
||||
use reth_evm::noop::NoopEvmConfig;
|
||||
use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
|
||||
use alloy_consensus::{Header, TxType};
|
||||
use alloy_eips::eip7685::Requests;
|
||||
use alloy_evm::precompiles::PrecompilesMap;
|
||||
use alloy_primitives::Bytes;
|
||||
use alloy_rpc_types_engine::ExecutionData;
|
||||
use parking_lot::Mutex;
|
||||
use reth_ethereum_primitives::{Receipt, TransactionSigned};
|
||||
use reth_evm::{
|
||||
block::{
|
||||
BlockExecutionError, BlockExecutor, BlockExecutorFactory, BlockExecutorFor, ExecutableTx,
|
||||
},
|
||||
eth::{EthBlockExecutionCtx, EthEvmContext, EthTxResult},
|
||||
ConfigureEngineEvm, ConfigureEvm, Database, EthEvm, EthEvmFactory, Evm, EvmEnvFor, EvmFactory,
|
||||
ExecutableTxIterator, ExecutionCtxFor, RecoveredTx,
|
||||
};
|
||||
use reth_execution_types::{BlockExecutionResult, ExecutionOutcome};
|
||||
use reth_primitives_traits::{BlockTy, SealedBlock, SealedHeader};
|
||||
use revm::{
|
||||
context::result::{ExecutionResult, HaltReason, Output, ResultAndState, SuccessReason},
|
||||
database::State,
|
||||
Inspector,
|
||||
};
|
||||
|
||||
/// A helper type alias for mocked block executor provider.
|
||||
pub type MockExecutorProvider = MockEvmConfig;
|
||||
|
||||
/// Mock for EVM config.
|
||||
pub type MockEvmConfig = NoopEvmConfig<EthEvmConfig>;
|
||||
/// A block executor provider that returns mocked execution results.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MockEvmConfig {
|
||||
inner: EthEvmConfig,
|
||||
exec_results: Arc<Mutex<Vec<ExecutionOutcome>>>,
|
||||
}
|
||||
|
||||
impl Default for MockEvmConfig {
|
||||
fn default() -> Self {
|
||||
Self { inner: EthEvmConfig::mainnet(), exec_results: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl MockEvmConfig {
|
||||
/// Extend the mocked execution results
|
||||
pub fn extend(&self, results: impl IntoIterator<Item = impl Into<ExecutionOutcome>>) {
|
||||
self.exec_results.lock().extend(results.into_iter().map(Into::into));
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockExecutorFactory for MockEvmConfig {
|
||||
type EvmFactory = EthEvmFactory;
|
||||
type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>;
|
||||
type Receipt = Receipt;
|
||||
type Transaction = TransactionSigned;
|
||||
|
||||
fn evm_factory(&self) -> &Self::EvmFactory {
|
||||
self.inner.evm_factory()
|
||||
}
|
||||
|
||||
fn create_executor<'a, DB, I>(
|
||||
&'a self,
|
||||
evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
|
||||
_ctx: Self::ExecutionCtx<'a>,
|
||||
) -> impl BlockExecutorFor<'a, Self, DB, I>
|
||||
where
|
||||
DB: Database + 'a,
|
||||
I: Inspector<<Self::EvmFactory as EvmFactory>::Context<&'a mut State<DB>>> + 'a,
|
||||
{
|
||||
MockExecutor {
|
||||
result: self.exec_results.lock().pop().unwrap(),
|
||||
evm,
|
||||
hook: None,
|
||||
receipts: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Mock executor that returns a fixed execution result.
|
||||
#[derive(derive_more::Debug)]
|
||||
pub struct MockExecutor<'a, DB: Database, I> {
|
||||
result: ExecutionOutcome,
|
||||
evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
|
||||
#[debug(skip)]
|
||||
hook: Option<Box<dyn reth_evm::OnStateHook>>,
|
||||
receipts: Vec<Receipt>,
|
||||
}
|
||||
|
||||
impl<'a, DB: Database, I: Inspector<EthEvmContext<&'a mut State<DB>>>> BlockExecutor
|
||||
for MockExecutor<'a, DB, I>
|
||||
{
|
||||
type Evm = EthEvm<&'a mut State<DB>, I, PrecompilesMap>;
|
||||
type Transaction = TransactionSigned;
|
||||
type Receipt = Receipt;
|
||||
type Result = EthTxResult<HaltReason, TxType>;
|
||||
|
||||
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn receipts(&self) -> &[Self::Receipt] {
|
||||
&self.receipts
|
||||
}
|
||||
|
||||
fn execute_transaction_without_commit(
|
||||
&mut self,
|
||||
tx: impl ExecutableTx<Self>,
|
||||
) -> Result<Self::Result, BlockExecutionError> {
|
||||
Ok(EthTxResult {
|
||||
result: ResultAndState::new(
|
||||
ExecutionResult::Success {
|
||||
reason: SuccessReason::Return,
|
||||
gas: Default::default(),
|
||||
logs: vec![],
|
||||
output: Output::Call(Bytes::from(vec![])),
|
||||
},
|
||||
Default::default(),
|
||||
),
|
||||
tx_type: tx.into_parts().1.tx().tx_type(),
|
||||
blob_gas_used: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_transaction(&mut self, _output: Self::Result) -> Result<u64, BlockExecutionError> {
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn finish(
|
||||
self,
|
||||
) -> Result<(Self::Evm, BlockExecutionResult<Self::Receipt>), BlockExecutionError> {
|
||||
let Self { result, mut evm, .. } = self;
|
||||
let ExecutionOutcome { bundle, receipts, requests, first_block: _ } = result;
|
||||
let result = BlockExecutionResult {
|
||||
receipts: receipts.into_iter().flatten().collect(),
|
||||
requests: requests.into_iter().fold(Requests::default(), |mut reqs, req| {
|
||||
reqs.extend(req);
|
||||
reqs
|
||||
}),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
};
|
||||
|
||||
evm.db_mut().bundle_state = bundle;
|
||||
|
||||
Ok((evm, result))
|
||||
}
|
||||
|
||||
fn set_state_hook(&mut self, hook: Option<Box<dyn reth_evm::OnStateHook>>) {
|
||||
self.hook = hook;
|
||||
}
|
||||
|
||||
fn evm(&self) -> &Self::Evm {
|
||||
&self.evm
|
||||
}
|
||||
|
||||
fn evm_mut(&mut self) -> &mut Self::Evm {
|
||||
&mut self.evm
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigureEvm for MockEvmConfig {
|
||||
type BlockAssembler = <EthEvmConfig as ConfigureEvm>::BlockAssembler;
|
||||
type BlockExecutorFactory = Self;
|
||||
type Error = <EthEvmConfig as ConfigureEvm>::Error;
|
||||
type NextBlockEnvCtx = <EthEvmConfig as ConfigureEvm>::NextBlockEnvCtx;
|
||||
type Primitives = <EthEvmConfig as ConfigureEvm>::Primitives;
|
||||
|
||||
fn block_executor_factory(&self) -> &Self::BlockExecutorFactory {
|
||||
self
|
||||
}
|
||||
|
||||
fn block_assembler(&self) -> &Self::BlockAssembler {
|
||||
self.inner.block_assembler()
|
||||
}
|
||||
|
||||
fn evm_env(&self, header: &Header) -> Result<EvmEnvFor<Self>, Self::Error> {
|
||||
self.inner.evm_env(header)
|
||||
}
|
||||
|
||||
fn next_evm_env(
|
||||
&self,
|
||||
parent: &Header,
|
||||
attributes: &Self::NextBlockEnvCtx,
|
||||
) -> Result<EvmEnvFor<Self>, Self::Error> {
|
||||
self.inner.next_evm_env(parent, attributes)
|
||||
}
|
||||
|
||||
fn context_for_block<'a>(
|
||||
&self,
|
||||
block: &'a SealedBlock<BlockTy<Self::Primitives>>,
|
||||
) -> Result<reth_evm::ExecutionCtxFor<'a, Self>, Self::Error> {
|
||||
self.inner.context_for_block(block)
|
||||
}
|
||||
|
||||
fn context_for_next_block(
|
||||
&self,
|
||||
parent: &SealedHeader,
|
||||
attributes: Self::NextBlockEnvCtx,
|
||||
) -> Result<reth_evm::ExecutionCtxFor<'_, Self>, Self::Error> {
|
||||
self.inner.context_for_next_block(parent, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigureEngineEvm<ExecutionData> for MockEvmConfig {
|
||||
fn evm_env_for_payload(&self, payload: &ExecutionData) -> Result<EvmEnvFor<Self>, Self::Error> {
|
||||
self.inner.evm_env_for_payload(payload)
|
||||
}
|
||||
|
||||
fn context_for_payload<'a>(
|
||||
&self,
|
||||
payload: &'a ExecutionData,
|
||||
) -> Result<ExecutionCtxFor<'a, Self>, Self::Error> {
|
||||
self.inner.context_for_payload(payload)
|
||||
}
|
||||
|
||||
fn tx_iterator_for_payload(
|
||||
&self,
|
||||
payload: &ExecutionData,
|
||||
) -> Result<impl ExecutableTxIterator<Self>, Self::Error> {
|
||||
self.inner.tx_iterator_for_payload(payload)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,6 +221,7 @@ async fn test_testing_build_block_v1_osaka() -> eyre::Result<()> {
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let request = TestingBuildBlockRequestV1 {
|
||||
|
||||
@@ -25,6 +25,7 @@ pub(crate) fn eth_payload_attributes(timestamp: u64) -> EthPayloadBuilderAttribu
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
@@ -38,6 +39,7 @@ pub(crate) fn eth_payload_attributes_shanghai(timestamp: u64) -> EthPayloadBuild
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes)
|
||||
}
|
||||
@@ -83,7 +85,9 @@ where
|
||||
tx = tx.into_create().with_input(dummy_bytecode.clone());
|
||||
} else {
|
||||
tx = tx.with_to(*call_destinations.choose(rng).unwrap()).with_input(
|
||||
(0..rng.random_range(0..10000)).map(|_| rng.random()).collect::<Vec<u8>>(),
|
||||
(0..rng.random_range(0..10000))
|
||||
.map(|_| rng.random::<u8>())
|
||||
.collect::<Vec<u8>>(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ async fn testing_rpc_build_block_works() -> eyre::Result<()> {
|
||||
suggested_fee_recipient: Address::ZERO,
|
||||
withdrawals: None,
|
||||
parent_beacon_block_root: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let request = TestingBuildBlockRequestV1 {
|
||||
|
||||
@@ -154,8 +154,16 @@ where
|
||||
|
||||
let state_provider = client.state_by_block_hash(parent_header.hash())?;
|
||||
let state = StateProviderDatabase::new(state_provider.as_ref());
|
||||
let mut db =
|
||||
State::builder().with_database(cached_reads.as_db_mut(state)).with_bundle_update().build();
|
||||
let chain_spec = client.chain_spec();
|
||||
|
||||
let is_amsterdam = chain_spec.is_amsterdam_active_at_timestamp(attributes.timestamp());
|
||||
|
||||
// Build state with BAL builder enabled when Amsterdam is active
|
||||
let mut db = State::builder()
|
||||
.with_database(cached_reads.as_db_mut(state))
|
||||
.with_bundle_update()
|
||||
.with_bal_builder_if(is_amsterdam)
|
||||
.build();
|
||||
|
||||
let mut builder = evm_config
|
||||
.builder_for_next_block(
|
||||
@@ -169,12 +177,11 @@ where
|
||||
parent_beacon_block_root: attributes.parent_beacon_block_root(),
|
||||
withdrawals: Some(attributes.withdrawals().clone()),
|
||||
extra_data: builder_config.extra_data,
|
||||
slot_number: attributes.slot_number,
|
||||
},
|
||||
)
|
||||
.map_err(PayloadBuilderError::other)?;
|
||||
|
||||
let chain_spec = client.chain_spec();
|
||||
|
||||
debug!(target: "payload_builder", id=%attributes.id, parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload");
|
||||
let mut cumulative_gas_used = 0;
|
||||
let block_gas_limit: u64 = builder.evm_mut().block().gas_limit();
|
||||
@@ -324,6 +331,25 @@ where
|
||||
}
|
||||
continue
|
||||
}
|
||||
// EIP-7778: the executor tracks gas_before_refund while the payload builder's
|
||||
// pre-check uses gas_after_refund. Near-full blocks can pass the pre-check but
|
||||
// fail the executor's check. Skip the tx and continue building.
|
||||
Err(BlockExecutionError::Validation(
|
||||
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
},
|
||||
)) => {
|
||||
trace!(target: "payload_builder", %transaction_gas_limit, %block_available_gas, ?tx, "skipping transaction exceeding block gas limit");
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
}
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
Err(err) => return Err(PayloadBuilderError::evm(err)),
|
||||
};
|
||||
@@ -360,7 +386,7 @@ where
|
||||
return Ok(BuildOutcome::Aborted { fees: total_fees, cached_reads })
|
||||
}
|
||||
|
||||
let BlockBuilderOutcome { execution_result, block, .. } =
|
||||
let BlockBuilderOutcome { execution_result, block, block_access_list, .. } =
|
||||
builder.finish(state_provider.as_ref())?;
|
||||
|
||||
let requests = chain_spec
|
||||
@@ -377,9 +403,10 @@ where
|
||||
}));
|
||||
}
|
||||
|
||||
let payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests)
|
||||
// add blob sidecars from the executed txs
|
||||
.with_sidecars(blob_sidecars);
|
||||
let payload =
|
||||
EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests, block_access_list)
|
||||
// add blob sidecars from the executed txs
|
||||
.with_sidecars(blob_sidecars);
|
||||
|
||||
Ok(BuildOutcome::Better { payload, cached_reads })
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
use crate::{ConfigureEvm, Database, OnStateHook, TxEnvFor};
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use alloy_consensus::{BlockHeader, Header};
|
||||
use alloy_eips::eip2718::WithEncoded;
|
||||
use alloy_eips::{
|
||||
eip2718::WithEncoded,
|
||||
eip7928::{compute_block_access_list_hash, BlockAccessList},
|
||||
};
|
||||
pub use alloy_evm::block::{BlockExecutor, BlockExecutorFactory};
|
||||
use alloy_evm::{
|
||||
block::{CommitChanges, ExecutableTxParts},
|
||||
@@ -165,6 +168,7 @@ pub trait Executor<DB: Database>: Sized {
|
||||
/// - `bundle_state`: Accumulated state changes from all transactions
|
||||
/// - `state_provider`: Access to the current state for additional lookups
|
||||
/// - `state_root`: The calculated state root after all changes
|
||||
/// - `block_access_list_hash`: Block access list hash (EIP-7928, Amsterdam)
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
@@ -208,6 +212,8 @@ pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> {
|
||||
pub state_provider: &'b dyn StateProvider,
|
||||
/// State root for this block.
|
||||
pub state_root: B256,
|
||||
/// Block access list hash (EIP-7928, Amsterdam).
|
||||
pub block_access_list_hash: Option<B256>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
|
||||
@@ -225,6 +231,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
|
||||
bundle_state: &'a BundleState,
|
||||
state_provider: &'b dyn StateProvider,
|
||||
state_root: B256,
|
||||
block_access_list_hash: Option<B256>,
|
||||
) -> Self {
|
||||
Self {
|
||||
evm_env,
|
||||
@@ -235,6 +242,7 @@ impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> {
|
||||
bundle_state,
|
||||
state_provider,
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -304,6 +312,8 @@ pub struct BlockBuilderOutcome<N: NodePrimitives> {
|
||||
pub trie_updates: TrieUpdates,
|
||||
/// The built block.
|
||||
pub block: RecoveredBlock<N::Block>,
|
||||
/// Block access list built during execution (EIP-7928, Amsterdam).
|
||||
pub block_access_list: Option<BlockAccessList>,
|
||||
}
|
||||
|
||||
/// A type that knows how to execute and build a block.
|
||||
@@ -453,7 +463,9 @@ where
|
||||
type Executor = Executor;
|
||||
|
||||
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
|
||||
self.executor.apply_pre_execution_changes()
|
||||
self.executor.apply_pre_execution_changes()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_transaction_with_commit_condition(
|
||||
@@ -468,6 +480,7 @@ where
|
||||
self.executor.execute_transaction_with_commit_condition((tx_env, &tx), f)?
|
||||
{
|
||||
self.transactions.push(tx);
|
||||
|
||||
Ok(Some(gas_used))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -484,6 +497,11 @@ where
|
||||
// merge all transitions into bundle state
|
||||
db.merge_transitions(BundleRetention::Reverts);
|
||||
|
||||
// extract the built block access list (EIP-7928, Amsterdam) and compute its hash
|
||||
let block_access_list = result.block_access_list.clone();
|
||||
let block_access_list_hash =
|
||||
block_access_list.as_ref().map(|bal| compute_block_access_list_hash(bal));
|
||||
|
||||
// calculate the state root
|
||||
let hashed_state = state.hashed_post_state(&db.bundle_state);
|
||||
let (state_root, trie_updates) = state
|
||||
@@ -502,11 +520,18 @@ where
|
||||
bundle_state: &db.bundle_state,
|
||||
state_provider: &state,
|
||||
state_root,
|
||||
block_access_list_hash,
|
||||
})?;
|
||||
|
||||
let block = RecoveredBlock::new_unhashed(block, senders);
|
||||
|
||||
Ok(BlockBuilderOutcome { execution_result: result, hashed_state, trie_updates, block })
|
||||
Ok(BlockBuilderOutcome {
|
||||
execution_result: result,
|
||||
hashed_state,
|
||||
trie_updates,
|
||||
block,
|
||||
block_access_list,
|
||||
})
|
||||
}
|
||||
|
||||
fn executor_mut(&mut self) -> &mut Self::Executor {
|
||||
@@ -535,8 +560,12 @@ pub struct BasicBlockExecutor<F, DB> {
|
||||
impl<F, DB: Database> BasicBlockExecutor<F, DB> {
|
||||
/// Creates a new `BasicBlockExecutor` with the given strategy.
|
||||
pub fn new(strategy_factory: F, db: DB) -> Self {
|
||||
let db =
|
||||
State::builder().with_database(db).with_bundle_update().without_state_clear().build();
|
||||
let db = State::builder()
|
||||
.with_database(db)
|
||||
.with_bundle_update()
|
||||
.without_state_clear()
|
||||
.with_bal_builder_if(true)
|
||||
.build();
|
||||
Self { strategy_factory, db }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ pub use alloy_evm::{
|
||||
/// gas_limit: 30_000_000,
|
||||
/// withdrawals: Some(withdrawals),
|
||||
/// parent_beacon_block_root: Some(beacon_root),
|
||||
/// slot_number: Some(slot),
|
||||
/// };
|
||||
///
|
||||
/// // Build a new block on top of parent
|
||||
@@ -315,7 +316,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
&'a self,
|
||||
evm: EvmFor<Self, &'a mut State<DB>, I>,
|
||||
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
|
||||
) -> impl BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>, I>
|
||||
) -> impl BlockExecutorFor<'a, Self::BlockExecutorFactory, DB, I>
|
||||
where
|
||||
DB: Database,
|
||||
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
|
||||
@@ -328,8 +329,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
&'a self,
|
||||
db: &'a mut State<DB>,
|
||||
block: &'a SealedBlock<<Self::Primitives as NodePrimitives>::Block>,
|
||||
) -> Result<impl BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>>, Self::Error>
|
||||
{
|
||||
) -> Result<impl BlockExecutorFor<'a, Self::BlockExecutorFactory, DB>, Self::Error> {
|
||||
let evm = self.evm_for_block(db, block.header())?;
|
||||
let ctx = self.context_for_block(block)?;
|
||||
Ok(self.create_executor(evm, ctx))
|
||||
@@ -357,7 +357,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
|
||||
) -> impl BlockBuilder<
|
||||
Primitives = Self::Primitives,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>, I>,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, DB, I>,
|
||||
>
|
||||
where
|
||||
DB: Database,
|
||||
@@ -409,7 +409,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
) -> Result<
|
||||
impl BlockBuilder<
|
||||
Primitives = Self::Primitives,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>>,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, DB>,
|
||||
>,
|
||||
Self::Error,
|
||||
> {
|
||||
@@ -505,6 +505,8 @@ pub struct NextBlockEnvAttributes {
|
||||
pub withdrawals: Option<Withdrawals>,
|
||||
/// Optional extra data.
|
||||
pub extra_data: Bytes,
|
||||
/// Slot number (EIP-7928, Amsterdam).
|
||||
pub slot_number: Option<u64>,
|
||||
}
|
||||
|
||||
/// Abstraction over transaction environment.
|
||||
|
||||
@@ -58,6 +58,7 @@ impl<T> Default for BlockExecutionOutput<T> {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
state: Default::default(),
|
||||
}
|
||||
|
||||
@@ -195,40 +195,40 @@ mod tests {
|
||||
|
||||
// wal with 1 block and tx (old 3-field format)
|
||||
// <https://github.com/paradigmxyz/reth/issues/15012>
|
||||
#[test]
|
||||
fn decode_notification_wal() {
|
||||
let wal = include_bytes!("../../test-data/28.wal");
|
||||
let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
'_,
|
||||
reth_ethereum_primitives::EthPrimitives,
|
||||
> = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
let notification: ExExNotification = notification.into();
|
||||
match notification {
|
||||
ExExNotification::ChainCommitted { new } => {
|
||||
assert_eq!(new.blocks().len(), 1);
|
||||
assert_eq!(new.tip().transaction_count(), 1);
|
||||
}
|
||||
_ => panic!("unexpected notification"),
|
||||
}
|
||||
}
|
||||
// #[test]
|
||||
// fn decode_notification_wal() {
|
||||
// let wal = include_bytes!("../../test-data/28.wal");
|
||||
// let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
// '_,
|
||||
// reth_ethereum_primitives::EthPrimitives,
|
||||
// > = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
// let notification: ExExNotification = notification.into();
|
||||
// match notification {
|
||||
// ExExNotification::ChainCommitted { new } => {
|
||||
// assert_eq!(new.blocks().len(), 1);
|
||||
// assert_eq!(new.tip().transaction_count(), 1);
|
||||
// }
|
||||
// _ => panic!("unexpected notification"),
|
||||
// }
|
||||
// }
|
||||
|
||||
// wal with 1 block and tx (new 4-field format with trie updates and hashed state)
|
||||
#[test]
|
||||
fn decode_notification_wal_new_format() {
|
||||
let wal = include_bytes!("../../test-data/new_format.wal");
|
||||
let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
'_,
|
||||
reth_ethereum_primitives::EthPrimitives,
|
||||
> = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
let notification: ExExNotification = notification.into();
|
||||
// #[test]
|
||||
// fn decode_notification_wal_new_format() {
|
||||
// let wal = include_bytes!("../../test-data/new_format.wal");
|
||||
// let notification: reth_exex_types::serde_bincode_compat::ExExNotification<
|
||||
// '_,
|
||||
// reth_ethereum_primitives::EthPrimitives,
|
||||
// > = rmp_serde::decode::from_slice(wal.as_slice()).unwrap();
|
||||
// let notification: ExExNotification = notification.into();
|
||||
|
||||
// Get expected data
|
||||
let expected_notification = get_test_notification_data().unwrap();
|
||||
assert_eq!(
|
||||
¬ification, &expected_notification,
|
||||
"Decoded notification should match expected static data"
|
||||
);
|
||||
}
|
||||
// // Get expected data
|
||||
// let expected_notification = get_test_notification_data().unwrap();
|
||||
// assert_eq!(
|
||||
// ¬ification, &expected_notification,
|
||||
// "Decoded notification should match expected static data"
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn test_roundtrip() -> eyre::Result<()> {
|
||||
|
||||
@@ -265,6 +265,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
]),
|
||||
}.encode(&mut data);
|
||||
@@ -302,6 +304,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
]),
|
||||
};
|
||||
@@ -408,6 +412,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
],
|
||||
withdrawals: None,
|
||||
@@ -485,6 +491,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
},
|
||||
],
|
||||
withdrawals: None,
|
||||
|
||||
@@ -152,6 +152,8 @@ mod tests {
|
||||
excess_blob_gas: None,
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
assert_eq!(header.hash_slow(), expected_hash);
|
||||
}
|
||||
@@ -268,6 +270,8 @@ mod tests {
|
||||
excess_blob_gas: Some(0),
|
||||
parent_beacon_block_root: None,
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let header = Header::decode(&mut data.as_slice()).unwrap();
|
||||
@@ -310,6 +314,8 @@ mod tests {
|
||||
blob_gas_used: Some(0),
|
||||
excess_blob_gas: Some(0x1600000),
|
||||
requests_hash: None,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let header = Header::decode(&mut data.as_slice()).unwrap();
|
||||
|
||||
@@ -120,8 +120,14 @@ where
|
||||
Self::Right(r) => r.withdrawals(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
match self {
|
||||
Self::Left(l) => l.slot_number(),
|
||||
Self::Right(r) => r.slot_number(),
|
||||
}
|
||||
}
|
||||
}
|
||||
/// this structure enables the chaining of multiple `PayloadBuilder` implementations,
|
||||
/// creating a hierarchical fallback system. It's designed to be nestable, allowing
|
||||
/// for complex builder arrangements like `Stack<Stack<A, B>, C>` with different
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
//! },
|
||||
//! ..Default::default()
|
||||
//! };
|
||||
//! let payload = EthBuiltPayload::new(self.attributes.id, Arc::new(SealedBlock::seal_slow(block)), U256::ZERO, None);
|
||||
//! let payload = EthBuiltPayload::new(self.attributes.id, Arc::new(SealedBlock::seal_slow(block)), U256::ZERO, None, None);
|
||||
//! Ok(payload)
|
||||
//! }
|
||||
//!
|
||||
|
||||
@@ -91,6 +91,7 @@ impl PayloadJob for TestPayloadJob {
|
||||
Arc::new(Block::<_>::default().seal_slow()),
|
||||
U256::ZERO,
|
||||
Some(Default::default()),
|
||||
None,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -120,6 +120,28 @@ pub enum VersionSpecificValidationError {
|
||||
/// root after Cancun
|
||||
#[error("no parent beacon block root post-cancun")]
|
||||
NoParentBeaconBlockRootPostCancun,
|
||||
/// Thrown if the pre-V6 `PayloadAttributes` or `ExecutionPayload` contains a block access list
|
||||
#[error("block access list not before V6")]
|
||||
BlockAccessListNotSupportedBeforeV6,
|
||||
/// Thrown if `engine_newPayload` contains no block access list
|
||||
/// after Amsterdam
|
||||
#[error("no block access list post-Amsterdam")]
|
||||
NoBlockAccessListPostAmsterdam,
|
||||
/// Thrown if `engine_newPayload` contains block access list
|
||||
/// before Amsterdam
|
||||
#[error("block access list pre-Amsterdam")]
|
||||
HasBlockAccessListPreAmsterdam,
|
||||
/// Thrown if the pre-V6 `PayloadAttributes` or `ExecutionPayload` contains a slot number
|
||||
#[error("slot number not before V6")]
|
||||
SlotNumberNotSupportedBeforeV6,
|
||||
/// Thrown if `engine_newPayload` contains no slot number
|
||||
/// after Amsterdam
|
||||
#[error("no slot number post-Amsterdam")]
|
||||
NoSlotNumberPostAmsterdam,
|
||||
/// Thrown if `engine_newPayload` contains slot number
|
||||
/// before Amsterdam
|
||||
#[error("slot number pre-Amsterdam")]
|
||||
HasSlotNumberPreAmsterdam,
|
||||
}
|
||||
|
||||
/// Error validating payload received over `newPayload` API.
|
||||
|
||||
@@ -159,12 +159,23 @@ pub fn validate_payload_timestamp(
|
||||
// built payload does not fall within the time frame of the Osaka fork.
|
||||
return Err(EngineObjectValidationError::UnsupportedFork)
|
||||
}
|
||||
|
||||
// `engine_getPayloadV4` MUST reject payloads with a timestamp >= Osaka.
|
||||
if version.is_v4() && kind == MessageValidationKind::GetPayload && is_osaka {
|
||||
return Err(EngineObjectValidationError::UnsupportedFork)
|
||||
}
|
||||
|
||||
let is_amsterdam = chain_spec.is_amsterdam_active_at_timestamp(timestamp);
|
||||
if version.is_v6() && !is_amsterdam {
|
||||
// From the Engine API spec:
|
||||
// <https://github.com/ethereum/execution-apis/blob/15399c2e2f16a5f800bf3f285640357e2c245ad9/src/engine/osaka.md#specification>
|
||||
//
|
||||
// For `engine_getPayloadV6`
|
||||
//
|
||||
// 1. Client software MUST return -38005: Unsupported fork error if the timestamp of the
|
||||
// built payload does not fall within the time frame of the Amsterdam fork.
|
||||
|
||||
return Err(EngineObjectValidationError::UnsupportedFork)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -190,7 +201,8 @@ pub fn validate_withdrawals_presence<T: EthereumHardforks>(
|
||||
EngineApiMessageVersion::V2 |
|
||||
EngineApiMessageVersion::V3 |
|
||||
EngineApiMessageVersion::V4 |
|
||||
EngineApiMessageVersion::V5 => {
|
||||
EngineApiMessageVersion::V5 |
|
||||
EngineApiMessageVersion::V6 => {
|
||||
if is_shanghai_active && !has_withdrawals {
|
||||
return Err(message_validation_kind
|
||||
.to_error(VersionSpecificValidationError::NoWithdrawalsPostShanghai))
|
||||
@@ -205,6 +217,84 @@ pub fn validate_withdrawals_presence<T: EthereumHardforks>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the presence of the `block access lists` field according to the payload timestamp.
|
||||
/// After Amsterdam, block access list field must be [Some].
|
||||
/// Before Amsterdam, block access list field must be [None];
|
||||
pub fn validate_block_access_list_presence<T: EthereumHardforks>(
|
||||
chain_spec: &T,
|
||||
version: EngineApiMessageVersion,
|
||||
message_validation_kind: MessageValidationKind,
|
||||
timestamp: u64,
|
||||
has_block_access_list: bool,
|
||||
) -> Result<(), EngineObjectValidationError> {
|
||||
let is_amsterdam_active = chain_spec.is_amsterdam_active_at_timestamp(timestamp);
|
||||
|
||||
match version {
|
||||
EngineApiMessageVersion::V1 |
|
||||
EngineApiMessageVersion::V2 |
|
||||
EngineApiMessageVersion::V3 |
|
||||
EngineApiMessageVersion::V4 |
|
||||
EngineApiMessageVersion::V5 => {
|
||||
if has_block_access_list {
|
||||
return Err(message_validation_kind
|
||||
.to_error(VersionSpecificValidationError::BlockAccessListNotSupportedBeforeV6))
|
||||
}
|
||||
}
|
||||
|
||||
EngineApiMessageVersion::V6 => {
|
||||
if is_amsterdam_active && !has_block_access_list {
|
||||
return Err(message_validation_kind
|
||||
.to_error(VersionSpecificValidationError::NoBlockAccessListPostAmsterdam))
|
||||
}
|
||||
if !is_amsterdam_active && has_block_access_list {
|
||||
return Err(message_validation_kind
|
||||
.to_error(VersionSpecificValidationError::HasBlockAccessListPreAmsterdam))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates the presence of the `slot number` field according to the payload timestamp.
|
||||
/// After Amsterdam, slot number field must be [Some].
|
||||
/// Before Amsterdam, slot number field must be [None];
|
||||
pub fn validate_slot_number_presence<T: EthereumHardforks>(
|
||||
chain_spec: &T,
|
||||
version: EngineApiMessageVersion,
|
||||
message_validation_kind: MessageValidationKind,
|
||||
timestamp: u64,
|
||||
has_slot_number: bool,
|
||||
) -> Result<(), EngineObjectValidationError> {
|
||||
let is_amsterdam_active = chain_spec.is_amsterdam_active_at_timestamp(timestamp);
|
||||
|
||||
match version {
|
||||
EngineApiMessageVersion::V1 |
|
||||
EngineApiMessageVersion::V2 |
|
||||
EngineApiMessageVersion::V3 |
|
||||
EngineApiMessageVersion::V4 |
|
||||
EngineApiMessageVersion::V5 => {
|
||||
if has_slot_number {
|
||||
return Err(message_validation_kind
|
||||
.to_error(VersionSpecificValidationError::SlotNumberNotSupportedBeforeV6))
|
||||
}
|
||||
}
|
||||
|
||||
EngineApiMessageVersion::V6 => {
|
||||
if is_amsterdam_active && !has_slot_number {
|
||||
return Err(message_validation_kind
|
||||
.to_error(VersionSpecificValidationError::NoSlotNumberPostAmsterdam))
|
||||
}
|
||||
if !is_amsterdam_active && has_slot_number {
|
||||
return Err(message_validation_kind
|
||||
.to_error(VersionSpecificValidationError::HasSlotNumberPreAmsterdam))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate the presence of the `parentBeaconBlockRoot` field according to the given timestamp.
|
||||
/// This method is meant to be used with either a `payloadAttributes` field or a full payload, with
|
||||
/// the `engine_forkchoiceUpdated` and `engine_newPayload` methods respectively.
|
||||
@@ -291,7 +381,10 @@ pub fn validate_parent_beacon_block_root_presence<T: EthereumHardforks>(
|
||||
))
|
||||
}
|
||||
}
|
||||
EngineApiMessageVersion::V3 | EngineApiMessageVersion::V4 | EngineApiMessageVersion::V5 => {
|
||||
EngineApiMessageVersion::V3 |
|
||||
EngineApiMessageVersion::V4 |
|
||||
EngineApiMessageVersion::V5 |
|
||||
EngineApiMessageVersion::V6 => {
|
||||
if !has_parent_beacon_block_root {
|
||||
return Err(validation_kind
|
||||
.to_error(VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun))
|
||||
@@ -364,6 +457,25 @@ where
|
||||
Type: PayloadAttributes,
|
||||
T: EthereumHardforks,
|
||||
{
|
||||
// BAL only exists in ExecutionPayload, not PayloadAttributes (EIP-7928)
|
||||
if let PayloadOrAttributes::ExecutionPayload(_) = payload_or_attrs {
|
||||
validate_block_access_list_presence(
|
||||
chain_spec,
|
||||
version,
|
||||
payload_or_attrs.message_validation_kind(),
|
||||
payload_or_attrs.timestamp(),
|
||||
payload_or_attrs.block_access_list().is_some(),
|
||||
)?;
|
||||
}
|
||||
|
||||
validate_slot_number_presence(
|
||||
chain_spec,
|
||||
version,
|
||||
payload_or_attrs.message_validation_kind(),
|
||||
payload_or_attrs.timestamp(),
|
||||
payload_or_attrs.slot_number().is_some(),
|
||||
)?;
|
||||
|
||||
validate_withdrawals_presence(
|
||||
chain_spec,
|
||||
version,
|
||||
@@ -402,6 +514,10 @@ pub enum EngineApiMessageVersion {
|
||||
///
|
||||
/// Added in the Osaka hardfork.
|
||||
V5 = 5,
|
||||
/// Version 6
|
||||
///
|
||||
/// Added in the Amsterdam hardfork
|
||||
V6 = 6,
|
||||
}
|
||||
|
||||
impl EngineApiMessageVersion {
|
||||
@@ -430,6 +546,11 @@ impl EngineApiMessageVersion {
|
||||
matches!(self, Self::V5)
|
||||
}
|
||||
|
||||
/// Returns true if version is V6
|
||||
pub const fn is_v6(&self) -> bool {
|
||||
matches!(self, Self::V6)
|
||||
}
|
||||
|
||||
/// Returns the method name for the given version.
|
||||
pub const fn method_name(&self) -> &'static str {
|
||||
match self {
|
||||
@@ -437,7 +558,7 @@ impl EngineApiMessageVersion {
|
||||
Self::V2 => "engine_newPayloadV2",
|
||||
Self::V3 => "engine_newPayloadV3",
|
||||
Self::V4 => "engine_newPayloadV4",
|
||||
Self::V5 => "engine_newPayloadV5",
|
||||
Self::V5 | Self::V6 => "engine_newPayloadV5",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,11 @@ pub trait ExecutionPayload:
|
||||
/// Returns `None` for pre-Amsterdam blocks.
|
||||
fn block_access_list(&self) -> Option<&Bytes>;
|
||||
|
||||
/// Returns the slot number included in this payload.
|
||||
///
|
||||
/// Returns `None` for pre-Amsterdam blocks.
|
||||
fn slot_number(&self) -> Option<u64>;
|
||||
|
||||
/// Returns the beacon block root associated with this payload.
|
||||
///
|
||||
/// Returns `None` for pre-merge payloads.
|
||||
@@ -78,7 +83,11 @@ impl ExecutionPayload for ExecutionData {
|
||||
}
|
||||
|
||||
fn block_access_list(&self) -> Option<&Bytes> {
|
||||
None
|
||||
self.payload.block_access_list()
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
self.payload.slot_number()
|
||||
}
|
||||
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
@@ -135,6 +144,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `block_access_list` from payload.
|
||||
pub fn block_access_list(&self) -> Option<&Bytes> {
|
||||
match self {
|
||||
Self::ExecutionPayload(payload) => payload.block_access_list(),
|
||||
Self::PayloadAttributes(_attributes) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `slot_number` from payload.
|
||||
pub fn slot_number(&self) -> Option<u64> {
|
||||
match self {
|
||||
Self::ExecutionPayload(payload) => payload.slot_number(),
|
||||
Self::PayloadAttributes(attributes) => attributes.slot_number(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the timestamp from either the payload or attributes.
|
||||
pub fn timestamp(&self) -> u64 {
|
||||
match self {
|
||||
@@ -192,6 +217,10 @@ impl ExecutionPayload for op_alloy_rpc_types_engine::OpExecutionData {
|
||||
None
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
self.sidecar.parent_beacon_block_root()
|
||||
}
|
||||
|
||||
@@ -141,6 +141,9 @@ pub trait PayloadBuilderAttributes: Send + Sync + Unpin + fmt::Debug + 'static {
|
||||
|
||||
/// Returns the list of withdrawals to be processed in this block.
|
||||
fn withdrawals(&self) -> &Withdrawals;
|
||||
|
||||
/// Returns the slot number to be used in the payload's header.
|
||||
fn slot_number(&self) -> Option<u64>;
|
||||
}
|
||||
|
||||
/// Basic attributes required to initiate payload construction.
|
||||
@@ -162,6 +165,11 @@ pub trait PayloadAttributes:
|
||||
///
|
||||
/// `Some` for post-merge blocks, `None` for pre-merge blocks.
|
||||
fn parent_beacon_block_root(&self) -> Option<B256>;
|
||||
|
||||
/// Returns the slot number for the new payload.
|
||||
///
|
||||
/// `Some` for post-Amsterdam blocks, `None` for earlier blocks.
|
||||
fn slot_number(&self) -> Option<u64>;
|
||||
}
|
||||
|
||||
impl PayloadAttributes for EthPayloadAttributes {
|
||||
@@ -176,6 +184,10 @@ impl PayloadAttributes for EthPayloadAttributes {
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
self.parent_beacon_block_root
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
self.slot_number
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "op")]
|
||||
@@ -191,6 +203,10 @@ impl PayloadAttributes for op_alloy_rpc_types_engine::OpPayloadAttributes {
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
self.payload_attributes.parent_beacon_block_root
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Factory trait for creating payload attributes.
|
||||
|
||||
@@ -4,6 +4,8 @@ use alloy_genesis::GenesisAccount;
|
||||
use alloy_primitives::{keccak256, Bytes, B256, U256};
|
||||
use alloy_trie::TrieAccount;
|
||||
use derive_more::Deref;
|
||||
#[cfg(any(test, feature = "reth-codec"))]
|
||||
use revm_bytecode::BytecodeKind;
|
||||
use revm_bytecode::{Bytecode as RevmBytecode, BytecodeDecodeError};
|
||||
use revm_state::AccountInfo;
|
||||
|
||||
@@ -16,10 +18,10 @@ pub mod compact_ids {
|
||||
/// Identifier for removed bytecode variant.
|
||||
pub const REMOVED_BYTECODE_ID: u8 = 1;
|
||||
|
||||
/// Identifier for [`LegacyAnalyzed`](revm_bytecode::Bytecode::LegacyAnalyzed).
|
||||
/// Identifier for [`LegacyAnalyzed`](revm_bytecode::BytecodeKind::LegacyAnalyzed).
|
||||
pub const LEGACY_ANALYZED_BYTECODE_ID: u8 = 2;
|
||||
|
||||
/// Identifier for [`Eip7702`](revm_bytecode::Bytecode::Eip7702).
|
||||
/// Identifier for [`Eip7702`](revm_bytecode::BytecodeKind::Eip7702).
|
||||
pub const EIP7702_BYTECODE_ID: u8 = 4;
|
||||
}
|
||||
|
||||
@@ -141,22 +143,19 @@ impl reth_codecs::Compact for Bytecode {
|
||||
{
|
||||
use compact_ids::{EIP7702_BYTECODE_ID, LEGACY_ANALYZED_BYTECODE_ID};
|
||||
|
||||
let bytecode = match &self.0 {
|
||||
RevmBytecode::LegacyAnalyzed(analyzed) => analyzed.bytecode(),
|
||||
RevmBytecode::Eip7702(eip7702) => eip7702.raw(),
|
||||
};
|
||||
let bytecode = self.0.bytecode();
|
||||
buf.put_u32(bytecode.len() as u32);
|
||||
buf.put_slice(bytecode.as_ref());
|
||||
let len = match &self.0 {
|
||||
let len = match self.0.kind() {
|
||||
// [`REMOVED_BYTECODE_ID`] has been removed.
|
||||
RevmBytecode::LegacyAnalyzed(analyzed) => {
|
||||
BytecodeKind::LegacyAnalyzed => {
|
||||
buf.put_u8(LEGACY_ANALYZED_BYTECODE_ID);
|
||||
buf.put_u64(analyzed.original_len() as u64);
|
||||
let map = analyzed.jump_table().as_slice();
|
||||
buf.put_u64(self.0.len() as u64);
|
||||
let map = self.0.legacy_jump_table().unwrap().as_slice();
|
||||
buf.put_slice(map);
|
||||
1 + 8 + map.len()
|
||||
}
|
||||
RevmBytecode::Eip7702(_) => {
|
||||
BytecodeKind::Eip7702 => {
|
||||
buf.put_u8(EIP7702_BYTECODE_ID);
|
||||
1
|
||||
}
|
||||
@@ -255,12 +254,10 @@ impl From<Account> for AccountInfo {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use alloy_primitives::{hex_literal::hex, B256, U256};
|
||||
use reth_codecs::Compact;
|
||||
use revm_bytecode::{JumpTable, LegacyAnalyzedBytecode};
|
||||
use revm_bytecode::JumpTable;
|
||||
|
||||
#[test]
|
||||
fn test_account() {
|
||||
@@ -317,12 +314,11 @@ mod tests {
|
||||
assert_eq!(len, 17);
|
||||
|
||||
let mut buf = vec![];
|
||||
let bytecode =
|
||||
Bytecode(RevmBytecode::LegacyAnalyzed(Arc::new(LegacyAnalyzedBytecode::new(
|
||||
Bytes::from(&hex!("ff00")),
|
||||
2,
|
||||
JumpTable::from_slice(&[0], 2),
|
||||
))));
|
||||
let bytecode = Bytecode(RevmBytecode::new_analyzed(
|
||||
Bytes::from(&hex!("ff00")),
|
||||
2,
|
||||
JumpTable::from_slice(&[0], 2),
|
||||
));
|
||||
let len = bytecode.to_compact(&mut buf);
|
||||
assert_eq!(len, 16);
|
||||
|
||||
|
||||
@@ -452,6 +452,14 @@ impl<B: Block> BlockHeader for RecoveredBlock<B> {
|
||||
fn extra_data(&self) -> &Bytes {
|
||||
self.header().extra_data()
|
||||
}
|
||||
|
||||
fn block_access_list_hash(&self) -> Option<B256> {
|
||||
self.header().block_access_list_hash()
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
self.header().slot_number()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Block> Eq for RecoveredBlock<B> {}
|
||||
|
||||
@@ -70,8 +70,8 @@ impl ExecutionWitnessRecord {
|
||||
}
|
||||
}
|
||||
}
|
||||
// BTreeMap keys are ordered, so the first key is the smallest
|
||||
self.lowest_block_number = statedb.block_hashes.keys().next().copied()
|
||||
// Get the lowest block number from the cache
|
||||
self.lowest_block_number = statedb.block_hashes.lowest().map(|(n, _)| n)
|
||||
}
|
||||
|
||||
/// Creates the record from the state after execution.
|
||||
|
||||
@@ -11,10 +11,14 @@ use alloy_eips::{
|
||||
use alloy_json_rpc::RpcObject;
|
||||
use alloy_primitives::{Address, BlockHash, Bytes, B256, U256, U64};
|
||||
use alloy_rpc_types_engine::{
|
||||
ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadBodiesV2, ExecutionPayloadInputV2,
|
||||
ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated,
|
||||
PayloadId, PayloadStatus,
|
||||
ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV1,
|
||||
ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId,
|
||||
PayloadStatus,
|
||||
};
|
||||
|
||||
// TODO: Replace with alloy_rpc_types_engine::ExecutionPayloadBodiesV2 once available in alloy
|
||||
// bal-devnet2 branch. V2 adds block_access_list field for EIP-7928.
|
||||
type ExecutionPayloadBodiesV2 = ExecutionPayloadBodiesV1;
|
||||
use alloy_rpc_types_eth::{
|
||||
state::StateOverride, BlockOverrides, EIP1186AccountProofResponse, Filter, Log, SyncStatus,
|
||||
};
|
||||
@@ -126,6 +130,20 @@ pub trait EngineApi<Engine: EngineTypes> {
|
||||
payload_attributes: Option<Engine::PayloadAttributes>,
|
||||
) -> RpcResult<ForkchoiceUpdated>;
|
||||
|
||||
/// Post Amsterdam forkchoice update handler
|
||||
///
|
||||
/// This is the same as `forkchoiceUpdatedV3`, but expects an additional
|
||||
/// `slotNumber` field in the `payloadAttributes`, if payload attributes
|
||||
/// are provided.
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_forkchoiceupdatedv4>
|
||||
#[method(name = "forkchoiceUpdatedV4")]
|
||||
async fn fork_choice_updated_v4(
|
||||
&self,
|
||||
fork_choice_state: ForkchoiceState,
|
||||
payload_attributes: Option<Engine::PayloadAttributes>,
|
||||
) -> RpcResult<ForkchoiceUpdated>;
|
||||
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_getpayloadv1>
|
||||
///
|
||||
/// Returns the most recent version of the payload that is available in the corresponding
|
||||
|
||||
@@ -17,16 +17,19 @@ pub const CAPABILITIES: &[&str] = &[
|
||||
"engine_forkchoiceUpdatedV1",
|
||||
"engine_forkchoiceUpdatedV2",
|
||||
"engine_forkchoiceUpdatedV3",
|
||||
"engine_forkchoiceUpdatedV4",
|
||||
"engine_getClientVersionV1",
|
||||
"engine_getPayloadV1",
|
||||
"engine_getPayloadV2",
|
||||
"engine_getPayloadV3",
|
||||
"engine_getPayloadV4",
|
||||
"engine_getPayloadV5",
|
||||
"engine_getPayloadV6",
|
||||
"engine_newPayloadV1",
|
||||
"engine_newPayloadV2",
|
||||
"engine_newPayloadV3",
|
||||
"engine_newPayloadV4",
|
||||
"engine_newPayloadV5",
|
||||
"engine_getPayloadBodiesByHashV1",
|
||||
"engine_getPayloadBodiesByHashV2",
|
||||
"engine_getPayloadBodiesByRangeV1",
|
||||
|
||||
@@ -10,11 +10,14 @@ use alloy_eips::{
|
||||
use alloy_primitives::{BlockHash, BlockNumber, B256, U64};
|
||||
use alloy_rpc_types_engine::{
|
||||
CancunPayloadFields, ClientVersionV1, ExecutionData, ExecutionPayloadBodiesV1,
|
||||
ExecutionPayloadBodiesV2, ExecutionPayloadBodyV1, ExecutionPayloadBodyV2,
|
||||
ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1, ExecutionPayloadV3,
|
||||
ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus,
|
||||
PraguePayloadFields,
|
||||
ExecutionPayloadBodyV1, ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1,
|
||||
ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId,
|
||||
PayloadStatus, PraguePayloadFields,
|
||||
};
|
||||
|
||||
// TODO: Replace with alloy types once available in alloy bal-devnet2 branch
|
||||
type ExecutionPayloadBodiesV2 = ExecutionPayloadBodiesV1;
|
||||
type ExecutionPayloadBodyV2 = ExecutionPayloadBodyV1;
|
||||
use async_trait::async_trait;
|
||||
use jsonrpsee_core::{server::RpcModule, RpcResult};
|
||||
use reth_chainspec::EthereumHardforks;
|
||||
@@ -26,7 +29,10 @@ use reth_payload_primitives::{
|
||||
PayloadOrAttributes, PayloadTypes,
|
||||
};
|
||||
use reth_primitives_traits::{Block, BlockBody};
|
||||
use reth_rpc_api::{EngineApiServer, IntoEngineApiRpcModule};
|
||||
use reth_rpc_api::{
|
||||
EngineApiServer, IntoEngineApiRpcModule, RethEngineApiServer, RethNewPayloadInput,
|
||||
RethPayloadStatus,
|
||||
};
|
||||
use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory};
|
||||
use reth_tasks::Runtime;
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
@@ -257,6 +263,66 @@ where
|
||||
pub fn accept_execution_requests_hash(&self) -> bool {
|
||||
self.inner.accept_execution_requests_hash
|
||||
}
|
||||
|
||||
/// Handler for `engine_newPayloadV5`
|
||||
///
|
||||
/// Post-Amsterdam payload handler.
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_newpayloadv5>
|
||||
pub async fn new_payload_v5(
|
||||
&self,
|
||||
payload: PayloadT::ExecutionData,
|
||||
) -> EngineApiResult<PayloadStatus> {
|
||||
let payload_or_attrs = PayloadOrAttributes::<
|
||||
'_,
|
||||
PayloadT::ExecutionData,
|
||||
PayloadT::PayloadAttributes,
|
||||
>::from_execution_payload(&payload);
|
||||
self.inner
|
||||
.validator
|
||||
.validate_version_specific_fields(EngineApiMessageVersion::V6, payload_or_attrs)?;
|
||||
Ok(self.inner.beacon_consensus.new_payload(payload).await?)
|
||||
}
|
||||
|
||||
/// Metrics version of `new_payload_v5`
|
||||
pub async fn new_payload_v5_metered(
|
||||
&self,
|
||||
payload: PayloadT::ExecutionData,
|
||||
) -> RpcResult<PayloadStatus> {
|
||||
let start = Instant::now();
|
||||
let res = Self::new_payload_v5(self, payload).await;
|
||||
let elapsed = start.elapsed();
|
||||
self.inner.metrics.latency.new_payload_v5.record(elapsed);
|
||||
Ok(res?)
|
||||
}
|
||||
|
||||
/// Waits for persistence, execution cache, and sparse trie locks before processing.
|
||||
///
|
||||
/// Used by `reth_newPayload` endpoint.
|
||||
pub async fn reth_new_payload(
|
||||
&self,
|
||||
payload: PayloadT::ExecutionData,
|
||||
) -> EngineApiResult<RethPayloadStatus> {
|
||||
let (status, timings) = self.inner.beacon_consensus.reth_new_payload(payload).await?;
|
||||
Ok(RethPayloadStatus {
|
||||
status,
|
||||
latency_us: timings.latency.as_micros() as u64,
|
||||
persistence_wait_us: timings.persistence_wait.map(|d| d.as_micros() as u64),
|
||||
execution_cache_wait_us: timings.execution_cache_wait.as_micros() as u64,
|
||||
sparse_trie_wait_us: timings.sparse_trie_wait.as_micros() as u64,
|
||||
})
|
||||
}
|
||||
|
||||
/// Metered version of `reth_new_payload`.
|
||||
pub async fn reth_new_payload_metered(
|
||||
&self,
|
||||
payload: PayloadT::ExecutionData,
|
||||
) -> RpcResult<RethPayloadStatus> {
|
||||
let start = Instant::now();
|
||||
let res = Self::reth_new_payload(self, payload).await;
|
||||
self.inner.metrics.latency.new_payload_v1.record(start.elapsed());
|
||||
Ok(res?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider, EngineT, Pool, Validator, ChainSpec>
|
||||
@@ -345,6 +411,31 @@ where
|
||||
res
|
||||
}
|
||||
|
||||
/// Sends a message to the beacon consensus engine to update the fork choice _with_ slot number,
|
||||
/// but only _after_ amsterdam.
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_forkchoiceupdatedv4>
|
||||
pub async fn fork_choice_updated_v4(
|
||||
&self,
|
||||
state: ForkchoiceState,
|
||||
payload_attrs: Option<EngineT::PayloadAttributes>,
|
||||
) -> EngineApiResult<ForkchoiceUpdated> {
|
||||
self.validate_and_execute_forkchoice(EngineApiMessageVersion::V6, state, payload_attrs)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Metrics version of `fork_choice_updated_v4`
|
||||
pub async fn fork_choice_updated_v4_metered(
|
||||
&self,
|
||||
state: ForkchoiceState,
|
||||
payload_attrs: Option<EngineT::PayloadAttributes>,
|
||||
) -> EngineApiResult<ForkchoiceUpdated> {
|
||||
let start = Instant::now();
|
||||
let res = Self::fork_choice_updated_v4(self, state, payload_attrs).await;
|
||||
self.inner.metrics.latency.fork_choice_updated_v4.record(start.elapsed());
|
||||
res
|
||||
}
|
||||
|
||||
/// Helper function for retrieving the build payload by id.
|
||||
async fn get_built_payload(
|
||||
&self,
|
||||
@@ -517,6 +608,29 @@ where
|
||||
res
|
||||
}
|
||||
|
||||
/// Handler for `engine_getPayloadV6`
|
||||
///
|
||||
/// Post-Amsterdam payload handler that includes Block Access Lists (BAL).
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_getpayloadv6>
|
||||
pub async fn get_payload_v6(
|
||||
&self,
|
||||
payload_id: PayloadId,
|
||||
) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV6> {
|
||||
self.get_payload_inner(payload_id, EngineApiMessageVersion::V6).await
|
||||
}
|
||||
|
||||
/// Metrics version of `get_payload_v6`
|
||||
pub async fn get_payload_v6_metered(
|
||||
&self,
|
||||
payload_id: PayloadId,
|
||||
) -> EngineApiResult<EngineT::ExecutionPayloadEnvelopeV6> {
|
||||
let start = Instant::now();
|
||||
let res = Self::get_payload_v6(self, payload_id).await;
|
||||
self.inner.metrics.latency.get_payload_v6.record(start.elapsed());
|
||||
res
|
||||
}
|
||||
|
||||
/// Fetches all the blocks for the provided range starting at `start`, containing `count`
|
||||
/// blocks and returns the mapped payload bodies.
|
||||
pub async fn get_payload_bodies_by_range_with<F, R>(
|
||||
@@ -621,10 +735,10 @@ where
|
||||
start: BlockNumber,
|
||||
count: u64,
|
||||
) -> EngineApiResult<ExecutionPayloadBodiesV2> {
|
||||
// TODO: add block_access_list field once ExecutionPayloadBodyV2 is in alloy bal-devnet2
|
||||
self.get_payload_bodies_by_range_with(start, count, |block| ExecutionPayloadBodyV2 {
|
||||
transactions: block.body().encoded_2718_transactions(),
|
||||
withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
|
||||
block_access_list: None,
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -709,10 +823,10 @@ where
|
||||
&self,
|
||||
hashes: Vec<BlockHash>,
|
||||
) -> EngineApiResult<ExecutionPayloadBodiesV2> {
|
||||
// TODO: add block_access_list field once ExecutionPayloadBodyV2 is in alloy bal-devnet2
|
||||
self.get_payload_bodies_by_hash_with(hashes, |block| ExecutionPayloadBodyV2 {
|
||||
transactions: block.body().encoded_2718_transactions(),
|
||||
withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
|
||||
block_access_list: None,
|
||||
})
|
||||
.await
|
||||
}
|
||||
@@ -1020,20 +1134,32 @@ where
|
||||
|
||||
/// Handler for `engine_newPayloadV5`
|
||||
///
|
||||
/// Post Amsterdam payload handler. Currently returns unsupported fork error.
|
||||
/// Post-Amsterdam payload handler.
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_newpayloadv5>
|
||||
async fn new_payload_v5(
|
||||
&self,
|
||||
_payload: ExecutionPayloadV4,
|
||||
_versioned_hashes: Vec<B256>,
|
||||
_parent_beacon_block_root: B256,
|
||||
_execution_requests: RequestsOrHash,
|
||||
payload: ExecutionPayloadV4,
|
||||
versioned_hashes: Vec<B256>,
|
||||
parent_beacon_block_root: B256,
|
||||
requests: RequestsOrHash,
|
||||
) -> RpcResult<PayloadStatus> {
|
||||
trace!(target: "rpc::engine", "Serving engine_newPayloadV5");
|
||||
Err(EngineApiError::EngineObjectValidationError(
|
||||
reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
|
||||
))?
|
||||
|
||||
// Accept requests as a hash only if it is explicitly allowed
|
||||
if requests.is_hash() && !self.inner.accept_execution_requests_hash {
|
||||
return Err(EngineApiError::UnexpectedRequestsHash.into());
|
||||
}
|
||||
|
||||
let payload = ExecutionData {
|
||||
payload: payload.into(),
|
||||
sidecar: ExecutionPayloadSidecar::v4(
|
||||
CancunPayloadFields { versioned_hashes, parent_beacon_block_root },
|
||||
PraguePayloadFields { requests },
|
||||
),
|
||||
};
|
||||
|
||||
Ok(self.new_payload_v5_metered(payload).await?)
|
||||
}
|
||||
|
||||
/// Handler for `engine_forkchoiceUpdatedV1`
|
||||
@@ -1072,6 +1198,18 @@ where
|
||||
Ok(self.fork_choice_updated_v3_metered(fork_choice_state, payload_attributes).await?)
|
||||
}
|
||||
|
||||
/// Handler for `engine_forkchoiceUpdatedV4`
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_forkchoiceupdatedv4>
|
||||
async fn fork_choice_updated_v4(
|
||||
&self,
|
||||
fork_choice_state: ForkchoiceState,
|
||||
payload_attributes: Option<EngineT::PayloadAttributes>,
|
||||
) -> RpcResult<ForkchoiceUpdated> {
|
||||
trace!(target: "rpc::engine", "Serving engine_forkchoiceUpdatedV4");
|
||||
Ok(self.fork_choice_updated_v4_metered(fork_choice_state, payload_attributes).await?)
|
||||
}
|
||||
|
||||
/// Handler for `engine_getPayloadV1`
|
||||
///
|
||||
/// Returns the most recent version of the payload that is available in the corresponding
|
||||
@@ -1161,17 +1299,15 @@ where
|
||||
|
||||
/// Handler for `engine_getPayloadV6`
|
||||
///
|
||||
/// Post Amsterdam payload handler. Currently returns unsupported fork error.
|
||||
/// Post-Amsterdam payload handler.
|
||||
///
|
||||
/// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_getpayloadv6>
|
||||
async fn get_payload_v6(
|
||||
&self,
|
||||
_payload_id: PayloadId,
|
||||
payload_id: PayloadId,
|
||||
) -> RpcResult<EngineT::ExecutionPayloadEnvelopeV6> {
|
||||
trace!(target: "rpc::engine", "Serving engine_getPayloadV6");
|
||||
Err(EngineApiError::EngineObjectValidationError(
|
||||
reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
|
||||
))?
|
||||
Ok(self.get_payload_v6_metered(payload_id).await?)
|
||||
}
|
||||
|
||||
/// Handler for `engine_getPayloadBodiesByHashV1`
|
||||
@@ -1283,14 +1419,70 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `RethEngineApiServer` under the `reth_` namespace.
|
||||
///
|
||||
/// Waits for execution cache and sparse trie locks before processing.
|
||||
#[async_trait]
|
||||
impl<Provider, EngineT, Pool, Validator, ChainSpec> RethEngineApiServer<ExecutionData>
|
||||
for EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
|
||||
where
|
||||
Provider: HeaderProvider + BlockReader + StateProviderFactory + 'static,
|
||||
EngineT: EngineTypes<ExecutionData = ExecutionData>,
|
||||
Pool: TransactionPool + 'static,
|
||||
Validator: EngineApiValidator<EngineT>,
|
||||
ChainSpec: EthereumHardforks + Send + Sync + 'static,
|
||||
{
|
||||
async fn reth_new_payload(
|
||||
&self,
|
||||
input: RethNewPayloadInput<ExecutionData>,
|
||||
) -> RpcResult<RethPayloadStatus> {
|
||||
trace!(target: "rpc::engine", "Serving reth_newPayload");
|
||||
match input {
|
||||
RethNewPayloadInput::ExecutionData(payload) => {
|
||||
self.reth_new_payload_metered(payload).await
|
||||
}
|
||||
RethNewPayloadInput::BlockRlp(rlp) => {
|
||||
let block = alloy_rlp::Decodable::decode(&mut rlp.as_ref())
|
||||
.map_err(|err| EngineApiError::Internal(Box::new(err)))?;
|
||||
let payload =
|
||||
EngineT::block_to_payload(reth_primitives_traits::SealedBlock::new_unhashed(
|
||||
block,
|
||||
));
|
||||
self.reth_new_payload_metered(payload).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn reth_forkchoice_updated(
|
||||
&self,
|
||||
forkchoice_state: ForkchoiceState,
|
||||
) -> RpcResult<ForkchoiceUpdated> {
|
||||
trace!(target: "rpc::engine", "Serving reth_forkchoiceUpdated");
|
||||
self.validate_and_execute_forkchoice(
|
||||
EngineApiMessageVersion::V3,
|
||||
forkchoice_state,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider, EngineT, Pool, Validator, ChainSpec> IntoEngineApiRpcModule
|
||||
for EngineApi<Provider, EngineT, Pool, Validator, ChainSpec>
|
||||
where
|
||||
EngineT: EngineTypes,
|
||||
Self: EngineApiServer<EngineT>,
|
||||
Self: EngineApiServer<EngineT> + RethEngineApiServer<ExecutionData>,
|
||||
{
|
||||
fn into_rpc_module(self) -> RpcModule<()> {
|
||||
EngineApiServer::<EngineT>::into_rpc(self).remove_context()
|
||||
let mut module = EngineApiServer::<EngineT>::into_rpc(self.clone()).remove_context();
|
||||
|
||||
// Merge reth_newPayload endpoint
|
||||
module
|
||||
.merge(RethEngineApiServer::<ExecutionData>::into_rpc(self).remove_context())
|
||||
.expect("No conflicting methods");
|
||||
|
||||
module
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,6 @@ impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
|
||||
// Per Engine API spec, structure validation errors for PayloadAttributes
|
||||
// (e.g., missing withdrawals post-Shanghai) should return -32602 "Invalid params".
|
||||
// See: https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md
|
||||
// Fixes: https://github.com/paradigmxyz/reth/issues/8732
|
||||
EngineObjectValidationError::PayloadAttributes(
|
||||
VersionSpecificValidationError::WithdrawalsNotSupportedInV1 |
|
||||
VersionSpecificValidationError::NoWithdrawalsPostShanghai |
|
||||
@@ -148,11 +147,11 @@ impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
|
||||
Some(ErrorData::new(error)),
|
||||
)
|
||||
}
|
||||
// Per Engine API spec, payload attributes structure validation errors
|
||||
// should return -38003: Invalid payload attributes.
|
||||
// See: https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#specification-2
|
||||
EngineApiError::EngineObjectValidationError(
|
||||
EngineObjectValidationError::PayloadAttributes(
|
||||
VersionSpecificValidationError::ParentBeaconBlockRootNotSupportedBeforeV3 |
|
||||
VersionSpecificValidationError::NoParentBeaconBlockRootPostCancun,
|
||||
),
|
||||
EngineObjectValidationError::PayloadAttributes(_),
|
||||
) => jsonrpsee_types::error::ErrorObject::owned(
|
||||
INVALID_PAYLOAD_ATTRIBUTES_ERROR,
|
||||
INVALID_PAYLOAD_ATTRIBUTES_ERROR_MSG,
|
||||
@@ -211,6 +210,7 @@ impl From<EngineApiError> for jsonrpsee_types::error::ErrorObject<'static> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloy_rpc_types_engine::ForkchoiceUpdateError;
|
||||
use reth_payload_primitives::VersionSpecificValidationError;
|
||||
#[track_caller]
|
||||
fn ensure_engine_rpc_error(
|
||||
code: i32,
|
||||
@@ -266,7 +266,7 @@ mod tests {
|
||||
|
||||
// PayloadAttributes structure validation errors (e.g., missing withdrawals post-Shanghai)
|
||||
// should return -32602 per the Engine API spec
|
||||
// See: https://github.com/paradigmxyz/reth/issues/8732
|
||||
// See: https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md
|
||||
ensure_engine_rpc_error(
|
||||
INVALID_PARAMS_CODE,
|
||||
INVALID_PARAMS_MSG,
|
||||
|
||||
@@ -22,12 +22,16 @@ pub(crate) struct EngineApiLatencyMetrics {
|
||||
pub(crate) new_payload_v3: Histogram,
|
||||
/// Latency for `engine_newPayloadV4`
|
||||
pub(crate) new_payload_v4: Histogram,
|
||||
/// Latency for `engine_newPayloadV5`
|
||||
pub(crate) new_payload_v5: Histogram,
|
||||
/// Latency for `engine_forkchoiceUpdatedV1`
|
||||
pub(crate) fork_choice_updated_v1: Histogram,
|
||||
/// Latency for `engine_forkchoiceUpdatedV2`
|
||||
pub(crate) fork_choice_updated_v2: Histogram,
|
||||
/// Latency for `engine_forkchoiceUpdatedV3`
|
||||
pub(crate) fork_choice_updated_v3: Histogram,
|
||||
/// Latency for `engine_forkchoiceUpdatedV4`
|
||||
pub(crate) fork_choice_updated_v4: Histogram,
|
||||
/// Latency for `engine_getPayloadV1`
|
||||
pub(crate) get_payload_v1: Histogram,
|
||||
/// Latency for `engine_getPayloadV2`
|
||||
@@ -38,6 +42,8 @@ pub(crate) struct EngineApiLatencyMetrics {
|
||||
pub(crate) get_payload_v4: Histogram,
|
||||
/// Latency for `engine_getPayloadV5`
|
||||
pub(crate) get_payload_v5: Histogram,
|
||||
/// Latency for `engine_getPayloadV6`
|
||||
pub(crate) get_payload_v6: Histogram,
|
||||
/// Latency for `engine_getPayloadBodiesByRangeV1`
|
||||
pub(crate) get_payload_bodies_by_range_v1: Histogram,
|
||||
/// Latency for `engine_getPayloadBodiesByRangeV2`
|
||||
|
||||
@@ -172,7 +172,7 @@ pub trait EstimateCall: Call {
|
||||
};
|
||||
|
||||
let gas_refund = match res.result {
|
||||
ExecutionResult::Success { gas_refunded, .. } => gas_refunded,
|
||||
ExecutionResult::Success { gas, .. } => gas.inner_refunded(),
|
||||
ExecutionResult::Halt { reason, .. } => {
|
||||
// here we don't check for invalid opcode because already executed with highest gas
|
||||
// limit
|
||||
|
||||
@@ -360,7 +360,7 @@ pub trait LoadPendingBlock:
|
||||
}
|
||||
}
|
||||
|
||||
let BlockBuilderOutcome { execution_result, block, hashed_state, trie_updates } =
|
||||
let BlockBuilderOutcome { execution_result, block, hashed_state, trie_updates, .. } =
|
||||
builder.finish(NoopProvider::default()).map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
let execution_outcome =
|
||||
@@ -418,6 +418,7 @@ impl<H: BlockHeader> BuildPendingEnv<H> for NextBlockEnvAttributes {
|
||||
parent_beacon_block_root: parent.parent_beacon_block_root(),
|
||||
withdrawals: parent.withdrawals_root().map(|_| Default::default()),
|
||||
extra_data: parent.extra_data().clone(),
|
||||
slot_number: parent.slot_number(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,8 +125,8 @@ pub trait FromEvmError<Evm: ConfigureEvm>:
|
||||
match result {
|
||||
ExecutionResult::Success { output, .. } => Ok(output.into_data()),
|
||||
ExecutionResult::Revert { output, .. } => Err(Self::from_revert(output)),
|
||||
ExecutionResult::Halt { reason, gas_used } => {
|
||||
Err(Self::from_evm_halt(reason, gas_used))
|
||||
ExecutionResult::Halt { reason, gas, .. } => {
|
||||
Err(Self::from_evm_halt(reason, gas.used()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +354,7 @@ where
|
||||
let mut log_index = 0;
|
||||
for (index, (result, tx)) in results.into_iter().zip(block.body().transactions()).enumerate() {
|
||||
let call = match result {
|
||||
ExecutionResult::Halt { reason, gas_used } => {
|
||||
ExecutionResult::Halt { reason, gas, .. } => {
|
||||
let error = Err::from_evm_halt(reason, tx.gas_limit());
|
||||
#[allow(clippy::needless_update)]
|
||||
SimCallResult {
|
||||
@@ -364,13 +364,13 @@ where
|
||||
code: SIMULATE_VM_ERROR_CODE,
|
||||
..SimulateError::invalid_params()
|
||||
}),
|
||||
gas_used,
|
||||
gas_used: gas.used(),
|
||||
logs: Vec::new(),
|
||||
status: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
ExecutionResult::Revert { output, gas_used } => {
|
||||
ExecutionResult::Revert { output, gas, .. } => {
|
||||
let error = Err::from_revert(output.clone());
|
||||
#[allow(clippy::needless_update)]
|
||||
SimCallResult {
|
||||
@@ -380,19 +380,19 @@ where
|
||||
code: SIMULATE_REVERT_CODE,
|
||||
..SimulateError::invalid_params()
|
||||
}),
|
||||
gas_used,
|
||||
gas_used: gas.used(),
|
||||
status: false,
|
||||
logs: Vec::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
ExecutionResult::Success { output, gas_used, logs, .. } =>
|
||||
ExecutionResult::Success { output, gas, logs, .. } =>
|
||||
{
|
||||
#[allow(clippy::needless_update)]
|
||||
SimCallResult {
|
||||
return_data: output.into_data(),
|
||||
error: None,
|
||||
gas_used,
|
||||
gas_used: gas.used(),
|
||||
logs: logs
|
||||
.into_iter()
|
||||
.map(|log| {
|
||||
|
||||
@@ -109,6 +109,7 @@ where
|
||||
parent_beacon_block_root: request.payload_attributes.parent_beacon_block_root,
|
||||
withdrawals: withdrawals.map(Into::into),
|
||||
extra_data: request.extra_data.unwrap_or_default(),
|
||||
slot_number: None,
|
||||
};
|
||||
|
||||
let mut builder = evm_config
|
||||
@@ -207,6 +208,7 @@ where
|
||||
sealed_block,
|
||||
total_fees,
|
||||
requests,
|
||||
None,
|
||||
)
|
||||
.try_into_v5()
|
||||
.map_err(RethError::other)
|
||||
|
||||
@@ -61,6 +61,8 @@ pub(crate) struct Header {
|
||||
#[add_arbitrary_tests(crate, compact)]
|
||||
pub(crate) struct HeaderExt {
|
||||
requests_hash: Option<B256>,
|
||||
block_access_list_hash: Option<B256>,
|
||||
slot_number: Option<u64>,
|
||||
}
|
||||
|
||||
impl HeaderExt {
|
||||
@@ -68,7 +70,10 @@ impl HeaderExt {
|
||||
///
|
||||
/// Required since [`Header`] uses `Option<HeaderExt>` as a field.
|
||||
const fn into_option(self) -> Option<Self> {
|
||||
if self.requests_hash.is_some() {
|
||||
if self.requests_hash.is_some() ||
|
||||
self.block_access_list_hash.is_some() ||
|
||||
self.slot_number.is_some()
|
||||
{
|
||||
Some(self)
|
||||
} else {
|
||||
None
|
||||
@@ -81,7 +86,11 @@ impl Compact for AlloyHeader {
|
||||
where
|
||||
B: bytes::BufMut + AsMut<[u8]>,
|
||||
{
|
||||
let extra_fields = HeaderExt { requests_hash: self.requests_hash };
|
||||
let extra_fields = HeaderExt {
|
||||
requests_hash: self.requests_hash,
|
||||
block_access_list_hash: self.block_access_list_hash,
|
||||
slot_number: self.slot_number,
|
||||
};
|
||||
|
||||
let header = Header {
|
||||
parent_hash: self.parent_hash,
|
||||
@@ -132,6 +141,11 @@ impl Compact for AlloyHeader {
|
||||
excess_blob_gas: header.excess_blob_gas,
|
||||
parent_beacon_block_root: header.parent_beacon_block_root,
|
||||
requests_hash: header.extra_fields.as_ref().and_then(|h| h.requests_hash),
|
||||
block_access_list_hash: header
|
||||
.extra_fields
|
||||
.as_ref()
|
||||
.and_then(|h| h.block_access_list_hash),
|
||||
slot_number: header.extra_fields.as_ref().and_then(|h| h.slot_number),
|
||||
extra_data: header.extra_data,
|
||||
};
|
||||
(alloy_header, buf)
|
||||
@@ -193,7 +207,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_extra_fields() {
|
||||
let mut header = HOLESKY_BLOCK;
|
||||
header.extra_fields = Some(HeaderExt { requests_hash: Some(B256::random()) });
|
||||
header.extra_fields = Some(HeaderExt {
|
||||
requests_hash: Some(B256::random()),
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
});
|
||||
|
||||
let mut encoded_header = vec![];
|
||||
let len = header.to_compact(&mut encoded_header);
|
||||
|
||||
@@ -942,6 +942,7 @@ mod tests {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
state: BundleState::default(),
|
||||
};
|
||||
@@ -1744,6 +1745,7 @@ mod tests {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
|
||||
@@ -217,9 +217,9 @@ impl<N: ProviderNodeTypes> ConsistentProvider<N> {
|
||||
/// Populate a [`BundleStateInit`] and [`RevertsInit`] based on the given storage and account
|
||||
/// changesets.
|
||||
///
|
||||
/// Storage changeset keys are always plain (unhashed). Current values are read via
|
||||
/// [`StateProvider::storage`], which handles hashing internally when `use_hashed_state` is
|
||||
/// enabled.
|
||||
/// When `use_hashed_state` is enabled, storage changeset keys are already hashed, so current
|
||||
/// values are read directly from [`reth_db_api::tables::HashedStorages`]. Otherwise, values
|
||||
/// are read via [`StateProvider::storage`] which queries plain state tables.
|
||||
fn populate_bundle_state(
|
||||
&self,
|
||||
account_changeset: Vec<(u64, AccountBeforeTx)>,
|
||||
@@ -262,8 +262,9 @@ impl<N: ProviderNodeTypes> ConsistentProvider<N> {
|
||||
// match storage.
|
||||
match account_state.2.entry(old_storage.key) {
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
let new_storage_value =
|
||||
state_provider.storage(address, old_storage.key)?.unwrap_or_default();
|
||||
let new_storage_value = state_provider
|
||||
.storage(address, old_storage.key)?
|
||||
.unwrap_or_default();
|
||||
entry.insert((old_storage.value, new_storage_value));
|
||||
}
|
||||
hash_map::Entry::Occupied(mut entry) => {
|
||||
@@ -1319,10 +1320,12 @@ impl<N: ProviderNodeTypes> StorageChangeSetReader for ConsistentProvider<N> {
|
||||
.flatten()
|
||||
.flat_map(|revert: PlainStorageRevert| {
|
||||
revert.storage_revert.into_iter().map(move |(key, value)| {
|
||||
let plain_key = B256::from(key.to_be_bytes());
|
||||
(
|
||||
BlockNumberAddress((block_number, revert.address)),
|
||||
StorageEntry { key: plain_key, value: value.to_previous_value() },
|
||||
StorageEntry {
|
||||
key: B256::from(key.to_be_bytes()),
|
||||
value: value.to_previous_value(),
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1376,9 +1379,9 @@ impl<N: ProviderNodeTypes> StorageChangeSetReader for ConsistentProvider<N> {
|
||||
return None
|
||||
}
|
||||
revert.storage_revert.into_iter().find_map(|(key, value)| {
|
||||
let plain_key = B256::from(key.to_be_bytes());
|
||||
(plain_key == storage_key).then(|| StorageEntry {
|
||||
key: plain_key,
|
||||
let slot_key = B256::from(key.to_be_bytes());
|
||||
(slot_key == storage_key).then(|| StorageEntry {
|
||||
key: slot_key,
|
||||
value: value.to_previous_value(),
|
||||
})
|
||||
})
|
||||
@@ -1427,10 +1430,12 @@ impl<N: ProviderNodeTypes> StorageChangeSetReader for ConsistentProvider<N> {
|
||||
.flatten()
|
||||
.flat_map(|revert: PlainStorageRevert| {
|
||||
revert.storage_revert.into_iter().map(move |(key, value)| {
|
||||
let plain_key = B256::from(key.to_be_bytes());
|
||||
(
|
||||
BlockNumberAddress((state.number(), revert.address)),
|
||||
StorageEntry { key: plain_key, value: value.to_previous_value() },
|
||||
StorageEntry {
|
||||
key: B256::from(key.to_be_bytes()),
|
||||
value: value.to_previous_value(),
|
||||
},
|
||||
)
|
||||
})
|
||||
});
|
||||
@@ -2034,6 +2039,7 @@ mod tests {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
@@ -2067,6 +2073,178 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_state_storage_value_hashed_state() -> eyre::Result<()> {
|
||||
use alloy_primitives::{keccak256, U256};
|
||||
use reth_db_api::{models::StorageSettings, tables, transaction::DbTxMut};
|
||||
use reth_primitives_traits::StorageEntry;
|
||||
use reth_storage_api::StorageSettingsCache;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let address = alloy_primitives::Address::with_last_byte(1);
|
||||
let account = reth_primitives_traits::Account {
|
||||
nonce: 1,
|
||||
balance: U256::from(1000),
|
||||
bytecode_hash: None,
|
||||
};
|
||||
let slot = U256::from(0x42);
|
||||
let slot_b256 = B256::from(slot);
|
||||
let hashed_address = keccak256(address);
|
||||
let hashed_slot = keccak256(slot_b256);
|
||||
|
||||
let mut rng = generators::rng();
|
||||
let factory = create_test_provider_factory();
|
||||
factory.set_storage_settings_cache(StorageSettings::v2());
|
||||
|
||||
let blocks = random_block_range(
|
||||
&mut rng,
|
||||
0..=1,
|
||||
BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..1, ..Default::default() },
|
||||
);
|
||||
|
||||
let provider_rw = factory.provider_rw()?;
|
||||
provider_rw.append_blocks_with_state(
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|b| b.try_recover().expect("failed to seal block with senders"))
|
||||
.collect(),
|
||||
&ExecutionOutcome {
|
||||
bundle: BundleState::new(
|
||||
[(address, None, Some(account.into()), {
|
||||
let mut s = HashMap::default();
|
||||
s.insert(slot, (U256::ZERO, U256::from(100)));
|
||||
s
|
||||
})],
|
||||
[
|
||||
Vec::new(),
|
||||
vec![(address, Some(Some(account.into())), vec![(slot, U256::ZERO)])],
|
||||
],
|
||||
[],
|
||||
),
|
||||
first_block: 0,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)?;
|
||||
|
||||
provider_rw.tx_ref().put::<tables::HashedStorages>(
|
||||
hashed_address,
|
||||
StorageEntry { key: hashed_slot, value: U256::from(100) },
|
||||
)?;
|
||||
provider_rw.tx_ref().put::<tables::HashedAccounts>(hashed_address, account)?;
|
||||
|
||||
provider_rw.commit()?;
|
||||
|
||||
let provider = BlockchainProvider::new(factory)?;
|
||||
let consistent_provider = provider.consistent_provider()?;
|
||||
|
||||
let outcome =
|
||||
consistent_provider.get_state(1..=1)?.expect("should return execution outcome");
|
||||
|
||||
let state = &outcome.bundle.state;
|
||||
let account_state = state.get(&address).expect("should have account in bundle state");
|
||||
let storage = &account_state.storage;
|
||||
|
||||
let slot_as_u256 = U256::from_be_bytes(*hashed_slot);
|
||||
let storage_slot = storage.get(&slot_as_u256).expect("should have the slot in storage");
|
||||
|
||||
assert_eq!(
|
||||
storage_slot.present_value,
|
||||
U256::from(100),
|
||||
"present_value should be 100 (the actual value in HashedStorages)"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(unix, feature = "rocksdb"))]
|
||||
fn test_get_state_storage_value_hashed_state_historical() -> eyre::Result<()> {
|
||||
use alloy_primitives::{keccak256, U256};
|
||||
use reth_db_api::{models::StorageSettings, tables, transaction::DbTxMut};
|
||||
use reth_primitives_traits::StorageEntry;
|
||||
use reth_storage_api::StorageSettingsCache;
|
||||
use std::collections::HashMap;
|
||||
|
||||
let address = alloy_primitives::Address::with_last_byte(1);
|
||||
let account = reth_primitives_traits::Account {
|
||||
nonce: 1,
|
||||
balance: U256::from(1000),
|
||||
bytecode_hash: None,
|
||||
};
|
||||
let slot = U256::from(0x42);
|
||||
let slot_b256 = B256::from(slot);
|
||||
let hashed_address = keccak256(address);
|
||||
let hashed_slot = keccak256(slot_b256);
|
||||
|
||||
let mut rng = generators::rng();
|
||||
let factory = create_test_provider_factory();
|
||||
factory.set_storage_settings_cache(StorageSettings::v2());
|
||||
|
||||
let blocks = random_block_range(
|
||||
&mut rng,
|
||||
0..=3,
|
||||
BlockRangeParams { parent: Some(B256::ZERO), tx_count: 0..1, ..Default::default() },
|
||||
);
|
||||
|
||||
let provider_rw = factory.provider_rw()?;
|
||||
provider_rw.append_blocks_with_state(
|
||||
blocks
|
||||
.into_iter()
|
||||
.map(|b| b.try_recover().expect("failed to seal block with senders"))
|
||||
.collect(),
|
||||
&ExecutionOutcome {
|
||||
bundle: BundleState::new(
|
||||
[(address, None, Some(account.into()), {
|
||||
let mut s = HashMap::default();
|
||||
s.insert(slot, (U256::ZERO, U256::from(300)));
|
||||
s
|
||||
})],
|
||||
[
|
||||
Vec::new(),
|
||||
vec![(address, Some(Some(account.into())), vec![(slot, U256::ZERO)])],
|
||||
vec![(address, Some(Some(account.into())), vec![(slot, U256::from(100))])],
|
||||
vec![(address, Some(Some(account.into())), vec![(slot, U256::from(200))])],
|
||||
],
|
||||
[],
|
||||
),
|
||||
first_block: 0,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)?;
|
||||
|
||||
provider_rw.tx_ref().put::<tables::HashedStorages>(
|
||||
hashed_address,
|
||||
StorageEntry { key: hashed_slot, value: U256::from(300) },
|
||||
)?;
|
||||
provider_rw.tx_ref().put::<tables::HashedAccounts>(hashed_address, account)?;
|
||||
|
||||
provider_rw.commit()?;
|
||||
|
||||
let provider = BlockchainProvider::new(factory)?;
|
||||
let consistent_provider = provider.consistent_provider()?;
|
||||
|
||||
let outcome =
|
||||
consistent_provider.get_state(1..=2)?.expect("should return execution outcome");
|
||||
|
||||
let state = &outcome.bundle.state;
|
||||
let account_state = state.get(&address).expect("should have account in bundle state");
|
||||
let storage = &account_state.storage;
|
||||
|
||||
let slot_as_u256 = U256::from_be_bytes(*hashed_slot);
|
||||
let storage_slot = storage.get(&slot_as_u256).expect("should have the slot in storage");
|
||||
|
||||
assert_eq!(
|
||||
storage_slot.present_value,
|
||||
U256::from(200),
|
||||
"present_value should be 200 (the value at block 2, not 300 which is the latest)"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_state_storage_value_plain_state() -> eyre::Result<()> {
|
||||
use alloy_primitives::U256;
|
||||
@@ -2148,6 +2326,105 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_changeset_consistent_keys_hashed_state() -> eyre::Result<()> {
|
||||
use alloy_primitives::{keccak256, U256};
|
||||
use reth_db_api::models::StorageSettings;
|
||||
use reth_storage_api::{StorageChangeSetReader, StorageSettingsCache};
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut rng = generators::rng();
|
||||
let factory = create_test_provider_factory();
|
||||
factory.set_storage_settings_cache(StorageSettings::v2());
|
||||
|
||||
let (database_blocks, in_memory_blocks) = random_blocks(&mut rng, 1, 1, None, None, 0..1);
|
||||
|
||||
let address = alloy_primitives::Address::with_last_byte(1);
|
||||
let account = reth_primitives_traits::Account {
|
||||
nonce: 1,
|
||||
balance: U256::from(1000),
|
||||
bytecode_hash: None,
|
||||
};
|
||||
let slot = U256::from(0x42);
|
||||
|
||||
let provider_rw = factory.provider_rw()?;
|
||||
provider_rw.append_blocks_with_state(
|
||||
database_blocks
|
||||
.into_iter()
|
||||
.map(|b| b.try_recover().expect("failed to seal block with senders"))
|
||||
.collect(),
|
||||
&ExecutionOutcome {
|
||||
bundle: BundleState::new(
|
||||
[(address, None, Some(account.into()), {
|
||||
let mut s = HashMap::default();
|
||||
s.insert(slot, (U256::ZERO, U256::from(100)));
|
||||
s
|
||||
})],
|
||||
[[(address, Some(Some(account.into())), vec![(slot, U256::ZERO)])]],
|
||||
[],
|
||||
),
|
||||
first_block: 0,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)?;
|
||||
provider_rw.commit()?;
|
||||
|
||||
let provider = BlockchainProvider::new(factory)?;
|
||||
|
||||
let in_mem_block = in_memory_blocks.first().unwrap();
|
||||
let senders = in_mem_block.senders().expect("failed to recover senders");
|
||||
let chain = NewCanonicalChain::Commit {
|
||||
new: vec![ExecutedBlock {
|
||||
recovered_block: Arc::new(RecoveredBlock::new_sealed(
|
||||
in_mem_block.clone(),
|
||||
senders,
|
||||
)),
|
||||
execution_output: Arc::new(BlockExecutionOutput {
|
||||
state: BundleState::new(
|
||||
[(address, None, Some(account.into()), {
|
||||
let mut s = HashMap::default();
|
||||
s.insert(slot, (U256::from(100), U256::from(200)));
|
||||
s
|
||||
})],
|
||||
[[(address, Some(Some(account.into())), vec![(slot, U256::from(100))])]],
|
||||
[],
|
||||
),
|
||||
result: BlockExecutionResult {
|
||||
receipts: Default::default(),
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
}],
|
||||
};
|
||||
provider.canonical_in_memory_state.update_chain(chain);
|
||||
|
||||
let consistent_provider = provider.consistent_provider()?;
|
||||
|
||||
let db_changeset = consistent_provider.storage_changeset(0)?;
|
||||
let mem_changeset = consistent_provider.storage_changeset(1)?;
|
||||
|
||||
let slot_b256 = B256::from(slot);
|
||||
let _hashed_slot_b256 = keccak256(slot_b256);
|
||||
|
||||
assert_eq!(db_changeset.len(), 1);
|
||||
assert_eq!(mem_changeset.len(), 1);
|
||||
|
||||
let db_key = db_changeset[0].1.key;
|
||||
let mem_key = mem_changeset[0].1.key;
|
||||
|
||||
assert_eq!(
|
||||
db_key, mem_key,
|
||||
"DB and in-memory changesets should return the same key format (hashed) for the same logical slot"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_changeset_consistent_keys_plain_state() -> eyre::Result<()> {
|
||||
use alloy_primitives::U256;
|
||||
@@ -2217,6 +2494,7 @@ mod tests {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
@@ -2234,8 +2512,8 @@ mod tests {
|
||||
assert_eq!(db_changeset.len(), 1);
|
||||
assert_eq!(mem_changeset.len(), 1);
|
||||
|
||||
let db_key = db_changeset[0].1.key;
|
||||
let mem_key = mem_changeset[0].1.key;
|
||||
let db_key = db_changeset[0].1.key.as_b256();
|
||||
let mem_key = mem_changeset[0].1.key.as_b256();
|
||||
|
||||
assert_eq!(db_key, slot_b256, "DB changeset should use plain (unhashed) key");
|
||||
assert_eq!(mem_key, slot_b256, "In-memory changeset should use plain (unhashed) key");
|
||||
@@ -2247,6 +2525,102 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_changesets_range_consistent_keys_hashed_state() -> eyre::Result<()> {
|
||||
use alloy_primitives::U256;
|
||||
use reth_db_api::models::StorageSettings;
|
||||
use reth_storage_api::{StorageChangeSetReader, StorageSettingsCache};
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut rng = generators::rng();
|
||||
let factory = create_test_provider_factory();
|
||||
factory.set_storage_settings_cache(StorageSettings::v2());
|
||||
|
||||
let (database_blocks, in_memory_blocks) = random_blocks(&mut rng, 2, 1, None, None, 0..1);
|
||||
|
||||
let address = alloy_primitives::Address::with_last_byte(1);
|
||||
let account = reth_primitives_traits::Account {
|
||||
nonce: 1,
|
||||
balance: U256::from(1000),
|
||||
bytecode_hash: None,
|
||||
};
|
||||
let slot = U256::from(0x42);
|
||||
|
||||
let provider_rw = factory.provider_rw()?;
|
||||
provider_rw.append_blocks_with_state(
|
||||
database_blocks
|
||||
.into_iter()
|
||||
.map(|b| b.try_recover().expect("failed to seal block with senders"))
|
||||
.collect(),
|
||||
&ExecutionOutcome {
|
||||
bundle: BundleState::new(
|
||||
[(address, None, Some(account.into()), {
|
||||
let mut s = HashMap::default();
|
||||
s.insert(slot, (U256::ZERO, U256::from(100)));
|
||||
s
|
||||
})],
|
||||
vec![
|
||||
vec![(address, Some(Some(account.into())), vec![(slot, U256::ZERO)])],
|
||||
vec![],
|
||||
],
|
||||
[],
|
||||
),
|
||||
first_block: 0,
|
||||
..Default::default()
|
||||
},
|
||||
Default::default(),
|
||||
)?;
|
||||
provider_rw.commit()?;
|
||||
|
||||
let provider = BlockchainProvider::new(factory)?;
|
||||
|
||||
let in_mem_block = in_memory_blocks.first().unwrap();
|
||||
let senders = in_mem_block.senders().expect("failed to recover senders");
|
||||
let chain = NewCanonicalChain::Commit {
|
||||
new: vec![ExecutedBlock {
|
||||
recovered_block: Arc::new(RecoveredBlock::new_sealed(
|
||||
in_mem_block.clone(),
|
||||
senders,
|
||||
)),
|
||||
execution_output: Arc::new(BlockExecutionOutput {
|
||||
state: BundleState::new(
|
||||
[(address, None, Some(account.into()), {
|
||||
let mut s = HashMap::default();
|
||||
s.insert(slot, (U256::from(100), U256::from(200)));
|
||||
s
|
||||
})],
|
||||
[[(address, Some(Some(account.into())), vec![(slot, U256::from(100))])]],
|
||||
[],
|
||||
),
|
||||
result: BlockExecutionResult {
|
||||
receipts: Default::default(),
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
}],
|
||||
};
|
||||
provider.canonical_in_memory_state.update_chain(chain);
|
||||
|
||||
let consistent_provider = provider.consistent_provider()?;
|
||||
|
||||
let all_changesets = consistent_provider.storage_changesets_range(0..=2)?;
|
||||
|
||||
assert_eq!(all_changesets.len(), 2, "should have one changeset entry per block");
|
||||
|
||||
let keys: Vec<B256> = all_changesets.iter().map(|(_, entry)| entry.key.as_b256()).collect();
|
||||
|
||||
assert_eq!(
|
||||
keys[0], keys[1],
|
||||
"same logical slot should produce identical keys whether from DB or memory"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_storage_changesets_range_consistent_keys_plain_state() -> eyre::Result<()> {
|
||||
use alloy_primitives::U256;
|
||||
@@ -2319,6 +2693,7 @@ mod tests {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
}),
|
||||
..Default::default()
|
||||
@@ -2333,7 +2708,7 @@ mod tests {
|
||||
assert_eq!(all_changesets.len(), 2, "should have one changeset entry per block");
|
||||
|
||||
let slot_b256 = B256::from(slot);
|
||||
let keys: Vec<B256> = all_changesets.iter().map(|(_, entry)| entry.key).collect();
|
||||
let keys: Vec<B256> = all_changesets.iter().map(|(_, entry)| entry.key.as_b256()).collect();
|
||||
|
||||
assert_eq!(
|
||||
keys[0], keys[1],
|
||||
|
||||
@@ -3471,6 +3471,7 @@ impl<TX: DbTxMut + DbTx + 'static, N: NodeTypesForProvider> BlockWriter
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
state: Default::default(),
|
||||
}),
|
||||
@@ -4940,8 +4941,6 @@ mod tests {
|
||||
}
|
||||
|
||||
fn run_save_blocks_and_verify(mode: StorageMode) {
|
||||
use alloy_primitives::map::HashMap;
|
||||
|
||||
let factory = create_test_provider_factory();
|
||||
|
||||
match mode {
|
||||
@@ -4969,6 +4968,7 @@ mod tests {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
state: Default::default(),
|
||||
}),
|
||||
@@ -4992,7 +4992,7 @@ mod tests {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let storage: HashMap<U256, (U256, U256)> = (1..=slots_per_account as u64)
|
||||
let storage = (1..=slots_per_account as u64)
|
||||
.map(|s| {
|
||||
(
|
||||
U256::from(s + acct_idx as u64 * 100),
|
||||
@@ -5037,6 +5037,7 @@ mod tests {
|
||||
requests: Default::default(),
|
||||
gas_used: 0,
|
||||
blob_gas_used: 0,
|
||||
block_access_list: Default::default(),
|
||||
},
|
||||
state: bundle,
|
||||
}),
|
||||
|
||||
@@ -87,6 +87,7 @@ unknown-git = "deny"
|
||||
allow-git = [
|
||||
# TODO: Please avoid adding new entries to this list.
|
||||
"https://github.com/alloy-rs/alloy",
|
||||
"https://github.com/alloy-rs/op-alloy",
|
||||
"https://github.com/foundry-rs/block-explorers",
|
||||
"https://github.com/bluealloy/revm",
|
||||
"https://github.com/paradigmxyz/revm-inspectors",
|
||||
|
||||
@@ -17,13 +17,13 @@ use reth_ethereum::{
|
||||
cli::interface::Cli,
|
||||
evm::{
|
||||
primitives::{
|
||||
block::StateDB,
|
||||
execute::{BlockExecutionError, BlockExecutor, InternalBlockExecutionError},
|
||||
Evm, EvmEnv, EvmEnvFor, ExecutionCtxFor, InspectorFor, NextBlockEnvAttributes,
|
||||
OnStateHook,
|
||||
Database, Evm, EvmEnv, EvmEnvFor, ExecutionCtxFor, InspectorFor,
|
||||
NextBlockEnvAttributes, OnStateHook,
|
||||
},
|
||||
revm::{
|
||||
context::TxEnv,
|
||||
db::State,
|
||||
primitives::{address, hardfork::SpecId, Address},
|
||||
DatabaseCommit,
|
||||
},
|
||||
@@ -101,12 +101,12 @@ impl BlockExecutorFactory for CustomEvmConfig {
|
||||
|
||||
fn create_executor<'a, DB, I>(
|
||||
&'a self,
|
||||
evm: EthEvm<DB, I, PrecompilesMap>,
|
||||
evm: EthEvm<&'a mut State<DB>, I, PrecompilesMap>,
|
||||
ctx: EthBlockExecutionCtx<'a>,
|
||||
) -> impl BlockExecutorFor<'a, Self, DB, I>
|
||||
where
|
||||
DB: StateDB + 'a,
|
||||
I: InspectorFor<Self, DB> + 'a,
|
||||
DB: Database + 'a,
|
||||
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
|
||||
{
|
||||
CustomBlockExecutor {
|
||||
inner: EthBlockExecutor::new(
|
||||
@@ -187,9 +187,10 @@ pub struct CustomBlockExecutor<'a, Evm> {
|
||||
inner: EthBlockExecutor<'a, Evm, &'a Arc<ChainSpec>, &'a RethReceiptBuilder>,
|
||||
}
|
||||
|
||||
impl<E> BlockExecutor for CustomBlockExecutor<'_, E>
|
||||
impl<'db, DB, E> BlockExecutor for CustomBlockExecutor<'_, E>
|
||||
where
|
||||
E: Evm<DB: StateDB, Tx = TxEnv>,
|
||||
DB: Database + 'db,
|
||||
E: Evm<DB = &'db mut State<DB>, Tx = TxEnv>,
|
||||
{
|
||||
type Transaction = TransactionSigned;
|
||||
type Receipt = Receipt;
|
||||
|
||||
@@ -93,6 +93,10 @@ impl PayloadAttributes for CustomPayloadAttributes {
|
||||
fn parent_beacon_block_root(&self) -> Option<B256> {
|
||||
self.inner.parent_beacon_block_root()
|
||||
}
|
||||
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// New type around the payload builder attributes type
|
||||
@@ -138,6 +142,9 @@ impl PayloadBuilderAttributes for CustomPayloadBuilderAttributes {
|
||||
fn withdrawals(&self) -> &Withdrawals {
|
||||
&self.0.withdrawals
|
||||
}
|
||||
fn slot_number(&self) -> Option<u64> {
|
||||
self.0.slot_number
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom engine types - uses a custom payload attributes RPC type, but uses the default
|
||||
|
||||
@@ -252,8 +252,16 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> {
|
||||
.map_err(|err| Error::block_failed(block_number, err))?;
|
||||
|
||||
// Consensus checks after block execution
|
||||
validate_block_post_execution(block, &chain_spec, &output.receipts, &output.requests, None)
|
||||
.map_err(|err| Error::block_failed(block_number, err))?;
|
||||
validate_block_post_execution(
|
||||
block,
|
||||
&chain_spec,
|
||||
&output.receipts,
|
||||
&output.requests,
|
||||
None,
|
||||
&output.block_access_list,
|
||||
Some(output.gas_used),
|
||||
)
|
||||
.map_err(|err| Error::block_failed(block_number, err))?;
|
||||
|
||||
// Compute and check the post state root
|
||||
let hashed_state =
|
||||
|
||||
@@ -116,6 +116,8 @@ impl From<Header> for SealedHeader {
|
||||
excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
|
||||
parent_beacon_block_root: value.parent_beacon_block_root,
|
||||
requests_hash: value.requests_hash,
|
||||
block_access_list_hash: None,
|
||||
slot_number: None,
|
||||
};
|
||||
Self::new(header, value.hash)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user