diff --git a/.config/nextest.toml b/.config/nextest.toml index 94d55bf0311..26b4a000b93 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -6,6 +6,10 @@ slow-timeout = { period = "30s", terminate-after = 4 } filter = "test(general_state_tests)" slow-timeout = { period = "1m", terminate-after = 10 } +[[profile.default.overrides]] +filter = "test(eest_fixtures)" +slow-timeout = { period = "2m", terminate-after = 10 } + # E2E tests using the testsuite framework from crates/e2e-test-utils # These tests are located in tests/e2e-testsuite/ directories across various crates [[profile.default.overrides]] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 01fe80522d5..00000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,45 +0,0 @@ -* @gakonst -crates/blockchain-tree-api/ @rakita @rkrasiuk @mattsse @Rjected -crates/blockchain-tree/ @rakita @rkrasiuk @mattsse @Rjected -crates/chain-state/ @fgimenez @mattsse @rkrasiuk -crates/chainspec/ @Rjected @joshieDo @mattsse -crates/cli/ @mattsse -crates/consensus/ @rkrasiuk @mattsse @Rjected -crates/e2e-test-utils/ @mattsse @Rjected -crates/engine @rkrasiuk @mattsse @Rjected -crates/engine/ @rkrasiuk @mattsse @Rjected @fgimenez -crates/era/ @mattsse @RomanHodulak -crates/errors/ @mattsse -crates/ethereum-forks/ @mattsse @Rjected -crates/ethereum/ @mattsse @Rjected -crates/etl/ @joshieDo @shekhirin -crates/evm/ @rakita @mattsse @Rjected -crates/exex/ @shekhirin -crates/net/ @mattsse @Rjected -crates/net/downloaders/ @rkrasiuk -crates/node/ @mattsse @Rjected @klkvr -crates/optimism/ @mattsse @Rjected @fgimenez -crates/payload/ @mattsse @Rjected -crates/primitives-traits/ @Rjected @RomanHodulak @mattsse @klkvr -crates/primitives/ @Rjected @mattsse @klkvr -crates/prune/ @shekhirin @joshieDo -crates/ress @rkrasiuk -crates/revm/ @mattsse @rakita -crates/rpc/ @mattsse @Rjected @RomanHodulak -crates/stages/ @rkrasiuk @shekhirin -crates/static-file/ @joshieDo @shekhirin -crates/storage/codecs/ @joshieDo -crates/storage/db-api/ @joshieDo @rakita -crates/storage/db-common/ @Rjected -crates/storage/db/ @joshieDo @rakita -crates/storage/errors/ @rakita -crates/storage/libmdbx-rs/ @rakita @shekhirin -crates/storage/nippy-jar/ @joshieDo @shekhirin -crates/storage/provider/ @rakita @joshieDo @shekhirin -crates/storage/storage-api/ @joshieDo @rkrasiuk -crates/tasks/ @mattsse -crates/tokio-util/ @fgimenez -crates/transaction-pool/ @mattsse -crates/trie/ @rkrasiuk @Rjected @shekhirin @mediocregopher -etc/ @Rjected @shekhirin -.github/ @gakonst @DaniPopes diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml deleted file mode 100644 index b01d4518f75..00000000000 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ /dev/null @@ -1,127 +0,0 @@ -name: Bug Report -description: Create a bug report -labels: ["C-bug", "S-needs-triage"] -body: - - type: markdown - attributes: - value: | - Thanks for taking the time to fill out this bug report! Please provide as much detail as possible. - - If you believe you have found a vulnerability, please provide details [here](mailto:georgios@paradigm.xyz) instead. - - type: textarea - id: what-happened - attributes: - label: Describe the bug - description: | - A clear and concise description of what the bug is. - - If the bug is in a crate you are using (i.e. you are not running the standard `reth` binary) please mention that as well. - validations: - required: true - - type: textarea - id: reproduction-steps - attributes: - label: Steps to reproduce - description: Please provide any steps you think might be relevant to reproduce the bug. - placeholder: | - Steps to reproduce: - - 1. Start '...' - 2. Then '...' - 3. Check '...' - 4. See error - validations: - required: true - - type: textarea - id: logs - attributes: - label: Node logs - description: | - If applicable, please provide the node logs leading up to the bug. - - **Please also provide debug logs.** By default, these can be found in: - - - `~/.cache/reth/logs` on Linux - - `~/Library/Caches/reth/logs` on macOS - - `%localAppData%/reth/logs` on Windows - render: text - validations: - required: false - - type: dropdown - id: platform - attributes: - label: Platform(s) - description: What platform(s) did this occur on? - multiple: true - options: - - Linux (x86) - - Linux (ARM) - - Mac (Intel) - - Mac (Apple Silicon) - - Windows (x86) - - Windows (ARM) - - type: dropdown - id: container_type - attributes: - label: Container Type - description: Were you running it in a container? - multiple: true - options: - - Not running in a container - - Docker - - Kubernetes - - LXC/LXD - - Other - validations: - required: true - - type: textarea - id: client-version - attributes: - label: What version/commit are you on? - description: This can be obtained with `reth --version` - validations: - required: true - - type: textarea - id: database-version - attributes: - label: What database version are you on? - description: This can be obtained with `reth db version` - validations: - required: true - - type: textarea - id: network - attributes: - label: Which chain / network are you on? - description: This is the argument you pass to `reth --chain`. If you are using `--dev`, type in 'dev' here. If you are not running with `--chain` or `--dev` then it is mainnet. - validations: - required: true - - type: dropdown - id: node-type - attributes: - label: What type of node are you running? - options: - - Archive (default) - - Full via --full flag - - Pruned with custom reth.toml config - validations: - required: true - - type: textarea - id: prune-config - attributes: - label: What prune config do you use, if any? - description: The `[prune]` section in `reth.toml` file - validations: - required: false - - type: input - attributes: - label: If you've built Reth from source, provide the full command you used - validations: - required: false - - type: checkboxes - id: terms - attributes: - label: Code of Conduct - description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/paradigmxyz/reth/blob/main/CONTRIBUTING.md#code-of-conduct) - options: - - label: I agree to follow the Code of Conduct - required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index cfefdb13a69..00000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: GitHub Discussions - url: https://github.com/paradigmxyz/reth/discussions - about: Please ask and answer questions here to keep the issue tracker clean. diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml deleted file mode 100644 index c1c1c2d51b4..00000000000 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Documentation -description: Suggest a change to our documentation -labels: ["C-docs", "S-needs-triage"] -body: - - type: markdown - attributes: - value: | - If you are unsure if the docs are relevant or needed, please open up a discussion first. - - type: textarea - attributes: - label: Describe the change - description: | - Please describe the documentation you want to change or add, and if it is for end-users or contributors. - validations: - required: true - - type: textarea - attributes: - label: Additional context - description: Add any other context to the feature (like screenshots, resources) diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml deleted file mode 100644 index 005c33ae3fa..00000000000 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Feature request -description: Suggest a feature -labels: ["C-enhancement", "S-needs-triage"] -body: - - type: markdown - attributes: - value: | - Please ensure that the feature has not already been requested in the issue tracker. - - type: textarea - attributes: - label: Describe the feature - description: | - Please describe the feature and what it is aiming to solve, if relevant. - - If the feature is for a crate, please include a proposed API surface. - validations: - required: true - - type: textarea - attributes: - label: Additional context - description: Add any other context to the feature (like screenshots, resources) diff --git a/.github/assets/check_rv32imac.sh b/.github/assets/check_rv32imac.sh deleted file mode 100755 index 9d9c421ca20..00000000000 --- a/.github/assets/check_rv32imac.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash -set +e # Disable immediate exit on error - -# Array of crates to check -crates_to_check=( - reth-codecs-derive - reth-primitives - reth-primitives-traits - reth-network-peers - reth-trie-common - reth-trie-sparse - reth-chainspec - reth-consensus - reth-consensus-common - reth-prune-types - reth-static-file-types - reth-storage-errors - reth-execution-errors - reth-errors - reth-execution-types - reth-db-models - reth-evm - reth-revm - reth-storage-api - - ## ethereum - reth-evm-ethereum - reth-ethereum-forks - reth-ethereum-primitives - reth-ethereum-consensus - reth-stateless - - ## optimism - reth-optimism-chainspec - reth-optimism-forks - reth-optimism-consensus - reth-optimism-primitives - reth-optimism-evm -) - -# Array to hold the results -results=() -# Flag to track if any command fails -any_failed=0 - -for crate in "${crates_to_check[@]}"; do - cmd="cargo +stable build -p $crate --target riscv32imac-unknown-none-elf --no-default-features" - - if [ -n "$CI" ]; then - echo "::group::$cmd" - else - printf "\n%s:\n %s\n" "$crate" "$cmd" - fi - - set +e # Disable immediate exit on error - # Run the command and capture the return code - $cmd - ret_code=$? - set -e # Re-enable immediate exit on error - - # Store the result in the dictionary - if [ $ret_code -eq 0 ]; then - results+=("1:✅:$crate") - else - results+=("2:❌:$crate") - any_failed=1 - fi - - if [ -n "$CI" ]; then - echo "::endgroup::" - fi -done - -# Sort the results by status and then by crate name -IFS=$'\n' sorted_results=($(sort <<<"${results[*]}")) -unset IFS - -# Print summary -echo -e "\nSummary of build results:" -for result in "${sorted_results[@]}"; do - status="${result#*:}" - status="${status%%:*}" - crate="${result##*:}" - echo "$status $crate" -done - -# Exit with a non-zero status if any command fails -exit $any_failed diff --git a/.github/assets/check_wasm.sh b/.github/assets/check_wasm.sh deleted file mode 100755 index e140d01e796..00000000000 --- a/.github/assets/check_wasm.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env bash -set +e # Disable immediate exit on error - -# Array of crates to compile -crates=($(cargo metadata --format-version=1 --no-deps | jq -r '.packages[].name' | grep '^reth' | sort)) - -# Array of crates to exclude -# Used with the `contains` function. -# shellcheck disable=SC2034 -exclude_crates=( - # The following require investigation if they can be fixed - reth-basic-payload-builder - reth-bench - reth-cli - reth-cli-commands - reth-cli-runner - reth-consensus-debug-client - reth-db-common - reth-discv4 - reth-discv5 - reth-dns-discovery - reth-downloaders - reth-e2e-test-utils - reth-engine-service - reth-engine-tree - reth-engine-util - reth-eth-wire - reth-ethereum-cli - reth-ethereum-payload-builder - reth-etl - reth-exex - reth-exex-test-utils - reth-ipc - reth-net-nat - reth-network - reth-node-api - reth-node-builder - reth-node-core - reth-node-ethereum - reth-node-events - reth-node-metrics - reth-optimism-cli - reth-optimism-node - reth-optimism-payload-builder - reth-optimism-rpc - reth-optimism-storage - reth-rpc - reth-rpc-api - reth-rpc-api-testing-util - reth-rpc-builder - reth-rpc-convert - reth-rpc-e2e-tests - reth-rpc-engine-api - reth-rpc-eth-api - reth-rpc-eth-types - reth-rpc-layer - reth-stages - reth-engine-local - reth-ress-protocol - reth-ress-provider - # The following are not supposed to be working - reth # all of the crates below - reth-storage-rpc-provider - reth-invalid-block-hooks # reth-provider - reth-libmdbx # mdbx - reth-mdbx-sys # mdbx - reth-payload-builder # reth-metrics - reth-provider # tokio - reth-prune # tokio - reth-stages-api # reth-provider, reth-prune - reth-static-file # tokio - reth-transaction-pool # c-kzg - reth-payload-util # reth-transaction-pool - reth-trie-parallel # tokio - reth-trie-sparse-parallel # rayon - reth-testing-utils - reth-optimism-txpool # reth-transaction-pool - reth-era-downloader # tokio - reth-era-utils # tokio - reth-tracing-otlp - reth-node-ethstats -) - -# Array to hold the results -results=() -# Flag to track if any command fails -any_failed=0 - -# Function to check if a value exists in an array -contains() { - local array="$1[@]" - local seeking=$2 - local in=1 - for element in "${!array}"; do - if [[ "$element" == "$seeking" ]]; then - in=0 - break - fi - done - return $in -} - -for crate in "${crates[@]}"; do - if contains exclude_crates "$crate"; then - results+=("3:⏭️:$crate") - continue - fi - - cmd="cargo +stable build -p $crate --target wasm32-wasip1 --no-default-features" - - if [ -n "$CI" ]; then - echo "::group::$cmd" - else - printf "\n%s:\n %s\n" "$crate" "$cmd" - fi - - set +e # Disable immediate exit on error - # Run the command and capture the return code - $cmd - ret_code=$? - set -e # Re-enable immediate exit on error - - # Store the result in the dictionary - if [ $ret_code -eq 0 ]; then - results+=("1:✅:$crate") - else - results+=("2:❌:$crate") - any_failed=1 - fi - - if [ -n "$CI" ]; then - echo "::endgroup::" - fi -done - -# Sort the results by status and then by crate name -IFS=$'\n' sorted_results=($(sort <<<"${results[*]}")) -unset IFS - -# Print summary -echo -e "\nSummary of build results:" -for result in "${sorted_results[@]}"; do - status="${result#*:}" - status="${status%%:*}" - crate="${result##*:}" - echo "$status $crate" -done - -# Exit with a non-zero status if any command fails -exit $any_failed diff --git a/.github/assets/hive/Dockerfile b/.github/assets/hive/Dockerfile deleted file mode 100644 index e059ecf977d..00000000000 --- a/.github/assets/hive/Dockerfile +++ /dev/null @@ -1,62 +0,0 @@ -# syntax=docker.io/docker/dockerfile:1.7-labs - -# -# We'll use cargo-chef to speed up the build -# -FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef -WORKDIR /app - -# Install system dependencies -RUN apt-get update && apt-get -y upgrade && apt-get install -y libclang-dev pkg-config - -# -# We prepare the build plan -# -FROM chef AS planner - -ARG CARGO_BIN - -COPY --exclude=.git --exclude=dist . . -RUN cargo chef prepare --recipe-path recipe.json --bin ${CARGO_BIN} - -# -# And build the app -# -FROM chef AS builder -WORKDIR /app - -ARG CARGO_BIN -ARG BUILD_PROFILE=hivetests -ARG FEATURES="" -ARG MANIFEST_PATH="" - -COPY --from=planner /app/recipe.json recipe.json - -RUN cargo chef cook \ - --profile $BUILD_PROFILE \ - --bin $CARGO_BIN \ - ${FEATURES:+--features "$FEATURES"} \ - ${MANIFEST_PATH:+--manifest-path $MANIFEST_PATH} \ - --recipe-path recipe.json - -COPY --exclude=.git --exclude=dist . . -RUN cargo build \ - --profile $BUILD_PROFILE \ - --bin $CARGO_BIN \ - ${FEATURES:+--features "$FEATURES"} \ - ${MANIFEST_PATH:+--manifest-path $MANIFEST_PATH} \ - --locked - -# -# The runtime will then just use the build artifact without building anything -# -FROM ubuntu AS runtime - -ARG CARGO_BIN - -COPY --from=builder /app/target/hivetests/$CARGO_BIN /usr/local/bin/reth -COPY LICENSE-* ./ - -EXPOSE 30303 30303/udp 9001 8545 8546 -ENV RUST_LOG=debug -ENTRYPOINT ["/usr/local/bin/reth"] diff --git a/.github/assets/hive/build_simulators.sh b/.github/assets/hive/build_simulators.sh deleted file mode 100755 index 44792bde076..00000000000 --- a/.github/assets/hive/build_simulators.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# Create the hive_assets directory -mkdir hive_assets/ - -cd hivetests -go build . - -./hive -client reth # first builds and caches the client - -# Run each hive command in the background for each simulator and wait -echo "Building images" -./hive -client reth --sim "ethereum/eest" --sim.buildarg fixtures=https://github.com/ethereum/execution-spec-tests/releases/download/v4.4.0/fixtures_develop.tar.gz --sim.buildarg branch=v4.4.0 -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 & -./hive -client reth --sim "smoke/genesis" -sim.timelimit 1s || true & -./hive -client reth --sim "smoke/network" -sim.timelimit 1s || true & -./hive -client reth --sim "ethereum/sync" -sim.timelimit 1s || true & -wait - -# Run docker save in parallel, wait and exit on error -echo "Saving images" -saving_pids=( ) -docker save hive/hiveproxy:latest -o ../hive_assets/hiveproxy.tar & saving_pids+=( $! ) -docker save hive/simulators/devp2p:latest -o ../hive_assets/devp2p.tar & saving_pids+=( $! ) -docker save hive/simulators/ethereum/engine:latest -o ../hive_assets/engine.tar & saving_pids+=( $! ) -docker save hive/simulators/ethereum/rpc-compat:latest -o ../hive_assets/rpc_compat.tar & saving_pids+=( $! ) -docker save hive/simulators/ethereum/eest/consume-engine:latest -o ../hive_assets/eest_engine.tar & saving_pids+=( $! ) -docker save hive/simulators/ethereum/eest/consume-rlp:latest -o ../hive_assets/eest_rlp.tar & saving_pids+=( $! ) -docker save hive/simulators/smoke/genesis:latest -o ../hive_assets/smoke_genesis.tar & saving_pids+=( $! ) -docker save hive/simulators/smoke/network:latest -o ../hive_assets/smoke_network.tar & saving_pids+=( $! ) -docker save hive/simulators/ethereum/sync:latest -o ../hive_assets/ethereum_sync.tar & saving_pids+=( $! ) -for pid in "${saving_pids[@]}"; do - wait "$pid" || exit -done - -# Make sure we don't rebuild images on the CI jobs -git apply ../.github/assets/hive/no_sim_build.diff -go build . -mv ./hive ../hive_assets/ diff --git a/.github/assets/hive/expected_failures.yaml b/.github/assets/hive/expected_failures.yaml deleted file mode 100644 index a4dd3376efd..00000000000 --- a/.github/assets/hive/expected_failures.yaml +++ /dev/null @@ -1,105 +0,0 @@ -# tracked by https://github.com/paradigmxyz/reth/issues/13879 -rpc-compat: - - debug_getRawBlock/get-invalid-number (reth) - - debug_getRawHeader/get-invalid-number (reth) - - debug_getRawReceipts/get-invalid-number (reth) - - debug_getRawReceipts/get-block-n (reth) - - debug_getRawTransaction/get-invalid-hash (reth) - - - eth_getStorageAt/get-storage-invalid-key-too-large (reth) - - eth_getStorageAt/get-storage-invalid-key (reth) - - eth_getTransactionReceipt/get-access-list (reth) - - eth_getTransactionReceipt/get-blob-tx (reth) - - eth_getTransactionReceipt/get-dynamic-fee (reth) - - eth_getTransactionReceipt/get-legacy-contract (reth) - - eth_getTransactionReceipt/get-legacy-input (reth) - - eth_getTransactionReceipt/get-legacy-receipt (reth) - - # after https://github.com/paradigmxyz/reth/pull/16742 we start the node in - # syncing mode, the test expects syncing to be false on start - - eth_syncing/check-syncing (reth) - -# 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 Block 1 - 8 Block Re-Org NewPayload (Paris) (reth) - - Withdrawals Fork on Canonical Block 8 / Side Block 7 - 10 Block Re-Org (Paris) (reth) - -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 verions 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: [] - -# https://github.com/ethereum/hive/issues/1277 -engine-auth: - - "JWT Authentication: No time drift, correct secret (Paris) (reth)" - - "JWT Authentication: Negative time drift, within limit, correct secret (Paris) (reth)" - - "JWT Authentication: Positive time drift, within limit, correct secret (Paris) (reth)" - -# 7702 test - no fix: it’s too expensive to check whether the storage is empty on each creation -# 6110 related tests - may start passing when fixtures improve -# 7002 related tests - post-fork test, should fix for spec compliance but not -# realistic on mainnet -# 7251 related tests - modified contract, not necessarily practical on mainnet, -# worth re-visiting when more of these related tests are passing -eest/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 - # the next test expects a concrete new format in the error message, there is no spec for this message, so it is ok to ignore - - tests/cancun/eip4844_blobs/test_blob_txs.py::test_blob_type_tx_pre_fork[fork_ShanghaiToCancunAtTime15k-blockchain_test_engine_from_state_test-one_blob_tx]-reth -# 7702 test - no fix: it’s too expensive to check whether the storage is empty on each creation -# rest of tests - see above -eest/consume-rlp: - - tests/prague/eip7702_set_code_tx/test_set_code_txs.py::test_set_code_to_non_empty_storage[fork_Prague-blockchain_test-zero_nonce]-reth - - tests/prague/eip7251_consolidations/test_modified_consolidation_contract.py::test_system_contract_errors[fork_Prague-blockchain_test_engine-system_contract_reaches_gas_limit-system_contract_0x0000bbddc7ce488642fb579f8b00f3a590007251]-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/eip7002_el_triggerable_withdrawals/test_modified_withdrawal_contract.py::test_system_contract_errors[fork_Prague-blockchain_test_engine-system_contract_reaches_gas_limit-system_contract_0x00000961ef480eb55e80d19ad83579a64c007002]-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/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-nonzero_balance]-reth - - tests/prague/eip7251_consolidations/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-zero_balance]-reth - - tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-nonzero_balance]-reth - - tests/prague/eip7002_el_triggerable_withdrawals/test_contract_deployment.py::test_system_contract_deployment[fork_CancunToPragueAtTime15k-blockchain_test-deploy_after_fork-zero_balance]-reth diff --git a/.github/assets/hive/ignored_tests.yaml b/.github/assets/hive/ignored_tests.yaml deleted file mode 100644 index 43021de8420..00000000000 --- a/.github/assets/hive/ignored_tests.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# Ignored Tests Configuration -# -# This file contains tests that should be ignored for various reasons (flaky, known issues, etc). -# These tests will be IGNORED in the CI results - they won't cause the build to fail -# regardless of whether they pass or fail. -# -# Format -# test_suite: -# - "test name 1" -# - "test name 2" -# -# When a test should no longer be ignored, remove it from this list. - -engine-withdrawals: - # flaky - - Withdrawals Fork on Block 1 - 8 Block Re-Org NewPayload (Paris) (reth) - diff --git a/.github/assets/hive/load_images.sh b/.github/assets/hive/load_images.sh deleted file mode 100755 index 37a2f82de54..00000000000 --- a/.github/assets/hive/load_images.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# List of tar files to load -IMAGES=( - "/tmp/hiveproxy.tar" - "/tmp/devp2p.tar" - "/tmp/engine.tar" - "/tmp/rpc_compat.tar" - "/tmp/pyspec.tar" - "/tmp/smoke_genesis.tar" - "/tmp/smoke_network.tar" - "/tmp/ethereum_sync.tar" - "/tmp/eest_engine.tar" - "/tmp/eest_rlp.tar" - "/tmp/reth_image.tar" -) - -# Loop through the images and load them -for IMAGE_TAR in "${IMAGES[@]}"; do - echo "Loading image $IMAGE_TAR..." - docker load -i "$IMAGE_TAR" & -done - -wait - -docker image ls -a diff --git a/.github/assets/hive/no_sim_build.diff b/.github/assets/hive/no_sim_build.diff deleted file mode 100644 index 6127a4ecb73..00000000000 --- a/.github/assets/hive/no_sim_build.diff +++ /dev/null @@ -1,52 +0,0 @@ -diff --git a/internal/libdocker/builder.go b/internal/libdocker/builder.go -index e4bf99b6..2023f7e2 100644 ---- a/internal/libdocker/builder.go -+++ b/internal/libdocker/builder.go -@@ -8,7 +8,6 @@ import ( - "io" - "io/fs" - "log/slog" -- "os" - "path/filepath" - "slices" - "strings" -@@ -49,25 +48,8 @@ func (b *Builder) BuildClientImage(ctx context.Context, client libhive.ClientDes - - // BuildSimulatorImage builds a docker image of a simulator. - func (b *Builder) BuildSimulatorImage(ctx context.Context, name string, buildArgs map[string]string) (string, error) { -- dir := b.config.Inventory.SimulatorDirectory(name) -- buildContextPath := dir -- buildDockerfile := "Dockerfile" -- -- // build context dir of simulator can be overridden with "hive_context.txt" file containing the desired build path -- if contextPathBytes, err := os.ReadFile(filepath.Join(filepath.FromSlash(dir), "hive_context.txt")); err == nil { -- buildContextPath = filepath.Join(dir, strings.TrimSpace(string(contextPathBytes))) -- if strings.HasPrefix(buildContextPath, "../") { -- return "", fmt.Errorf("cannot access build directory outside of Hive root: %q", buildContextPath) -- } -- if p, err := filepath.Rel(buildContextPath, filepath.Join(filepath.FromSlash(dir), "Dockerfile")); err != nil { -- return "", fmt.Errorf("failed to derive relative simulator Dockerfile path: %v", err) -- } else { -- buildDockerfile = p -- } -- } - tag := fmt.Sprintf("hive/simulators/%s:latest", name) -- err := b.buildImage(ctx, buildContextPath, buildDockerfile, tag, buildArgs) -- return tag, err -+ return tag, nil - } - - // BuildImage creates a container by archiving the given file system, -diff --git a/internal/libdocker/proxy.go b/internal/libdocker/proxy.go -index d3a14ae6..8779671e 100644 ---- a/internal/libdocker/proxy.go -+++ b/internal/libdocker/proxy.go -@@ -16,7 +16,7 @@ const hiveproxyTag = "hive/hiveproxy" - - // Build builds the hiveproxy image. - func (cb *ContainerBackend) Build(ctx context.Context, b libhive.Builder) error { -- return b.BuildImage(ctx, hiveproxyTag, hiveproxy.Source) -+ return nil - } - - // ServeAPI starts the API server. diff --git a/.github/assets/hive/parse.py b/.github/assets/hive/parse.py deleted file mode 100644 index 11a30ae095b..00000000000 --- a/.github/assets/hive/parse.py +++ /dev/null @@ -1,78 +0,0 @@ -import json -import yaml -import sys -import argparse - -# Argument parser setup -parser = argparse.ArgumentParser(description="Check for unexpected test results based on an exclusion list.") -parser.add_argument("report_json", help="Path to the hive report JSON file.") -parser.add_argument("--exclusion", required=True, help="Path to the exclusion YAML file.") -parser.add_argument("--ignored", required=True, help="Path to the ignored tests YAML file.") -args = parser.parse_args() - -# Load hive JSON -with open(args.report_json, 'r') as file: - report = json.load(file) - -# Load exclusion YAML -with open(args.exclusion, 'r') as file: - exclusion_data = yaml.safe_load(file) - exclusions = exclusion_data.get(report['name'], []) - -# Load ignored tests YAML -with open(args.ignored, 'r') as file: - ignored_data = yaml.safe_load(file) - ignored_tests = ignored_data.get(report['name'], []) - -# Collect unexpected failures and passes -unexpected_failures = [] -unexpected_passes = [] -ignored_results = {'passed': [], 'failed': []} - -for test in report['testCases'].values(): - test_name = test['name'] - test_pass = test['summaryResult']['pass'] - - # Check if this is an ignored test - if test_name in ignored_tests: - # Track ignored test results for informational purposes - if test_pass: - ignored_results['passed'].append(test_name) - else: - ignored_results['failed'].append(test_name) - continue # Skip this test - don't count it as unexpected - - # Check against expected failures - if test_name in exclusions: - if test_pass: - unexpected_passes.append(test_name) - else: - if not test_pass: - unexpected_failures.append(test_name) - -# Print summary of ignored tests if any were ignored -if ignored_results['passed'] or ignored_results['failed']: - print("Ignored Tests:") - if ignored_results['passed']: - print(f" Passed ({len(ignored_results['passed'])} tests):") - for test in ignored_results['passed']: - print(f" {test}") - if ignored_results['failed']: - print(f" Failed ({len(ignored_results['failed'])} tests):") - for test in ignored_results['failed']: - print(f" {test}") - print() - -# Check if there are any unexpected failures or passes and exit with error -if unexpected_failures or unexpected_passes: - if unexpected_failures: - print("Unexpected Failures:") - for test in unexpected_failures: - print(f" {test}") - if unexpected_passes: - print("Unexpected Passes:") - for test in unexpected_passes: - print(f" {test}") - sys.exit(1) - -print("Success.") diff --git a/.github/assets/hive/run_simulator.sh b/.github/assets/hive/run_simulator.sh deleted file mode 100755 index cb4d8110dfa..00000000000 --- a/.github/assets/hive/run_simulator.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# set -x - -cd hivetests/ - -sim="${1}" -limit="${2}" - -run_hive() { - hive --sim "${sim}" --sim.limit "${limit}" --sim.parallelism 8 --client reth 2>&1 | tee /tmp/log || true -} - -check_log() { - tail -n 1 /tmp/log | sed -r 's/\x1B\[[0-9;]*[mK]//g' -} - -attempt=0 -max_attempts=5 - -while [ $attempt -lt $max_attempts ]; do - run_hive - - # Check if no tests were run. sed removes ansi colors - if check_log | grep -q "suites=0"; then - echo "no tests were run, retrying in 10 seconds" - sleep 10 - attempt=$((attempt + 1)) - continue - fi - - # Check the last line of the log for "finished", "tests failed", or "test failed" - if check_log | grep -Eq "(finished|tests? failed)"; then - exit 0 - else - exit 1 - fi -done -exit 1 diff --git a/.github/assets/install_geth.sh b/.github/assets/install_geth.sh deleted file mode 100755 index 8469f5a73f8..00000000000 --- a/.github/assets/install_geth.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -# Installs Geth (https://geth.ethereum.org) in $HOME/bin for x86_64 Linux. - -set -eo pipefail - -GETH_BUILD=${GETH_BUILD:-"1.13.4-3f907d6a"} - -name="geth-linux-amd64-$GETH_BUILD" - -mkdir -p "$HOME/bin" -wget "https://gethstore.blob.core.windows.net/builds/$name.tar.gz" -tar -xvf "$name.tar.gz" -rm "$name.tar.gz" -mv "$name/geth" "$HOME/bin/geth" -rm -rf "$name" -chmod +x "$HOME/bin/geth" - -# Add $HOME/bin to $PATH -[[ "$PATH" != *$HOME/bin* ]] && export PATH=$HOME/bin:$PATH -[ -n "$CI" ] && echo "$HOME/bin" >> "$GITHUB_PATH" - -geth version diff --git a/.github/assets/kurtosis_network_params.yaml b/.github/assets/kurtosis_network_params.yaml deleted file mode 100644 index e8cc1b51dc8..00000000000 --- a/.github/assets/kurtosis_network_params.yaml +++ /dev/null @@ -1,13 +0,0 @@ -participants: - - el_type: geth - cl_type: lighthouse - - el_type: reth - el_image: "ghcr.io/paradigmxyz/reth:kurtosis-ci" - cl_type: teku -additional_services: - - assertoor -assertoor_params: - run_block_proposal_check: true - run_transaction_test: true - run_blob_transaction_test: true - run_opcodes_transaction_test: true diff --git a/.github/assets/kurtosis_op_network_params.yaml b/.github/assets/kurtosis_op_network_params.yaml deleted file mode 100644 index 5dcc418fe08..00000000000 --- a/.github/assets/kurtosis_op_network_params.yaml +++ /dev/null @@ -1,29 +0,0 @@ -ethereum_package: - participants: - - el_type: reth - el_extra_params: - - "--rpc.eth-proof-window=100" - cl_type: teku - network_params: - preset: minimal - genesis_delay: 5 - additional_preloaded_contracts: ' - { - "0x4e59b44847b379578588920cA78FbF26c0B4956C": { - "balance": "0ETH", - "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", - "storage": {}, - "nonce": "1" - } - }' -optimism_package: - chains: - - participants: - - el_type: op-geth - cl_type: op-node - - el_type: op-reth - cl_type: op-node - el_image: "ghcr.io/paradigmxyz/op-reth:kurtosis-ci" - network_params: - holocene_time_offset: 0 - isthmus_time_offset: 0 diff --git a/.github/assets/label_pr.js b/.github/assets/label_pr.js deleted file mode 100644 index 16ace2db032..00000000000 --- a/.github/assets/label_pr.js +++ /dev/null @@ -1,57 +0,0 @@ -// Filter function for labels we do not want on PRs automatically. -function shouldIncludeLabel (label) { - const isStatus = label.startsWith('S-'); - const isTrackingIssue = label === 'C-tracking-issue'; - const isPreventStale = label === 'M-prevent-stale'; - const isDifficulty = label.startsWith('D-'); - - return !isStatus && !isTrackingIssue && !isPreventStale && !isDifficulty; -} - -// Get the issue number from an issue link in the forms ` ` or ` #`. -function getIssueLink (repoUrl, body) { - const urlPattern = new RegExp(`(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) ${repoUrl}/issues/(?\\d+)`, 'i') - const issuePattern = new RegExp(`(close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved) \#(?\\d+)`, 'i') - - const urlRe = body.match(urlPattern); - const issueRe = body.match(issuePattern); - if (urlRe?.groups?.issue_number) { - return urlRe.groups.issue_number - } else { - return issueRe?.groups?.issue_number - } -} - -module.exports = async ({ github, context }) => { - try { - const prNumber = context.payload.pull_request.number; - const prBody = context.payload.pull_request.body; - const repo = context.repo; - - const repoUrl = context.payload.repository.html_url; - const issueNumber = getIssueLink(repoUrl, prBody); - if (!issueNumber) { - console.log('No issue reference found in PR description.'); - return; - } - - const issue = await github.rest.issues.get({ - ...repo, - issue_number: issueNumber, - }); - - const issueLabels = issue.data.labels - .map(label => label.name) - .filter(shouldIncludeLabel); - if (issueLabels.length > 0) { - await github.rest.issues.addLabels({ - ...repo, - issue_number: prNumber, - labels: issueLabels, - }); - } - } catch (err) { - console.error('Failed to label PR'); - console.error(err); - } -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 8c139c7bec2..00000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" diff --git a/.github/scripts/codspeed-build.sh b/.github/scripts/codspeed-build.sh deleted file mode 100755 index 9976a3314c9..00000000000 --- a/.github/scripts/codspeed-build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -eo pipefail - -# TODO: Benchmarks run WAY too slow due to excessive amount of iterations. - -cmd=(cargo codspeed build --profile profiling) -crates=( - -p reth-primitives - -p reth-trie - -p reth-trie-common - -p reth-trie-sparse -) - -"${cmd[@]}" --features test-utils "${crates[@]}" diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml deleted file mode 100644 index 43c43b503b1..00000000000 --- a/.github/workflows/bench.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Runs benchmarks. - -on: - pull_request: - # TODO: Disabled temporarily for https://github.com/CodSpeedHQ/runner/issues/55 - # merge_group: - push: - branches: [main] - -env: - CARGO_TERM_COLOR: always - BASELINE: base - SEED: reth - -name: bench -jobs: - codspeed: - runs-on: - group: Reth - steps: - - uses: actions/checkout@v5 - with: - submodules: true - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Install cargo-codspeed - uses: taiki-e/install-action@v2 - with: - tool: cargo-codspeed - - name: Build the benchmark target(s) - run: ./.github/scripts/codspeed-build.sh - - name: Run the benchmarks - uses: CodSpeedHQ/action@v3 - with: - run: cargo codspeed run --workspace - token: ${{ secrets.CODSPEED_TOKEN }} diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml deleted file mode 100644 index 712f28fd4b6..00000000000 --- a/.github/workflows/book.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Documentation and mdbook related jobs. - -name: book - -on: - push: - branches: [main] - pull_request: - branches: [main] - types: [opened, reopened, synchronize, closed] - merge_group: - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - name: Checkout - uses: actions/checkout@v5 - - - name: Install bun - uses: oven-sh/setup-bun@v2 - - - name: Install Playwright browsers - # Required for rehype-mermaid to render Mermaid diagrams during build - run: | - cd docs/vocs/ - bun i - npx playwright install --with-deps chromium - - - name: Install Rust nightly - uses: dtolnay/rust-toolchain@nightly - - - name: Build docs - run: cd docs/vocs && bash scripts/build-cargo-docs.sh - - - name: Build Vocs - run: | - cd docs/vocs/ && bun run build - echo "Vocs Build Complete" - - - name: Setup Pages - uses: actions/configure-pages@v5 - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: "./docs/vocs/docs/dist" - - deploy: - # Only deploy if a push to main - if: github.ref_name == 'main' && github.event_name == 'push' - runs-on: ubuntu-latest - needs: [build] - - # Grant GITHUB_TOKEN the permissions required to make a Pages deployment - permissions: - pages: write - id-token: write - - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - - timeout-minutes: 60 - - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/compact.yml b/.github/workflows/compact.yml deleted file mode 100644 index 8a18df872d2..00000000000 --- a/.github/workflows/compact.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Ensures that `Compact` codec changes are backwards compatible. -# -# 1) checkout `main` -# 2) randomly generate and serialize to disk many different type vectors with `Compact` (eg. Header, Transaction, etc) -# 3) checkout `pr` -# 4) deserialize previously generated test vectors - -on: - pull_request: - merge_group: - push: - branches: [main] - -env: - CARGO_TERM_COLOR: always - -name: compact-codec -jobs: - compact-codec: - runs-on: - group: Reth - strategy: - matrix: - bin: - - cargo run --bin reth --features "dev" - - cargo run --bin op-reth --features "dev" --manifest-path crates/optimism/bin/Cargo.toml - steps: - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Checkout base - uses: actions/checkout@v5 - with: - ref: ${{ github.base_ref || 'main' }} - # On `main` branch, generates test vectors and serializes them to disk using `Compact`. - - name: Generate compact vectors - run: | - ${{ matrix.bin }} -- test-vectors compact --write - - name: Checkout PR - uses: actions/checkout@v5 - with: - clean: false - # On incoming merge try to read and decode previously generated vectors with `Compact` - - name: Read vectors - run: ${{ matrix.bin }} -- test-vectors compact --read diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml deleted file mode 100644 index 49c13d38b8d..00000000000 --- a/.github/workflows/dependencies.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Runs `cargo update` periodically. - -name: Update Dependencies - -on: - schedule: - # Run weekly - - cron: "0 0 * * SUN" - workflow_dispatch: - # Needed so we can run it manually - -permissions: - contents: write - pull-requests: write - -jobs: - update: - uses: ithacaxyz/ci/.github/workflows/cargo-update-pr.yml@main - secrets: - token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/docker-git.yml b/.github/workflows/docker-git.yml deleted file mode 100644 index 62830608d67..00000000000 --- a/.github/workflows/docker-git.yml +++ /dev/null @@ -1,54 +0,0 @@ -# Publishes the Docker image, only to be used with `workflow_dispatch`. The -# images from this workflow will be tagged with the git sha of the branch used -# and will NOT tag it as `latest`. - -name: docker-git - -on: - workflow_dispatch: {} - -env: - REPO_NAME: ${{ github.repository_owner }}/reth - IMAGE_NAME: ${{ github.repository_owner }}/reth - OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth - CARGO_TERM_COLOR: always - DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth - OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth - DOCKER_USERNAME: ${{ github.actor }} - GIT_SHA: ${{ github.sha }} - -jobs: - build: - name: build and push - runs-on: ubuntu-24.04 - permissions: - packages: write - contents: read - strategy: - fail-fast: false - matrix: - build: - - name: 'Build and push the git-sha-tagged reth image' - command: 'make PROFILE=maxperf GIT_SHA=$GIT_SHA docker-build-push-git-sha' - - name: 'Build and push the git-sha-tagged op-reth image' - command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME GIT_SHA=$GIT_SHA PROFILE=maxperf op-docker-build-push-git-sha' - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Install cross main - id: cross_main - run: | - cargo install cross --git https://github.com/cross-rs/cross - - name: Log in to Docker - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin - - name: Set up Docker builder - run: | - docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 - docker buildx create --use --name cross-builder - - name: Build and push ${{ matrix.build.name }} - run: ${{ matrix.build.command }} diff --git a/.github/workflows/docker-nightly.yml b/.github/workflows/docker-nightly.yml deleted file mode 100644 index 213b2314060..00000000000 --- a/.github/workflows/docker-nightly.yml +++ /dev/null @@ -1,61 +0,0 @@ -# Publishes the nightly Docker image. - -name: docker-nightly - -on: - workflow_dispatch: - schedule: - - cron: "0 1 * * *" -env: - REPO_NAME: ${{ github.repository_owner }}/reth - IMAGE_NAME: ${{ github.repository_owner }}/reth - OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth - CARGO_TERM_COLOR: always - DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth - OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth - DOCKER_USERNAME: ${{ github.actor }} - -jobs: - build: - name: build and push - runs-on: ubuntu-24.04 - permissions: - packages: write - contents: read - strategy: - fail-fast: false - matrix: - build: - - name: 'Build and push the nightly reth image' - command: 'make PROFILE=maxperf docker-build-push-nightly' - - name: 'Build and push the nightly profiling reth image' - command: 'make PROFILE=profiling docker-build-push-nightly-profiling' - - name: 'Build and push the nightly op-reth image' - command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push-nightly' - - name: 'Build and push the nightly profiling op-reth image' - command: 'make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=profiling op-docker-build-push-nightly-profiling' - steps: - - uses: actions/checkout@v5 - - name: Remove bloatware - uses: laverdet/remove-bloatware@v1.0.0 - with: - docker: true - lang: rust - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Install cross main - id: cross_main - run: | - cargo install cross --git https://github.com/cross-rs/cross - - name: Log in to Docker - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin - - name: Set up Docker builder - run: | - docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 - docker buildx create --use --name cross-builder - - name: Build and push ${{ matrix.build.name }} - run: ${{ matrix.build.command }} \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml deleted file mode 100644 index 0768ea8e79a..00000000000 --- a/.github/workflows/docker.yml +++ /dev/null @@ -1,89 +0,0 @@ -# Publishes the Docker image. - -name: docker - -on: - push: - tags: - - v* - -env: - IMAGE_NAME: ${{ github.repository_owner }}/reth - OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth - CARGO_TERM_COLOR: always - DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth - OP_DOCKER_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/op-reth - DOCKER_USERNAME: ${{ github.actor }} - -jobs: - build-rc: - if: contains(github.ref, '-rc') - name: build and push as release candidate - runs-on: ubuntu-24.04 - permissions: - packages: write - contents: read - strategy: - fail-fast: false - matrix: - build: - - name: "Build and push reth image" - command: "make IMAGE_NAME=$IMAGE_NAME DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME PROFILE=maxperf docker-build-push" - - name: "Build and push op-reth image" - command: "make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push" - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Install cross main - id: cross_main - run: | - cargo install cross --git https://github.com/cross-rs/cross - - name: Log in to Docker - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin - - name: Set up Docker builder - run: | - docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 - docker buildx create --use --name cross-builder - - name: Build and push ${{ matrix.build.name }} - run: ${{ matrix.build.command }} - - build: - if: ${{ !contains(github.ref, '-rc') }} - name: build and push as latest - runs-on: ubuntu-24.04 - permissions: - packages: write - contents: read - strategy: - fail-fast: false - matrix: - build: - - name: "Build and push reth image" - command: "make IMAGE_NAME=$IMAGE_NAME DOCKER_IMAGE_NAME=$DOCKER_IMAGE_NAME PROFILE=maxperf docker-build-push-latest" - - name: "Build and push op-reth image" - command: "make IMAGE_NAME=$OP_IMAGE_NAME DOCKER_IMAGE_NAME=$OP_DOCKER_IMAGE_NAME PROFILE=maxperf op-docker-build-push-latest" - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Install cross main - id: cross_main - run: | - cargo install cross --git https://github.com/cross-rs/cross - - name: Log in to Docker - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin - - name: Set up Docker builder - run: | - docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64 - docker buildx create --use --name cross-builder - - name: Build and push ${{ matrix.build.name }} - run: ${{ matrix.build.command }} diff --git a/.github/workflows/docker_build.yml b/.github/workflows/docker_build.yml new file mode 100644 index 00000000000..020631ab1d6 --- /dev/null +++ b/.github/workflows/docker_build.yml @@ -0,0 +1,88 @@ +name: Docker Build Release + +on: + push: + branches: + - main # Trigger the workflow on pushes to the main branch + tags: + - "**" # Trigger the workflow on tags including hierarchical tags like v1.0/beta + pull_request: + types: [opened, synchronize] # Trigger the workflow when a PR is opened or updated + +jobs: + extract-version: + name: Extract version + runs-on: ubuntu-latest + outputs: + VERSION: ${{ steps.extract_version.outputs.VERSION }} + steps: + - name: Extract version + id: extract_version + run: | + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + VERSION="${GITHUB_REF#refs/tags/}" + else + VERSION="$(echo ${GITHUB_SHA} | cut -c1-7)" + fi + echo "VERSION=${VERSION}" >> $GITHUB_OUTPUT + + echo "| | |" >> $GITHUB_STEP_SUMMARY + echo "| ------------------- | ---------------------- |" >> $GITHUB_STEP_SUMMARY + echo "| \`GITHUB_REF_TYPE\` | \`${GITHUB_REF_TYPE}\` |" >> $GITHUB_STEP_SUMMARY + echo "| \`GITHUB_REF_NAME\` | \`${GITHUB_REF_NAME}\` |" >> $GITHUB_STEP_SUMMARY + echo "| \`GITHUB_REF\` | \`${GITHUB_REF}\` |" >> $GITHUB_STEP_SUMMARY + echo "| \`GITHUB_SHA\` | \`${GITHUB_SHA}\` |" >> $GITHUB_STEP_SUMMARY + echo "| \`VERSION\` | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY + + build-docker: + name: Build and publish Docker image + needs: extract-version + runs-on: ${{ matrix.configs.runner }} + env: + VERSION: ${{ needs.extract-version.outputs.VERSION }} + permissions: + contents: read + packages: write + strategy: + matrix: + configs: + - target: linux/amd64 + runner: ubuntu-latest + steps: + - name: checkout sources + uses: actions/checkout@v4 + + - name: docker qemu + uses: docker/setup-qemu-action@v3 + + - name: docker buildx + uses: docker/setup-buildx-action@v3 + + - name: docker metadata + uses: docker/metadata-action@v5 + id: meta + with: + images: ghcr.io/${{ github.repository }}/op-reth + labels: org.opencontainers.image.source=${{ github.repositoryUrl }} + tags: | + type=sha + type=schedule,pattern=nightly + + - name: docker login + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: docker build and push op-reth + uses: docker/build-push-action@v5 + with: + cache-from: type=gha + cache-to: type=gha,mode=max + file: DockerfileOp + context: . + labels: ${{ steps.meta.outputs.labels }} + platforms: ${{ matrix.configs.target }} + push: true + tags: ${{ steps.meta.outputs.tags }} diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 16c9fb2f613..00000000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Runs e2e tests using the testsuite framework - -name: e2e - -on: - pull_request: - merge_group: - push: - branches: [main] - -env: - CARGO_TERM_COLOR: always - SEED: rustethereumethereumrust - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - test: - name: e2e-testsuite - runs-on: - group: Reth - env: - RUST_BACKTRACE: 1 - timeout-minutes: 90 - steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Run e2e tests - run: | - cargo nextest run \ - --locked --features "asm-keccak" \ - --workspace \ - --exclude 'example-*' \ - --exclude 'exex-subscription' \ - --exclude 'reth-bench' \ - --exclude 'ef-tests' \ - --exclude 'op-reth' \ - --exclude 'reth' \ - -E 'binary(e2e_testsuite)' - diff --git a/.github/workflows/hive.yml b/.github/workflows/hive.yml deleted file mode 100644 index e6d604564f3..00000000000 --- a/.github/workflows/hive.yml +++ /dev/null @@ -1,225 +0,0 @@ -# Runs `ethereum/hive` tests. - -name: hive - -on: - workflow_dispatch: - schedule: - - cron: "0 */6 * * *" - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - prepare-reth: - uses: ./.github/workflows/prepare-reth.yml - with: - image_tag: ghcr.io/paradigmxyz/reth:latest - binary_name: reth - - prepare-hive: - if: github.repository == 'paradigmxyz/reth' - timeout-minutes: 45 - runs-on: - group: Reth - steps: - - uses: actions/checkout@v5 - - name: Checkout hive tests - uses: actions/checkout@v5 - with: - repository: ethereum/hive - path: hivetests - - - uses: actions/setup-go@v5 - with: - go-version: "^1.13.1" - - run: go version - - - name: Build hive assets - run: .github/assets/hive/build_simulators.sh - - - name: Upload hive assets - uses: actions/upload-artifact@v4 - with: - name: hive_assets - path: ./hive_assets - test: - timeout-minutes: 60 - strategy: - fail-fast: false - matrix: - # ethereum/rpc to be deprecated: - # https://github.com/ethereum/hive/pull/1117 - scenario: - - sim: smoke/genesis - - sim: smoke/network - - sim: ethereum/sync - - sim: devp2p - limit: discv4 - # started failing after https://github.com/ethereum/go-ethereum/pull/31843, no - # action on our side, remove from here when we get unxpected passes on these tests - # - sim: devp2p - # limit: eth - # include: - # - MaliciousHandshake - # # failures tracked in https://github.com/paradigmxyz/reth/issues/14825 - # - Status - # - GetBlockHeaders - # - ZeroRequestID - # - GetBlockBodies - # - Transaction - # - NewPooledTxs - - sim: devp2p - limit: discv5 - include: - # failures tracked at https://github.com/paradigmxyz/reth/issues/14825 - - PingLargeRequestID - - sim: ethereum/engine - limit: engine-exchange-capabilities - - sim: ethereum/engine - limit: engine-withdrawals - - sim: ethereum/engine - limit: engine-auth - - sim: ethereum/engine - limit: engine-api - - sim: ethereum/engine - limit: cancun - # eth_ rpc methods - - sim: ethereum/rpc-compat - include: - - 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_ - - # consume-engine - - sim: ethereum/eest/consume-engine - limit: .*tests/prague.* - - sim: ethereum/eest/consume-engine - limit: .*tests/cancun.* - - sim: ethereum/eest/consume-engine - limit: .*tests/shanghai.* - - sim: ethereum/eest/consume-engine - limit: .*tests/berlin.* - - sim: ethereum/eest/consume-engine - limit: .*tests/istanbul.* - - sim: ethereum/eest/consume-engine - limit: .*tests/homestead.* - - sim: ethereum/eest/consume-engine - limit: .*tests/frontier.* - - # consume-rlp - - sim: ethereum/eest/consume-rlp - limit: .*tests/prague.* - - sim: ethereum/eest/consume-rlp - limit: .*tests/cancun.* - - sim: ethereum/eest/consume-rlp - limit: .*tests/shanghai.* - - sim: ethereum/eest/consume-rlp - limit: .*tests/berlin.* - - sim: ethereum/eest/consume-rlp - limit: .*tests/istanbul.* - - sim: ethereum/eest/consume-rlp - limit: .*tests/homestead.* - - sim: ethereum/eest/consume-rlp - limit: .*tests/frontier.* - needs: - - prepare-reth - - prepare-hive - name: run ${{ matrix.scenario.sim }}${{ matrix.scenario.limit && format(' - {0}', matrix.scenario.limit) }} - runs-on: - group: Reth - permissions: - issues: write - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Download hive assets - uses: actions/download-artifact@v5 - with: - name: hive_assets - path: /tmp - - - name: Download reth image - uses: actions/download-artifact@v5 - with: - name: artifacts - path: /tmp - - - name: Load Docker images - run: .github/assets/hive/load_images.sh - - - name: Move hive binary - run: | - mv /tmp/hive /usr/local/bin - chmod +x /usr/local/bin/hive - - - name: Checkout hive tests - uses: actions/checkout@v5 - with: - repository: ethereum/hive - ref: master - path: hivetests - - - name: Run simulator - run: | - LIMIT="${{ matrix.scenario.limit }}" - TESTS="${{ join(matrix.scenario.include, '|') }}" - if [ -n "$LIMIT" ] && [ -n "$TESTS" ]; then - FILTER="$LIMIT/$TESTS" - elif [ -n "$LIMIT" ]; then - FILTER="$LIMIT" - elif [ -n "$TESTS" ]; then - FILTER="/$TESTS" - else - FILTER="/" - fi - echo "filter: $FILTER" - .github/assets/hive/run_simulator.sh "${{ matrix.scenario.sim }}" "$FILTER" - - - name: Parse hive output - run: | - find hivetests/workspace/logs -type f -name "*.json" ! -name "hive.json" | xargs -I {} python .github/assets/hive/parse.py {} --exclusion .github/assets/hive/expected_failures.yaml --ignored .github/assets/hive/ignored_tests.yaml - - - name: Print simulator output - if: ${{ failure() }} - run: | - cat hivetests/workspace/logs/*simulator*.log - - - name: Print reth client logs - if: ${{ failure() }} - run: | - cat hivetests/workspace/logs/reth/client-*.log - notify-on-error: - needs: test - if: failure() - runs-on: - group: Reth - steps: - - name: Slack Webhook Action - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: ${{ job.status }} - SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}" - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml deleted file mode 100644 index 90e3287917e..00000000000 --- a/.github/workflows/integration.yml +++ /dev/null @@ -1,82 +0,0 @@ -# Runs integration tests. - -name: integration - -on: - pull_request: - merge_group: - push: - branches: [main] - schedule: - # Run once a day at 3:00 UTC - - cron: "0 3 * * *" - -env: - CARGO_TERM_COLOR: always - SEED: rustethereumethereumrust - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - test: - name: test / ${{ matrix.network }} - if: github.event_name != 'schedule' - runs-on: - group: Reth - env: - RUST_BACKTRACE: 1 - strategy: - matrix: - network: ["ethereum", "optimism"] - timeout-minutes: 60 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - name: Install Geth - run: .github/assets/install_geth.sh - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - if: matrix.network == 'ethereum' - name: Run tests - run: | - cargo nextest run \ - --locked --features "asm-keccak ${{ matrix.network }}" \ - --workspace --exclude ef-tests \ - -E "kind(test) and not binary(e2e_testsuite)" - - if: matrix.network == 'optimism' - name: Run tests - run: | - cargo nextest run \ - --locked -p reth-optimism-node - - integration-success: - name: integration success - runs-on: ubuntu-latest - if: always() && github.event_name != 'schedule' - needs: [test] - timeout-minutes: 30 - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} - - era-files: - name: era1 file integration tests once a day - if: github.event_name == 'schedule' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: run era1 files integration tests - run: cargo nextest run --package reth-era --test it -- --ignored diff --git a/.github/workflows/kurtosis-op.yml b/.github/workflows/kurtosis-op.yml deleted file mode 100644 index 0ccc0f55bd9..00000000000 --- a/.github/workflows/kurtosis-op.yml +++ /dev/null @@ -1,100 +0,0 @@ -# Runs simple OP stack setup in Kurtosis - -name: kurtosis-op - -on: - workflow_dispatch: - schedule: - - cron: "0 */6 * * *" - - push: - tags: - - '*' - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - prepare-reth: - uses: ./.github/workflows/prepare-reth.yml - with: - image_tag: ghcr.io/paradigmxyz/op-reth:kurtosis-ci - binary_name: op-reth - cargo_features: asm-keccak - cargo_package: crates/optimism/bin/Cargo.toml - - test: - timeout-minutes: 60 - strategy: - fail-fast: false - name: run kurtosis - runs-on: - group: Reth - needs: - - prepare-reth - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Download reth image - uses: actions/download-artifact@v5 - with: - name: artifacts - path: /tmp - - - name: Load Docker image - run: | - docker load -i /tmp/reth_image.tar & - wait - docker image ls -a - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Run kurtosis - run: | - echo "deb [trusted=yes] https://apt.fury.io/kurtosis-tech/ /" | sudo tee /etc/apt/sources.list.d/kurtosis.list - sudo apt update - sudo apt install kurtosis-cli - kurtosis engine start - # TODO: unpin optimism-package when https://github.com/ethpandaops/optimism-package/issues/340 is fixed - # kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package --args-file .github/assets/kurtosis_op_network_params.yaml - kurtosis run --enclave op-devnet github.com/ethpandaops/optimism-package@452133367b693e3ba22214a6615c86c60a1efd5e --args-file .github/assets/kurtosis_op_network_params.yaml - ENCLAVE_ID=$(curl http://127.0.0.1:9779/api/enclaves | jq --raw-output 'keys[0]') - GETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-1-op-geth-op-node-op-kurtosis".public_ports.rpc.number') - RETH_PORT=$(curl "http://127.0.0.1:9779/api/enclaves/$ENCLAVE_ID/services" | jq '."op-el-2151908-2-op-reth-op-node-op-kurtosis".public_ports.rpc.number') - echo "GETH_RPC=http://127.0.0.1:$GETH_PORT" >> $GITHUB_ENV - echo "RETH_RPC=http://127.0.0.1:$RETH_PORT" >> $GITHUB_ENV - - - name: Assert that clients advance - run: | - for i in {1..100}; do - sleep 5 - BLOCK_GETH=$(cast bn --rpc-url $GETH_RPC) - BLOCK_RETH=$(cast bn --rpc-url $RETH_RPC) - - if [ $BLOCK_GETH -ge 100 ] && [ $BLOCK_RETH -ge 100 ] ; then exit 0; fi - echo "Waiting for clients to advance..., Reth: $BLOCK_RETH Geth: $BLOCK_GETH" - done - kurtosis service logs -a op-devnet op-el-2151908-2-op-reth-op-node-op-kurtosis - kurtosis service logs -a op-devnet op-cl-2151908-2-op-node-op-reth-op-kurtosis - exit 1 - - - notify-on-error: - needs: test - if: failure() - runs-on: - group: Reth - steps: - - name: Slack Webhook Action - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: ${{ job.status }} - SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}" - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/kurtosis.yml b/.github/workflows/kurtosis.yml deleted file mode 100644 index f78fc81235a..00000000000 --- a/.github/workflows/kurtosis.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Runs `assertoor` tests on a `kurtosis` testnet. - -name: kurtosis - -on: - workflow_dispatch: - schedule: - - cron: "0 */6 * * *" - - push: - tags: - - '*' - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - prepare-reth: - uses: ./.github/workflows/prepare-reth.yml - with: - image_tag: ghcr.io/paradigmxyz/reth:kurtosis-ci - binary_name: reth - - test: - timeout-minutes: 60 - strategy: - fail-fast: false - name: run kurtosis - runs-on: - group: Reth - needs: - - prepare-reth - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Download reth image - uses: actions/download-artifact@v5 - with: - name: artifacts - path: /tmp - - - name: Load Docker image - run: | - docker load -i /tmp/reth_image.tar & - wait - docker image ls -a - - - name: Run kurtosis - uses: ethpandaops/kurtosis-assertoor-github-action@v1 - with: - ethereum_package_args: '.github/assets/kurtosis_network_params.yaml' - - notify-on-error: - needs: test - if: failure() - runs-on: - group: Reth - steps: - - name: Slack Webhook Action - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: ${{ job.status }} - SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}" - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml deleted file mode 100644 index 686ffc172c1..00000000000 --- a/.github/workflows/label-pr.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Label PRs - -on: - pull_request: - types: [opened] - -jobs: - label_prs: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Label PRs - uses: actions/github-script@v7 - with: - script: | - const label_pr = require('./.github/assets/label_pr.js') - await label_pr({github, context}) diff --git a/.github/workflows/lint-actions.yml b/.github/workflows/lint-actions.yml deleted file mode 100644 index f408c4f50a5..00000000000 --- a/.github/workflows/lint-actions.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Lint GitHub Actions workflows -on: - pull_request: - paths: - - '.github/**' - merge_group: - push: - paths: - - '.github/**' - -jobs: - actionlint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Download actionlint - id: get_actionlint - run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash) - shell: bash - - name: Check workflow files - run: SHELLCHECK_OPTS="-S error" ${{ steps.get_actionlint.outputs.executable }} -color - shell: bash diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index a01ae5e81b5..00000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,290 +0,0 @@ -name: lint - -on: - pull_request: - merge_group: - push: - branches: [main] - -env: - CARGO_TERM_COLOR: always - -jobs: - clippy-binaries: - name: clippy binaries / ${{ matrix.type }} - runs-on: ubuntu-latest - timeout-minutes: 30 - strategy: - matrix: - include: - - type: ethereum - args: --workspace --lib --examples --tests --benches --locked - features: "ethereum asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs" - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@clippy - with: - components: clippy - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - if: "${{ matrix.type == 'book' }}" - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Run clippy on binaries - run: cargo clippy ${{ matrix.args }} --features "${{ matrix.features }}" - env: - RUSTFLAGS: -D warnings - - clippy: - name: clippy - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@nightly - with: - components: clippy - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked - env: - RUSTFLAGS: -D warnings - - wasm: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - with: - target: wasm32-wasip1 - - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - uses: dcarbone/install-jq-action@v3 - - name: Run Wasm checks - run: | - sudo apt update && sudo apt install gcc-multilib - .github/assets/check_wasm.sh - - riscv: - runs-on: ubuntu-latest - timeout-minutes: 60 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - with: - target: riscv32imac-unknown-none-elf - - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - uses: dcarbone/install-jq-action@v3 - - name: Run RISC-V checks - run: .github/assets/check_rv32imac.sh - - crate-checks: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@cargo-hack - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo hack check --workspace - - msrv: - name: MSRV - runs-on: ubuntu-latest - timeout-minutes: 30 - strategy: - matrix: - include: - - binary: reth - - binary: op-reth - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: "1.88" # MSRV - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo build --bin "${{ matrix.binary }}" --workspace - env: - RUSTFLAGS: -D warnings - - docs: - name: docs - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo docs --document-private-items - env: - # Keep in sync with ./book.yml:jobs.build - # This should only add `-D warnings` - RUSTDOCFLAGS: --cfg docsrs --show-type-layout --generate-link-to-definition --enable-index-page -Zunstable-options -D warnings - - fmt: - name: fmt - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt - - name: Run fmt - run: cargo fmt --all --check - - udeps: - name: udeps - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - uses: taiki-e/install-action@cargo-udeps - - run: cargo udeps --workspace --lib --examples --tests --benches --all-features --locked - - book: - name: book - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@nightly - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo build --bin reth --workspace --features ethereum - env: - RUSTFLAGS: -D warnings - - run: ./docs/cli/update.sh target/debug/reth - - name: Check docs changes - run: git diff --exit-code - - typos: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: crate-ci/typos@v1 - - check-toml: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - name: Run dprint - uses: dprint/check@v2.3 - with: - config-path: dprint.json - - grafana: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - name: Check dashboard JSON with jq - uses: sergeysova/jq-action@v2 - with: - cmd: jq empty etc/grafana/dashboards/overview.json - - no-test-deps: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - name: Ensure no arbitrary or proptest dependency on default build - run: cargo tree --package reth -e=features,no-dev | grep -Eq "arbitrary|proptest" && exit 1 || exit 0 - - # Checks that selected rates can compile with power set of features - features: - name: features - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@clippy - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: cargo install cargo-hack - uses: taiki-e/install-action@cargo-hack - - run: make check-features - env: - RUSTFLAGS: -D warnings - - # Check crates correctly propagate features - feature-propagation: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v5 - - name: fetch deps - run: | - # Eagerly pull dependencies - time cargo metadata --format-version=1 --locked > /dev/null - - name: run zepter - run: | - cargo install zepter -f --locked - zepter --version - time zepter run check - - deny: - uses: ithacaxyz/ci/.github/workflows/deny.yml@main - - lint-success: - name: lint success - runs-on: ubuntu-latest - if: always() - needs: - - clippy-binaries - - clippy - - wasm - - crate-checks - - docs - - fmt - - udeps - - book - - typos - - grafana - - no-test-deps - - features - - feature-propagation - - deny - timeout-minutes: 30 - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/pr-title.yml b/.github/workflows/pr-title.yml deleted file mode 100644 index e30045423bd..00000000000 --- a/.github/workflows/pr-title.yml +++ /dev/null @@ -1,86 +0,0 @@ -name: Pull Request - -on: - pull_request: - types: - - opened - - reopened - - edited - - synchronize - -permissions: - pull-requests: read - contents: read - -jobs: - conventional-title: - name: Validate PR title is Conventional Commit - runs-on: ubuntu-latest - permissions: - pull-requests: write - steps: - - name: Check title - id: lint_pr_title - uses: amannn/action-semantic-pull-request@v6 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - types: | - feat - fix - chore - test - bench - perf - refactor - docs - ci - revert - deps - continue-on-error: true - - name: Add PR Comment for Invalid Title - if: steps.lint_pr_title.outcome == 'failure' - uses: marocchino/sticky-pull-request-comment@v2 - with: - header: pr-title-lint-error - message: | - Your PR title doesn't follow the Conventional Commit guidelines. - - **Example of valid titles:** - - `feat: add new user login` - - `fix: correct button size` - - `docs: update README` - - **Usage:** - - `feat`: Introduces a new feature - - `fix`: Patches a bug - - `chore`: General maintenance tasks or updates - - `test`: Adding new tests or modifying existing tests - - `bench`: Adding new benchmarks or modifying existing benchmarks - - `perf`: Performance improvements - - `refactor`: Changes to improve code structure - - `docs`: Documentation updates - - `ci`: Changes to CI/CD configurations - - `revert`: Reverts a previously merged PR - - `deps`: Updates dependencies - - **Breaking Changes** - - Breaking changes are noted by using an exclamation mark. For example: - - `feat!: changed the API` - - `chore(node)!: Removed unused public function` - - **Help** - - For more information, follow the guidelines here: https://www.conventionalcommits.org/en/v1.0.0/ - - - name: Remove Comment for Valid Title - if: steps.lint_pr_title.outcome == 'success' - uses: marocchino/sticky-pull-request-comment@v2 - with: - header: pr-title-lint-error - delete: true - - - name: Fail workflow if title invalid - if: steps.lint_pr_title.outcome == 'failure' - run: exit 1 diff --git a/.github/workflows/prepare-reth.yml b/.github/workflows/prepare-reth.yml deleted file mode 100644 index 37a9445af72..00000000000 --- a/.github/workflows/prepare-reth.yml +++ /dev/null @@ -1,57 +0,0 @@ -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" - -jobs: - prepare-reth: - if: github.repository == 'paradigmxyz/reth' - timeout-minutes: 45 - runs-on: - group: Reth - steps: - - uses: actions/checkout@v5 - - 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/assets/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@v4 - with: - name: artifacts - path: ./artifacts diff --git a/.github/workflows/release-dist.yml b/.github/workflows/release-dist.yml deleted file mode 100644 index 57a6f311d0b..00000000000 --- a/.github/workflows/release-dist.yml +++ /dev/null @@ -1,20 +0,0 @@ -# This workflow auto-publishes Reth to external package managers such as -# Homebrew when a release is published. - -name: release externally - -on: - release: - types: [published] - -jobs: - release-homebrew: - runs-on: ubuntu-latest - steps: - - name: Update Homebrew formula - uses: dawidd6/action-homebrew-bump-formula@v5 - with: - token: ${{ secrets.HOMEBREW }} - no_fork: true - tap: paradigmxyz/brew - formula: reth diff --git a/.github/workflows/release-reproducible.yml b/.github/workflows/release-reproducible.yml deleted file mode 100644 index e0e7f78aa58..00000000000 --- a/.github/workflows/release-reproducible.yml +++ /dev/null @@ -1,56 +0,0 @@ -# This workflow is for building and pushing reproducible Docker images for releases. - -name: release-reproducible - -on: - push: - tags: - - v* - -env: - DOCKER_REPRODUCIBLE_IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/reth-reproducible - -jobs: - extract-version: - name: extract version - runs-on: ubuntu-latest - steps: - - name: Extract version - run: echo "VERSION=$(echo ${GITHUB_REF#refs/tags/})" >> $GITHUB_OUTPUT - id: extract_version - outputs: - VERSION: ${{ steps.extract_version.outputs.VERSION }} - - build-reproducible: - name: build and push reproducible image - runs-on: ubuntu-latest - needs: extract-version - permissions: - packages: write - contents: read - steps: - - uses: actions/checkout@v5 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push reproducible image - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile.reproducible - push: true - tags: | - ${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:${{ needs.extract-version.outputs.VERSION }} - ${{ env.DOCKER_REPRODUCIBLE_IMAGE_NAME }}:latest - cache-from: type=gha - cache-to: type=gha,mode=max - provenance: false - env: - DOCKER_BUILD_RECORD_UPLOAD: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 7a647c29687..00000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,288 +0,0 @@ -# This workflow is modified from Lighthouse: -# https://github.com/sigp/lighthouse/blob/441fc1691b69f9edc4bbdc6665f3efab16265c9b/.github/workflows/release.yml - -name: release - -on: - push: - tags: - - v* - workflow_dispatch: - inputs: - dry_run: - description: "Enable dry run mode (builds artifacts but skips uploads and release creation)" - type: boolean - default: false - -env: - REPO_NAME: ${{ github.repository_owner }}/reth - IMAGE_NAME: ${{ github.repository_owner }}/reth - OP_IMAGE_NAME: ${{ github.repository_owner }}/op-reth - REPRODUCIBLE_IMAGE_NAME: ${{ github.repository_owner }}/reth-reproducible - CARGO_TERM_COLOR: always - DOCKER_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/reth - DOCKER_OP_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/op-reth - -jobs: - dry-run: - name: check dry run - runs-on: ubuntu-latest - steps: - - run: | - echo "Dry run value: ${{ github.event.inputs.dry_run }}" - echo "Dry run enabled: ${{ github.event.inputs.dry_run == 'true'}}" - echo "Dry run disabled: ${{ github.event.inputs.dry_run != 'true'}}" - - extract-version: - name: extract version - runs-on: ubuntu-latest - steps: - - name: Extract version - run: echo "VERSION=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT - id: extract_version - outputs: - VERSION: ${{ steps.extract_version.outputs.VERSION }} - - check-version: - name: check version - runs-on: ubuntu-latest - needs: extract-version - if: ${{ github.event.inputs.dry_run != 'true' }} - steps: - - uses: actions/checkout@v5 - - uses: dtolnay/rust-toolchain@stable - - name: Verify crate version matches tag - # Check that the Cargo version starts with the tag, - # so that Cargo version 1.4.8 can be matched against both v1.4.8 and v1.4.8-rc.1 - run: | - tag="${{ needs.extract-version.outputs.VERSION }}" - tag=${tag#v} - cargo_ver=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version') - [[ "$tag" == "$cargo_ver"* ]] || { echo "Tag $tag doesn’t match the Cargo version $cargo_ver"; exit 1; } - - build: - name: build release - runs-on: ${{ matrix.configs.os }} - needs: extract-version - continue-on-error: ${{ matrix.configs.allow_fail }} - strategy: - fail-fast: true - matrix: - configs: - - target: x86_64-unknown-linux-gnu - os: ubuntu-24.04 - profile: maxperf - allow_fail: false - - target: aarch64-unknown-linux-gnu - os: ubuntu-24.04 - profile: maxperf - allow_fail: false - - target: x86_64-apple-darwin - os: macos-13 - profile: maxperf - allow_fail: false - - target: aarch64-apple-darwin - os: macos-14 - profile: maxperf - allow_fail: false - - target: x86_64-pc-windows-gnu - os: ubuntu-24.04 - profile: maxperf - allow_fail: false - - target: riscv64gc-unknown-linux-gnu - os: ubuntu-24.04 - profile: maxperf - allow_fail: true - build: - - command: build - binary: reth - - command: op-build - binary: op-reth - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - with: - target: ${{ matrix.configs.target }} - - name: Install cross main - id: cross_main - run: | - cargo install cross --git https://github.com/cross-rs/cross - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - - name: Apple M1 setup - if: matrix.configs.target == 'aarch64-apple-darwin' - run: | - echo "SDKROOT=$(xcrun -sdk macosx --show-sdk-path)" >> $GITHUB_ENV - echo "MACOSX_DEPLOYMENT_TARGET=$(xcrun -sdk macosx --show-sdk-platform-version)" >> $GITHUB_ENV - - - name: Build Reth - run: make PROFILE=${{ matrix.configs.profile }} ${{ matrix.build.command }}-${{ matrix.configs.target }} - - name: Move binary - run: | - mkdir artifacts - [[ "${{ matrix.configs.target }}" == *windows* ]] && ext=".exe" - mv "target/${{ matrix.configs.target }}/${{ matrix.configs.profile }}/${{ matrix.build.binary }}${ext}" ./artifacts - - - name: Configure GPG and create artifacts - env: - GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }} - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - run: | - export GPG_TTY=$(tty) - echo -n "$GPG_SIGNING_KEY" | base64 --decode | gpg --batch --import - cd artifacts - tar -czf ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz ${{ matrix.build.binary }}* - echo "$GPG_PASSPHRASE" | gpg --passphrase-fd 0 --pinentry-mode loopback --batch -ab ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz - mv *tar.gz* .. - shell: bash - - - name: Upload artifact - if: ${{ github.event.inputs.dry_run != 'true' }} - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz - path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz - - - name: Upload signature - if: ${{ github.event.inputs.dry_run != 'true' }} - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc - path: ${{ matrix.build.binary }}-${{ needs.extract-version.outputs.VERSION }}-${{ matrix.configs.target }}.tar.gz.asc - - draft-release: - name: draft release - runs-on: ubuntu-latest - needs: [build, extract-version] - if: ${{ github.event.inputs.dry_run != 'true' }} - env: - VERSION: ${{ needs.extract-version.outputs.VERSION }} - permissions: - # Required to post the release - contents: write - steps: - # This is necessary for generating the changelog. - # It has to come before "Download Artifacts" or else it deletes the artifacts. - - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - name: Download artifacts - uses: actions/download-artifact@v5 - - name: Generate full changelog - id: changelog - run: | - echo "CHANGELOG<> $GITHUB_OUTPUT - echo "$(git log --pretty=format:"- %s" $(git describe --tags --abbrev=0 ${{ env.VERSION }}^)..${{ env.VERSION }})" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - name: Create release draft - env: - GITHUB_USER: ${{ github.repository_owner }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # The formatting here is borrowed from Lighthouse (which is borrowed from OpenEthereum): - # https://github.com/openethereum/openethereum/blob/6c2d392d867b058ff867c4373e40850ca3f96969/.github/workflows/build.yml - run: | - prerelease_flag="" - if [[ "${GITHUB_REF}" == *-rc* ]]; then - prerelease_flag="--prerelease" - fi - - body=$(cat <<- "ENDBODY" - ![image](https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-prod.png) - - ## Testing Checklist (DELETE ME) - - - [ ] Run on testnet for 1-3 days. - - [ ] Resync a mainnet node. - - [ ] Ensure all CI checks pass. - - ## Release Checklist (DELETE ME) - - - [ ] Ensure all crates have had their versions bumped. - - [ ] Write the summary. - - [ ] Fill out the update priority. - - [ ] Ensure all binaries have been added. - - [ ] Prepare release posts (Twitter, ...). - - ## Summary - - Add a summary, including: - - - Critical bug fixes - - New features - - Any breaking changes (and what to expect) - - ## Update Priority - - This table provides priorities for which classes of users should update particular components. - - | User Class | Priority | - |----------------------|-----------------| - | Payload Builders | | - | Non-Payload Builders | | - - *See [Update Priorities](https://reth.rs/installation/priorities) for more information about this table.* - - ## All Changes - - ${{ steps.changelog.outputs.CHANGELOG }} - - ## Binaries - - [See pre-built binaries documentation.](https://reth.rs/installation/binaries) - - The binaries are signed with the PGP key: `50FB 7CC5 5B2E 8AFA 59FE 03B7 AA5E D56A 7FBF 253E` - - ### Reth - - | System | Architecture | Binary | PGP Signature | - |:---:|:---:|:---:|:---| - | | x86_64 | [reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc) | - | | aarch64 | [reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz.asc) | - | | x86_64 | [reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz.asc) | - | | x86_64 | [reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz.asc) | - | | aarch64 | [reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz.asc) | - | | Docker | [${{ env.IMAGE_NAME }}](${{ env.DOCKER_IMAGE_NAME_URL }}) | - | - - ### OP-Reth - - | System | Architecture | Binary | PGP Signature | - |:---:|:---:|:---:|:---| - | | x86_64 | [op-reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-unknown-linux-gnu.tar.gz.asc) | - | | aarch64 | [op-reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-unknown-linux-gnu.tar.gz.asc) | - | | x86_64 | [op-reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-pc-windows-gnu.tar.gz.asc) | - | | x86_64 | [op-reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-x86_64-apple-darwin.tar.gz.asc) | - | | aarch64 | [op-reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz) | [PGP Signature](https://github.com/${{ env.REPO_NAME }}/releases/download/${{ env.VERSION }}/op-reth-${{ env.VERSION }}-aarch64-apple-darwin.tar.gz.asc) | - | | Docker | [${{ env.OP_IMAGE_NAME }}](${{ env.DOCKER_OP_IMAGE_NAME_URL }}) | - | - ENDBODY - ) - assets=() - for asset in ./*reth-*.tar.gz*; do - assets+=("$asset/$asset") - done - tag_name="${{ env.VERSION }}" - echo "$body" | gh release create --draft $prerelease_flag -t "Reth $tag_name" -F "-" "$tag_name" "${assets[@]}" - - dry-run-summary: - name: dry run summary - runs-on: ubuntu-latest - needs: [build, extract-version] - if: ${{ github.event.inputs.dry_run == 'true' }} - env: - VERSION: ${{ needs.extract-version.outputs.VERSION }} - steps: - - name: Summarize dry run - run: | - echo "## 🧪 Release Dry Run Summary" - echo "" - echo "✅ Successfully completed dry run for commit ${{ github.sha }}" - echo "" - echo "### What would happen in a real release:" - echo "- Binary artifacts would be uploaded to GitHub" - echo "- Docker images would be pushed to registry" - echo "- A draft release would be created" - echo "" - echo "### Next Steps" - echo "To perform a real release, push a git tag." diff --git a/.github/workflows/reproducible-build.yml b/.github/workflows/reproducible-build.yml deleted file mode 100644 index b4a93cedaba..00000000000 --- a/.github/workflows/reproducible-build.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: reproducible-build - -on: - workflow_dispatch: {} - schedule: - - cron: "0 1 */2 * *" - -jobs: - build: - name: build reproducible binaries - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - with: - target: x86_64-unknown-linux-gnu - - name: Install cross main - run: | - cargo install cross --git https://github.com/cross-rs/cross - - name: Install cargo-cache - run: | - cargo install cargo-cache - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Build Reth - run: | - make build-reproducible - mv target/x86_64-unknown-linux-gnu/release/reth reth-build-1 - - name: Clean cache - run: make clean && cargo cache -a - - name: Build Reth again - run: | - make build-reproducible - mv target/x86_64-unknown-linux-gnu/release/reth reth-build-2 - - name: Compare binaries - run: cmp reth-build-1 reth-build-2 diff --git a/.github/workflows/stage.yml b/.github/workflows/stage.yml deleted file mode 100644 index 7225d84cffa..00000000000 --- a/.github/workflows/stage.yml +++ /dev/null @@ -1,73 +0,0 @@ -# Runs all `stage run` commands. - -name: stage-test - -on: - pull_request: - merge_group: - push: - branches: [main] - -env: - CARGO_TERM_COLOR: always - FROM_BLOCK: 0 - TO_BLOCK: 50000 - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - stage: - name: stage-run-test - # Only run stage commands test in merge groups - if: github.event_name == 'merge_group' - runs-on: - group: Reth - env: - RUST_LOG: info,sync=error - RUST_BACKTRACE: 1 - timeout-minutes: 60 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Build reth - run: | - cargo install --features asm-keccak,jemalloc --path bin/reth - - name: Run headers stage - run: | - reth stage run headers --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run bodies stage - run: | - reth stage run bodies --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run senders stage - run: | - reth stage run senders --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run execution stage - run: | - reth stage run execution --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run account-hashing stage - run: | - reth stage run account-hashing --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run storage hashing stage - run: | - reth stage run storage-hashing --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run hashing stage - run: | - reth stage run hashing --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run merkle stage - run: | - reth stage run merkle --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run transaction lookup stage - run: | - reth stage run tx-lookup --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run account history stage - run: | - reth stage run account-history --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints - - name: Run storage history stage - run: | - reth stage run storage-history --from ${{ env.FROM_BLOCK }} --to ${{ env.TO_BLOCK }} --commit --checkpoints diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 38cca2fb1a9..00000000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Marks issues as stale. - -name: stale issues - -on: - workflow_dispatch: {} - schedule: - - cron: "30 1 * * *" - -jobs: - close-issues: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: actions/stale@v9 - with: - days-before-stale: 21 - days-before-close: 7 - stale-issue-label: "S-stale" - stale-pr-label: "S-stale" - exempt-issue-labels: "M-prevent-stale" - exempt-pr-labels: "M-prevent-stale" - stale-issue-message: "This issue is stale because it has been open for 21 days with no activity." - close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale." - exempt-all-milestones: true - exempt-all-assignees: true - repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sync-era.yml b/.github/workflows/sync-era.yml deleted file mode 100644 index f2539b2fdc2..00000000000 --- a/.github/workflows/sync-era.yml +++ /dev/null @@ -1,67 +0,0 @@ -# Runs sync tests with ERA stage enabled. - -name: sync-era test - -on: - workflow_dispatch: - schedule: - - cron: "0 */6 * * *" - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - sync: - name: sync (${{ matrix.chain.bin }}) - runs-on: - group: Reth - env: - RUST_LOG: info,sync=error - RUST_BACKTRACE: 1 - timeout-minutes: 60 - strategy: - matrix: - chain: - - build: install - bin: reth - chain: mainnet - tip: "0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4" - block: 100000 - unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a" - - build: install-op - bin: op-reth - chain: base - tip: "0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7" - block: 10000 - unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de" - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Build ${{ matrix.chain.bin }} - run: make ${{ matrix.chain.build }} - - name: Run sync with ERA enabled - run: | - ${{ matrix.chain.bin }} node \ - --chain ${{ matrix.chain.chain }} \ - --debug.tip ${{ matrix.chain.tip }} \ - --debug.max-block ${{ matrix.chain.block }} \ - --debug.terminate \ - --era.enable - - name: Verify the target block hash - run: | - ${{ matrix.chain.bin }} db --chain ${{ matrix.chain.chain }} get static-file headers ${{ matrix.chain.block }} \ - | grep ${{ matrix.chain.tip }} - - name: Run stage unwind for 100 blocks - run: | - ${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }} - - name: Run stage unwind to block hash - run: | - ${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }} diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml deleted file mode 100644 index e57082b83e7..00000000000 --- a/.github/workflows/sync.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Runs sync tests. - -name: sync test - -on: - workflow_dispatch: - schedule: - - cron: "0 */6 * * *" - -env: - CARGO_TERM_COLOR: always - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - sync: - name: sync (${{ matrix.chain.bin }}) - runs-on: - group: Reth - env: - RUST_LOG: info,sync=error - RUST_BACKTRACE: 1 - timeout-minutes: 60 - strategy: - matrix: - chain: - - build: install - bin: reth - chain: mainnet - tip: "0x91c90676cab257a59cd956d7cb0bceb9b1a71d79755c23c7277a0697ccfaf8c4" - block: 100000 - unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a" - - build: install-op - bin: op-reth - chain: base - tip: "0xbb9b85352c7ebca6ba8efc63bd66cecd038c92ec8ebd02e153a3e0b197e672b7" - block: 10000 - unwind-target: "0x118a6e922a8c6cab221fc5adfe5056d2b72d58c6580e9c5629de55299e2cf8de" - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Build ${{ matrix.chain.bin }} - run: make ${{ matrix.chain.build }} - - name: Run sync - run: | - ${{ matrix.chain.bin }} node \ - --chain ${{ matrix.chain.chain }} \ - --debug.tip ${{ matrix.chain.tip }} \ - --debug.max-block ${{ matrix.chain.block }} \ - --debug.terminate - - name: Verify the target block hash - run: | - ${{ matrix.chain.bin }} db --chain ${{ matrix.chain.chain }} get static-file headers ${{ matrix.chain.block }} \ - | grep ${{ matrix.chain.tip }} - - name: Run stage unwind for 100 blocks - run: | - ${{ matrix.chain.bin }} stage unwind num-blocks 100 --chain ${{ matrix.chain.chain }} - - name: Run stage unwind to block hash - run: | - ${{ matrix.chain.bin }} stage unwind to-block ${{ matrix.chain.unwind-target }} --chain ${{ matrix.chain.chain }} diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml deleted file mode 100644 index 39aeebde21d..00000000000 --- a/.github/workflows/unit.yml +++ /dev/null @@ -1,119 +0,0 @@ -# Runs unit tests. - -name: unit - -on: - pull_request: - merge_group: - push: - branches: [main] - -env: - CARGO_TERM_COLOR: always - SEED: rustethereumethereumrust - -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - -jobs: - test: - name: test / ${{ matrix.type }} (${{ matrix.partition }}/${{ matrix.total_partitions }}) - runs-on: - group: Reth - env: - RUST_BACKTRACE: 1 - strategy: - matrix: - include: - - type: ethereum - args: --features "asm-keccak ethereum" --locked - partition: 1 - total_partitions: 2 - - type: ethereum - args: --features "asm-keccak ethereum" --locked - partition: 2 - total_partitions: 2 - - type: optimism - args: --features "asm-keccak" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" - partition: 1 - total_partitions: 2 - - type: optimism - args: --features "asm-keccak" --locked --exclude reth --exclude reth-bench --exclude "example-*" --exclude "reth-ethereum-*" --exclude "*-ethereum" - partition: 2 - total_partitions: 2 - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - uses: taiki-e/install-action@nextest - - if: "${{ matrix.type == 'book' }}" - uses: arduino/setup-protoc@v3 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Run tests - run: | - cargo nextest run \ - ${{ matrix.args }} --workspace \ - --exclude ef-tests --no-tests=warn \ - --partition hash:${{ matrix.partition }}/2 \ - -E "!kind(test) and not binary(e2e_testsuite)" - - state: - name: Ethereum state tests - runs-on: - group: Reth - env: - RUST_LOG: info,sync=error - RUST_BACKTRACE: 1 - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - name: Checkout ethereum/tests - uses: actions/checkout@v5 - with: - repository: ethereum/tests - ref: 81862e4848585a438d64f911a19b3825f0f4cd95 - path: testing/ef-tests/ethereum-tests - submodules: recursive - fetch-depth: 1 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: taiki-e/install-action@nextest - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - run: cargo nextest run --release -p ef-tests --features "asm-keccak ef-tests" - - doc: - name: doc tests - runs-on: - group: Reth - env: - RUST_BACKTRACE: 1 - timeout-minutes: 30 - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: Run doctests - run: cargo test --doc --workspace --all-features - - unit-success: - name: unit success - runs-on: ubuntu-latest - if: always() - needs: [test, state, doc] - timeout-minutes: 30 - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/update-superchain.yml b/.github/workflows/update-superchain.yml deleted file mode 100644 index f682f35a17d..00000000000 --- a/.github/workflows/update-superchain.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Update Superchain Config - -on: - schedule: - - cron: '0 3 * * 0' - workflow_dispatch: - -permissions: - contents: write - -jobs: - update-superchain: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Install required tools - run: | - sudo apt-get update - sudo apt-get install -y jq zstd qpdf yq - - - name: Run fetch_superchain_config.sh - run: | - chmod +x crates/optimism/chainspec/res/fetch_superchain_config.sh - cd crates/optimism/chainspec/res - ./fetch_superchain_config.sh - - - name: Create Pull Request - uses: peter-evans/create-pull-request@v7 - with: - commit-message: "chore: update superchain config" - title: "chore: update superchain config" - body: "This PR updates the superchain configs via scheduled workflow." - branch: "ci/update-superchain-config" - delete-branch: true diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index 81181c2cb1a..00000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Windows build - -name: windows - -on: - push: - branches: [main] - pull_request: - branches: [main] - merge_group: - -jobs: - check-reth: - runs-on: ubuntu-24.04 - timeout-minutes: 60 - - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - with: - target: x86_64-pc-windows-gnu - - uses: taiki-e/install-action@cross - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: mingw-w64 - run: sudo apt-get install -y mingw-w64 - - name: Check Reth - run: cargo check --target x86_64-pc-windows-gnu - - check-op-reth: - runs-on: ubuntu-24.04 - timeout-minutes: 60 - - steps: - - uses: actions/checkout@v5 - - uses: rui314/setup-mold@v1 - - uses: dtolnay/rust-toolchain@stable - with: - target: x86_64-pc-windows-gnu - - uses: taiki-e/install-action@cross - - uses: Swatinem/rust-cache@v2 - with: - cache-on-failure: true - - name: mingw-w64 - run: sudo apt-get install -y mingw-w64 - - name: Check OP-Reth - run: cargo check -p op-reth --target x86_64-pc-windows-gnu diff --git a/.gitignore b/.gitignore index a9b9f4768d5..e0e615bfbe8 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,5 @@ __pycache__/ # direnv .envrc .direnv/ +# Random place to put references +/references/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5d3775cd0f..8c51b03d19a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,7 +3,7 @@ Thanks for your interest in improving Reth! There are multiple opportunities to contribute at any level. It doesn't matter if you are just getting started with Rust -or are the most weathered expert, we can use your help. +or if you are already the most weathered expert, we can use your help. **No contribution is too small and all contributions are valued.** diff --git a/Cargo.lock b/Cargo.lock index 623c187277f..339ec39b488 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -59,7 +50,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -97,9 +88,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alloy-chains" -version = "0.2.6" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4195a29a4b87137b2bb02105e746102873bc03561805cf45c0e510c961f160e6" +checksum = "0bbb778f50ecb0cebfb5c05580948501927508da7bd628833a8c4bd8545e23e2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -112,9 +103,9 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda689f7287f15bd3582daba6be8d1545bad3740fd1fb778f629a1fe866bb43b" +checksum = "b9b151e38e42f1586a01369ec52a6934702731d07e8509a7307331b09f6c46dc" dependencies = [ "alloy-eips", "alloy-primitives", @@ -132,15 +123,16 @@ dependencies = [ "rand 0.8.5", "secp256k1 0.30.0", "serde", + "serde_json", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-consensus-any" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5659581e41e8fe350ecc3593cb5c9dcffddfd550896390f2b78a07af67b0fa" +checksum = "6e2d5e8668ef6215efdb7dcca6f22277b4e483a5650e05f5de22b2350971f4b8" dependencies = [ "alloy-consensus", "alloy-eips", @@ -153,9 +145,9 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "944085cf3ac8f32d96299aa26c03db7c8ca6cdaafdbc467910b889f0328e6b70" +checksum = "630288cf4f3a34a8c6bc75c03dce1dbd47833138f65f37d53a1661eafc96b83f" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -170,14 +162,14 @@ dependencies = [ "futures", "futures-util", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-dyn-abi" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f56873f3cac7a2c63d8e98a4314b8311aa96adb1a0f82ae923eb2119809d2c" +checksum = "3fdff496dd4e98a81f4861e66f7eaf5f2488971848bb42d9c892f871730245c8" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -202,7 +194,7 @@ dependencies = [ "crc", "rand 0.8.5", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] @@ -231,14 +223,14 @@ dependencies = [ "rand 0.8.5", "serde", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-eips" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f35887da30b5fc50267109a3c61cd63e6ca1f45967983641053a40ee83468c1" +checksum = "e5434834adaf64fa20a6fb90877bc1d33214c41b055cc49f82189c98614368cc" dependencies = [ "alloy-eip2124", "alloy-eip2930", @@ -254,34 +246,39 @@ dependencies = [ "ethereum_ssz", "ethereum_ssz_derive", "serde", + "serde_with", "sha2 0.10.9", + "thiserror 2.0.17", ] [[package]] name = "alloy-evm" -version = "0.18.3" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4d88e267e4b599e944e1d32fbbfeaf4b8ea414e54da27306ede37c0798684d" +checksum = "2f1bfade4de9f464719b5aca30cf5bb02b9fda7036f0cf43addc3a0e66a0340c" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-hardforks", + "alloy-op-hardforks", "alloy-primitives", + "alloy-rpc-types-engine", "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", "derive_more", "op-alloy-consensus", + "op-alloy-rpc-types-engine", "op-revm", "revm", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-genesis" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d4009efea6f403b3a80531f9c6f70fc242399498ff71196a1688cc1c901f44" +checksum = "919a8471cfbed7bcd8cf1197a57dda583ce0e10c6385f6ff4e8b41304b223392" dependencies = [ "alloy-eips", "alloy-primitives", @@ -293,9 +290,9 @@ dependencies = [ [[package]] name = "alloy-hardforks" -version = "0.2.13" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165210652f71dfc094b051602bafd691f506c54050a174b1cba18fb5ef706a3" +checksum = "889eb3949b58368a09d4f16931c660275ef5fb08e5fbd4a96573b19c7085c41f" dependencies = [ "alloy-chains", "alloy-eip2124", @@ -307,9 +304,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125a1c373261b252e53e04d6e92c37d881833afc1315fceab53fd46045695640" +checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -319,24 +316,24 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883dee3b4020fcb5667ee627b4f401e899dad82bf37b246620339dd980720ed9" +checksum = "d7c69f6c9c68a1287c9d5ff903d0010726934de0dac10989be37b75a29190d55" dependencies = [ "alloy-primitives", "alloy-sol-types", "http", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] [[package]] name = "alloy-network" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6e5b8ac1654a05c224390008e43634a2bdc74e181e02cf8ed591d8b3d4ad08" +checksum = "8eaf2ae05219e73e0979cb2cf55612aafbab191d130f203079805eaf881cca58" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -355,14 +352,14 @@ dependencies = [ "futures-utils-wasm", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-network-primitives" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7980333dd9391719756ac28bc2afa9baa705fc70ffd11dc86ab078dd64477" +checksum = "e58f4f345cef483eab7374f2b6056973c7419ffe8ad35e994b7a7f5d8e0c7ba4" dependencies = [ "alloy-consensus", "alloy-eips", @@ -373,9 +370,9 @@ dependencies = [ [[package]] name = "alloy-op-evm" -version = "0.18.3" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead219a54943c27b0bb568401cbfa6afe04398b97a76fd33b29745d0c0f35b43" +checksum = "d0b6679dc8854285d6c34ef6a9f9ade06dec1f5db8aab96e941d99b8abcefb72" dependencies = [ "alloy-consensus", "alloy-eips", @@ -390,21 +387,22 @@ dependencies = [ [[package]] name = "alloy-op-hardforks" -version = "0.2.13" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3417f4187eaf7f7fb0d7556f0197bca26f0b23c4bb3aca0c9d566dc1c5d727a2" +checksum = "599c1d7dfbccb66603cb93fde00980d12848d32fe5e814f50562104a92df6487" dependencies = [ "alloy-chains", "alloy-hardforks", + "alloy-primitives", "auto_impl", "serde", ] [[package]] name = "alloy-primitives" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9485c56de23438127a731a6b4c87803d49faf1a7068dcd1d8768aca3a9edb9" +checksum = "355bf68a433e0fd7f7d33d5a9fc2583fde70bf5c530f63b80845f8da5505cf28" dependencies = [ "alloy-rlp", "arbitrary", @@ -412,19 +410,19 @@ dependencies = [ "cfg-if", "const-hex", "derive_more", - "foldhash", - "getrandom 0.3.3", - "hashbrown 0.15.5", - "indexmap 2.10.0", + "foldhash 0.2.0", + "getrandom 0.3.4", + "hashbrown 0.16.0", + "indexmap 2.12.0", "itoa", "k256", "keccak-asm", "paste", "proptest", - "proptest-derive", + "proptest-derive 0.6.0", "rand 0.9.2", "ruint", - "rustc-hash 2.1.1", + "rustc-hash", "serde", "sha3", "tiny-keccak", @@ -432,9 +430,9 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478a42fe167057b7b919cd8b0c2844f0247f667473340dad100eaf969de5754e" +checksum = "de2597751539b1cc8fe4204e5325f9a9ed83fcacfb212018dfcfa7877e76de21" dependencies = [ "alloy-chains", "alloy-consensus", @@ -468,7 +466,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", "url", @@ -477,9 +475,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a99b17987f40a066b29b6b56d75e84cd193b866cac27cae17b59f40338de95" +checksum = "06e45a68423e732900a0c824b8e22237db461b79d2e472dd68b7547c16104427" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -516,14 +514,14 @@ checksum = "64b728d511962dda67c1bc7ea7c03736ec275ed2cf4c35d9585298ac9ccf3b73" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "alloy-rpc-client" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0c6d723fbdf4a87454e2e3a275e161be27edcfbf46e2e3255dd66c138634b6" +checksum = "edf8eb8be597cfa8c312934d2566ec4516f066d69164f9212d7a148979fdcfd8" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -547,9 +545,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41492dac39365b86a954de86c47ec23dcc7452cdb2fde591caadc194b3e34c6" +checksum = "339af7336571dd39ae3a15bde08ae6a647e62f75350bd415832640268af92c06" dependencies = [ "alloy-primitives", "alloy-rpc-types-engine", @@ -560,9 +558,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-admin" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c0f415ad97cc68d2f49eb08214f45c6827a6932a69773594f4ce178f8a41dc0" +checksum = "19b33cdc0483d236cdfff763dae799ccef9646e94fb549a74f7adac6a7f7bb86" dependencies = [ "alloy-genesis", "alloy-primitives", @@ -572,9 +570,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10493fa300a2757d8134f584800fef545c15905c95122bed1f6dde0b0d9dae27" +checksum = "83d98fb386a462e143f5efa64350860af39950c49e7c0cbdba419c16793116ef" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -584,9 +582,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-any" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f7eb22670a972ad6c222a6c6dac3eef905579acffe9d63ab42be24c7d158535" +checksum = "fbde0801a32d21c5f111f037bee7e22874836fba7add34ed4a6919932dd7cf23" dependencies = [ "alloy-consensus-any", "alloy-rpc-types-eth", @@ -595,38 +593,41 @@ dependencies = [ [[package]] name = "alloy-rpc-types-beacon" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53381ffba0110a8aed4c9f108ef34a382ed21aeefb5f50f91c73451ae68b89aa" +checksum = "55c8d51ebb7c5fa8be8ea739a3933c5bfea08777d2d662b30b2109ac5ca71e6b" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", + "derive_more", "ethereum_ssz", "ethereum_ssz_derive", "serde", + "serde_json", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", "tree_hash", "tree_hash_derive", ] [[package]] name = "alloy-rpc-types-debug" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9b6f0482c82310366ec3dcf4e5212242f256a69fcf1a26e5017e6704091ee95" +checksum = "388cf910e66bd4f309a81ef746dcf8f9bca2226e3577890a8d56c5839225cf46" dependencies = [ "alloy-primitives", "derive_more", "serde", + "serde_with", ] [[package]] name = "alloy-rpc-types-engine" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24c171377c0684e3860385f6d93fbfcc8ecc74f6cce8304c822bf1a50bacce0" +checksum = "605ec375d91073851f566a3082548af69a28dca831b27a8be7c1b4c49f5c6ca2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -645,9 +646,9 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b777b98526bbe5b7892ca22a7fd5f18ed624ff664a79f40d0f9f2bf94ba79a84" +checksum = "361cd87ead4ba7659bda8127902eda92d17fa7ceb18aba1676f7be10f7222487" dependencies = [ "alloy-consensus", "alloy-consensus-any", @@ -662,14 +663,14 @@ dependencies = [ "serde", "serde_json", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-rpc-types-mev" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15e8ccb6c16e196fcc968e16a71cd8ce4160f3ec5871d2ea196b75bf569ac02" +checksum = "1397926d8d06a2531578bafc3e0ec78f97a02f0e6d1631c67d80d22af6a3af02" dependencies = [ "alloy-consensus", "alloy-eips", @@ -682,23 +683,23 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6a854af3fe8fce1cfe319fcf84ee8ba8cda352b14d3dd4221405b5fc6cce9e1" +checksum = "de4e95fb0572b97b17751d0fdf5cdc42b0050f9dd9459eddd1bf2e2fbfed0a33" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-serde", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-rpc-types-txpool" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cc803e9b8d16154c856a738c376e002abe4b388e5fef91c8aebc8373e99fd45" +checksum = "cddde1bbd4feeb0d363ae7882af1e2e7955ef77c17f933f31402aad9343b57c5" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -708,9 +709,9 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8d2c52adebf3e6494976c8542fbdf12f10123b26e11ad56f77274c16a2a039" +checksum = "64600fc6c312b7e0ba76f73a381059af044f4f21f43e07f51f1fa76c868fe302" dependencies = [ "alloy-primitives", "arbitrary", @@ -720,9 +721,9 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c0494d1e0f802716480aabbe25549c7f6bc2a25ff33b08fd332bbb4b7d06894" +checksum = "5772858492b26f780468ae693405f895d6a27dea6e3eab2c36b6217de47c2647" dependencies = [ "alloy-primitives", "async-trait", @@ -730,14 +731,14 @@ dependencies = [ "either", "elliptic-curve", "k256", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "alloy-signer-local" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59c2435eb8979a020763ced3fb478932071c56e5f75ea86db41f320915d325ba" +checksum = "f4195b803d0a992d8dbaab2ca1986fc86533d4bc80967c0cce7668b26ad99ef9" dependencies = [ "alloy-consensus", "alloy-network", @@ -748,46 +749,47 @@ dependencies = [ "coins-bip39", "k256", "rand 0.8.5", - "thiserror 2.0.15", + "thiserror 2.0.17", + "zeroize", ] [[package]] name = "alloy-sol-macro" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d20d867dcf42019d4779519a1ceb55eba8d7f3d0e4f0a89bcba82b8f9eb01e48" +checksum = "f3ce480400051b5217f19d6e9a82d9010cdde20f1ae9c00d53591e4a1afbb312" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "alloy-sol-macro-expander" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74e91b0b553c115d14bd0ed41898309356dc85d0e3d4b9014c4e7715e48c8ad" +checksum = "6d792e205ed3b72f795a8044c52877d2e6b6e9b1d13f431478121d8d4eaa9028" dependencies = [ "alloy-sol-macro-input", "const-hex", "heck", - "indexmap 2.10.0", + "indexmap 2.12.0", "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "syn-solidity", "tiny-keccak", ] [[package]] name = "alloy-sol-macro-input" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84194d31220803f5f62d0a00f583fd3a062b36382e2bea446f1af96727754565" +checksum = "0bd1247a8f90b465ef3f1207627547ec16940c35597875cdc09c49d58b19693c" dependencies = [ "const-hex", "dunce", @@ -795,15 +797,15 @@ dependencies = [ "macro-string", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "syn-solidity", ] [[package]] name = "alloy-sol-type-parser" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe8c27b3cf6b2bb8361904732f955bc7c05e00be5f469cec7e2280b6167f3ff0" +checksum = "954d1b2533b9b2c7959652df3076954ecb1122a28cc740aa84e7b0a49f6ac0a9" dependencies = [ "serde", "winnow", @@ -811,9 +813,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5383d34ea00079e6dd89c652bcbdb764db160cef84e6250926961a0b2295d04" +checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -823,9 +825,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0107675e10c7f248bf7273c1e7fdb02409a717269cc744012e6f3c39959bfb" +checksum = "025a940182bddaeb594c26fe3728525ae262d0806fe6a4befdf5d7bc13d54bce" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -837,7 +839,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tower", "tracing", @@ -847,9 +849,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e3736701b5433afd06eecff08f0688a71a10e0e1352e0bbf0bed72f0dd4e35" +checksum = "e3b5064d1e1e1aabc918b5954e7fb8154c39e77ec6903a581b973198b26628fa" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -862,9 +864,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c79064b5a08259581cb5614580010007c2df6deab1e8f3e8c7af8d7e9227008f" +checksum = "d47962f3f1d9276646485458dc842b4e35675f42111c9d814ae4711c664c8300" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -882,9 +884,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77fd607158cb9bc54cbcfcaab4c5f36c5b26994c7dc58b6f095ce27a54f270f3" +checksum = "9476a36a34e2fb51b6746d009c53d309a186a825aa95435407f0e07149f4ad2d" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -900,9 +902,9 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bada1fc392a33665de0dc50d401a3701b62583c655e3522a323490a5da016962" +checksum = "e3412d52bb97c6c6cc27ccc28d4e6e8cf605469101193b50b0bd5813b1f990b5" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -912,7 +914,7 @@ dependencies = [ "derive_more", "nybbles", "proptest", - "proptest-derive", + "proptest-derive 0.5.1", "serde", "smallvec", "tracing", @@ -920,23 +922,17 @@ dependencies = [ [[package]] name = "alloy-tx-macros" -version = "1.0.24" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6acb36318dfa50817154064fea7932adf2eec3f51c86680e2b37d7e8906c66bb" +checksum = "f8e52276fdb553d3c11563afad2898f4085165e4093604afe3d78b69afbf408f" dependencies = [ "alloy-primitives", - "darling 0.20.11", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -954,9 +950,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.20" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -969,9 +965,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -1004,9 +1000,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "aquamarine" @@ -1019,7 +1015,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1161,7 +1157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1199,7 +1195,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1288,7 +1284,7 @@ checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1361,18 +1357,15 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.27" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ - "brotli", - "flate2", + "compression-codecs", + "compression-core", "futures-core", - "memchr", "pin-project-lite", "tokio", - "zstd", - "zstd-safe", ] [[package]] @@ -1408,7 +1401,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1419,7 +1412,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1457,7 +1450,7 @@ checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1474,29 +1467,14 @@ checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" [[package]] name = "backon" -version = "1.5.2" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592277618714fbcecda9a02ba7a8781f319d26532a88553bbacc77ba5d2b3a8d" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" dependencies = [ "fastrand 2.3.0", "tokio", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base-x" version = "0.2.11" @@ -1509,6 +1487,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + [[package]] name = "base64" version = "0.13.1" @@ -1576,20 +1564,38 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.70.1" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.10.0", + "cexpr", + "clang-sys", + "itertools 0.13.0", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.108", +] + +[[package]] +name = "bindgen" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "cexpr", "clang-sys", "itertools 0.13.0", "proc-macro2", "quote", "regex", - "rustc-hash 1.1.0", + "rustc-hash", "shlex", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1631,12 +1637,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ "arbitrary", - "serde", + "serde_core", ] [[package]] @@ -1681,9 +1687,9 @@ dependencies = [ [[package]] name = "blst" -version = "0.3.15" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" dependencies = [ "cc", "glob", @@ -1697,13 +1703,13 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c340fe0f0b267787095cbe35240c6786ff19da63ec7b69367ba338eace8169b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "boa_interner", "boa_macros", "boa_string", - "indexmap 2.10.0", + "indexmap 2.12.0", "num-bigint", - "rustc-hash 2.1.1", + "rustc-hash", ] [[package]] @@ -1713,7 +1719,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f620c3f06f51e65c0504ddf04978be1b814ac6586f0b45f6019801ab5efd37f9" dependencies = [ "arrayvec", - "bitflags 2.9.2", + "bitflags 2.10.0", "boa_ast", "boa_gc", "boa_interner", @@ -1727,7 +1733,7 @@ dependencies = [ "fast-float2", "hashbrown 0.15.5", "icu_normalizer 1.5.0", - "indexmap 2.10.0", + "indexmap 2.12.0", "intrusive-collections", "itertools 0.13.0", "num-bigint", @@ -1739,7 +1745,7 @@ dependencies = [ "portable-atomic", "rand 0.8.5", "regress", - "rustc-hash 2.1.1", + "rustc-hash", "ryu-js", "serde", "serde_json", @@ -1747,7 +1753,7 @@ dependencies = [ "static_assertions", "tap", "thin-vec", - "thiserror 2.0.15", + "thiserror 2.0.17", "time", ] @@ -1773,10 +1779,10 @@ dependencies = [ "boa_gc", "boa_macros", "hashbrown 0.15.5", - "indexmap 2.10.0", + "indexmap 2.12.0", "once_cell", "phf", - "rustc-hash 2.1.1", + "rustc-hash", "static_assertions", ] @@ -1788,7 +1794,7 @@ checksum = "9fd3f870829131332587f607a7ff909f1af5fc523fd1b192db55fbbdf52e8d3c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] @@ -1798,7 +1804,7 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cc142dac798cdc6e2dbccfddeb50f36d2523bb977a976e19bdb3ae19b740804" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "boa_ast", "boa_interner", "boa_macros", @@ -1808,7 +1814,7 @@ dependencies = [ "num-bigint", "num-traits", "regress", - "rustc-hash 2.1.1", + "rustc-hash", ] [[package]] @@ -1825,7 +1831,7 @@ checksum = "7debc13fbf7997bf38bf8e9b20f1ad5e2a7d27a900e1f6039fe244ce30f589b5" dependencies = [ "fast-float2", "paste", - "rustc-hash 2.1.1", + "rustc-hash", "sptr", "static_assertions", ] @@ -1872,12 +1878,12 @@ dependencies = [ [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata", "serde", ] @@ -1901,22 +1907,22 @@ checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" [[package]] name = "bytemuck" -version = "1.23.2" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" dependencies = [ "bytemuck_derive", ] [[package]] name = "bytemuck_derive" -version = "1.10.1" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f154e572231cb6ba2bd1176980827e3d5dc04cc183a75dea38109fbdd672d29" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -1936,9 +1942,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.1" +version = "2.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" +checksum = "e00bf4b112b07b505472dbefd19e37e53307e2bfed5a79e0cc161d58ccd0e687" dependencies = [ "arbitrary", "blst", @@ -1952,11 +1958,11 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.11" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1976,7 +1982,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", ] @@ -1989,10 +1995,10 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] @@ -2044,9 +2050,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -2056,17 +2062,16 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -2119,9 +2124,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" dependencies = [ "clap_builder", "clap_derive", @@ -2129,9 +2134,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" dependencies = [ "anstream", "anstyle", @@ -2141,21 +2146,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "cmake" @@ -2300,11 +2305,11 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.4" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" dependencies = [ - "crossterm", + "crossterm 0.29.0", "unicode-segmentation", "unicode-width 0.2.0", ] @@ -2323,6 +2328,26 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "compression-codecs" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + [[package]] name = "concat-kdf" version = "0.1.0" @@ -2355,15 +2380,14 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.14.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" +checksum = "3bb320cac8a0750d7f25280aa97b09c26edfe161164238ecbbb31092b079e735" dependencies = [ "cfg-if", "cpufeatures", - "hex", "proptest", - "serde", + "serde_core", ] [[package]] @@ -2372,11 +2396,17 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -2515,7 +2545,7 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "crossterm_winapi", "mio", "parking_lot", @@ -2525,6 +2555,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "crossterm" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" +dependencies = [ + "bitflags 2.10.0", + "crossterm_winapi", + "document-features", + "parking_lot", + "rustix 1.1.2", + "winapi", +] + [[package]] name = "crossterm_winapi" version = "0.9.1" @@ -2565,21 +2609,21 @@ dependencies = [ [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -2617,7 +2661,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2632,12 +2676,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08440b3dd222c3d0433e63e097463969485f112baff337dfdaca043a0d760570" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core 0.21.2", - "darling_macro 0.21.2", + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -2651,21 +2695,22 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "darling_core" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25b7912bc28a04ab1b7715a68ea03aaa15662b43a1a4b2c480531fd19f8bf7e" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", + "serde", "strsim", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2676,18 +2721,18 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "darling_macro" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce154b9bea7fb0c8e8326e62d00354000c36e79770ff21b8c84e3aa267d9d531" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core 0.21.2", + "darling_core 0.21.3", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2740,7 +2785,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" dependencies = [ "data-encoding", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2772,12 +2817,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -2799,7 +2844,7 @@ checksum = "ef941ded77d15ca19b40374869ac6000af1c9f2a4c0f3d4c70926287e6364a8f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2810,7 +2855,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2831,7 +2876,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2841,7 +2886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2862,7 +2907,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "unicode-xid", ] @@ -2921,7 +2966,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2976,7 +3021,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -2985,6 +3030,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "dunce" version = "1.0.5" @@ -3046,12 +3100,20 @@ dependencies = [ "enum-ordinalize", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", +] + +[[package]] +name = "ef-test-runner" +version = "1.8.2" +dependencies = [ + "clap", + "ef-tests", ] [[package]] name = "ef-tests" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -3078,8 +3140,7 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.15", - "tracing", + "thiserror 2.0.17", "walkdir", ] @@ -3147,7 +3208,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -3167,7 +3228,7 @@ checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -3178,12 +3239,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3221,9 +3282,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca8ba45b63c389c6e115b095ca16381534fdcc03cf58176a3f8554db2dbe19b" +checksum = "0dcddb2554d19cde19b099fadddde576929d7a4d0c1cd3512d1fd95cf174375c" dependencies = [ "alloy-primitives", "ethereum_serde_utils", @@ -3236,14 +3297,14 @@ dependencies = [ [[package]] name = "ethereum_ssz_derive" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd55d08012b4e0dfcc92b8d6081234df65f2986ad34cc76eeed69c5e2ce7506" +checksum = "a657b6b3b7e153637dc6bdc6566ad9279d9ee11a15b12cfb24a2e04360637e9f" dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -3267,7 +3328,7 @@ dependencies = [ "reth-ethereum", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] @@ -3291,7 +3352,6 @@ dependencies = [ "alloy-rlp", "alloy-rpc-types", "bytes", - "derive_more", "futures", "reth-chainspec", "reth-discv4", @@ -3311,7 +3371,7 @@ dependencies = [ "secp256k1 0.30.0", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -3357,7 +3417,7 @@ dependencies = [ "reth-payload-builder", "reth-tracing", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", ] @@ -3419,6 +3479,7 @@ dependencies = [ "reth-network-peers", "reth-node-builder", "reth-op", + "reth-optimism-flashblocks", "reth-optimism-forks", "reth-payload-builder", "reth-rpc-api", @@ -3427,7 +3488,7 @@ dependencies = [ "revm-primitives", "serde", "test-fuzz", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] @@ -3479,23 +3540,10 @@ dependencies = [ name = "example-engine-api-access" version = "0.0.0" dependencies = [ - "alloy-rpc-types-engine", - "async-trait", - "clap", - "eyre", - "futures", - "jsonrpsee", "reth-db", - "reth-node-api", "reth-node-builder", "reth-optimism-chainspec", - "reth-optimism-consensus", "reth-optimism-node", - "reth-provider", - "reth-rpc-api", - "reth-tasks", - "reth-tracing", - "serde_json", "tokio", ] @@ -3526,7 +3574,7 @@ dependencies = [ [[package]] name = "example-full-contract-state" -version = "1.6.0" +version = "1.8.2" dependencies = [ "eyre", "reth-ethereum", @@ -3577,6 +3625,13 @@ dependencies = [ "tokio", ] +[[package]] +name = "example-node-builder-api" +version = "0.0.0" +dependencies = [ + "reth-ethereum", +] + [[package]] name = "example-node-custom-rpc" version = "0.0.0" @@ -3658,7 +3713,7 @@ dependencies = [ [[package]] name = "exex-subscription" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "clap", @@ -3753,14 +3808,14 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -3777,9 +3832,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -3797,11 +3852,17 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -3892,7 +3953,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -3943,9 +4004,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "generator" -version = "0.8.5" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" dependencies = [ "cc", "cfg-if", @@ -3957,9 +4018,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "serde", "typenum", @@ -3993,15 +4054,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -4015,19 +4076,13 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "git2" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "libc", "libgit2-sys", "log", @@ -4088,12 +4143,12 @@ dependencies = [ [[package]] name = "gmp-mpfr-sys" -version = "1.6.5" +version = "1.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66d61197a68f6323b9afa616cf83d55d69191e1bf364d4eb7d35ae18defe776" +checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -4119,7 +4174,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -4128,12 +4183,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -4171,7 +4227,16 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +dependencies = [ + "foldhash 0.2.0", "serde", ] @@ -4211,9 +4276,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hex-conservative" @@ -4243,7 +4305,7 @@ dependencies = [ "rand 0.9.2", "ring", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tinyvec", "tokio", "tracing", @@ -4267,7 +4329,7 @@ dependencies = [ "resolv-conf", "serde", "smallvec", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -4370,9 +4432,9 @@ checksum = "91f255a4535024abf7640cb288260811fc14794f62b063652ed349f9a6c2348e" [[package]] name = "humantime" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "humantime-serde" @@ -4386,13 +4448,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -4400,6 +4463,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -4421,14 +4485,14 @@ dependencies = [ "tokio", "tokio-rustls", "tower-service", - "webpki-roots 1.0.2", + "webpki-roots 1.0.3", ] [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64 0.22.1", "bytes", @@ -4442,7 +4506,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -4450,9 +4514,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -4460,7 +4524,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.2", + "windows-core 0.62.2", ] [[package]] @@ -4673,7 +4737,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -4684,9 +4748,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -4730,7 +4794,7 @@ checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -4771,21 +4835,25 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "arbitrary", "equivalent", - "hashbrown 0.15.5", + "hashbrown 0.16.0", "serde", + "serde_core", ] [[package]] name = "indoc" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] [[package]] name = "infer" @@ -4799,7 +4867,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "inotify-sys", "libc", ] @@ -4833,7 +4901,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -4869,17 +4937,6 @@ dependencies = [ "memoffset", ] -[[package]] -name = "io-uring" -version = "0.7.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags 2.9.2", - "cfg-if", - "libc", -] - [[package]] name = "ipconfig" version = "0.3.2" @@ -4910,20 +4967,20 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" @@ -4982,19 +5039,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -5002,9 +5059,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fba77a59c4c644fd48732367624d1bcf6f409f9c9a286fbc71d2f1fc0b2ea16" +checksum = "3f3f48dc3e6b8bd21e15436c1ddd0bc22a6a54e8ec46fedd6adf3425f396ec6a" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -5020,9 +5077,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a320a3f1464e4094f780c4d48413acd786ce5627aaaecfac9e9c7431d13ae1" +checksum = "cf36eb27f8e13fa93dcb50ccb44c417e25b818cfa1a481b5470cd07b19c60b98" dependencies = [ "base64 0.22.1", "futures-channel", @@ -5035,7 +5092,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "soketto", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-rustls", "tokio-util", @@ -5045,9 +5102,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693c93cbb7db25f4108ed121304b671a36002c2db67dff2ee4391a688c738547" +checksum = "316c96719901f05d1137f19ba598b5fe9c9bc39f4335f67f6be8613921946480" dependencies = [ "async-trait", "bytes", @@ -5060,10 +5117,10 @@ dependencies = [ "parking_lot", "pin-project", "rand 0.9.2", - "rustc-hash 2.1.1", + "rustc-hash", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tower", @@ -5073,9 +5130,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-client" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6962d2bd295f75e97dd328891e58fce166894b974c1f7ce2e7597f02eeceb791" +checksum = "790bedefcec85321e007ff3af84b4e417540d5c87b3c9779b9e247d1bcc3dab8" dependencies = [ "base64 0.22.1", "http-body", @@ -5088,7 +5145,7 @@ dependencies = [ "rustls-platform-verifier", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tower", "url", @@ -5096,22 +5153,22 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fa4f5daed39f982a1bb9d15449a28347490ad42b212f8eaa2a2a344a0dce9e9" +checksum = "2da3f8ab5ce1bb124b6d082e62dffe997578ceaf0aeb9f3174a214589dc00f07" dependencies = [ "heck", "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "jsonrpsee-server" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38b0bcf407ac68d241f90e2d46041e6a06988f97fe1721fb80b91c42584fae6" +checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f" dependencies = [ "futures-util", "http", @@ -5126,7 +5183,7 @@ dependencies = [ "serde", "serde_json", "soketto", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -5136,21 +5193,21 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66df7256371c45621b3b7d2fb23aea923d577616b9c0e9c0b950a6ea5c2be0ca" +checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" dependencies = [ "http", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "jsonrpsee-wasm-client" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b67695cbcf4653f39f8f8738925547e0e23fd9fe315bccf951097b9f6a38781" +checksum = "7902885de4779f711a95d82c8da2d7e5f9f3a7c7cfa44d51c067fd1c29d72a3c" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -5160,9 +5217,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.25.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da2694c9ff271a9d3ebfe520f6b36820e85133a51be77a3cb549fd615095261" +checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79" dependencies = [ "http", "jsonrpsee-client-transport", @@ -5249,9 +5306,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libgit2-sys" @@ -5267,12 +5324,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.53.3", + "windows-link 0.2.1", ] [[package]] @@ -5295,29 +5352,29 @@ dependencies = [ "multihash", "quick-protobuf", "sha2 0.10.9", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", "zeroize", ] [[package]] name = "libproc" -version = "0.14.10" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb" +checksum = "a54ad7278b8bc5301d5ffd2a94251c004feb971feba96c971ea4063645990757" dependencies = [ - "bindgen", + "bindgen 0.72.1", "errno", "libc", ] [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "libc", "redox_syscall", ] @@ -5404,9 +5461,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -5420,22 +5477,27 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", "serde", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "loom" @@ -5447,7 +5509,7 @@ dependencies = [ "generator", "scoped-tls", "tracing", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] @@ -5501,9 +5563,9 @@ checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" [[package]] name = "mach2" -version = "0.4.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" dependencies = [ "libc", ] @@ -5516,29 +5578,40 @@ checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", +] + +[[package]] +name = "match-lookup" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1265724d8cb29dbbc2b0f06fffb8bf1a8c0cf73a78eede9ba73a4a66c52a981e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memmap2" -version = "0.9.7" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" +checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" dependencies = [ "libc", ] @@ -5571,7 +5644,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5581,7 +5654,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" dependencies = [ "base64 0.22.1", - "indexmap 2.10.0", + "indexmap 2.12.0", "metrics", "metrics-util", "quanta", @@ -5590,18 +5663,18 @@ dependencies = [ [[package]] name = "metrics-process" -version = "2.4.0" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a82c8add4382f29a122fa64fff1891453ed0f6b2867d971e7d60cb8dfa322ff" +checksum = "f615e08e049bd14a44c4425415782efb9bcd479fc1e19ddeb971509074c060d0" dependencies = [ "libc", "libproc", "mach2", "metrics", "once_cell", - "procfs", + "procfs 0.18.0", "rlimit", - "windows 0.58.0", + "windows 0.62.2", ] [[package]] @@ -5613,7 +5686,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.15.5", - "indexmap 2.10.0", + "indexmap 2.12.0", "metrics", "ordered-float", "quanta", @@ -5637,7 +5710,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] @@ -5687,18 +5760,19 @@ checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "serde", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "log", "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5724,20 +5798,19 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.10" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "loom", + "equivalent", "parking_lot", "portable-atomic", "rustc_version 0.4.1", "smallvec", "tagptr", - "thiserror 1.0.69", "uuid", ] @@ -5768,11 +5841,12 @@ dependencies = [ [[package]] name = "multibase" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +checksum = "8694bb4835f452b0e3bb06dbebb1d6fc5385b6ca1caf2e55fd165c042390ec77" dependencies = [ "base-x", + "base256emoji", "data-encoding", "data-encoding-macro", ] @@ -5803,7 +5877,7 @@ version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "fsevent-sys", "inotify", "kqueue", @@ -5832,12 +5906,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.61.2", ] [[package]] @@ -5933,9 +6006,9 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" dependencies = [ "num_enum_derive", "rustversion", @@ -5943,14 +6016,14 @@ dependencies = [ [[package]] name = "num_enum_derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -5964,9 +6037,9 @@ dependencies = [ [[package]] name = "nybbles" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63cb50036b1ad148038105af40aaa70ff24d8a14fbc44ae5c914e1348533d12e" +checksum = "2c4b5ecbd0beec843101bffe848217f770e8b8da81d8355b7d6e226f2199b3dc" dependencies = [ "alloy-rlp", "arbitrary", @@ -5977,15 +6050,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -5998,9 +6062,9 @@ dependencies = [ [[package]] name = "once_cell_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" @@ -6010,9 +6074,9 @@ checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "op-alloy-consensus" -version = "0.18.14" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c88d2940558fd69f8f07b3cbd7bb3c02fc7d31159c1a7ba9deede50e7881024" +checksum = "3a501241474c3118833d6195312ae7eb7cc90bbb0d5f524cbb0b06619e49ff67" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6025,7 +6089,7 @@ dependencies = [ "derive_more", "serde", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] @@ -6036,9 +6100,9 @@ checksum = "a79f352fc3893dcd670172e615afef993a41798a1d3fc0db88a3e60ef2e70ecc" [[package]] name = "op-alloy-network" -version = "0.18.14" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7071d7c3457d02aa0d35799cb8fbd93eabd51a21d100dcf411f4fcab6fdd2ea5" +checksum = "f80108e3b36901200a4c5df1db1ee9ef6ce685b59ea79d7be1713c845e3765da" dependencies = [ "alloy-consensus", "alloy-network", @@ -6052,9 +6116,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-jsonrpsee" -version = "0.18.14" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fc8be822ca7d4be006c69779853fa27e747cff4456a1c2ef521a68ac26432" +checksum = "e8eb878fc5ea95adb5abe55fb97475b3eb0dcc77dfcd6f61bd626a68ae0bdba1" dependencies = [ "alloy-primitives", "jsonrpsee", @@ -6062,9 +6126,9 @@ dependencies = [ [[package]] name = "op-alloy-rpc-types" -version = "0.18.14" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f22201e53e8cbb67a053e88b534b4e7f02265c5406994bf35978482a9ad0ae26" +checksum = "753d6f6b03beca1ba9cbd344c05fee075a2ce715ee9d61981c10b9c764a824a2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6077,14 +6141,14 @@ dependencies = [ "op-alloy-consensus", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "op-alloy-rpc-types-engine" -version = "0.18.14" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4f977b51e9e177e69a4d241ab7c4b439df9a3a5a998c000ae01be724de271" +checksum = "14e50c94013a1d036a529df259151991dbbd6cf8dc215e3b68b784f95eec60e6" dependencies = [ "alloy-consensus", "alloy-eips", @@ -6099,12 +6163,12 @@ dependencies = [ "op-alloy-consensus", "serde", "snap", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "op-reth" -version = "1.6.0" +version = "1.8.2" dependencies = [ "clap", "reth-cli-util", @@ -6122,9 +6186,9 @@ dependencies = [ [[package]] name = "op-revm" -version = "9.0.1" +version = "10.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6889cdfed74c6c924a54b2357982fce232e06473c6bb73b9a0c71a9151bfabd" +checksum = "826f43a5b1613c224f561847c152bfbaefcb593a9ae2c612ff4dc4661c6e625f" dependencies = [ "auto_impl", "revm", @@ -6153,7 +6217,7 @@ dependencies = [ "futures-sink", "js-sys", "pin-project-lite", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] @@ -6185,7 +6249,7 @@ dependencies = [ "opentelemetry_sdk", "prost", "reqwest", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] @@ -6221,7 +6285,7 @@ dependencies = [ "percent-encoding", "rand 0.9.2", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] @@ -6240,12 +6304,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p256" version = "0.13.2" @@ -6295,7 +6353,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6306,9 +6364,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -6316,15 +6374,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -6345,28 +6403,27 @@ dependencies = [ [[package]] name = "pem" -version = "3.0.5" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ "base64 0.22.1", - "serde", + "serde_core", ] [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", - "thiserror 2.0.15", "ucd-trie", ] @@ -6411,7 +6468,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6440,7 +6497,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6534,9 +6591,9 @@ checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec 0.11.4", ] @@ -6568,12 +6625,12 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.2.36" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6598,11 +6655,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.7", ] [[package]] @@ -6624,14 +6681,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -6642,40 +6699,60 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "chrono", "flate2", "hex", - "procfs-core", + "procfs-core 0.17.0", "rustix 0.38.44", ] +[[package]] +name = "procfs" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7" +dependencies = [ + "bitflags 2.10.0", + "procfs-core 0.18.0", + "rustix 1.1.2", +] + [[package]] name = "procfs-core" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "chrono", "hex", ] +[[package]] +name = "procfs-core" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405" +dependencies = [ + "bitflags 2.10.0", + "hex", +] + [[package]] name = "proptest" -version = "1.7.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" +checksum = "bee689443a2bd0a16ab0348b52ee43e3b2d1b1f931c8aa5c9f8de4c86fbe8c40" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.2", - "lazy_static", + "bitflags 2.10.0", "num-traits", "rand 0.9.2", "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.5", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -6699,7 +6776,18 @@ checksum = "4ee1c9ac207483d5e7db4940700de86a9aae46ef90c48b57f99fe7edb8345e49" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", +] + +[[package]] +name = "proptest-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "095a99f75c69734802359b682be8daaf8980296731f6470434ea2c652af1dd30" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", ] [[package]] @@ -6722,7 +6810,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -6731,7 +6819,7 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "memchr", "unicase", ] @@ -6768,19 +6856,19 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", - "socket2 0.5.10", - "thiserror 2.0.15", + "socket2 0.6.1", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -6788,20 +6876,20 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", - "rustc-hash 2.1.1", + "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.15", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -6809,23 +6897,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.1", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -6932,7 +7020,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "serde", ] @@ -6969,10 +7057,10 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "cassowary", "compact_str", - "crossterm", + "crossterm 0.28.1", "indoc", "instability", "itertools 0.13.0", @@ -6986,11 +7074,11 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.5.0" +version = "11.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", ] [[package]] @@ -7021,11 +7109,11 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.17" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", ] [[package]] @@ -7047,72 +7135,57 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "regress" @@ -7132,9 +7205,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.23" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", @@ -7170,18 +7243,18 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.2", + "webpki-roots 1.0.3", ] [[package]] name = "resolv-conf" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" [[package]] name = "reth" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-rpc-types", "aquamarine", @@ -7228,7 +7301,7 @@ dependencies = [ [[package]] name = "reth-basic-payload-builder" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7251,7 +7324,7 @@ dependencies = [ [[package]] name = "reth-bench" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-json-rpc", @@ -7282,7 +7355,7 @@ dependencies = [ "reth-tracing", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tower", "tracing", @@ -7290,13 +7363,14 @@ dependencies = [ [[package]] name = "reth-chain-state" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-signer", "alloy-signer-local", + "codspeed-criterion-compat", "derive_more", "metrics", "parking_lot", @@ -7321,7 +7395,7 @@ dependencies = [ [[package]] name = "reth-chainspec" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-chains", "alloy-consensus", @@ -7341,7 +7415,7 @@ dependencies = [ [[package]] name = "reth-cli" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-genesis", "clap", @@ -7354,9 +7428,8 @@ dependencies = [ [[package]] name = "reth-cli-commands" -version = "1.6.0" +version = "1.8.2" dependencies = [ - "ahash", "alloy-chains", "alloy-consensus", "alloy-eips", @@ -7366,11 +7439,12 @@ dependencies = [ "backon", "clap", "comfy-table", - "crossterm", + "crossterm 0.28.1", "eyre", "fdlimit", "futures", "human_bytes", + "humantime", "itertools 0.14.0", "lz4", "proptest", @@ -7430,11 +7504,12 @@ dependencies = [ "tokio-stream", "toml", "tracing", + "zstd", ] [[package]] name = "reth-cli-runner" -version = "1.6.0" +version = "1.8.2" dependencies = [ "reth-tasks", "tokio", @@ -7443,7 +7518,7 @@ dependencies = [ [[package]] name = "reth-cli-util" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7456,14 +7531,14 @@ dependencies = [ "secp256k1 0.30.0", "serde", "snmalloc-rs", - "thiserror 2.0.15", + "thiserror 2.0.17", "tikv-jemallocator", "tracy-client", ] [[package]] name = "reth-codecs" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7487,18 +7562,18 @@ dependencies = [ [[package]] name = "reth-codecs-derive" -version = "1.6.0" +version = "1.8.2" dependencies = [ "convert_case", "proc-macro2", "quote", "similar-asserts", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "reth-config" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "eyre", @@ -7515,19 +7590,19 @@ dependencies = [ [[package]] name = "reth-consensus" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", "auto_impl", "reth-execution-types", "reth-primitives-traits", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-consensus-common" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7541,7 +7616,7 @@ dependencies = [ [[package]] name = "reth-consensus-debug-client" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7549,6 +7624,7 @@ dependencies = [ "alloy-primitives", "alloy-provider", "alloy-rpc-types-engine", + "alloy-transport", "auto_impl", "derive_more", "eyre", @@ -7565,7 +7641,7 @@ dependencies = [ [[package]] name = "reth-db" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7584,21 +7660,22 @@ dependencies = [ "reth-metrics", "reth-nippy-jar", "reth-primitives-traits", + "reth-prune-types", "reth-static-file-types", "reth-storage-errors", "reth-tracing", - "rustc-hash 2.1.1", + "rustc-hash", "serde", "serde_json", "strum 0.27.2", "sysinfo", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-db-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7628,7 +7705,7 @@ dependencies = [ [[package]] name = "reth-db-common" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -7652,13 +7729,13 @@ dependencies = [ "reth-trie-db", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] [[package]] name = "reth-db-models" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7675,7 +7752,7 @@ dependencies = [ [[package]] name = "reth-discv4" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7694,7 +7771,7 @@ dependencies = [ "schnellru", "secp256k1 0.30.0", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -7702,7 +7779,7 @@ dependencies = [ [[package]] name = "reth-discv5" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -7720,14 +7797,14 @@ dependencies = [ "reth-network-peers", "reth-tracing", "secp256k1 0.30.0", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-dns-discovery" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-chains", "alloy-primitives", @@ -7747,7 +7824,7 @@ dependencies = [ "secp256k1 0.30.0", "serde", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -7755,7 +7832,7 @@ dependencies = [ [[package]] name = "reth-downloaders" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7785,7 +7862,7 @@ dependencies = [ "reth-testing-utils", "reth-tracing", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -7794,11 +7871,10 @@ dependencies = [ [[package]] name = "reth-e2e-test-utils" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", - "alloy-genesis", "alloy-network", "alloy-primitives", "alloy-provider", @@ -7818,9 +7894,7 @@ dependencies = [ "reth-db", "reth-db-common", "reth-engine-local", - "reth-ethereum-consensus", "reth-ethereum-primitives", - "reth-evm", "reth-network-api", "reth-network-p2p", "reth-network-peers", @@ -7834,14 +7908,11 @@ dependencies = [ "reth-primitives", "reth-primitives-traits", "reth-provider", - "reth-prune-types", "reth-rpc-api", "reth-rpc-builder", "reth-rpc-eth-api", - "reth-rpc-layer", "reth-rpc-server-types", "reth-stages-types", - "reth-static-file", "reth-tasks", "reth-tokio-util", "reth-tracing", @@ -7856,7 +7927,7 @@ dependencies = [ [[package]] name = "reth-ecies" -version = "1.6.0" +version = "1.8.2" dependencies = [ "aes", "alloy-primitives", @@ -7876,7 +7947,7 @@ dependencies = [ "secp256k1 0.30.0", "sha2 0.10.9", "sha3", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -7886,7 +7957,7 @@ dependencies = [ [[package]] name = "reth-engine-local" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -7909,7 +7980,7 @@ dependencies = [ [[package]] name = "reth-engine-primitives" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7927,13 +7998,13 @@ dependencies = [ "reth-primitives-traits", "reth-trie-common", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", ] [[package]] name = "reth-engine-service" -version = "1.6.0" +version = "1.8.2" dependencies = [ "futures", "pin-project", @@ -7956,14 +8027,14 @@ dependencies = [ "reth-prune", "reth-stages-api", "reth-tasks", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", ] [[package]] name = "reth-engine-tree" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -7978,6 +8049,7 @@ dependencies = [ "eyre", "futures", "metrics", + "metrics-util", "mini-moka", "parking_lot", "proptest", @@ -7997,6 +8069,7 @@ dependencies = [ "reth-ethereum-primitives", "reth-evm", "reth-evm-ethereum", + "reth-execution-types", "reth-exex-types", "reth-metrics", "reth-network-p2p", @@ -8008,7 +8081,6 @@ dependencies = [ "reth-prune", "reth-prune-types", "reth-revm", - "reth-rpc-convert", "reth-stages", "reth-stages-api", "reth-static-file", @@ -8025,14 +8097,15 @@ dependencies = [ "revm-state", "schnellru", "serde_json", - "thiserror 2.0.15", + "smallvec", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-engine-util" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -8059,7 +8132,7 @@ dependencies = [ [[package]] name = "reth-era" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8075,13 +8148,13 @@ dependencies = [ "snap", "tempfile", "test-case", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", ] [[package]] name = "reth-era-downloader" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "bytes", @@ -8098,21 +8171,18 @@ dependencies = [ [[package]] name = "reth-era-utils" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", - "alloy-rlp", "bytes", "eyre", - "futures", "futures-util", "reqwest", "reth-db-api", "reth-db-common", "reth-era", "reth-era-downloader", - "reth-ethereum-primitives", "reth-etl", "reth-fs-util", "reth-primitives-traits", @@ -8127,17 +8197,17 @@ dependencies = [ [[package]] name = "reth-errors" -version = "1.6.0" +version = "1.8.2" dependencies = [ "reth-consensus", "reth-execution-errors", "reth-storage-errors", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-eth-wire" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-chains", "alloy-consensus", @@ -8166,7 +8236,7 @@ dependencies = [ "serde", "snap", "test-fuzz", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -8175,7 +8245,7 @@ dependencies = [ [[package]] name = "reth-eth-wire-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-chains", "alloy-consensus", @@ -8195,12 +8265,12 @@ dependencies = [ "reth-ethereum-primitives", "reth-primitives-traits", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-ethereum" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-rpc-types-engine", "alloy-rpc-types-eth", @@ -8240,7 +8310,7 @@ dependencies = [ [[package]] name = "reth-ethereum-cli" -version = "1.6.0" +version = "1.8.2" dependencies = [ "clap", "eyre", @@ -8254,6 +8324,7 @@ dependencies = [ "reth-node-core", "reth-node-ethereum", "reth-node-metrics", + "reth-rpc-server-types", "reth-tracing", "tempfile", "tracing", @@ -8261,7 +8332,7 @@ dependencies = [ [[package]] name = "reth-ethereum-consensus" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8277,7 +8348,7 @@ dependencies = [ [[package]] name = "reth-ethereum-engine-primitives" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8290,12 +8361,12 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-ethereum-forks" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eip2124", "alloy-hardforks", @@ -8303,19 +8374,21 @@ dependencies = [ "arbitrary", "auto_impl", "once_cell", - "rustc-hash 2.1.1", + "rustc-hash", ] [[package]] name = "reth-ethereum-payload-builder" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", + "alloy-rlp", "alloy-rpc-types-engine", "reth-basic-payload-builder", "reth-chainspec", + "reth-consensus-common", "reth-errors", "reth-ethereum-primitives", "reth-evm", @@ -8334,12 +8407,14 @@ dependencies = [ [[package]] name = "reth-ethereum-primitives" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", "arbitrary", "bincode 1.3.3", "derive_more", @@ -8353,12 +8428,13 @@ dependencies = [ "reth-zstd-compressors", "secp256k1 0.30.0", "serde", + "serde_json", "serde_with", ] [[package]] name = "reth-etl" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "rayon", @@ -8368,7 +8444,7 @@ dependencies = [ [[package]] name = "reth-evm" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8378,7 +8454,6 @@ dependencies = [ "derive_more", "futures-util", "metrics", - "metrics-util", "reth-ethereum-forks", "reth-ethereum-primitives", "reth-execution-errors", @@ -8393,7 +8468,7 @@ dependencies = [ [[package]] name = "reth-evm-ethereum" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8417,19 +8492,19 @@ dependencies = [ [[package]] name = "reth-execution-errors" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-evm", "alloy-primitives", "alloy-rlp", "nybbles", "reth-storage-errors", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-execution-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8449,7 +8524,7 @@ dependencies = [ [[package]] name = "reth-exex" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8485,7 +8560,7 @@ dependencies = [ "rmp-serde", "secp256k1 0.30.0", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -8493,7 +8568,7 @@ dependencies = [ [[package]] name = "reth-exex-test-utils" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "eyre", @@ -8518,13 +8593,13 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", ] [[package]] name = "reth-exex-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -8541,16 +8616,29 @@ dependencies = [ [[package]] name = "reth-fs-util" -version = "1.6.0" +version = "1.8.2" dependencies = [ "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", +] + +[[package]] +name = "reth-gas-station" +version = "0.0.1" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "hex", + "reth-primitives-traits", + "reth-storage-api", + "revm-database", + "thiserror 2.0.17", ] [[package]] name = "reth-invalid-block-hooks" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8560,7 +8648,6 @@ dependencies = [ "futures", "jsonrpsee", "pretty_assertions", - "reth-chainspec", "reth-engine-primitives", "reth-evm", "reth-primitives-traits", @@ -8577,7 +8664,7 @@ dependencies = [ [[package]] name = "reth-ipc" -version = "1.6.0" +version = "1.8.2" dependencies = [ "bytes", "futures", @@ -8589,7 +8676,7 @@ dependencies = [ "reth-tracing", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -8599,34 +8686,33 @@ dependencies = [ [[package]] name = "reth-libmdbx" -version = "1.6.0" +version = "1.8.2" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "byteorder", "codspeed-criterion-compat", "dashmap 6.1.0", "derive_more", - "indexmap 2.10.0", "parking_lot", "rand 0.9.2", "reth-mdbx-sys", "smallvec", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] [[package]] name = "reth-mdbx-sys" -version = "1.6.0" +version = "1.8.2" dependencies = [ - "bindgen", + "bindgen 0.71.1", "cc", ] [[package]] name = "reth-metrics" -version = "1.6.0" +version = "1.8.2" dependencies = [ "futures", "metrics", @@ -8637,28 +8723,28 @@ dependencies = [ [[package]] name = "reth-net-banlist" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", ] [[package]] name = "reth-net-nat" -version = "1.6.0" +version = "1.8.2" dependencies = [ "futures-util", "if-addrs", "reqwest", "reth-tracing", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-network" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8703,13 +8789,12 @@ dependencies = [ "reth-tokio-util", "reth-tracing", "reth-transaction-pool", - "rustc-hash 2.1.1", + "rustc-hash", "schnellru", "secp256k1 0.30.0", "serde", "smallvec", - "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-util", @@ -8719,7 +8804,7 @@ dependencies = [ [[package]] name = "reth-network-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -8736,14 +8821,14 @@ dependencies = [ "reth-network-types", "reth-tokio-util", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", ] [[package]] name = "reth-network-p2p" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8765,7 +8850,7 @@ dependencies = [ [[package]] name = "reth-network-peers" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -8775,14 +8860,14 @@ dependencies = [ "secp256k1 0.30.0", "serde_json", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "url", ] [[package]] name = "reth-network-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eip2124", "humantime-serde", @@ -8795,7 +8880,7 @@ dependencies = [ [[package]] name = "reth-nippy-jar" -version = "1.6.0" +version = "1.8.2" dependencies = [ "anyhow", "bincode 1.3.3", @@ -8806,14 +8891,14 @@ dependencies = [ "reth-fs-util", "serde", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", "zstd", ] [[package]] name = "reth-node-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-rpc-types-engine", "eyre", @@ -8836,7 +8921,7 @@ dependencies = [ [[package]] name = "reth-node-builder" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8907,7 +8992,7 @@ dependencies = [ [[package]] name = "reth-node-core" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -8949,7 +9034,7 @@ dependencies = [ "serde", "shellexpand", "strum 0.27.2", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "toml", "tracing", @@ -8960,7 +9045,7 @@ dependencies = [ [[package]] name = "reth-node-ethereum" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-contract", @@ -9013,7 +9098,7 @@ dependencies = [ [[package]] name = "reth-node-ethstats" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9026,7 +9111,7 @@ dependencies = [ "reth-transaction-pool", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tokio-tungstenite", @@ -9036,7 +9121,7 @@ dependencies = [ [[package]] name = "reth-node-events" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9059,7 +9144,7 @@ dependencies = [ [[package]] name = "reth-node-metrics" -version = "1.6.0" +version = "1.8.2" dependencies = [ "eyre", "http", @@ -9068,7 +9153,7 @@ dependencies = [ "metrics-exporter-prometheus", "metrics-process", "metrics-util", - "procfs", + "procfs 0.17.0", "reqwest", "reth-metrics", "reth-tasks", @@ -9081,7 +9166,7 @@ dependencies = [ [[package]] name = "reth-node-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "reth-chainspec", "reth-db-api", @@ -9092,7 +9177,7 @@ dependencies = [ [[package]] name = "reth-op" -version = "1.6.0" +version = "1.8.2" dependencies = [ "reth-chainspec", "reth-cli-util", @@ -9132,7 +9217,7 @@ dependencies = [ [[package]] name = "reth-optimism-chainspec" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-chains", "alloy-consensus", @@ -9154,12 +9239,12 @@ dependencies = [ "serde", "serde_json", "tar-no-std", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-optimism-cli" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9194,6 +9279,7 @@ dependencies = [ "reth-primitives-traits", "reth-provider", "reth-prune", + "reth-rpc-server-types", "reth-stages", "reth-static-file", "reth-static-file-types", @@ -9207,7 +9293,7 @@ dependencies = [ [[package]] name = "reth-optimism-consensus" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-chains", "alloy-consensus", @@ -9218,7 +9304,6 @@ dependencies = [ "reth-chainspec", "reth-consensus", "reth-consensus-common", - "reth-db-api", "reth-db-common", "reth-execution-types", "reth-optimism-chainspec", @@ -9233,13 +9318,13 @@ dependencies = [ "reth-trie", "reth-trie-common", "revm", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] [[package]] name = "reth-optimism-evm" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9263,12 +9348,47 @@ dependencies = [ "reth-rpc-eth-api", "reth-storage-errors", "revm", - "thiserror 2.0.15", + "thiserror 2.0.17", +] + +[[package]] +name = "reth-optimism-flashblocks" +version = "1.8.2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "alloy-serde", + "brotli", + "eyre", + "futures-util", + "reth-chain-state", + "reth-errors", + "reth-evm", + "reth-execution-types", + "reth-node-api", + "reth-optimism-evm", + "reth-optimism-payload-builder", + "reth-optimism-primitives", + "reth-primitives-traits", + "reth-revm", + "reth-rpc-eth-types", + "reth-storage-api", + "reth-tasks", + "ringbuffer", + "serde", + "serde_json", + "test-case", + "tokio", + "tokio-tungstenite", + "tracing", + "url", ] [[package]] name = "reth-optimism-forks" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-op-hardforks", "alloy-primitives", @@ -9278,7 +9398,7 @@ dependencies = [ [[package]] name = "reth-optimism-node" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -9300,7 +9420,6 @@ dependencies = [ "reth-engine-local", "reth-evm", "reth-network", - "reth-network-api", "reth-node-api", "reth-node-builder", "reth-node-core", @@ -9316,7 +9435,6 @@ dependencies = [ "reth-optimism-txpool", "reth-payload-builder", "reth-payload-util", - "reth-payload-validator", "reth-primitives-traits", "reth-provider", "reth-revm", @@ -9332,13 +9450,13 @@ dependencies = [ "revm", "serde", "serde_json", - "tempfile", "tokio", + "url", ] [[package]] name = "reth-optimism-payload-builder" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9354,6 +9472,7 @@ dependencies = [ "reth-chainspec", "reth-evm", "reth-execution-types", + "reth-gas-station", "reth-optimism-evm", "reth-optimism-forks", "reth-optimism-primitives", @@ -9370,13 +9489,13 @@ dependencies = [ "revm", "serde", "sha2 0.10.9", - "thiserror 2.0.15", + "thiserror 2.0.17", "tracing", ] [[package]] name = "reth-optimism-primitives" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9403,7 +9522,7 @@ dependencies = [ [[package]] name = "reth-optimism-rpc" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9418,6 +9537,7 @@ dependencies = [ "async-trait", "derive_more", "eyre", + "futures", "jsonrpsee", "jsonrpsee-core", "jsonrpsee-types", @@ -9429,6 +9549,7 @@ dependencies = [ "op-alloy-rpc-types-engine", "op-revm", "reqwest", + "reth-chain-state", "reth-chainspec", "reth-evm", "reth-metrics", @@ -9436,6 +9557,7 @@ dependencies = [ "reth-node-builder", "reth-optimism-chainspec", "reth-optimism-evm", + "reth-optimism-flashblocks", "reth-optimism-forks", "reth-optimism-payload-builder", "reth-optimism-primitives", @@ -9443,6 +9565,7 @@ dependencies = [ "reth-primitives-traits", "reth-rpc", "reth-rpc-api", + "reth-rpc-convert", "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", @@ -9452,25 +9575,20 @@ dependencies = [ "reth-transaction-pool", "revm", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", + "tokio-stream", "tower", "tracing", ] [[package]] name = "reth-optimism-storage" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", - "alloy-primitives", - "reth-chainspec", "reth-codecs", - "reth-db-api", - "reth-node-api", "reth-optimism-primitives", - "reth-primitives-traits", - "reth-provider", "reth-prune-types", "reth-stages-types", "reth-storage-api", @@ -9478,7 +9596,7 @@ dependencies = [ [[package]] name = "reth-optimism-txpool" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9508,14 +9626,14 @@ dependencies = [ "reth-storage-api", "reth-transaction-pool", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-payload-builder" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9535,7 +9653,7 @@ dependencies = [ [[package]] name = "reth-payload-builder-primitives" -version = "1.6.0" +version = "1.8.2" dependencies = [ "pin-project", "reth-payload-primitives", @@ -9546,26 +9664,27 @@ dependencies = [ [[package]] name = "reth-payload-primitives" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", "alloy-rpc-types-engine", "assert_matches", "auto_impl", + "either", "op-alloy-rpc-types-engine", "reth-chain-state", "reth-chainspec", "reth-errors", "reth-primitives-traits", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", ] [[package]] name = "reth-payload-util" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9574,7 +9693,7 @@ dependencies = [ [[package]] name = "reth-payload-validator" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-rpc-types-engine", @@ -9583,7 +9702,7 @@ dependencies = [ [[package]] name = "reth-primitives" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9605,7 +9724,7 @@ dependencies = [ [[package]] name = "reth-primitives-traits" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9638,12 +9757,12 @@ dependencies = [ "serde_json", "serde_with", "test-fuzz", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-provider" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9692,7 +9811,7 @@ dependencies = [ [[package]] name = "reth-prune" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -9716,15 +9835,15 @@ dependencies = [ "reth-testing-utils", "reth-tokio-util", "reth-tracing", - "rustc-hash 2.1.1", - "thiserror 2.0.15", + "rustc-hash", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-prune-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "arbitrary", @@ -9737,13 +9856,13 @@ dependencies = [ "serde", "serde_json", "test-fuzz", - "thiserror 2.0.15", + "thiserror 2.0.17", "toml", ] [[package]] name = "reth-ress-protocol" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9769,7 +9888,7 @@ dependencies = [ [[package]] name = "reth-ress-provider" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9795,7 +9914,7 @@ dependencies = [ [[package]] name = "reth-revm" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -9809,7 +9928,7 @@ dependencies = [ [[package]] name = "reth-rpc" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -9819,6 +9938,7 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-rlp", + "alloy-rpc-client", "alloy-rpc-types", "alloy-rpc-types-admin", "alloy-rpc-types-beacon", @@ -9833,6 +9953,7 @@ dependencies = [ "alloy-signer-local", "async-trait", "derive_more", + "dyn-clone", "futures", "http", "http-body", @@ -9847,6 +9968,7 @@ dependencies = [ "reth-chain-state", "reth-chainspec", "reth-consensus", + "reth-consensus-common", "reth-db-api", "reth-engine-primitives", "reth-errors", @@ -9879,7 +10001,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.9", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tower", @@ -9889,7 +10011,7 @@ dependencies = [ [[package]] name = "reth-rpc-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-genesis", @@ -9916,7 +10038,7 @@ dependencies = [ [[package]] name = "reth-rpc-api-testing-util" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -9935,7 +10057,7 @@ dependencies = [ [[package]] name = "reth-rpc-builder" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-network", @@ -9945,6 +10067,7 @@ dependencies = [ "alloy-rpc-types-eth", "alloy-rpc-types-trace", "clap", + "dyn-clone", "http", "jsonrpsee", "metrics", @@ -9953,7 +10076,6 @@ dependencies = [ "reth-chainspec", "reth-consensus", "reth-engine-primitives", - "reth-engine-tree", "reth-ethereum-engine-primitives", "reth-ethereum-primitives", "reth-evm", @@ -9969,7 +10091,6 @@ dependencies = [ "reth-provider", "reth-rpc", "reth-rpc-api", - "reth-rpc-convert", "reth-rpc-engine-api", "reth-rpc-eth-api", "reth-rpc-eth-types", @@ -9981,7 +10102,7 @@ dependencies = [ "reth-transaction-pool", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-util", "tower", @@ -9991,7 +10112,7 @@ dependencies = [ [[package]] name = "reth-rpc-convert" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-json-rpc", @@ -9999,6 +10120,8 @@ dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", "alloy-signer", + "auto_impl", + "dyn-clone", "jsonrpsee-types", "op-alloy-consensus", "op-alloy-network", @@ -10010,12 +10133,13 @@ dependencies = [ "reth-primitives-traits", "reth-storage-api", "revm-context", - "thiserror 2.0.15", + "serde_json", + "thiserror 2.0.17", ] [[package]] name = "reth-rpc-e2e-tests" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-genesis", "alloy-rpc-types-engine", @@ -10035,7 +10159,7 @@ dependencies = [ [[package]] name = "reth-rpc-engine-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10064,14 +10188,14 @@ dependencies = [ "reth-testing-utils", "reth-transaction-pool", "serde", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-rpc-eth-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -10114,15 +10238,17 @@ dependencies = [ [[package]] name = "reth-rpc-eth-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-evm", "alloy-network", "alloy-primitives", + "alloy-rpc-client", "alloy-rpc-types-eth", "alloy-sol-types", + "alloy-transport", "derive_more", "futures", "itertools 0.14.0", @@ -10130,6 +10256,7 @@ dependencies = [ "jsonrpsee-types", "metrics", "rand 0.9.2", + "reqwest", "reth-chain-state", "reth-chainspec", "reth-errors", @@ -10150,7 +10277,7 @@ dependencies = [ "schnellru", "serde", "serde_json", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -10158,7 +10285,7 @@ dependencies = [ [[package]] name = "reth-rpc-layer" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-rpc-types-engine", "http", @@ -10175,7 +10302,7 @@ dependencies = [ [[package]] name = "reth-rpc-server-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10190,7 +10317,7 @@ dependencies = [ [[package]] name = "reth-stages" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10222,7 +10349,6 @@ dependencies = [ "reth-etl", "reth-evm", "reth-evm-ethereum", - "reth-execution-errors", "reth-execution-types", "reth-exex", "reth-fs-util", @@ -10238,18 +10364,17 @@ dependencies = [ "reth-static-file-types", "reth-storage-errors", "reth-testing-utils", - "reth-tracing", "reth-trie", "reth-trie-db", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-stages-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10270,7 +10395,7 @@ dependencies = [ "reth-static-file-types", "reth-testing-utils", "reth-tokio-util", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -10278,7 +10403,7 @@ dependencies = [ [[package]] name = "reth-stages-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "arbitrary", @@ -10295,7 +10420,7 @@ dependencies = [ [[package]] name = "reth-stateless" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10315,19 +10440,18 @@ dependencies = [ "reth-trie-sparse", "serde", "serde_with", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-static-file" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "assert_matches", "parking_lot", "rayon", "reth-codecs", - "reth-db", "reth-db-api", "reth-primitives-traits", "reth-provider", @@ -10344,7 +10468,7 @@ dependencies = [ [[package]] name = "reth-static-file-types" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "clap", @@ -10356,7 +10480,7 @@ dependencies = [ [[package]] name = "reth-storage-api" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10378,7 +10502,7 @@ dependencies = [ [[package]] name = "reth-storage-errors" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-eips", "alloy-primitives", @@ -10388,12 +10512,12 @@ dependencies = [ "reth-prune-types", "reth-static-file-types", "revm-database-interface", - "thiserror 2.0.15", + "thiserror 2.0.17", ] [[package]] name = "reth-storage-rpc-provider" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10422,7 +10546,7 @@ dependencies = [ [[package]] name = "reth-tasks" -version = "1.6.0" +version = "1.8.2" dependencies = [ "auto_impl", "dyn-clone", @@ -10431,7 +10555,7 @@ dependencies = [ "pin-project", "rayon", "reth-metrics", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", "tracing-futures", @@ -10439,7 +10563,7 @@ dependencies = [ [[package]] name = "reth-testing-utils" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10454,7 +10578,7 @@ dependencies = [ [[package]] name = "reth-tokio-util" -version = "1.6.0" +version = "1.8.2" dependencies = [ "tokio", "tokio-stream", @@ -10463,7 +10587,7 @@ dependencies = [ [[package]] name = "reth-tracing" -version = "1.6.0" +version = "1.8.2" dependencies = [ "clap", "eyre", @@ -10472,12 +10596,12 @@ dependencies = [ "tracing-appender", "tracing-journald", "tracing-logfmt", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] name = "reth-tracing-otlp" -version = "1.6.0" +version = "1.8.2" dependencies = [ "opentelemetry", "opentelemetry-otlp", @@ -10485,12 +10609,12 @@ dependencies = [ "opentelemetry_sdk", "tracing", "tracing-opentelemetry", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] name = "reth-transaction-pool" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", @@ -10499,7 +10623,7 @@ dependencies = [ "aquamarine", "assert_matches", "auto_impl", - "bitflags 2.9.2", + "bitflags 2.10.0", "codspeed-criterion-compat", "futures", "futures-util", @@ -10516,21 +10640,23 @@ dependencies = [ "reth-ethereum-primitives", "reth-execution-types", "reth-fs-util", + "reth-gas-station", "reth-metrics", + "reth-optimism-primitives", "reth-primitives-traits", "reth-provider", "reth-storage-api", "reth-tasks", "reth-tracing", - "revm-interpreter 23.0.2", + "revm-interpreter", "revm-primitives", - "rustc-hash 2.1.1", + "rustc-hash", "schnellru", "serde", "serde_json", "smallvec", "tempfile", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tokio-stream", "tracing", @@ -10538,13 +10664,14 @@ dependencies = [ [[package]] name = "reth-trie" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-eips", "alloy-primitives", "alloy-rlp", "alloy-trie", + "assert_matches", "auto_impl", "codspeed-criterion-compat", "itertools 0.14.0", @@ -10570,7 +10697,7 @@ dependencies = [ [[package]] name = "reth-trie-common" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-genesis", @@ -10602,7 +10729,7 @@ dependencies = [ [[package]] name = "reth-trie-db" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -10615,7 +10742,6 @@ dependencies = [ "reth-execution-errors", "reth-primitives-traits", "reth-provider", - "reth-storage-errors", "reth-trie", "reth-trie-common", "revm", @@ -10628,7 +10754,7 @@ dependencies = [ [[package]] name = "reth-trie-parallel" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -10650,14 +10776,14 @@ dependencies = [ "reth-trie-common", "reth-trie-db", "reth-trie-sparse", - "thiserror 2.0.15", + "thiserror 2.0.17", "tokio", "tracing", ] [[package]] name = "reth-trie-sparse" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -10690,7 +10816,7 @@ dependencies = [ [[package]] name = "reth-trie-sparse-parallel" -version = "1.6.0" +version = "1.8.2" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -10719,25 +10845,24 @@ dependencies = [ [[package]] name = "reth-zstd-compressors" -version = "1.6.0" +version = "1.8.2" dependencies = [ "zstd", ] [[package]] name = "revm" -version = "28.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee5d3f7d031e90ab47c7488061bdc4875abc4e9dcea6c18f5dee09732d0436fb" +version = "29.0.1" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "revm-bytecode", "revm-context", - "revm-context-interface 10.0.1", + "revm-context-interface", "revm-database", "revm-database-interface", "revm-handler", "revm-inspector", - "revm-interpreter 25.0.1", + "revm-interpreter", "revm-precompile", "revm-primitives", "revm-state", @@ -10745,9 +10870,8 @@ dependencies = [ [[package]] name = "revm-bytecode" -version = "6.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d800e6c2119457ded5b0af71634eb2468040bf97de468eee5a730272a106da0" +version = "6.2.2" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "bitvec", "phf", @@ -10757,15 +10881,14 @@ dependencies = [ [[package]] name = "revm-context" -version = "9.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c63485b4d1b0e67f342f9a8c0e9f78b6b5f1750863a39bdf6ceabdbaaf4aed1" +version = "9.1.0" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "bitvec", "cfg-if", "derive-where", "revm-bytecode", - "revm-context-interface 10.0.1", + "revm-context-interface", "revm-database-interface", "revm-primitives", "revm-state", @@ -10774,25 +10897,8 @@ dependencies = [ [[package]] name = "revm-context-interface" -version = "8.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a303a93102fceccec628265efd550ce49f2817b38ac3a492c53f7d524f18a1ca" -dependencies = [ - "alloy-eip2930", - "alloy-eip7702", - "auto_impl", - "either", - "revm-database-interface", - "revm-primitives", - "revm-state", - "serde", -] - -[[package]] -name = "revm-context-interface" -version = "10.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "550cb8b9465e00bdb0a384922b69f864c5bcc228bed19c8ecbfa69fff2256382" +version = "10.2.0" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -10806,9 +10912,8 @@ dependencies = [ [[package]] name = "revm-database" -version = "7.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40000c7d917c865f6c232a78581b78e70c43f52db17282bd1b52d4f0565bc8a2" +version = "7.0.5" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "alloy-eips", "revm-bytecode", @@ -10820,9 +10925,8 @@ dependencies = [ [[package]] name = "revm-database-interface" -version = "7.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ccea7a168cba1196b1e57dd3e22c36047208c135f600f8e58cbe7d49957dba" +version = "7.0.5" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "auto_impl", "either", @@ -10833,17 +10937,16 @@ dependencies = [ [[package]] name = "revm-handler" -version = "9.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cb09d07e6799823ce5a344f1604236b53fe1a92bacd7122c0b16286f92254c2" +version = "10.0.1" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "auto_impl", "derive-where", "revm-bytecode", "revm-context", - "revm-context-interface 10.0.1", + "revm-context-interface", "revm-database-interface", - "revm-interpreter 25.0.1", + "revm-interpreter", "revm-precompile", "revm-primitives", "revm-state", @@ -10852,16 +10955,15 @@ dependencies = [ [[package]] name = "revm-inspector" -version = "9.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2770c0d7e9f4f23660dc0b8b954b7a1eee8989ec97f936ebce366c78b6d7b915" +version = "10.0.1" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "auto_impl", "either", "revm-context", "revm-database-interface", "revm-handler", - "revm-interpreter 25.0.1", + "revm-interpreter", "revm-primitives", "revm-state", "serde", @@ -10870,9 +10972,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.28.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a76ba086ca57a718368e46e792a81c5eb7a30366956aa6293adbcec8b1181ce" +checksum = "de23199c4b6181a6539e4131cf7e31cde4df05e1192bcdce491c34a511241588" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -10885,38 +10987,24 @@ dependencies = [ "revm", "serde", "serde_json", - "thiserror 2.0.15", -] - -[[package]] -name = "revm-interpreter" -version = "23.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95c4a9a1662d10b689b66b536ddc2eb1e89f5debfcabc1a2d7b8417a2fa47cd" -dependencies = [ - "revm-bytecode", - "revm-context-interface 8.0.1", - "revm-primitives", - "serde", + "thiserror 2.0.17", ] [[package]] name = "revm-interpreter" -version = "25.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c938c0d4d617c285203cad8aba1cefeec383fcff2fdf94a4469f588ab979b5" +version = "25.0.3" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "revm-bytecode", - "revm-context-interface 10.0.1", + "revm-context-interface", "revm-primitives", "serde", ] [[package]] name = "revm-precompile" -version = "26.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7bb5e8b92891c5ac9dd8dae157bd1d90aab01973ad4f99d4135d507facc3e7" +version = "27.0.0" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -10941,8 +11029,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "20.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa29d9da06fe03b249b6419b33968ecdf92ad6428e2f012dc57bcd619b5d94e" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ "alloy-primitives", "num_enum", @@ -10952,11 +11039,10 @@ dependencies = [ [[package]] name = "revm-state" -version = "7.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d7f39ea56df3bfbb3c81c99b1f028d26f205b6004156baffbf1a4f84b46cfa" +version = "7.0.5" +source = "git+https://github.com/lightlink-network/revm?branch=feat%2Fgasless-2#960cbd6ceb0a2dbc5db8959a2a289ea42d94eb57" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "revm-bytecode", "revm-primitives", "serde", @@ -11093,15 +11179,15 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.106", + "syn 2.0.108", "unicode-ident", ] [[package]] name = "rug" -version = "1.27.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4207e8d668e5b8eb574bda8322088ccd0d7782d3d03c7e8d562e82ed82bdcbc3" +checksum = "58ad2e973fe3c3214251a840a621812a4f40468da814b1a3d6947d433c2af11f" dependencies = [ "az", "gmp-mpfr-sys", @@ -11111,14 +11197,15 @@ dependencies = [ [[package]] name = "ruint" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecb38f82477f20c5c3d62ef52d7c4e536e38ea9b73fb570a20c5cae0e14bcf6" +checksum = "a68df0380e5c9d20ce49534f292a36a7514ae21350726efe1865bdb1fa91d278" dependencies = [ "alloy-rlp", "arbitrary", "ark-ff 0.3.0", "ark-ff 0.4.2", + "ark-ff 0.5.0", "bytes", "fastrlp 0.3.1", "fastrlp 0.4.0", @@ -11132,7 +11219,7 @@ dependencies = [ "rand 0.9.2", "rlp", "ruint-macro", - "serde", + "serde_core", "valuable", "zeroize", ] @@ -11149,12 +11236,6 @@ version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -11185,7 +11266,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", + "semver 1.0.27", ] [[package]] @@ -11194,7 +11275,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "errno", "libc", "linux-raw-sys 0.4.15", @@ -11203,22 +11284,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" dependencies = [ "log", "once_cell", @@ -11231,9 +11312,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -11243,9 +11324,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "web-time", "zeroize", @@ -11280,9 +11361,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -11297,9 +11378,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" dependencies = [ "fnv", "quick-error", @@ -11330,11 +11411,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -11442,11 +11523,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.3.0" +version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -11455,9 +11536,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -11474,11 +11555,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -11504,10 +11586,11 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -11520,28 +11603,38 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "serde_json" -version = "1.0.142" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.12.0", "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -11578,19 +11671,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.12.0", "schemars 0.9.0", "schemars 1.0.4", - "serde", - "serde_derive", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -11598,14 +11690,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" dependencies = [ - "darling 0.20.11", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -11709,9 +11801,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -11737,6 +11829,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "similar" version = "2.7.0" @@ -11766,7 +11864,7 @@ checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror 2.0.15", + "thiserror 2.0.17", "time", ] @@ -11850,12 +11948,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -11892,9 +11990,9 @@ checksum = "3b9b39299b249ad65f3b7e96443bad61c02ca5cd3589f46cb6d610a0fd6c0d6a" [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -11936,7 +12034,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -11948,7 +12046,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -11970,9 +12068,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -11981,14 +12079,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0b198d366dbec045acfcd97295eb653a7a2b40e4dc764ef1e79aafcad439d3c" +checksum = "ff790eb176cc81bb8936aed0f7b9f14fc4670069a2d371b3e3b0ecce908b2cb3" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -12008,7 +12106,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -12053,22 +12151,22 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac9ee8b664c9f1740cd813fea422116f8ba29997bb7c878d1940424889802897" dependencies = [ - "bitflags 2.9.2", + "bitflags 2.10.0", "log", "num-traits", ] [[package]] name = "tempfile" -version = "3.20.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] @@ -12089,7 +12187,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -12100,15 +12198,15 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "test-case-core", ] [[package]] name = "test-fuzz" -version = "7.2.2" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8d521c6196e60e389bfa3c6e13a8c7abe579a05fcfb8fb695c6129e2b28c7" +checksum = "6696b1bcee3edb0553566f632c31b3b18fda42cf4d529327ca47f230c4acd3ab" dependencies = [ "serde", "serde_combinators", @@ -12119,9 +12217,9 @@ dependencies = [ [[package]] name = "test-fuzz-internal" -version = "7.2.2" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb966d72fcf4e04773f5f77a2203893d095c24ddcc69c192815195a9503033f" +checksum = "5988511fdb342582013a17a4263e994bce92828a1bae039f92a2f05a5f95ce78" dependencies = [ "bincode 2.0.1", "cargo_metadata 0.19.2", @@ -12130,24 +12228,24 @@ dependencies = [ [[package]] name = "test-fuzz-macro" -version = "7.2.2" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37101b5b033dcf2b50236f61c6773318e00a1792fea4e020b5b2fc613bfb60d0" +checksum = "c8893e583c5af79a67761a9285535d26612cb1617fcbf388c3abc0c1d35a0b89" dependencies = [ - "darling 0.21.2", + "darling 0.21.3", "heck", "itertools 0.14.0", "prettyplease", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "test-fuzz-runtime" -version = "7.2.2" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c13409f445cdfdf04fc8effa1f94cd2cc1358005d4ca21bfad3831949d16d07" +checksum = "47be06afdb9cb50c76ef938e2e4bda2e28e1cbb4d3d305603d57a5e374a6d6e7" dependencies = [ "hex", "num-traits", @@ -12173,11 +12271,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.15" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d76d3f064b981389ecb4b6b7f45a0bf9fdac1d5b9204c7bd6714fecc302850" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.15", + "thiserror-impl 2.0.17", ] [[package]] @@ -12188,18 +12286,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "thiserror-impl" -version = "2.0.15" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d29feb33e986b6ea906bd9c3559a856983f92371b3eaa5e83782a351623de0" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -12222,9 +12320,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-ctl" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21f216790c8df74ce3ab25b534e0718da5a1916719771d3fec23315c99e468b" +checksum = "661f1f6a57b3a36dc9174a2c10f19513b4866816e13425d3e418b11cc37bc24c" dependencies = [ "libc", "paste", @@ -12233,9 +12331,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.6.0+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" +version = "0.6.1+5.3.0-1-ge13ca993e8ccb9ba9847cc330696e02839f328f7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3c60906412afa9c2b5b5a48ca6a5abe5736aec9eb48ad05037a677e52e4e2d" +checksum = "cd8aa5b2ab86a2cefa406d889139c162cbb230092f7d1d7cbc1716405d852a3b" dependencies = [ "cc", "libc", @@ -12243,9 +12341,9 @@ dependencies = [ [[package]] name = "tikv-jemallocator" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cec5ff18518d81584f477e9bfdf957f5bb0979b0bac3af4ca30b5b3ae2d2865" +checksum = "0359b4327f954e0567e69fb191cf1436617748813819c94b8cd4a431422d053a" dependencies = [ "libc", "tikv-jemalloc-sys", @@ -12253,9 +12351,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -12271,15 +12369,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -12326,9 +12424,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -12341,40 +12439,37 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ "rustls", "tokio", @@ -12432,8 +12527,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -12445,20 +12540,50 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.12.0", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.11", "toml_write", "winnow", ] +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap 2.12.0", + "toml_datetime 0.7.3", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + [[package]] name = "toml_write" version = "0.1.2" @@ -12495,7 +12620,7 @@ dependencies = [ "futures-core", "futures-util", "hdrhistogram", - "indexmap 2.10.0", + "indexmap 2.12.0", "pin-project-lite", "slab", "sync_wrapper", @@ -12514,7 +12639,7 @@ checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ "async-compression", "base64 0.22.1", - "bitflags 2.9.2", + "bitflags 2.10.0", "bytes", "futures-core", "futures-util", @@ -12570,7 +12695,7 @@ dependencies = [ "crossbeam-channel", "thiserror 1.0.69", "time", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] @@ -12581,7 +12706,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -12612,7 +12737,7 @@ checksum = "fc0b4143302cf1022dac868d521e36e8b27691f72c84b3311750d5188ebba657" dependencies = [ "libc", "tracing-core", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] @@ -12635,7 +12760,7 @@ dependencies = [ "time", "tracing", "tracing-core", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", ] [[package]] @@ -12652,7 +12777,7 @@ dependencies = [ "tracing", "tracing-core", "tracing-log", - "tracing-subscriber 0.3.19", + "tracing-subscriber 0.3.20", "web-time", ] @@ -12677,14 +12802,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "serde", "serde_json", "sharded-slab", @@ -12740,7 +12865,7 @@ dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -12755,9 +12880,9 @@ dependencies = [ [[package]] name = "triomphe" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85" +checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39" [[package]] name = "try-lock" @@ -12780,15 +12905,15 @@ dependencies = [ "rustls", "rustls-pki-types", "sha1", - "thiserror 2.0.15", + "thiserror 2.0.17", "utf-8", ] [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -12834,9 +12959,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "unicode-segmentation" @@ -12903,9 +13028,9 @@ checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -12939,11 +13064,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "wasm-bindgen", ] @@ -13021,7 +13146,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -13071,45 +13196,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn 2.0.106", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -13120,9 +13232,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -13130,22 +13242,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.106", - "wasm-bindgen-backend", + "syn 2.0.108", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -13165,9 +13277,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ "futures", "js-sys", @@ -13179,9 +13291,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -13203,14 +13315,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" dependencies = [ - "webpki-root-certs 1.0.2", + "webpki-root-certs 1.0.3", ] [[package]] name = "webpki-root-certs" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4ffd8df1c57e87c325000a3d6ef93db75279dc3a231125aac571650f22b12a" +checksum = "05d651ec480de84b762e7be71e6efa7461699c19d9e2c272c8d93455f567786e" dependencies = [ "rustls-pki-types", ] @@ -13221,23 +13333,23 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.2", + "webpki-roots 1.0.3", ] [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" dependencies = [ "rustls-pki-types", ] [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -13257,11 +13369,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -13282,25 +13394,27 @@ dependencies = [ [[package]] name = "windows" -version = "0.58.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", ] [[package]] name = "windows" -version = "0.61.3" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-collections", - "windows-core 0.61.2", - "windows-future", - "windows-link", - "windows-numerics", + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -13312,6 +13426,15 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.57.0" @@ -13326,28 +13449,28 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.58.0", - "windows-interface 0.58.0", - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-implement 0.60.2", + "windows-interface 0.59.3", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -13357,41 +13480,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", - "windows-link", - "windows-threading", + "windows-link 0.1.3", + "windows-threading 0.1.0", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "windows-future" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] name = "windows-implement" -version = "0.58.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -13402,36 +13525,31 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] name = "windows-interface" -version = "0.58.0" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] -name = "windows-interface" -version = "0.59.1" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-numerics" @@ -13440,23 +13558,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", ] [[package]] -name = "windows-result" -version = "0.1.2" +name = "windows-numerics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-targets 0.52.6", + "windows-core 0.62.2", + "windows-link 0.2.1", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ "windows-targets 0.52.6", ] @@ -13467,17 +13586,16 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] -name = "windows-strings" -version = "0.1.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-result 0.2.0", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -13486,7 +13604,16 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13531,7 +13658,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.3", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13582,19 +13718,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.3" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -13603,7 +13739,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -13626,9 +13771,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -13650,9 +13795,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -13674,9 +13819,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -13686,9 +13831,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -13710,9 +13855,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -13734,9 +13879,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -13758,9 +13903,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -13782,15 +13927,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -13806,13 +13951,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.2", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "write16" @@ -13845,7 +13987,7 @@ dependencies = [ "pharos", "rustc_version 0.4.1", "send_wrapper 0.6.0", - "thiserror 2.0.15", + "thiserror 2.0.17", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -13862,12 +14004,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.0.8", + "rustix 1.1.2", ] [[package]] @@ -13908,7 +14050,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] @@ -13920,28 +14062,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -13961,15 +14103,15 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -13982,7 +14124,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -14026,7 +14168,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -14037,7 +14179,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.108", ] [[package]] @@ -14060,9 +14202,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 072fe9649fa..50e4dd1d17d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace.package] -version = "1.6.0" -edition = "2021" +version = "1.8.2" +edition = "2024" rust-version = "1.88" license = "MIT OR Apache-2.0" homepage = "https://paradigmxyz.github.io/reth" @@ -42,6 +42,7 @@ members = [ "crates/ethereum/payload/", "crates/ethereum/primitives/", "crates/ethereum/reth/", + "crates/gas-station/", "crates/etl/", "crates/evm/evm", "crates/evm/execution-errors", @@ -76,6 +77,7 @@ members = [ "crates/optimism/cli", "crates/optimism/consensus", "crates/optimism/evm/", + "crates/optimism/flashblocks/", "crates/optimism/hardforks/", "crates/optimism/node/", "crates/optimism/payload/", @@ -160,6 +162,7 @@ members = [ "examples/network-txpool/", "examples/network/", "examples/network-proxy/", + "examples/node-builder-api/", "examples/node-custom-rpc/", "examples/node-event-hooks/", "examples/op-db-access/", @@ -170,6 +173,7 @@ members = [ "examples/custom-beacon-withdrawals", "testing/ef-tests/", "testing/testing-utils", + "testing/runner", "crates/tracing-otlp", ] default-members = ["bin/reth"] @@ -185,6 +189,7 @@ rust.missing_docs = "warn" rust.rust_2018_idioms = { level = "deny", priority = -1 } rust.unreachable_pub = "warn" rust.unused_must_use = "deny" +rust.rust_2024_incompatible_pat = "warn" rustdoc.all = "warn" # rust.unnameable-types = "warn" @@ -280,8 +285,8 @@ too_long_first_doc_paragraph = "allow" # NOTE: Debuggers may provide less useful information with this setting. # Uncomment this section if you're using a debugger. [profile.dev] -# https://davidlattimore.github.io/posts/2024/02/04/speeding-up-the-rust-edit-build-run-cycle.html -debug = "line-tables-only" +# Use full debug info so debuggers (LLDB/CodeLLDB) can show local variables and complex types. +debug = "full" split-debuginfo = "unpacked" # Speed up tests. @@ -314,7 +319,7 @@ strip = "none" # Include debug info in benchmarks too. [profile.bench] -inherits = "profiling" +# inherits = "profiling" [profile.maxperf] inherits = "release" @@ -378,6 +383,7 @@ reth-exex = { path = "crates/exex/exex" } reth-exex-test-utils = { path = "crates/exex/test-utils" } reth-exex-types = { path = "crates/exex/types" } reth-fs-util = { path = "crates/fs-util" } +reth-gas-station = { path = "crates/gas-station" } reth-invalid-block-hooks = { path = "crates/engine/invalid-block-hooks" } reth-ipc = { path = "crates/rpc/ipc" } reth-libmdbx = { path = "crates/storage/libmdbx-rs" } @@ -430,6 +436,7 @@ reth-rpc-engine-api = { path = "crates/rpc/rpc-engine-api" } reth-rpc-eth-api = { path = "crates/rpc/rpc-eth-api" } reth-rpc-eth-types = { path = "crates/rpc/rpc-eth-types", default-features = false } reth-rpc-layer = { path = "crates/rpc/rpc-layer" } +reth-optimism-flashblocks = { path = "crates/optimism/flashblocks" } reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-convert = { path = "crates/rpc/rpc-convert" } reth-stages = { path = "crates/stages/stages" } @@ -456,79 +463,95 @@ reth-ress-protocol = { path = "crates/ress/protocol" } reth-ress-provider = { path = "crates/ress/provider" } # revm -revm = { version = "28.0.1", default-features = false } -revm-bytecode = { version = "6.0.1", default-features = false } -revm-database = { version = "7.0.1", default-features = false } -revm-state = { version = "7.0.1", default-features = false } -revm-primitives = { version = "20.0.0", default-features = false } -revm-interpreter = { version = "23.0.1", default-features = false } -revm-inspector = { version = "8.0.2", default-features = false } -revm-context = { version = "9.0.1", default-features = false } -revm-context-interface = { version = "8.0.1", default-features = false } -revm-database-interface = { version = "7.0.1", default-features = false } -op-revm = { version = "9.0.1", default-features = false } -revm-inspectors = "0.28.0" +revm = { version = "29.0.1", default-features = false, features = [ + "optional_gasless", +] } +revm-bytecode = { version = "6.2.2", default-features = false } +revm-database = { version = "7.0.5", default-features = false } +revm-state = { version = "7.0.5", default-features = false } +revm-primitives = { version = "20.2.1", default-features = false } +revm-interpreter = { version = "25.0.3", default-features = false } +revm-inspector = { version = "10.0.1", default-features = false } +revm-context = { version = "9.1.0", default-features = false } +revm-context-interface = { version = "10.2.0", default-features = false } +revm-database-interface = { version = "7.0.5", default-features = false } +op-revm = { version = "10.1.0", default-features = false } +revm-inspectors = "0.30.0" # eth alloy-chains = { version = "0.2.5", default-features = false } alloy-dyn-abi = "1.3.1" alloy-eip2124 = { version = "0.2.0", default-features = false } -alloy-evm = { version = "0.18.2", default-features = false } -alloy-primitives = { version = "1.3.1", default-features = false, features = ["map-foldhash"] } -alloy-rlp = { version = "0.3.10", default-features = false, features = ["core-net"] } +alloy-evm = { version = "0.21.0", default-features = false } +alloy-primitives = { version = "1.3.1", default-features = false, features = [ + "map-foldhash", +] } +alloy-rlp = { version = "0.3.10", default-features = false, features = [ + "core-net", +] } alloy-sol-macro = "1.3.1" alloy-sol-types = { version = "1.3.1", default-features = false } -alloy-trie = { version = "0.9.0", default-features = false } - -alloy-hardforks = "0.2.7" - -alloy-consensus = { version = "1.0.23", default-features = false } -alloy-contract = { version = "1.0.23", default-features = false } -alloy-eips = { version = "1.0.23", default-features = false } -alloy-genesis = { version = "1.0.23", default-features = false } -alloy-json-rpc = { version = "1.0.23", default-features = false } -alloy-network = { version = "1.0.23", default-features = false } -alloy-network-primitives = { version = "1.0.23", default-features = false } -alloy-provider = { version = "1.0.23", features = ["reqwest"], default-features = false } -alloy-pubsub = { version = "1.0.23", default-features = false } -alloy-rpc-client = { version = "1.0.23", default-features = false } -alloy-rpc-types = { version = "1.0.23", features = ["eth"], default-features = false } -alloy-rpc-types-admin = { version = "1.0.23", default-features = false } -alloy-rpc-types-anvil = { version = "1.0.23", default-features = false } -alloy-rpc-types-beacon = { version = "1.0.23", default-features = false } -alloy-rpc-types-debug = { version = "1.0.23", default-features = false } -alloy-rpc-types-engine = { version = "1.0.23", default-features = false } -alloy-rpc-types-eth = { version = "1.0.23", default-features = false } -alloy-rpc-types-mev = { version = "1.0.23", default-features = false } -alloy-rpc-types-trace = { version = "1.0.23", default-features = false } -alloy-rpc-types-txpool = { version = "1.0.23", default-features = false } -alloy-serde = { version = "1.0.23", default-features = false } -alloy-signer = { version = "1.0.23", default-features = false } -alloy-signer-local = { version = "1.0.23", default-features = false } -alloy-transport = { version = "1.0.23" } -alloy-transport-http = { version = "1.0.23", features = ["reqwest-rustls-tls"], default-features = false } -alloy-transport-ipc = { version = "1.0.23", default-features = false } -alloy-transport-ws = { version = "1.0.23", default-features = false } +alloy-trie = { version = "0.9.1", default-features = false } + +alloy-hardforks = "0.3.5" + +alloy-consensus = { version = "1.0.37", default-features = false } +alloy-contract = { version = "1.0.37", default-features = false } +alloy-eips = { version = "1.0.37", default-features = false } +alloy-genesis = { version = "1.0.37", default-features = false } +alloy-json-rpc = { version = "1.0.37", default-features = false } +alloy-network = { version = "1.0.37", default-features = false } +alloy-network-primitives = { version = "1.0.37", default-features = false } +alloy-provider = { version = "1.0.37", features = [ + "reqwest", +], default-features = false } +alloy-pubsub = { version = "1.0.37", default-features = false } +alloy-rpc-client = { version = "1.0.37", default-features = false } +alloy-rpc-types = { version = "1.0.37", features = [ + "eth", +], default-features = false } +alloy-rpc-types-admin = { version = "1.0.37", default-features = false } +alloy-rpc-types-anvil = { version = "1.0.37", default-features = false } +alloy-rpc-types-beacon = { version = "1.0.37", default-features = false } +alloy-rpc-types-debug = { version = "1.0.37", default-features = false } +alloy-rpc-types-engine = { version = "1.0.37", default-features = false } +alloy-rpc-types-eth = { version = "1.0.37", default-features = false } +alloy-rpc-types-mev = { version = "1.0.37", default-features = false } +alloy-rpc-types-trace = { version = "1.0.37", default-features = false } +alloy-rpc-types-txpool = { version = "1.0.37", default-features = false } +alloy-serde = { version = "1.0.37", default-features = false } +alloy-signer = { version = "1.0.37", default-features = false } +alloy-signer-local = { version = "1.0.37", default-features = false } +alloy-transport = { version = "1.0.37" } +alloy-transport-http = { version = "1.0.37", features = [ + "reqwest-rustls-tls", +], default-features = false } +alloy-transport-ipc = { version = "1.0.37", default-features = false } +alloy-transport-ws = { version = "1.0.37", default-features = false } # op -alloy-op-evm = { version = "0.18", default-features = false } -alloy-op-hardforks = "0.2.2" -op-alloy-rpc-types = { version = "0.18.12", default-features = false } -op-alloy-rpc-types-engine = { version = "0.18.12", default-features = false } -op-alloy-network = { version = "0.18.12", default-features = false } -op-alloy-consensus = { version = "0.18.12", default-features = false } -op-alloy-rpc-jsonrpsee = { version = "0.18.12", default-features = false } +alloy-op-evm = { version = "0.21.0", default-features = false } +alloy-op-hardforks = "0.3.5" +op-alloy-rpc-types = { version = "0.20.0", default-features = false } +op-alloy-rpc-types-engine = { version = "0.20.0", default-features = false } +op-alloy-network = { version = "0.20.0", default-features = false } +op-alloy-consensus = { version = "0.20.0", default-features = false } +op-alloy-rpc-jsonrpsee = { version = "0.20.0", default-features = false } op-alloy-flz = { version = "0.13.1", default-features = false } # misc either = { version = "1.15.0", default-features = false } aquamarine = "0.6" auto_impl = "1" -backon = { version = "1.2", default-features = false, features = ["std-blocking-sleep", "tokio-sleep"] } +backon = { version = "1.2", default-features = false, features = [ + "std-blocking-sleep", + "tokio-sleep", +] } bincode = "1.3" bitflags = "2.4" boyer-moore-magiclen = "0.2.16" bytes = { version = "1.5", default-features = false } +brotli = "8" cfg-if = "1.0" clap = "4" dashmap = "6.0" @@ -544,9 +567,13 @@ itertools = { version = "0.14", default-features = false } linked_hash_set = "0.1" lz4 = "1.28.1" modular-bitfield = "0.11.2" -notify = { version = "8.0.0", default-features = false, features = ["macos_fsevent"] } +notify = { version = "8.0.0", default-features = false, features = [ + "macos_fsevent", +] } nybbles = { version = "0.4.2", default-features = false } -once_cell = { version = "1.19", default-features = false, features = ["critical-section"] } +once_cell = { version = "1.19", default-features = false, features = [ + "critical-section", +] } parking_lot = "0.12" paste = "1.0" rand = "0.9" @@ -610,11 +637,11 @@ discv5 = "0.9" if-addrs = "0.13" # rpc -jsonrpsee = "0.25.1" -jsonrpsee-core = "0.25.1" -jsonrpsee-server = "0.25.1" -jsonrpsee-http-client = "0.25.1" -jsonrpsee-types = "0.25.1" +jsonrpsee = "0.26.0" +jsonrpsee-core = "0.26.0" +jsonrpsee-server = "0.26.0" +jsonrpsee-http-client = "0.26.0" +jsonrpsee-types = "0.26.0" # http http = "1.0" @@ -626,12 +653,15 @@ proptest-arbitrary-interop = "0.1.0" # crypto enr = { version = "0.13", default-features = false } k256 = { version = "0.13", default-features = false, features = ["ecdsa"] } -secp256k1 = { version = "0.30", default-features = false, features = ["global-context", "recovery"] } +secp256k1 = { version = "0.30", default-features = false, features = [ + "global-context", + "recovery", +] } # rand 8 for secp256k1 rand_08 = { package = "rand", version = "0.8" } # for eip-4844 -c-kzg = "2.1.1" +c-kzg = "2.1.4" # config toml = "0.8" @@ -661,7 +691,7 @@ snmalloc-rs = { version = "0.3.7", features = ["build_cc"] } aes = "0.8.1" ahash = "0.8" anyhow = "1.0" -bindgen = { version = "0.70", default-features = false } +bindgen = { version = "0.71", default-features = false } block-padding = "0.3.2" cc = "=1.2.15" cipher = "0.4.3" @@ -708,6 +738,19 @@ visibility = "0.1.1" walkdir = "2.3.3" vergen-git2 = "1.0.5" +[patch.crates-io] +revm = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-primitives = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-interpreter = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-handler = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-context = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-context-interface = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-state = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-database = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-database-interface = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-bytecode = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } +revm-inspector = { git = "https://github.com/lightlink-network/revm", branch = "feat/gasless-2" } + # [patch.crates-io] # alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } # alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "3049f232fbb44d1909883e154eb38ec5962f53a3" } diff --git a/Dockerfile.x86_64-pc-windows-gnu b/Dockerfile.x86_64-pc-windows-gnu index e4b5a531abe..c4611c249ff 100644 --- a/Dockerfile.x86_64-pc-windows-gnu +++ b/Dockerfile.x86_64-pc-windows-gnu @@ -17,7 +17,13 @@ RUN apt-get update && apt-get install --assume-yes --no-install-recommends git RUN git clone https://github.com/cross-rs/cross /cross WORKDIR /cross/docker -RUN git checkout 9e2298e17170655342d3248a9c8ac37ef92ba38f +RUN git checkout baf457efc2555225af47963475bd70e8d2f5993f + +# xargo doesn't work with Rust 1.89 and higher: https://github.com/cross-rs/cross/issues/1701. +# +# When this PR https://github.com/cross-rs/cross/pull/1580 is merged, +# we can update the checkout above and remove this replacement. +RUN sed -i 's|sh rustup-init.sh -y --no-modify-path --profile minimal|sh rustup-init.sh -y --no-modify-path --profile minimal --default-toolchain=1.88.0|' xargo.sh RUN cp common.sh lib.sh / && /common.sh RUN cp cmake.sh / && /cmake.sh diff --git a/DockerfileOp b/DockerfileOp index 51a567317d2..8255debe2d3 100644 --- a/DockerfileOp +++ b/DockerfileOp @@ -6,13 +6,13 @@ LABEL org.opencontainers.image.licenses="MIT OR Apache-2.0" RUN apt-get update && apt-get -y upgrade && apt-get install -y libclang-dev pkg-config +# Builds a cargo-chef plan FROM chef AS planner COPY . . RUN cargo chef prepare --recipe-path recipe.json FROM chef AS builder COPY --from=planner /app/recipe.json recipe.json -COPY . . ARG BUILD_PROFILE=release ENV BUILD_PROFILE=$BUILD_PROFILE @@ -20,15 +20,18 @@ ENV BUILD_PROFILE=$BUILD_PROFILE ARG RUSTFLAGS="" ENV RUSTFLAGS="$RUSTFLAGS" -RUN cargo chef cook --profile $BUILD_PROFILE --recipe-path recipe.json --manifest-path /app/crates/optimism/bin/Cargo.toml +ARG FEATURES="" +ENV FEATURES=$FEATURES + +RUN cargo chef cook --profile $BUILD_PROFILE --features "$FEATURES" --recipe-path recipe.json --manifest-path /app/crates/optimism/bin/Cargo.toml COPY . . -RUN cargo build --profile $BUILD_PROFILE --bin op-reth --manifest-path /app/crates/optimism/bin/Cargo.toml +RUN cargo build --profile $BUILD_PROFILE --features "$FEATURES" --bin op-reth --manifest-path /app/crates/optimism/bin/Cargo.toml RUN ls -la /app/target/$BUILD_PROFILE/op-reth RUN cp /app/target/$BUILD_PROFILE/op-reth /app/op-reth -FROM ubuntu:22.04 AS runtime +FROM ubuntu:24.04 AS runtime RUN apt-get update && \ apt-get install -y ca-certificates libssl-dev pkg-config strace && \ diff --git a/Makefile b/Makefile index 5dbe2191282..30f6b0aa478 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,11 @@ EF_TESTS_TAG := v17.0 EF_TESTS_URL := https://github.com/ethereum/tests/archive/refs/tags/$(EF_TESTS_TAG).tar.gz 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_URL := https://github.com/ethereum/execution-spec-tests/releases/download/$(EEST_TESTS_TAG)/fixtures_stable.tar.gz +EEST_TESTS_DIR := ./testing/ef-tests/execution-spec-tests + # The docker image name DOCKER_IMAGE_NAME ?= ghcr.io/paradigmxyz/reth @@ -202,9 +207,18 @@ $(EF_TESTS_DIR): tar -xzf ethereum-tests.tar.gz --strip-components=1 -C $(EF_TESTS_DIR) rm ethereum-tests.tar.gz +# Downloads and unpacks EEST tests in the `$(EEST_TESTS_DIR)` directory. +# +# Requires `wget` and `tar` +$(EEST_TESTS_DIR): + mkdir $(EEST_TESTS_DIR) + wget $(EEST_TESTS_URL) -O execution-spec-tests.tar.gz + tar -xzf execution-spec-tests.tar.gz --strip-components=1 -C $(EEST_TESTS_DIR) + rm execution-spec-tests.tar.gz + .PHONY: ef-tests -ef-tests: $(EF_TESTS_DIR) ## Runs Ethereum Foundation tests. - cargo nextest run -p ef-tests --features ef-tests +ef-tests: $(EF_TESTS_DIR) $(EEST_TESTS_DIR) ## Runs Legacy and EEST tests. + cargo nextest run -p ef-tests --release --features ef-tests ##@ reth-bench @@ -212,7 +226,7 @@ ef-tests: $(EF_TESTS_DIR) ## Runs Ethereum Foundation tests. reth-bench: ## Build the reth-bench binary into the `target` directory. cargo build --manifest-path bin/reth-bench/Cargo.toml --features "$(FEATURES)" --profile "$(PROFILE)" -.PHONY: install-reth-bech +.PHONY: install-reth-bench install-reth-bench: ## Build and install the reth binary under `$(CARGO_HOME)/bin`. cargo install --path bin/reth-bench --bin reth-bench --force --locked \ --features "$(FEATURES)" \ @@ -420,7 +434,7 @@ lint-typos: ensure-typos ensure-typos: @if ! command -v typos &> /dev/null; then \ - echo "typos not found. Please install it by running the command `cargo install typos-cli` or refer to the following link for more information: https://github.com/crate-ci/typos" \ + echo "typos not found. Please install it by running the command 'cargo install typos-cli' or refer to the following link for more information: https://github.com/crate-ci/typos"; \ exit 1; \ fi @@ -439,7 +453,7 @@ lint-toml: ensure-dprint ensure-dprint: @if ! command -v dprint &> /dev/null; then \ - echo "dprint not found. Please install it by running the command `cargo install --locked dprint` or refer to the following link for more information: https://github.com/dprint/dprint" \ + echo "dprint not found. Please install it by running the command 'cargo install --locked dprint' or refer to the following link for more information: https://github.com/dprint/dprint"; \ exit 1; \ fi diff --git a/README.md b/README.md index f106f1cecb8..869d1e6406c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ As a full Ethereum node, Reth allows users to connect to the Ethereum network an More concretely, our goals are: 1. **Modularity**: Every component of Reth is built to be used as a library: well-tested, heavily documented and benchmarked. We envision that developers will import the node's crates, mix and match, and innovate on top of them. Examples of such usage include but are not limited to spinning up standalone P2P networks, talking directly to a node's database, or "unbundling" the node into the components you need. To achieve that, we are licensing Reth under the Apache/MIT permissive license. You can learn more about the project's components [here](./docs/repo/layout.md). -2. **Performance**: Reth aims to be fast, so we used Rust and the [Erigon staged-sync](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) node architecture. We also use our Ethereum libraries (including [Alloy](https://github.com/alloy-rs/alloy/) and [revm](https://github.com/bluealloy/revm/)) which we’ve battle-tested and optimized via [Foundry](https://github.com/foundry-rs/foundry/). +2. **Performance**: Reth aims to be fast, so we use Rust and the [Erigon staged-sync](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) node architecture. We also use our Ethereum libraries (including [Alloy](https://github.com/alloy-rs/alloy/) and [revm](https://github.com/bluealloy/revm/)) which we've battle-tested and optimized via [Foundry](https://github.com/foundry-rs/foundry/). 3. **Free for anyone to use any way they want**: Reth is free open source software, built for the community, by the community. By licensing the software under the Apache/MIT license, we want developers to use it without being bound by business licenses, or having to think about the implications of GPL-like licenses. 4. **Client Diversity**: The Ethereum protocol becomes more antifragile when no node implementation dominates. This ensures that if there's a software bug, the network does not finalize a bad block. By building a new client, we hope to contribute to Ethereum's antifragility. 5. **Support as many EVM chains as possible**: We aspire that Reth can full-sync not only Ethereum, but also other chains like Optimism, Polygon, BNB Smart Chain, and more. If you're working on any of these projects, please reach out. @@ -44,14 +44,14 @@ More historical context below: - We released 1.0 "production-ready" stable Reth in June 2024. - Reth completed an audit with [Sigma Prime](https://sigmaprime.io/), the developers of [Lighthouse](https://github.com/sigp/lighthouse), the Rust Consensus Layer implementation. Find it [here](./audit/sigma_prime_audit_v2.pdf). - Revm (the EVM used in Reth) underwent an audit with [Guido Vranken](https://twitter.com/guidovranken) (#1 [Ethereum Bug Bounty](https://ethereum.org/en/bug-bounty)). We will publish the results soon. -- We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3rd 2024 the last beta release. -- We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4th 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives. -- We shipped iterative improvements until the last alpha release on February 28th 2024, [0.1.0-alpha.21](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.21). -- We [initially announced](https://www.paradigm.xyz/2023/06/reth-alpha) [0.1.0-alpha.1](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.1) in June 20th 2023. +- We released multiple iterative beta versions, up to [beta.9](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.9) on Monday June 3, 2024,the last beta release. +- We released [beta](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) on Monday March 4, 2024, our first breaking change to the database model, providing faster query speed, smaller database footprint, and allowing "history" to be mounted on separate drives. +- We shipped iterative improvements until the last alpha release on February 28, 2024, [0.1.0-alpha.21](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.21). +- We [initially announced](https://www.paradigm.xyz/2023/06/reth-alpha) [0.1.0-alpha.1](https://github.com/paradigmxyz/reth/releases/tag/v0.1.0-alpha.1) on June 20, 2023. ### Database compatibility -We do not have any breaking database changes since beta.1, and do not plan any in the near future. +We do not have any breaking database changes since beta.1, and we do not plan any in the near future. Reth [v0.2.0-beta.1](https://github.com/paradigmxyz/reth/releases/tag/v0.2.0-beta.1) includes a [set of breaking database changes](https://github.com/paradigmxyz/reth/pull/5191) that makes it impossible to use database files produced by earlier versions. @@ -84,7 +84,6 @@ If you want to contribute, or follow along with contributor discussion, you can @@ -140,7 +139,7 @@ None of this would have been possible without them, so big shoutout to the teams - [Geth](https://github.com/ethereum/go-ethereum/): We would like to express our heartfelt gratitude to the go-ethereum team for their outstanding contributions to Ethereum over the years. Their tireless efforts and dedication have helped to shape the Ethereum ecosystem and make it the vibrant and innovative community it is today. Thank you for your hard work and commitment to the project. - [Erigon](https://github.com/ledgerwatch/erigon) (fka Turbo-Geth): Erigon pioneered the ["Staged Sync" architecture](https://erigon.substack.com/p/erigon-stage-sync-and-control-flows) that Reth is using, as well as [introduced MDBX](https://github.com/ledgerwatch/erigon/wiki/Choice-of-storage-engine) as the database of choice. We thank Erigon for pushing the state of the art research on the performance limits of Ethereum nodes. -- [Akula](https://github.com/akula-bft/akula/): Reth uses forks of the Apache versions of Akula's [MDBX Bindings](https://github.com/paradigmxyz/reth/pull/132), [FastRLP](https://github.com/paradigmxyz/reth/pull/63) and [ECIES](https://github.com/paradigmxyz/reth/pull/80) . Given that these packages were already released under the Apache License, and they implement standardized solutions, we decided not to reimplement them to iterate faster. We thank the Akula team for their contributions to the Rust Ethereum ecosystem and for publishing these packages. +- [Akula](https://github.com/akula-bft/akula/): Reth uses forks of the Apache versions of Akula's [MDBX Bindings](https://github.com/paradigmxyz/reth/pull/132), [FastRLP](https://github.com/paradigmxyz/reth/pull/63) and [ECIES](https://github.com/paradigmxyz/reth/pull/80). Given that these packages were already released under the Apache License, and they implement standardized solutions, we decided not to reimplement them to iterate faster. We thank the Akula team for their contributions to the Rust Ethereum ecosystem and for publishing these packages. ## Warning diff --git a/bin/reth-bench/README.md b/bin/reth-bench/README.md index 9d8a04f8deb..b8176749fc7 100644 --- a/bin/reth-bench/README.md +++ b/bin/reth-bench/README.md @@ -92,6 +92,18 @@ This should NOT be the node that is being used for the benchmark. The node behin the benchmark. The node being benchmarked will not have these blocks. Note that this assumes that the benchmark node's engine API is running on `http://127.0.0.1:8551`, which is set as a default value in `reth-bench`. To configure this value, use the `--engine-rpc-url` flag. +#### Using the `--advance` argument + +The `--advance` argument allows you to benchmark a relative number of blocks from the current head, without manually specifying `--from` and `--to`. + +```bash +# Benchmark the next 10 blocks from the current head +reth-bench new-payload-fcu --advance 10 --jwt-secret --rpc-url + +# Benchmark the next 50 blocks with a different subcommand +reth-bench new-payload-only --advance 50 --jwt-secret --rpc-url +``` + ### Observe Outputs After running the command, `reth-bench` will output benchmark results, showing processing speeds and gas usage, which are useful metrics for analyzing the node's performance. diff --git a/bin/reth-bench/src/bench/context.rs b/bin/reth-bench/src/bench/context.rs index c4006dc8155..75c8592ad3c 100644 --- a/bin/reth-bench/src/bench/context.rs +++ b/bin/reth-bench/src/bench/context.rs @@ -58,10 +58,6 @@ impl BenchContext { .await? .is_empty(); - // If neither `--from` nor `--to` are provided, we will run the benchmark continuously, - // starting at the latest block. - let mut benchmark_mode = BenchMode::new(bench_args.from, bench_args.to)?; - // construct the authenticated provider let auth_jwt = bench_args .auth_jwtsecret @@ -83,6 +79,31 @@ impl BenchContext { let client = ClientBuilder::default().connect_with(auth_transport).await?; let auth_provider = RootProvider::::new(client); + // Computes the block range for the benchmark. + // + // - If `--advance` is provided, fetches the latest block and sets: + // - `from = head + 1` + // - `to = head + advance` + // - Otherwise, uses the values from `--from` and `--to`. + let (from, to) = if let Some(advance) = bench_args.advance { + if advance == 0 { + return Err(eyre::eyre!("--advance must be greater than 0")); + } + + let head_block = auth_provider + .get_block_by_number(BlockNumberOrTag::Latest) + .await? + .ok_or_else(|| eyre::eyre!("Failed to fetch latest block for --advance"))?; + let head_number = head_block.header.number; + (Some(head_number), Some(head_number + advance)) + } else { + (bench_args.from, bench_args.to) + }; + + // If neither `--from` nor `--to` are provided, we will run the benchmark continuously, + // starting at the latest block. + let mut benchmark_mode = BenchMode::new(from, to)?; + let first_block = match benchmark_mode { BenchMode::Continuous => { // fetch Latest block diff --git a/bin/reth-bench/src/bench/new_payload_fcu.rs b/bin/reth-bench/src/bench/new_payload_fcu.rs index ac0ab66a864..90d35edc9b7 100644 --- a/bin/reth-bench/src/bench/new_payload_fcu.rs +++ b/bin/reth-bench/src/bench/new_payload_fcu.rs @@ -15,6 +15,7 @@ use alloy_provider::Provider; use alloy_rpc_types_engine::ForkchoiceState; use clap::Parser; use csv::Writer; +use eyre::Context; use humantime::parse_duration; use reth_cli_runner::CliContext; use reth_node_core::args::BenchmarkArgs; @@ -50,7 +51,11 @@ impl Command { let (sender, mut receiver) = tokio::sync::mpsc::channel(1000); tokio::task::spawn(async move { while benchmark_mode.contains(next_block) { - let block_res = block_provider.get_block_by_number(next_block.into()).full().await; + let block_res = block_provider + .get_block_by_number(next_block.into()) + .full() + .await + .wrap_err_with(|| format!("Failed to fetch block by number {next_block}")); let block = block_res.unwrap().unwrap(); let header = block.header.clone(); @@ -164,7 +169,7 @@ impl Command { } // accumulate the results and calculate the overall Ggas/s - let gas_output = TotalGasOutput::new(gas_output_results); + let gas_output = TotalGasOutput::new(gas_output_results)?; info!( total_duration=?gas_output.total_duration, total_gas_used=?gas_output.total_gas_used, diff --git a/bin/reth-bench/src/bench/new_payload_only.rs b/bin/reth-bench/src/bench/new_payload_only.rs index 8dda7df4ecd..34fe3780553 100644 --- a/bin/reth-bench/src/bench/new_payload_only.rs +++ b/bin/reth-bench/src/bench/new_payload_only.rs @@ -13,6 +13,7 @@ use crate::{ use alloy_provider::Provider; use clap::Parser; use csv::Writer; +use eyre::Context; use reth_cli_runner::CliContext; use reth_node_core::args::BenchmarkArgs; use std::time::{Duration, Instant}; @@ -43,7 +44,11 @@ impl Command { let (sender, mut receiver) = tokio::sync::mpsc::channel(1000); tokio::task::spawn(async move { while benchmark_mode.contains(next_block) { - let block_res = block_provider.get_block_by_number(next_block.into()).full().await; + let block_res = block_provider + .get_block_by_number(next_block.into()) + .full() + .await + .wrap_err_with(|| format!("Failed to fetch block by number {next_block}")); let block = block_res.unwrap().unwrap(); let header = block.header.clone(); @@ -118,7 +123,7 @@ impl Command { } // accumulate the results and calculate the overall Ggas/s - let gas_output = TotalGasOutput::new(gas_output_results); + let gas_output = TotalGasOutput::new(gas_output_results)?; info!( total_duration=?gas_output.total_duration, total_gas_used=?gas_output.total_gas_used, diff --git a/bin/reth-bench/src/bench/output.rs b/bin/reth-bench/src/bench/output.rs index 4fe463e91a5..794cd2768df 100644 --- a/bin/reth-bench/src/bench/output.rs +++ b/bin/reth-bench/src/bench/output.rs @@ -1,6 +1,7 @@ //! Contains various benchmark output formats, either for logging or for //! serialization to / from files. +use eyre::OptionExt; use reth_primitives_traits::constants::GIGAGAS; use serde::{ser::SerializeStruct, Serialize}; use std::time::Duration; @@ -52,7 +53,7 @@ impl Serialize for NewPayloadResult { { // convert the time to microseconds let time = self.latency.as_micros(); - let mut state = serializer.serialize_struct("NewPayloadResult", 3)?; + let mut state = serializer.serialize_struct("NewPayloadResult", 2)?; state.serialize_field("gas_used", &self.gas_used)?; state.serialize_field("latency", &time)?; state.end() @@ -145,15 +146,14 @@ pub(crate) struct TotalGasOutput { impl TotalGasOutput { /// Create a new [`TotalGasOutput`] from a list of [`TotalGasRow`]. - pub(crate) fn new(rows: Vec) -> Self { + pub(crate) fn new(rows: Vec) -> eyre::Result { // the duration is obtained from the last row - let total_duration = - rows.last().map(|row| row.time).expect("the row has at least one element"); + let total_duration = rows.last().map(|row| row.time).ok_or_eyre("empty results")?; let blocks_processed = rows.len() as u64; let total_gas_used: u64 = rows.into_iter().map(|row| row.gas_used).sum(); let total_gas_per_second = total_gas_used as f64 / total_duration.as_secs_f64(); - Self { total_gas_used, total_duration, total_gas_per_second, blocks_processed } + Ok(Self { total_gas_used, total_duration, total_gas_per_second, blocks_processed }) } /// Return the total gigagas per second. diff --git a/bin/reth-bench/src/main.rs b/bin/reth-bench/src/main.rs index f146af0f70d..7d7305591bb 100644 --- a/bin/reth-bench/src/main.rs +++ b/bin/reth-bench/src/main.rs @@ -26,7 +26,9 @@ use reth_cli_runner::CliRunner; fn main() { // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. if std::env::var_os("RUST_BACKTRACE").is_none() { - std::env::set_var("RUST_BACKTRACE", "1"); + unsafe { + std::env::set_var("RUST_BACKTRACE", "1"); + } } // Run until either exit or sigint or sigterm diff --git a/clippy.toml b/clippy.toml index 1e75cb34f32..9ddf1014802 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,4 +1,3 @@ -msrv = "1.88" too-large-for-stack = 128 doc-valid-idents = [ "P2P", diff --git a/crates/chain-state/Cargo.toml b/crates/chain-state/Cargo.toml index be3b5a981d1..cba12995015 100644 --- a/crates/chain-state/Cargo.toml +++ b/crates/chain-state/Cargo.toml @@ -54,6 +54,7 @@ reth-testing-utils.workspace = true alloy-signer.workspace = true alloy-signer-local.workspace = true rand.workspace = true +criterion.workspace = true [features] serde = [ @@ -82,3 +83,8 @@ test-utils = [ "reth-trie/test-utils", "reth-ethereum-primitives/test-utils", ] + +[[bench]] +name = "canonical_hashes_range" +harness = false +required-features = ["test-utils"] diff --git a/crates/chain-state/benches/canonical_hashes_range.rs b/crates/chain-state/benches/canonical_hashes_range.rs new file mode 100644 index 00000000000..58fdd73bf99 --- /dev/null +++ b/crates/chain-state/benches/canonical_hashes_range.rs @@ -0,0 +1,99 @@ +#![allow(missing_docs)] + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use reth_chain_state::{ + test_utils::TestBlockBuilder, ExecutedBlockWithTrieUpdates, MemoryOverlayStateProviderRef, +}; +use reth_ethereum_primitives::EthPrimitives; +use reth_storage_api::{noop::NoopProvider, BlockHashReader}; + +criterion_group!(benches, bench_canonical_hashes_range); +criterion_main!(benches); + +fn bench_canonical_hashes_range(c: &mut Criterion) { + let mut group = c.benchmark_group("canonical_hashes_range"); + + let scenarios = [("small", 10), ("medium", 100), ("large", 1000)]; + + for (name, num_blocks) in scenarios { + group.bench_function(format!("{}_blocks_{}", name, num_blocks), |b| { + let (provider, blocks) = setup_provider_with_blocks(num_blocks); + let start_block = blocks[0].recovered_block().number; + let end_block = blocks[num_blocks / 2].recovered_block().number; + + b.iter(|| { + black_box( + provider + .canonical_hashes_range(black_box(start_block), black_box(end_block)) + .unwrap(), + ) + }) + }); + } + + let (provider, blocks) = setup_provider_with_blocks(500); + let base_block = blocks[100].recovered_block().number; + + let range_sizes = [1, 10, 50, 100, 250]; + for range_size in range_sizes { + group.bench_function(format!("range_size_{}", range_size), |b| { + let end_block = base_block + range_size; + + b.iter(|| { + black_box( + provider + .canonical_hashes_range(black_box(base_block), black_box(end_block)) + .unwrap(), + ) + }) + }); + } + + // Benchmark edge cases + group.bench_function("no_in_memory_matches", |b| { + let (provider, blocks) = setup_provider_with_blocks(100); + let first_block = blocks[0].recovered_block().number; + let start_block = first_block - 50; + let end_block = first_block - 10; + + b.iter(|| { + black_box( + provider + .canonical_hashes_range(black_box(start_block), black_box(end_block)) + .unwrap(), + ) + }) + }); + + group.bench_function("all_in_memory_matches", |b| { + let (provider, blocks) = setup_provider_with_blocks(100); + let first_block = blocks[0].recovered_block().number; + let last_block = blocks[blocks.len() - 1].recovered_block().number; + + b.iter(|| { + black_box( + provider + .canonical_hashes_range(black_box(first_block), black_box(last_block + 1)) + .unwrap(), + ) + }) + }); + + group.finish(); +} + +fn setup_provider_with_blocks( + num_blocks: usize, +) -> ( + MemoryOverlayStateProviderRef<'static, EthPrimitives>, + Vec>, +) { + let mut builder = TestBlockBuilder::::default(); + + let blocks: Vec<_> = builder.get_executed_blocks(1000..1000 + num_blocks as u64).collect(); + + let historical = Box::new(NoopProvider::default()); + let provider = MemoryOverlayStateProviderRef::new(historical, blocks.clone()); + + (provider, blocks) +} diff --git a/crates/chain-state/src/in_memory.rs b/crates/chain-state/src/in_memory.rs index 72fc392fef1..cd194db81e3 100644 --- a/crates/chain-state/src/in_memory.rs +++ b/crates/chain-state/src/in_memory.rs @@ -6,7 +6,7 @@ use crate::{ }; use alloy_consensus::{transaction::TransactionMeta, BlockHeader}; use alloy_eips::{BlockHashOrNumber, BlockNumHash}; -use alloy_primitives::{map::HashMap, TxHash, B256}; +use alloy_primitives::{map::HashMap, BlockNumber, TxHash, B256}; use parking_lot::RwLock; use reth_chainspec::ChainInfo; use reth_ethereum_primitives::EthPrimitives; @@ -43,8 +43,9 @@ pub(crate) struct InMemoryStateMetrics { /// /// # Locking behavior on state updates /// -/// All update calls must be atomic, meaning that they must acquire all locks at once, before -/// modifying the state. This is to ensure that the internal state is always consistent. +/// All update calls must acquire all locks at once before modifying state to ensure the internal +/// state remains consistent. This prevents readers from observing partially updated state where +/// the numbers and blocks maps are out of sync. /// Update functions ensure that the numbers write lock is always acquired first, because lookup by /// numbers first read the numbers map and then the blocks map. /// By acquiring the numbers lock first, we ensure that read-only lookups don't deadlock updates. @@ -765,6 +766,12 @@ impl ExecutedBlock { pub fn hashed_state(&self) -> &HashedPostState { &self.hashed_state } + + /// Returns a [`BlockNumber`] of the block. + #[inline] + pub fn block_number(&self) -> BlockNumber { + self.recovered_block.header().number() + } } /// Trie updates that result from calculating the state root for the block. diff --git a/crates/chain-state/src/memory_overlay.rs b/crates/chain-state/src/memory_overlay.rs index dfb76d0e583..a035d833a46 100644 --- a/crates/chain-state/src/memory_overlay.rs +++ b/crates/chain-state/src/memory_overlay.rs @@ -21,7 +21,7 @@ pub struct MemoryOverlayStateProviderRef< 'a, N: NodePrimitives = reth_ethereum_primitives::EthPrimitives, > { - /// Historical state provider for state lookups that are not found in in-memory blocks. + /// Historical state provider for state lookups that are not found in memory blocks. pub(crate) historical: Box, /// The collection of executed parent blocks. Expected order is newest to oldest. pub(crate) in_memory: Vec>, @@ -84,14 +84,22 @@ impl BlockHashReader for MemoryOverlayStateProviderRef<'_, N> ) -> ProviderResult> { let range = start..end; let mut earliest_block_number = None; - let mut in_memory_hashes = Vec::new(); + let mut in_memory_hashes = Vec::with_capacity(range.size_hint().0); + + // iterate in ascending order (oldest to newest = low to high) for block in &self.in_memory { - if range.contains(&block.recovered_block().number()) { - in_memory_hashes.insert(0, block.recovered_block().hash()); - earliest_block_number = Some(block.recovered_block().number()); + let block_num = block.recovered_block().number(); + if range.contains(&block_num) { + in_memory_hashes.push(block.recovered_block().hash()); + earliest_block_number = Some(block_num); } } + // `self.in_memory` stores executed blocks in ascending order (oldest to newest). + // However, `in_memory_hashes` should be constructed in descending order (newest to oldest), + // so we reverse the vector after collecting the hashes. + in_memory_hashes.reverse(); + let mut hashes = self.historical.canonical_hashes_range(start, earliest_block_number.unwrap_or(end))?; hashes.append(&mut in_memory_hashes); diff --git a/crates/chain-state/src/notifications.rs b/crates/chain-state/src/notifications.rs index abf2405c872..1d2f4df10fa 100644 --- a/crates/chain-state/src/notifications.rs +++ b/crates/chain-state/src/notifications.rs @@ -122,16 +122,36 @@ impl CanonStateNotification { } } - /// Get the new tip of the chain. + /// Gets the new tip of the chain. /// /// Returns the new tip for [`Self::Reorg`] and [`Self::Commit`] variants which commit at least /// 1 new block. + /// + /// # Panics + /// + /// If chain doesn't have any blocks. pub fn tip(&self) -> &RecoveredBlock { match self { Self::Commit { new } | Self::Reorg { new, .. } => new.tip(), } } + /// Gets the new tip of the chain. + /// + /// If the chain has no blocks, it returns `None`. Otherwise, it returns the new tip for + /// [`Self::Reorg`] and [`Self::Commit`] variants. + pub fn tip_checked(&self) -> Option<&RecoveredBlock> { + match self { + Self::Commit { new } | Self::Reorg { new, .. } => { + if new.is_empty() { + None + } else { + Some(new.tip()) + } + } + } + } + /// Get receipts in the reverted and newly imported chain segments with their corresponding /// block numbers and transaction hashes. /// diff --git a/crates/chainspec/res/genesis/dev.json b/crates/chainspec/res/genesis/dev.json index ed0522167b0..e12037ab44b 100644 --- a/crates/chainspec/res/genesis/dev.json +++ b/crates/chainspec/res/genesis/dev.json @@ -1 +1,78 @@ -{"nonce":"0x0","timestamp":"0x6490fdd2","extraData":"0x","gasLimit":"0x1c9c380","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","stateRoot":"0x5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494","alloc":{"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266":{"balance":"0xD3C21BCECCEDA1000000"},"0x70997970C51812dc3A010C7d01b50e0d17dc79C8":{"balance":"0xD3C21BCECCEDA1000000"},"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC":{"balance":"0xD3C21BCECCEDA1000000"},"0x90F79bf6EB2c4f870365E785982E1f101E93b906":{"balance":"0xD3C21BCECCEDA1000000"},"0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65":{"balance":"0xD3C21BCECCEDA1000000"},"0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc":{"balance":"0xD3C21BCECCEDA1000000"},"0x976EA74026E726554dB657fA54763abd0C3a0aa9":{"balance":"0xD3C21BCECCEDA1000000"},"0x14dC79964da2C08b23698B3D3cc7Ca32193d9955":{"balance":"0xD3C21BCECCEDA1000000"},"0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f":{"balance":"0xD3C21BCECCEDA1000000"},"0xa0Ee7A142d267C1f36714E4a8F75612F20a79720":{"balance":"0xD3C21BCECCEDA1000000"},"0xBcd4042DE499D14e55001CcbB24a551F3b954096":{"balance":"0xD3C21BCECCEDA1000000"},"0x71bE63f3384f5fb98995898A86B02Fb2426c5788":{"balance":"0xD3C21BCECCEDA1000000"},"0xFABB0ac9d68B0B445fB7357272Ff202C5651694a":{"balance":"0xD3C21BCECCEDA1000000"},"0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec":{"balance":"0xD3C21BCECCEDA1000000"},"0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097":{"balance":"0xD3C21BCECCEDA1000000"},"0xcd3B766CCDd6AE721141F452C550Ca635964ce71":{"balance":"0xD3C21BCECCEDA1000000"},"0x2546BcD3c84621e976D8185a91A922aE77ECEc30":{"balance":"0xD3C21BCECCEDA1000000"},"0xbDA5747bFD65F08deb54cb465eB87D40e51B197E":{"balance":"0xD3C21BCECCEDA1000000"},"0xdD2FD4581271e230360230F9337D5c0430Bf44C0":{"balance":"0xD3C21BCECCEDA1000000"},"0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199":{"balance":"0xD3C21BCECCEDA1000000"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"} \ No newline at end of file +{ + "nonce": "0x0", + "timestamp": "0x6490fdd2", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494", + "alloc": { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x90F79bf6EB2c4f870365E785982E1f101E93b906": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x976EA74026E726554dB657fA54763abd0C3a0aa9": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xBcd4042DE499D14e55001CcbB24a551F3b954096": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x71bE63f3384f5fb98995898A86B02Fb2426c5788": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xcd3B766CCDd6AE721141F452C550Ca635964ce71": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x4300000000000000000000000000000000000001": { + "code": "0x6080604052600436106101c8575f3560e01c806399c6066c116100f2578063c55b6bb711610092578063e559afd911610062578063e559afd9146107c3578063e73a914c146107e2578063f6e4b62b14610801578063fac2c62114610820575f80fd5b8063c55b6bb7146106ee578063d124d1bc1461070d578063d7e5fbf31461073e578063d9ba32fc1461075d575f80fd5b8063ad3e080a116100cd578063ad3e080a1461062e578063b6b352721461064d578063c375c2ef1461066c578063c3c5a5471461068b575f80fd5b806399c6066c146105865780639e4f8ab8146105a55780639f8a13d7146105c6575f80fd5b80633b66e9f61161016857806364efb22b1161013857806364efb22b1461040e57806369dc9ff3146104775780637901868e14610548578063871ff40514610567575f80fd5b80633b66e9f6146103035780634162169f146103665780634782f779146103d05780635e35359e146103ef575f80fd5b806315ea16ad116101a357806315ea16ad146102695780631c5d647c146102a65780632ce962cf146102c5578063368da168146102e4575f80fd5b8063108f5c69146101d3578063139e0aa7146101f457806314695ea414610207575f80fd5b366101cf57005b5f80fd5b3480156101de575f80fd5b506101f26101ed366004612e30565b61083f565b005b6101f2610202366004612ea8565b610a4f565b348015610212575f80fd5b50610254610221366004612ed2565b5f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db02602052604090205460ff1690565b60405190151581526020015b60405180910390f35b348015610274575f80fd5b507fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db03545b604051908152602001610260565b3480156102b1575f80fd5b506101f26102c0366004612ef6565b610cb6565b3480156102d0575f80fd5b506101f26102df366004612f24565b610e02565b3480156102ef575f80fd5b506101f26102fe366004612ea8565b611000565b34801561030e575f80fd5b5061029861031d366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090206001015490565b348015610371575f80fd5b507fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001610260565b3480156103db575f80fd5b506101f26103ea366004612ea8565b611200565b3480156103fa575f80fd5b506101f2610409366004612f72565b611352565b348015610419575f80fd5b506103ab610428366004612f50565b73ffffffffffffffffffffffffffffffffffffffff9081165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160205260409020546201000090041690565b348015610482575f80fd5b50610501610491366004612f50565b73ffffffffffffffffffffffffffffffffffffffff9081165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090208054600182015460029092015460ff808316956101008404821695620100009094049093169392911690565b604080519515158652931515602086015273ffffffffffffffffffffffffffffffffffffffff9092169284019290925260608301919091521515608082015260a001610260565b348015610553575f80fd5b506101f2610562366004612fb0565b611672565b348015610572575f80fd5b506101f2610581366004612ea8565b6118bc565b348015610591575f80fd5b506101f26105a0366004612ea8565b6119d7565b3480156105b0575f80fd5b506105b9611ac1565b604051610260919061301e565b3480156105d1575f80fd5b506102546105e0366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054610100900460ff1690565b348015610639575f80fd5b506101f2610648366004613061565b611bcf565b348015610658575f80fd5b506102546106673660046130e2565b611d6e565b348015610677575f80fd5b506101f2610686366004612f50565b611e21565b348015610696575f80fd5b506102546106a5366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205460ff1690565b3480156106f9575f80fd5b506101f26107083660046130e2565b611f53565b348015610718575f80fd5b5061072c610727366004612ed2565b6121a0565b6040516102609695949392919061310e565b348015610749575f80fd5b506101f26107583660046130e2565b6122b6565b348015610768575f80fd5b50610254610777366004612f50565b73ffffffffffffffffffffffffffffffffffffffff165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090206002015460ff1690565b3480156107ce575f80fd5b506101f26107dd366004613061565b6124ee565b3480156107ed575f80fd5b506101f26107fc366004612f50565b612692565b34801561080c575f80fd5b506101f261081b366004612f24565b6127e6565b34801561082b575f80fd5b506101f261083a366004612f50565b612957565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146108af576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8590036108e9576040517f8dad8de600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710811115610925576040517fe05f723400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8781527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db02602052604090206001810180546109609061319c565b90505f0361099a576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600181016109a9878983613265565b5060028101859055600381018490556004810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff85161790556005810182905560405188907f73d628d7a9f63d75ab3f23c4bf349bfec022e61cc2ad8dc72f7ca093b45723e890610a3d908a908a908a908a908a908a9061337b565b60405180910390a25050505050505050565b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054829060ff16610ace576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8281527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0260205260409020600181018054610b099061319c565b90505f03610b43576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805460ff16610b7e576040517fd1d5af5600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600481015473ffffffffffffffffffffffffffffffffffffffff16610bab57610ba681612a89565b610bec565b3415610be3576040517ffbccebae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b610bec81612b12565b600381015473ffffffffffffffffffffffffffffffffffffffff85165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604081206001018054909190610c47908490613428565b92505081905550828473ffffffffffffffffffffffffffffffffffffffff167f7852f393fd6a99c61648e39af92ae0e784b77281fc2af871edce1b51304ecd7c83600301548460020154604051610ca8929190918252602082015260400190565b60405180910390a350505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314610d26576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f8281527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0260205260409020600181018054610d619061319c565b90505f03610d9b576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016821515908117825560405190815283907f10ae08733732b5e10d63d501510950b2a5967607149b3608881ecde96515780c906020015b60405180910390a2505050565b73ffffffffffffffffffffffffffffffffffffffff8083165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205483917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0091620100009004163314801590610e985750805473ffffffffffffffffffffffffffffffffffffffff163314155b15610ecf576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054849060ff16610f4e576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff85165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101008915159081029190911790915591519182527fa5ab8b72c18a722b7e92b557d227ba48dc2985b22fce6d0f95804be26703b595910160405180910390a25050505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611070576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054829060ff166110ef576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090206001015482811015611170576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61117a838261343b565b73ffffffffffffffffffffffffffffffffffffffff85165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020908152604091829020600101939093555185815290917f30a9d8d098632f590e4953b6171a6c999d2b1c4170ebde38136c9e27e6976b8191015b60405180910390a250505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611270576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff81166112be576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b475f83156112cc57836112ce565b815b90508181111561130a576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff86169082156108fc029083905f818181858888f1935050505015801561134a573d5f803e3d5ffd5b505050505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146113c2576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff8116611410576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166114bf57475f8315611439578361143b565b815b905081811115611477576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff86169082156108fc029083905f818181858888f193505050501580156114b7573d5f803e3d5ffd5b50505061166c565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015284905f9073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa15801561152b573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061154f919061344e565b90505f841561155e5784611560565b815b90508181111561159c576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff87811660048301526024820183905284169063a9059cbb906044016020604051808303815f875af115801561160e573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116329190613465565b611668576040517f045c4b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050505b50505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146116e2576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f85900361171c576040517f8dad8de600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710811115611758576040517fe05f723400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db03545f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db026020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811782557fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db009291908101611802898b83613265565b506002810187905560038082018790556004820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff88161790556005820185905583018054905f61186a83613480565b9190505550817f85855a4353e16703440df33dd6903f8689955fe665d2ed5b918f7a272286c8b98a8a8a8a8a8a6040516118a99695949392919061337b565b60405180910390a2505050505050505050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff16331461192c576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604081206001018054839290611982908490613428565b909155505060405181815273ffffffffffffffffffffffffffffffffffffffff8316907fed46984c46e11f42ec323727ba7d99dc16be2d248a8aaa8982d492688497f09d906020015b60405180910390a25050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611a47576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602090815260409182902060010184905590518381527fc2748283b871105da37ea4bdc2cc08eff4b3b0f472f66f2728cdf4a1b845ef7791016119cb565b60607fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005f60015b8260030154811015611b22575f81815260028401602052604090205460ff1615611b1a5781611b1681613480565b9250505b600101611ae8565b505f8167ffffffffffffffff811115611b3d57611b3d6131ed565b604051908082528060200260200182016040528015611b66578160200160208202803683370190505b5090505f60015b8460030154811015611bc5575f81815260028601602052604090205460ff1615611bbd5780838381518110611ba457611ba46134b7565b602090810291909101015281611bb981613480565b9250505b600101611b6d565b5090949350505050565b73ffffffffffffffffffffffffffffffffffffffff8084165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205484917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0091620100009004163314801590611c655750805473ffffffffffffffffffffffffffffffffffffffff163314155b15611c9c576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8381101561134a5773ffffffffffffffffffffffffffffffffffffffff86165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040812060030181878785818110611cff57611cff6134b7565b9050602002016020810190611d149190612f50565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040015f2080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055600101611c9e565b73ffffffffffffffffffffffffffffffffffffffff82165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604081206002015460ff161580611e18575073ffffffffffffffffffffffffffffffffffffffff8381165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160209081526040808320938616835260039093019052205460ff165b90505b92915050565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314611e91576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604080822080547fffffffffffffffffffff000000000000000000000000000000000000000000001681556001810183905560020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517f8d30d41865a0b811b9545d879520d2dde9f4cc49e4241f486ad9752bc904b5659190a250565b73ffffffffffffffffffffffffffffffffffffffff8083165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205483917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0091620100009004163314801590611fe95750805473ffffffffffffffffffffffffffffffffffffffff163314155b15612020576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db016020526040902054849060ff1661209f576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff81166120ed576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8681165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160205260408082208054620100008b87168181027fffffffffffffffffffff0000000000000000000000000000000000000000ffff84161790935592519290049094169392849290917f4eb572e99196bed0270fbd5b17a948e19c3f50a97838cb0d2a75a823ff8e6c509190a450505050505050565b5f606081808080807fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005f89815260029182016020526040902080549181015460038201546004830154600584015460018501805495975060ff909616959473ffffffffffffffffffffffffffffffffffffffff9092169185906122229061319c565b80601f016020809104026020016040519081016040528092919081815260200182805461224e9061319c565b80156122995780601f1061227057610100808354040283529160200191612299565b820191905f5260205f20905b81548152906001019060200180831161227c57829003601f168201915b505050505094509650965096509650965096505091939550919395565b8173ffffffffffffffffffffffffffffffffffffffff8116612304576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff8116612352576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205460ff16156123d0576040517f3a81d6fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b833b5f81900361240c576040517f6eefed2000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8581165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604080822080546101017fffffffffffffffffffff0000000000000000000000000000000000000000000090911662010000968b169687021717815560018082018490556002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001690911790559051909392917f768fb430a0d4b201cb764ab221c316dd14d8babf2e4b2348e05964c6565318b691a3505050505050565b73ffffffffffffffffffffffffffffffffffffffff8084165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205484917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db00916201000090041633148015906125845750805473ffffffffffffffffffffffffffffffffffffffff163314155b156125bb576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5b8381101561134a5773ffffffffffffffffffffffffffffffffffffffff86165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db0160205260408120600191600390910190878785818110612623576126236134b7565b90506020020160208101906126389190612f50565b73ffffffffffffffffffffffffffffffffffffffff16815260208101919091526040015f2080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790556001016125bd565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff163314612702576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff8116612750576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db00805473ffffffffffffffffffffffffffffffffffffffff8481167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f0429168a83556e356cd18563753346b9c9567cbf0fbea148d40aeb84a76cc5b9905f90a3505050565b73ffffffffffffffffffffffffffffffffffffffff8083165f9081527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604090205483917fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db009162010000900416331480159061287c5750805473ffffffffffffffffffffffffffffffffffffffff163314155b156128b3576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602090815260409182902060020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001687151590811790915591519182527f8daaf060c3306c38e068a75c054bf96ecd85a3db1252712c4d93632744c42e0d91016111f2565b7fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db005473ffffffffffffffffffffffffffffffffffffffff1633146129c7576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff81165f8181527fc2eaf2cedf9e23687c6eb7c4717aa3eacbd015cc86eaad3f51aae2d3c955db01602052604080822080547fffffffffffffffffffff000000000000000000000000000000000000000000001681556001810183905560020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517f3475b9891ecf29e996feed01eeb42a860ec225283a439d214ffaeac5e006be7d9190a250565b8060020154341015612ac7576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8060020154341115612b0f57600281015433906108fc90612ae8903461343b565b6040518115909202915f818181858888f19350505050158015612b0d573d5f803e3d5ffd5b505b50565b60048181015460028301546040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152339381019390935230602484015273ffffffffffffffffffffffffffffffffffffffff90911691829063dd62ed3e90604401602060405180830381865afa158015612b90573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612bb4919061344e565b1015612bec576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028201546040517f23b872dd000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481019190915273ffffffffffffffffffffffffffffffffffffffff8216906323b872dd906064016020604051808303815f875af1158015612c68573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c8c9190613465565b612cc2576040517f045c4b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600582015415612b0d575f61271083600501548460020154612ce491906134e4565b612cee91906134fb565b6004808501546040517f42966c6800000000000000000000000000000000000000000000000000000000815292935073ffffffffffffffffffffffffffffffffffffffff16916342966c6891612d4a9185910190815260200190565b5f604051808303815f87803b158015612d61575f80fd5b505af1925050508015612d72575060015b15612dc557600483015460405182815273ffffffffffffffffffffffffffffffffffffffff909116907ffd38818f5291bf0bb3a2a48aadc06ba8757865d1dabd804585338aab3009dcb690602001610df5565b505050565b5f8083601f840112612dda575f80fd5b50813567ffffffffffffffff811115612df1575f80fd5b602083019150836020828501011115612e08575f80fd5b9250929050565b73ffffffffffffffffffffffffffffffffffffffff81168114612b0f575f80fd5b5f805f805f805f60c0888a031215612e46575f80fd5b87359650602088013567ffffffffffffffff811115612e63575f80fd5b612e6f8a828b01612dca565b90975095505060408801359350606088013592506080880135612e9181612e0f565b8092505060a0880135905092959891949750929550565b5f8060408385031215612eb9575f80fd5b8235612ec481612e0f565b946020939093013593505050565b5f60208284031215612ee2575f80fd5b5035919050565b8015158114612b0f575f80fd5b5f8060408385031215612f07575f80fd5b823591506020830135612f1981612ee9565b809150509250929050565b5f8060408385031215612f35575f80fd5b8235612f4081612e0f565b91506020830135612f1981612ee9565b5f60208284031215612f60575f80fd5b8135612f6b81612e0f565b9392505050565b5f805f60608486031215612f84575f80fd5b8335612f8f81612e0f565b92506020840135612f9f81612e0f565b929592945050506040919091013590565b5f805f805f8060a08789031215612fc5575f80fd5b863567ffffffffffffffff811115612fdb575f80fd5b612fe789828a01612dca565b9097509550506020870135935060408701359250606087013561300981612e0f565b80925050608087013590509295509295509295565b602080825282518282018190525f9190848201906040850190845b8181101561305557835183529284019291840191600101613039565b50909695505050505050565b5f805f60408486031215613073575f80fd5b833561307e81612e0f565b9250602084013567ffffffffffffffff8082111561309a575f80fd5b818601915086601f8301126130ad575f80fd5b8135818111156130bb575f80fd5b8760208260051b85010111156130cf575f80fd5b6020830194508093505050509250925092565b5f80604083850312156130f3575f80fd5b82356130fe81612e0f565b91506020830135612f1981612e0f565b861515815260c060208201525f86518060c0840152806020890160e085015e5f60e0828501015260e07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011684010191505085604083015284606083015273ffffffffffffffffffffffffffffffffffffffff841660808301528260a0830152979650505050505050565b600181811c908216806131b057607f821691505b6020821081036131e7577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b601f821115612dc557805f5260205f20601f840160051c8101602085101561323f5750805b601f840160051c820191505b8181101561325e575f815560010161324b565b5050505050565b67ffffffffffffffff83111561327d5761327d6131ed565b6132918361328b835461319c565b8361321a565b5f601f8411600181146132e1575f85156132ab5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b17835561325e565b5f838152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08716915b8281101561332e578685013582556020948501946001909201910161330e565b5086821015613369577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555050505050565b60a081528560a0820152858760c08301375f60c087830101525f60c07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f890116830101905085602083015284604083015273ffffffffffffffffffffffffffffffffffffffff84166060830152826080830152979650505050505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b80820180821115611e1b57611e1b6133fb565b81810381811115611e1b57611e1b6133fb565b5f6020828403121561345e575f80fd5b5051919050565b5f60208284031215613475575f80fd5b8151612f6b81612ee9565b5f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036134b0576134b06133fb565b5060010190565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b8082028115828204841417611e1b57611e1b6133fb565b5f8261352e577f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b50049056fea164736f6c6343000819000a" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/crates/chainspec/src/api.rs b/crates/chainspec/src/api.rs index cb5b47bc245..80327d38b6d 100644 --- a/crates/chainspec/src/api.rs +++ b/crates/chainspec/src/api.rs @@ -24,9 +24,6 @@ pub trait EthChainSpec: Send + Sync + Unpin + Debug { self.chain().id() } - /// Get the [`BaseFeeParams`] for the chain at the given block. - fn base_fee_params_at_block(&self, block_number: u64) -> BaseFeeParams; - /// Get the [`BaseFeeParams`] for the chain at the given timestamp. fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams; @@ -85,10 +82,6 @@ impl EthChainSpec for ChainSpec { self.chain } - fn base_fee_params_at_block(&self, block_number: u64) -> BaseFeeParams { - self.base_fee_params_at_block(block_number) - } - fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams { self.base_fee_params_at_timestamp(timestamp) } diff --git a/crates/chainspec/src/spec.rs b/crates/chainspec/src/spec.rs index 2800640b708..02b199220b0 100644 --- a/crates/chainspec/src/spec.rs +++ b/crates/chainspec/src/spec.rs @@ -3,19 +3,20 @@ use alloy_evm::eth::spec::EthExecutorSpec; use crate::{ constants::{MAINNET_DEPOSIT_CONTRACT, MAINNET_PRUNE_DELETE_LIMIT}, - EthChainSpec, + holesky, hoodi, mainnet, sepolia, EthChainSpec, }; use alloc::{boxed::Box, sync::Arc, vec::Vec}; use alloy_chains::{Chain, NamedChain}; use alloy_consensus::{ constants::{ - DEV_GENESIS_HASH, EMPTY_WITHDRAWALS, HOLESKY_GENESIS_HASH, HOODI_GENESIS_HASH, - MAINNET_GENESIS_HASH, SEPOLIA_GENESIS_HASH, + EMPTY_WITHDRAWALS, HOLESKY_GENESIS_HASH, HOODI_GENESIS_HASH, MAINNET_GENESIS_HASH, + SEPOLIA_GENESIS_HASH, }, Header, }; use alloy_eips::{ - eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7892::BlobScheduleBlobParams, + eip1559::INITIAL_BASE_FEE, eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams, + eip7892::BlobScheduleBlobParams, }; use alloy_genesis::Genesis; use alloy_primitives::{address, b256, Address, BlockNumber, B256, U256}; @@ -107,7 +108,10 @@ pub static MAINNET: LazyLock> = LazyLock::new(|| { deposit_contract: Some(MAINNET_DEPOSIT_CONTRACT), base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: MAINNET_PRUNE_DELETE_LIMIT, - blob_params: BlobScheduleBlobParams::default(), + blob_params: BlobScheduleBlobParams::default().with_scheduled([ + (mainnet::MAINNET_BPO1_TIMESTAMP, BlobParams::bpo1()), + (mainnet::MAINNET_BPO2_TIMESTAMP, BlobParams::bpo2()), + ]), }; spec.genesis.config.dao_fork_support = true; spec.into() @@ -136,7 +140,10 @@ pub static SEPOLIA: LazyLock> = LazyLock::new(|| { )), base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 10000, - blob_params: BlobScheduleBlobParams::default(), + blob_params: BlobScheduleBlobParams::default().with_scheduled([ + (sepolia::SEPOLIA_BPO1_TIMESTAMP, BlobParams::bpo1()), + (sepolia::SEPOLIA_BPO2_TIMESTAMP, BlobParams::bpo2()), + ]), }; spec.genesis.config.dao_fork_support = true; spec.into() @@ -163,7 +170,10 @@ pub static HOLESKY: LazyLock> = LazyLock::new(|| { )), base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 10000, - blob_params: BlobScheduleBlobParams::default(), + blob_params: BlobScheduleBlobParams::default().with_scheduled([ + (holesky::HOLESKY_BPO1_TIMESTAMP, BlobParams::bpo1()), + (holesky::HOLESKY_BPO2_TIMESTAMP, BlobParams::bpo2()), + ]), }; spec.genesis.config.dao_fork_support = true; spec.into() @@ -192,7 +202,10 @@ pub static HOODI: LazyLock> = LazyLock::new(|| { )), base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), prune_delete_limit: 10000, - blob_params: BlobScheduleBlobParams::default(), + blob_params: BlobScheduleBlobParams::default().with_scheduled([ + (hoodi::HOODI_BPO1_TIMESTAMP, BlobParams::bpo1()), + (hoodi::HOODI_BPO2_TIMESTAMP, BlobParams::bpo2()), + ]), }; spec.genesis.config.dao_fork_support = true; spec.into() @@ -208,13 +221,10 @@ pub static DEV: LazyLock> = LazyLock::new(|| { let hardforks = DEV_HARDFORKS.clone(); ChainSpec { chain: Chain::dev(), - genesis_header: SealedHeader::new( - make_genesis_header(&genesis, &hardforks), - DEV_GENESIS_HASH, - ), + genesis_header: SealedHeader::seal_slow(make_genesis_header(&genesis, &hardforks)), genesis, paris_block_and_final_difficulty: Some((0, U256::from(0))), - hardforks: DEV_HARDFORKS.clone(), + hardforks, base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), deposit_contract: None, // TODO: do we even have? ..Default::default() @@ -393,25 +403,6 @@ impl ChainSpec { } } - /// Get the [`BaseFeeParams`] for the chain at the given block number - pub fn base_fee_params_at_block(&self, block_number: u64) -> BaseFeeParams { - match self.base_fee_params { - BaseFeeParamsKind::Constant(bf_params) => bf_params, - BaseFeeParamsKind::Variable(ForkBaseFeeParams(ref bf_params)) => { - // Walk through the base fee params configuration in reverse order, and return the - // first one that corresponds to a hardfork that is active at the - // given timestamp. - for (fork, params) in bf_params.iter().rev() { - if self.hardforks.is_fork_active_at_block(fork.clone(), block_number) { - return *params - } - } - - bf_params.first().map(|(_, params)| *params).unwrap_or(BaseFeeParams::ethereum()) - } - } - } - /// Get the hash of the genesis block. pub fn genesis_hash(&self) -> B256 { self.genesis_header.hash() @@ -474,8 +465,8 @@ impl ChainSpec { /// Creates a [`ForkFilter`] for the block described by [Head]. pub fn fork_filter(&self, head: Head) -> ForkFilter { let forks = self.hardforks.forks_iter().filter_map(|(_, condition)| { - // We filter out TTD-based forks w/o a pre-known block since those do not show up in the - // fork filter. + // We filter out TTD-based forks w/o a pre-known block since those do not show up in + // the fork filter. Some(match condition { ForkCondition::Block(block) | ForkCondition::TTD { fork_block: Some(block), .. } => ForkFilterKey::Block(block), @@ -689,6 +680,11 @@ impl From for ChainSpec { (EthereumHardfork::Cancun.boxed(), genesis.config.cancun_time), (EthereumHardfork::Prague.boxed(), genesis.config.prague_time), (EthereumHardfork::Osaka.boxed(), genesis.config.osaka_time), + (EthereumHardfork::Bpo1.boxed(), genesis.config.bpo1_time), + (EthereumHardfork::Bpo2.boxed(), genesis.config.bpo2_time), + (EthereumHardfork::Bpo3.boxed(), genesis.config.bpo3_time), + (EthereumHardfork::Bpo4.boxed(), genesis.config.bpo4_time), + (EthereumHardfork::Bpo5.boxed(), genesis.config.bpo5_time), ]; let mut time_hardforks = time_hardfork_opts @@ -804,6 +800,12 @@ impl ChainSpecBuilder { self } + /// Resets any existing hardforks from the builder. + pub fn reset(mut self) -> Self { + self.hardforks = ChainHardforks::default(); + self + } + /// Set the genesis block. pub fn genesis(mut self, genesis: Genesis) -> Self { self.genesis = Some(genesis); @@ -942,6 +944,12 @@ impl ChainSpecBuilder { self } + /// Enable Prague at the given timestamp. + pub fn with_prague_at(mut self, timestamp: u64) -> Self { + self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(timestamp)); + self + } + /// Enable Osaka at genesis. pub fn osaka_activated(mut self) -> Self { self = self.prague_activated(); @@ -949,6 +957,12 @@ impl ChainSpecBuilder { self } + /// Enable Osaka at the given timestamp. + pub fn with_osaka_at(mut self, timestamp: u64) -> Self { + self.hardforks.insert(EthereumHardfork::Osaka, ForkCondition::Timestamp(timestamp)); + self + } + /// Build the resulting [`ChainSpec`]. /// /// # Panics @@ -1087,7 +1101,10 @@ Merge hard forks: Post-merge hard forks (timestamp based): - Shanghai @1681338455 - Cancun @1710338135 -- Prague @1746612311" +- Prague @1746612311 +- Osaka @1764798551 +- Bpo1 @1765978199 +- Bpo2 @1767747671" ); } @@ -1331,7 +1348,10 @@ Post-merge hard forks (timestamp based): ), ( EthereumHardfork::Prague, - ForkId { hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), next: 0 }, + ForkId { + hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), + next: mainnet::MAINNET_OSAKA_TIMESTAMP, + }, ), ], ); @@ -1396,7 +1416,10 @@ Post-merge hard forks (timestamp based): ), ( EthereumHardfork::Prague, - ForkId { hash: ForkHash([0xed, 0x88, 0xb5, 0xfd]), next: 0 }, + ForkId { + hash: ForkHash([0xed, 0x88, 0xb5, 0xfd]), + next: sepolia::SEPOLIA_OSAKA_TIMESTAMP, + }, ), ], ); @@ -1472,12 +1495,22 @@ Post-merge hard forks (timestamp based): // First Prague block ( Head { number: 20000002, timestamp: 1746612311, ..Default::default() }, - ForkId { hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), next: 0 }, + ForkId { + hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), + next: mainnet::MAINNET_OSAKA_TIMESTAMP, + }, ), - // Future Prague block + // Osaka block ( - Head { number: 20000002, timestamp: 2000000000, ..Default::default() }, - ForkId { hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), next: 0 }, + Head { + number: 20000002, + timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP, + ..Default::default() + }, + ForkId { + hash: ForkHash(hex!("0x5167e2a6")), + next: mainnet::MAINNET_BPO1_TIMESTAMP, + }, ), ], ); @@ -1495,7 +1528,22 @@ Post-merge hard forks (timestamp based): // First Prague block ( Head { number: 0, timestamp: 1742999833, ..Default::default() }, - ForkId { hash: ForkHash([0x09, 0x29, 0xe2, 0x4e]), next: 0 }, + ForkId { + hash: ForkHash([0x09, 0x29, 0xe2, 0x4e]), + next: hoodi::HOODI_OSAKA_TIMESTAMP, + }, + ), + // First Osaka block + ( + Head { + number: 0, + timestamp: hoodi::HOODI_OSAKA_TIMESTAMP, + ..Default::default() + }, + ForkId { + hash: ForkHash(hex!("0xe7e0e7ff")), + next: hoodi::HOODI_BPO1_TIMESTAMP, + }, ), ], ) @@ -1543,7 +1591,22 @@ Post-merge hard forks (timestamp based): // First Prague block ( Head { number: 123, timestamp: 1740434112, ..Default::default() }, - ForkId { hash: ForkHash([0xdf, 0xbd, 0x9b, 0xed]), next: 0 }, + ForkId { + hash: ForkHash([0xdf, 0xbd, 0x9b, 0xed]), + next: holesky::HOLESKY_OSAKA_TIMESTAMP, + }, + ), + // First Osaka block + ( + Head { + number: 123, + timestamp: holesky::HOLESKY_OSAKA_TIMESTAMP, + ..Default::default() + }, + ForkId { + hash: ForkHash(hex!("0x783def52")), + next: holesky::HOLESKY_BPO1_TIMESTAMP, + }, ), ], ) @@ -1593,7 +1656,22 @@ Post-merge hard forks (timestamp based): // First Prague block ( Head { number: 1735377, timestamp: 1741159776, ..Default::default() }, - ForkId { hash: ForkHash([0xed, 0x88, 0xb5, 0xfd]), next: 0 }, + ForkId { + hash: ForkHash([0xed, 0x88, 0xb5, 0xfd]), + next: sepolia::SEPOLIA_OSAKA_TIMESTAMP, + }, + ), + // First Osaka block + ( + Head { + number: 1735377, + timestamp: sepolia::SEPOLIA_OSAKA_TIMESTAMP, + ..Default::default() + }, + ForkId { + hash: ForkHash(hex!("0xe2ae4999")), + next: sepolia::SEPOLIA_BPO1_TIMESTAMP, + }, ), ], ); @@ -1605,7 +1683,7 @@ Post-merge hard forks (timestamp based): &DEV, &[( Head { number: 0, ..Default::default() }, - ForkId { hash: ForkHash([0x45, 0xb8, 0x36, 0x12]), next: 0 }, + ForkId { hash: ForkHash([0x0b, 0x1a, 0x4e, 0xf7]), next: 0 }, )], ) } @@ -1741,11 +1819,22 @@ Post-merge hard forks (timestamp based): ), // First Prague block ( Head { number: 20000004, timestamp: 1746612311, ..Default::default() }, - ForkId { hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), next: 0 }, - ), // Future Prague block + ForkId { + hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), + next: mainnet::MAINNET_OSAKA_TIMESTAMP, + }, + ), + // Osaka block ( - Head { number: 20000004, timestamp: 2000000000, ..Default::default() }, - ForkId { hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), next: 0 }, + Head { + number: 20000004, + timestamp: mainnet::MAINNET_OSAKA_TIMESTAMP, + ..Default::default() + }, + ForkId { + hash: ForkHash(hex!("0x5167e2a6")), + next: mainnet::MAINNET_BPO1_TIMESTAMP, + }, ), ], ); @@ -2402,10 +2491,26 @@ Post-merge hard forks (timestamp based): #[test] fn latest_eth_mainnet_fork_id() { - assert_eq!( - ForkId { hash: ForkHash([0xc3, 0x76, 0xcf, 0x8b]), next: 0 }, - MAINNET.latest_fork_id() - ) + // BPO2 + assert_eq!(ForkId { hash: ForkHash(hex!("0xfd414558")), next: 0 }, MAINNET.latest_fork_id()) + } + + #[test] + fn latest_hoodi_mainnet_fork_id() { + // BPO2 + assert_eq!(ForkId { hash: ForkHash(hex!("0x23aa1351")), next: 0 }, HOODI.latest_fork_id()) + } + + #[test] + fn latest_holesky_mainnet_fork_id() { + // BPO2 + assert_eq!(ForkId { hash: ForkHash(hex!("0x9bc6cb31")), next: 0 }, HOLESKY.latest_fork_id()) + } + + #[test] + fn latest_sepolia_mainnet_fork_id() { + // BPO2 + assert_eq!(ForkId { hash: ForkHash(hex!("0x268956b6")), next: 0 }, SEPOLIA.latest_fork_id()) } #[test] @@ -2528,6 +2633,7 @@ Post-merge hard forks (timestamp based): update_fraction: 3338477, min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE, max_blobs_per_tx: 6, + blob_base_cost: 0, }, prague: BlobParams { target_blob_count: 3, @@ -2535,6 +2641,7 @@ Post-merge hard forks (timestamp based): update_fraction: 3338477, min_blob_fee: BLOB_TX_MIN_BLOB_GASPRICE, max_blobs_per_tx: 6, + blob_base_cost: 0, }, ..Default::default() }; diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index 06ceb9423c1..961c4a2116d 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -51,7 +51,7 @@ reth-static-file-types = { workspace = true, features = ["clap"] } reth-static-file.workspace = true reth-trie = { workspace = true, features = ["metrics"] } reth-trie-db = { workspace = true, features = ["metrics"] } -reth-trie-common = { workspace = true, optional = true } +reth-trie-common.workspace = true reth-primitives-traits.workspace = true reth-discv4.workspace = true reth-discv5.workspace = true @@ -68,11 +68,12 @@ futures.workspace = true tokio.workspace = true # misc -ahash.workspace = true +humantime.workspace = true human_bytes.workspace = true eyre.workspace = true clap = { workspace = true, features = ["derive", "env"] } lz4.workspace = true +zstd.workspace = true serde.workspace = true serde_json.workspace = true tar.workspace = true @@ -119,7 +120,7 @@ arbitrary = [ "reth-codecs/arbitrary", "reth-prune-types?/arbitrary", "reth-stages-types?/arbitrary", - "reth-trie-common?/arbitrary", + "reth-trie-common/arbitrary", "alloy-consensus/arbitrary", "reth-primitives-traits/arbitrary", "reth-ethereum-primitives/arbitrary", diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index bc5de96ff5f..46a6c479e67 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -5,7 +5,7 @@ use clap::Parser; use reth_chainspec::EthChainSpec; use reth_cli::chainspec::ChainSpecParser; use reth_config::{config::EtlConfig, Config}; -use reth_consensus::{noop::NoopConsensus, ConsensusError, FullConsensus}; +use reth_consensus::noop::NoopConsensus; use reth_db::{init_db, open_db_read_only, DatabaseEnv}; use reth_db_common::init::init_genesis; use reth_downloaders::{bodies::noop::NoopBodiesDownloader, headers::noop::NoopHeaderDownloader}; @@ -229,7 +229,7 @@ impl CliHeader for alloy_consensus::Header { /// Helper trait with a common set of requirements for the /// [`NodeTypes`] in CLI. -pub trait CliNodeTypes: NodeTypesForProvider { +pub trait CliNodeTypes: Node> + NodeTypesForProvider { type Evm: ConfigureEvm; type NetworkPrimitives: NetPrimitivesFor; } @@ -242,32 +242,29 @@ where type NetworkPrimitives = <<>>::Components as NodeComponents>>::Network as NetworkEventListenerProvider>::Primitives; } +type EvmFor = <<>>::ComponentsBuilder as NodeComponentsBuilder< + FullTypesAdapter, +>>::Components as NodeComponents>>::Evm; + +type ConsensusFor = + <<>>::ComponentsBuilder as NodeComponentsBuilder< + FullTypesAdapter, + >>::Components as NodeComponents>>::Consensus; + /// Helper trait aggregating components required for the CLI. pub trait CliNodeComponents: Send + Sync + 'static { - /// Evm to use. - type Evm: ConfigureEvm + 'static; - /// Consensus implementation. - type Consensus: FullConsensus + Clone + 'static; - /// Returns the configured EVM. - fn evm_config(&self) -> &Self::Evm; + fn evm_config(&self) -> &EvmFor; /// Returns the consensus implementation. - fn consensus(&self) -> &Self::Consensus; + fn consensus(&self) -> &ConsensusFor; } -impl CliNodeComponents for (E, C) -where - E: ConfigureEvm + 'static, - C: FullConsensus + Clone + 'static, -{ - type Evm = E; - type Consensus = C; - - fn evm_config(&self) -> &Self::Evm { +impl CliNodeComponents for (EvmFor, ConsensusFor) { + fn evm_config(&self) -> &EvmFor { &self.0 } - fn consensus(&self) -> &Self::Consensus { + fn consensus(&self) -> &ConsensusFor { &self.1 } } diff --git a/crates/cli/commands/src/db/checksum.rs b/crates/cli/commands/src/db/checksum.rs index 0d19bf914aa..e5ed9d909cd 100644 --- a/crates/cli/commands/src/db/checksum.rs +++ b/crates/cli/commands/src/db/checksum.rs @@ -2,7 +2,7 @@ use crate::{ common::CliNodeTypes, db::get::{maybe_json_value_parser, table_key}, }; -use ahash::RandomState; +use alloy_primitives::map::foldhash::fast::FixedState; use clap::Parser; use reth_chainspec::EthereumHardforks; use reth_db::DatabaseEnv; @@ -102,7 +102,7 @@ impl TableViewer<(u64, Duration)> for ChecksumViewer<'_, N }; let start_time = Instant::now(); - let mut hasher = RandomState::with_seeds(1, 2, 3, 4).build_hasher(); + let mut hasher = FixedState::with_seed(u64::from_be_bytes(*b"RETHRETH")).build_hasher(); let mut total = 0; let limit = self.limit.unwrap_or(usize::MAX); diff --git a/crates/cli/commands/src/db/mod.rs b/crates/cli/commands/src/db/mod.rs index 67b060f7e9a..6c66e7159a9 100644 --- a/crates/cli/commands/src/db/mod.rs +++ b/crates/cli/commands/src/db/mod.rs @@ -13,6 +13,7 @@ mod clear; mod diff; mod get; mod list; +mod repair_trie; mod stats; /// DB List TUI mod tui; @@ -48,6 +49,8 @@ pub enum Subcommands { }, /// Deletes all table entries Clear(clear::Command), + /// Verifies trie consistency and outputs any inconsistencies + RepairTrie(repair_trie::Command), /// Lists current and local database versions Version, /// Returns the full database path @@ -135,6 +138,12 @@ impl> Command let Environment { provider_factory, .. } = self.env.init::(AccessRights::RW)?; command.execute(provider_factory)?; } + Subcommands::RepairTrie(command) => { + let access_rights = + if command.dry_run { AccessRights::RO } else { AccessRights::RW }; + let Environment { provider_factory, .. } = self.env.init::(access_rights)?; + command.execute(provider_factory)?; + } Subcommands::Version => { let local_db_version = match get_db_version(&db_path) { Ok(version) => Some(version), diff --git a/crates/cli/commands/src/db/repair_trie.rs b/crates/cli/commands/src/db/repair_trie.rs new file mode 100644 index 00000000000..e5113e6cd48 --- /dev/null +++ b/crates/cli/commands/src/db/repair_trie.rs @@ -0,0 +1,240 @@ +use clap::Parser; +use reth_db_api::{ + cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO}, + database::Database, + tables, + transaction::{DbTx, DbTxMut}, +}; +use reth_node_builder::NodeTypesWithDB; +use reth_provider::{providers::ProviderNodeTypes, ProviderFactory, StageCheckpointReader}; +use reth_stages::StageId; +use reth_trie::{ + verify::{Output, Verifier}, + Nibbles, +}; +use reth_trie_common::{StorageTrieEntry, StoredNibbles, StoredNibblesSubKey}; +use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory}; +use std::time::{Duration, Instant}; +use tracing::{info, warn}; + +const PROGRESS_PERIOD: Duration = Duration::from_secs(5); + +/// The arguments for the `reth db repair-trie` command +#[derive(Parser, Debug)] +pub struct Command { + /// Only show inconsistencies without making any repairs + #[arg(long)] + pub(crate) dry_run: bool, +} + +impl Command { + /// Execute `db repair-trie` command + pub fn execute( + self, + provider_factory: ProviderFactory, + ) -> eyre::Result<()> { + if self.dry_run { + verify_only(provider_factory)? + } else { + verify_and_repair(provider_factory)? + } + + Ok(()) + } +} + +fn verify_only(provider_factory: ProviderFactory) -> eyre::Result<()> { + // Get a database transaction directly from the database + let db = provider_factory.db_ref(); + let mut tx = db.tx()?; + tx.disable_long_read_transaction_safety(); + + // Create the verifier + let hashed_cursor_factory = DatabaseHashedCursorFactory::new(&tx); + let trie_cursor_factory = DatabaseTrieCursorFactory::new(&tx); + let verifier = Verifier::new(trie_cursor_factory, hashed_cursor_factory)?; + + let mut inconsistent_nodes = 0; + let start_time = Instant::now(); + let mut last_progress_time = Instant::now(); + + // Iterate over the verifier and repair inconsistencies + for output_result in verifier { + let output = output_result?; + + if let Output::Progress(path) = output { + if last_progress_time.elapsed() > PROGRESS_PERIOD { + output_progress(path, start_time, inconsistent_nodes); + last_progress_time = Instant::now(); + } + } else { + warn!("Inconsistency found: {output:?}"); + inconsistent_nodes += 1; + } + } + + info!("Found {} inconsistencies (dry run - no changes made)", inconsistent_nodes); + + Ok(()) +} + +/// Checks that the merkle stage has completed running up to the account and storage hashing stages. +fn verify_checkpoints(provider: impl StageCheckpointReader) -> eyre::Result<()> { + let account_hashing_checkpoint = + provider.get_stage_checkpoint(StageId::AccountHashing)?.unwrap_or_default(); + let storage_hashing_checkpoint = + provider.get_stage_checkpoint(StageId::StorageHashing)?.unwrap_or_default(); + let merkle_checkpoint = + provider.get_stage_checkpoint(StageId::MerkleExecute)?.unwrap_or_default(); + + if account_hashing_checkpoint.block_number != merkle_checkpoint.block_number { + return Err(eyre::eyre!( + "MerkleExecute stage checkpoint ({}) != AccountHashing stage checkpoint ({}), you must first complete the pipeline sync by running `reth node`", + merkle_checkpoint.block_number, + account_hashing_checkpoint.block_number, + )) + } + + if storage_hashing_checkpoint.block_number != merkle_checkpoint.block_number { + return Err(eyre::eyre!( + "MerkleExecute stage checkpoint ({}) != StorageHashing stage checkpoint ({}), you must first complete the pipeline sync by running `reth node`", + merkle_checkpoint.block_number, + storage_hashing_checkpoint.block_number, + )) + } + + let merkle_checkpoint_progress = + provider.get_stage_checkpoint_progress(StageId::MerkleExecute)?; + if merkle_checkpoint_progress.is_some_and(|progress| !progress.is_empty()) { + return Err(eyre::eyre!( + "MerkleExecute sync stage in-progress, you must first complete the pipeline sync by running `reth node`", + )) + } + + Ok(()) +} + +fn verify_and_repair( + provider_factory: ProviderFactory, +) -> eyre::Result<()> { + // Get a read-write database provider + let mut provider_rw = provider_factory.provider_rw()?; + + // Check that a pipeline sync isn't in progress. + verify_checkpoints(provider_rw.as_ref())?; + + let tx = provider_rw.tx_mut(); + tx.disable_long_read_transaction_safety(); + + // Create the hashed cursor factory + let hashed_cursor_factory = DatabaseHashedCursorFactory::new(tx); + + // Create the trie cursor factory + let trie_cursor_factory = DatabaseTrieCursorFactory::new(tx); + + // Create the verifier + let verifier = Verifier::new(trie_cursor_factory, hashed_cursor_factory)?; + + let mut account_trie_cursor = tx.cursor_write::()?; + let mut storage_trie_cursor = tx.cursor_dup_write::()?; + + let mut inconsistent_nodes = 0; + let start_time = Instant::now(); + let mut last_progress_time = Instant::now(); + + // Iterate over the verifier and repair inconsistencies + for output_result in verifier { + let output = output_result?; + + if !matches!(output, Output::Progress(_)) { + warn!("Inconsistency found, will repair: {output:?}"); + inconsistent_nodes += 1; + } + + match output { + Output::AccountExtra(path, _node) => { + // Extra account node in trie, remove it + let nibbles = StoredNibbles(path); + if account_trie_cursor.seek_exact(nibbles)?.is_some() { + account_trie_cursor.delete_current()?; + } + } + Output::StorageExtra(account, path, _node) => { + // Extra storage node in trie, remove it + let nibbles = StoredNibblesSubKey(path); + if storage_trie_cursor + .seek_by_key_subkey(account, nibbles.clone())? + .filter(|e| e.nibbles == nibbles) + .is_some() + { + storage_trie_cursor.delete_current()?; + } + } + Output::AccountWrong { path, expected: node, .. } | + Output::AccountMissing(path, node) => { + // Wrong/missing account node value, upsert it + let nibbles = StoredNibbles(path); + account_trie_cursor.upsert(nibbles, &node)?; + } + Output::StorageWrong { account, path, expected: node, .. } | + Output::StorageMissing(account, path, node) => { + // Wrong/missing storage node value, upsert it + let nibbles = StoredNibblesSubKey(path); + let entry = StorageTrieEntry { nibbles, node }; + storage_trie_cursor.upsert(account, &entry)?; + } + Output::Progress(path) => { + if last_progress_time.elapsed() > PROGRESS_PERIOD { + output_progress(path, start_time, inconsistent_nodes); + last_progress_time = Instant::now(); + } + } + } + } + + if inconsistent_nodes == 0 { + info!("No inconsistencies found"); + } else { + info!("Repaired {} inconsistencies, committing changes", inconsistent_nodes); + provider_rw.commit()?; + } + + Ok(()) +} + +/// Output progress information based on the last seen account path. +fn output_progress(last_account: Nibbles, start_time: Instant, inconsistent_nodes: u64) { + // Calculate percentage based on position in the trie path space + // For progress estimation, we'll use the first few nibbles as an approximation + + // Convert the first 16 nibbles (8 bytes) to a u64 for progress calculation + let mut current_value: u64 = 0; + let nibbles_to_use = last_account.len().min(16); + + for i in 0..nibbles_to_use { + current_value = (current_value << 4) | (last_account.get(i).unwrap_or(0) as u64); + } + // Shift left to fill remaining bits if we have fewer than 16 nibbles + if nibbles_to_use < 16 { + current_value <<= (16 - nibbles_to_use) * 4; + } + + let progress_percent = current_value as f64 / u64::MAX as f64 * 100.0; + let progress_percent_str = format!("{progress_percent:.2}"); + + // Calculate ETA based on current speed + let elapsed = start_time.elapsed(); + let elapsed_secs = elapsed.as_secs_f64(); + + let estimated_total_time = + if progress_percent > 0.0 { elapsed_secs / (progress_percent / 100.0) } else { 0.0 }; + let remaining_time = estimated_total_time - elapsed_secs; + let eta_duration = Duration::from_secs(remaining_time as u64); + + info!( + progress_percent = progress_percent_str, + eta = %humantime::format_duration(eta_duration), + inconsistent_nodes, + "Repairing trie tables", + ); +} diff --git a/crates/cli/commands/src/download.rs b/crates/cli/commands/src/download.rs index 2e33729e395..8f09dc9b893 100644 --- a/crates/cli/commands/src/download.rs +++ b/crates/cli/commands/src/download.rs @@ -15,10 +15,12 @@ use std::{ use tar::Archive; use tokio::task; use tracing::info; +use zstd::stream::read::Decoder as ZstdDecoder; const BYTE_UNITS: [&str; 4] = ["B", "KB", "MB", "GB"]; -const MERKLE_BASE_URL: &str = "https://snapshots.merkle.io"; -const EXTENSION_TAR_FILE: &str = ".tar.lz4"; +const MERKLE_BASE_URL: &str = "https://downloads.merkle.io"; +const EXTENSION_TAR_LZ4: &str = ".tar.lz4"; +const EXTENSION_TAR_ZSTD: &str = ".tar.zst"; #[derive(Debug, Parser)] pub struct DownloadCommand { @@ -32,7 +34,7 @@ pub struct DownloadCommand { long_help = "Specify a snapshot URL or let the command propose a default one.\n\ \n\ Available snapshot sources:\n\ - - https://snapshots.merkle.io (default, mainnet archive)\n\ + - https://www.merkle.io/snapshots (default, mainnet archive)\n\ - https://publicnode.com/snapshots (full nodes & testnets)\n\ \n\ If no URL is provided, the latest mainnet archive snapshot\n\ @@ -139,16 +141,36 @@ impl ProgressReader { impl Read for ProgressReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { let bytes = self.reader.read(buf)?; - if bytes > 0 { - if let Err(e) = self.progress.update(bytes as u64) { - return Err(io::Error::other(e)); - } + if bytes > 0 && + let Err(e) = self.progress.update(bytes as u64) + { + return Err(io::Error::other(e)); } Ok(bytes) } } -/// Downloads and extracts a snapshot with blocking approach +/// Supported compression formats for snapshots +#[derive(Debug, Clone, Copy)] +enum CompressionFormat { + Lz4, + Zstd, +} + +impl CompressionFormat { + /// Detect compression format from file extension + fn from_url(url: &str) -> Result { + if url.ends_with(EXTENSION_TAR_LZ4) { + Ok(Self::Lz4) + } else if url.ends_with(EXTENSION_TAR_ZSTD) { + Ok(Self::Zstd) + } else { + Err(eyre::eyre!("Unsupported file format. Expected .tar.lz4 or .tar.zst, got: {}", url)) + } + } +} + +/// Downloads and extracts a snapshot, blocking until finished. fn blocking_download_and_extract(url: &str, target_dir: &Path) -> Result<()> { let client = reqwest::blocking::Client::builder().build()?; let response = client.get(url).send()?.error_for_status()?; @@ -160,11 +182,18 @@ fn blocking_download_and_extract(url: &str, target_dir: &Path) -> Result<()> { })?; let progress_reader = ProgressReader::new(response, total_size); + let format = CompressionFormat::from_url(url)?; - let decoder = Decoder::new(progress_reader)?; - let mut archive = Archive::new(decoder); - - archive.unpack(target_dir)?; + match format { + CompressionFormat::Lz4 => { + let decoder = Decoder::new(progress_reader)?; + Archive::new(decoder).unpack(target_dir)?; + } + CompressionFormat::Zstd => { + let decoder = ZstdDecoder::new(progress_reader)?; + Archive::new(decoder).unpack(target_dir)?; + } + } info!(target: "reth::cli", "Extraction complete."); Ok(()) @@ -191,9 +220,5 @@ async fn get_latest_snapshot_url() -> Result { .trim() .to_string(); - if !filename.ends_with(EXTENSION_TAR_FILE) { - return Err(eyre::eyre!("Unexpected snapshot filename format: {}", filename)); - } - Ok(format!("{MERKLE_BASE_URL}/{filename}")) } diff --git a/crates/cli/commands/src/import.rs b/crates/cli/commands/src/import.rs index 3a1ebd959dc..e8493c9ab33 100644 --- a/crates/cli/commands/src/import.rs +++ b/crates/cli/commands/src/import.rs @@ -12,7 +12,7 @@ use tracing::info; pub use crate::import_core::build_import_pipeline_impl as build_import_pipeline; -/// Syncs RLP encoded blocks from a file. +/// Syncs RLP encoded blocks from a file or files. #[derive(Debug, Parser)] pub struct ImportCommand { #[command(flatten)] @@ -26,12 +26,12 @@ pub struct ImportCommand { #[arg(long, value_name = "CHUNK_LEN", verbatim_doc_comment)] chunk_len: Option, - /// The path to a block file for import. + /// The path(s) to block file(s) for import. /// /// The online stages (headers and bodies) are replaced by a file import, after which the - /// remaining stages are executed. - #[arg(value_name = "IMPORT_PATH", verbatim_doc_comment)] - path: PathBuf, + /// remaining stages are executed. Multiple files will be imported sequentially. + #[arg(value_name = "IMPORT_PATH", required = true, num_args = 1.., verbatim_doc_comment)] + paths: Vec, } impl> ImportCommand { @@ -50,25 +50,57 @@ impl> ImportComm let components = components(provider_factory.chain_spec()); + info!(target: "reth::cli", "Starting import of {} file(s)", self.paths.len()); + let import_config = ImportConfig { no_state: self.no_state, chunk_len: self.chunk_len }; let executor = components.evm_config().clone(); let consensus = Arc::new(components.consensus().clone()); - let result = import_blocks_from_file( - &self.path, - import_config, - provider_factory, - &config, - executor, - consensus, - ) - .await?; - - if !result.is_complete() { - return Err(eyre::eyre!("Chain was partially imported")); + let mut total_imported_blocks = 0; + let mut total_imported_txns = 0; + let mut total_decoded_blocks = 0; + let mut total_decoded_txns = 0; + + // Import each file sequentially + for (index, path) in self.paths.iter().enumerate() { + info!(target: "reth::cli", "Importing file {} of {}: {}", index + 1, self.paths.len(), path.display()); + + let result = import_blocks_from_file( + path, + import_config.clone(), + provider_factory.clone(), + &config, + executor.clone(), + consensus.clone(), + ) + .await?; + + total_imported_blocks += result.total_imported_blocks; + total_imported_txns += result.total_imported_txns; + total_decoded_blocks += result.total_decoded_blocks; + total_decoded_txns += result.total_decoded_txns; + + if !result.is_complete() { + return Err(eyre::eyre!( + "Chain was partially imported from file: {}. Imported {}/{} blocks, {}/{} transactions", + path.display(), + result.total_imported_blocks, + result.total_decoded_blocks, + result.total_imported_txns, + result.total_decoded_txns + )); + } + + info!(target: "reth::cli", + "Successfully imported file {}: {} blocks, {} transactions", + path.display(), result.total_imported_blocks, result.total_imported_txns); } + info!(target: "reth::cli", + "All files imported successfully. Total: {}/{} blocks, {}/{} transactions", + total_imported_blocks, total_decoded_blocks, total_imported_txns, total_decoded_txns); + Ok(()) } } @@ -97,4 +129,14 @@ mod tests { ); } } + + #[test] + fn parse_import_command_with_multiple_paths() { + let args: ImportCommand = + ImportCommand::parse_from(["reth", "file1.rlp", "file2.rlp", "file3.rlp"]); + assert_eq!(args.paths.len(), 3); + assert_eq!(args.paths[0], PathBuf::from("file1.rlp")); + assert_eq!(args.paths[1], PathBuf::from("file2.rlp")); + assert_eq!(args.paths[2], PathBuf::from("file3.rlp")); + } } diff --git a/crates/cli/commands/src/import_core.rs b/crates/cli/commands/src/import_core.rs index c3adec10200..2370ebaa039 100644 --- a/crates/cli/commands/src/import_core.rs +++ b/crates/cli/commands/src/import_core.rs @@ -90,6 +90,11 @@ where // open file let mut reader = ChunkedFileReader::new(path, import_config.chunk_len).await?; + let provider = provider_factory.provider()?; + let init_blocks = provider.tx_ref().entries::()?; + let init_txns = provider.tx_ref().entries::()?; + drop(provider); + let mut total_decoded_blocks = 0; let mut total_decoded_txns = 0; @@ -125,10 +130,8 @@ where pipeline.set_tip(tip); debug!(target: "reth::import", ?tip, "Tip manually set"); - let provider = provider_factory.provider()?; - let latest_block_number = - provider.get_stage_checkpoint(StageId::Finish)?.map(|ch| ch.block_number); + provider_factory.get_stage_checkpoint(StageId::Finish)?.map(|ch| ch.block_number); tokio::spawn(reth_node_events::node::handle_events(None, latest_block_number, events)); // Run pipeline @@ -147,9 +150,9 @@ where } let provider = provider_factory.provider()?; - - let total_imported_blocks = provider.tx_ref().entries::()?; - let total_imported_txns = provider.tx_ref().entries::()?; + let total_imported_blocks = provider.tx_ref().entries::()? - init_blocks; + let total_imported_txns = + provider.tx_ref().entries::()? - init_txns; let result = ImportResult { total_decoded_blocks, @@ -170,7 +173,7 @@ where info!(target: "reth::import", total_imported_blocks, total_imported_txns, - "Chain file imported" + "Chain was fully imported" ); } @@ -189,7 +192,7 @@ pub fn build_import_pipeline_impl( static_file_producer: StaticFileProducer>, disable_exec: bool, evm_config: E, -) -> eyre::Result<(Pipeline, impl futures::Stream>)> +) -> eyre::Result<(Pipeline, impl futures::Stream> + use)> where N: ProviderNodeTypes, C: FullConsensus + 'static, diff --git a/crates/cli/commands/src/lib.rs b/crates/cli/commands/src/lib.rs index 84586359b36..85bc0f1510a 100644 --- a/crates/cli/commands/src/lib.rs +++ b/crates/cli/commands/src/lib.rs @@ -24,7 +24,6 @@ pub mod node; pub mod p2p; pub mod prune; pub mod re_execute; -pub mod recover; pub mod stage; #[cfg(feature = "arbitrary")] pub mod test_vectors; diff --git a/crates/cli/commands/src/node.rs b/crates/cli/commands/src/node.rs index 1714a06d678..86e59ce28bd 100644 --- a/crates/cli/commands/src/node.rs +++ b/crates/cli/commands/src/node.rs @@ -59,7 +59,7 @@ pub struct NodeCommand, /// Sets all ports to unused, allowing the OS to choose random unused ports when sockets are diff --git a/crates/cli/commands/src/p2p/bootnode.rs b/crates/cli/commands/src/p2p/bootnode.rs index c27586b243f..5db54ddf80d 100644 --- a/crates/cli/commands/src/p2p/bootnode.rs +++ b/crates/cli/commands/src/p2p/bootnode.rs @@ -5,7 +5,7 @@ use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config}; use reth_discv5::{discv5::Event, Config, Discv5}; use reth_net_nat::NatResolver; use reth_network_peers::NodeRecord; -use std::{net::SocketAddr, str::FromStr}; +use std::net::SocketAddr; use tokio::select; use tokio_stream::StreamExt; use tracing::info; @@ -13,9 +13,9 @@ use tracing::info; /// Start a discovery only bootnode. #[derive(Parser, Debug)] pub struct Command { - /// Listen address for the bootnode (default: ":30301"). - #[arg(long, default_value = ":30301")] - pub addr: String, + /// Listen address for the bootnode (default: "0.0.0.0:30301"). + #[arg(long, default_value = "0.0.0.0:30301")] + pub addr: SocketAddr, /// Generate a new node key and save it to the specified file. #[arg(long, default_value = "")] @@ -39,15 +39,13 @@ impl Command { pub async fn execute(self) -> eyre::Result<()> { info!("Bootnode started with config: {:?}", self); let sk = reth_network::config::rng_secret_key(); - let socket_addr = SocketAddr::from_str(&self.addr)?; - let local_enr = NodeRecord::from_secret_key(socket_addr, &sk); + let local_enr = NodeRecord::from_secret_key(self.addr, &sk); let config = Discv4Config::builder().external_ip_resolver(Some(self.nat)).build(); - let (_discv4, mut discv4_service) = - Discv4::bind(socket_addr, local_enr, sk, config).await?; + let (_discv4, mut discv4_service) = Discv4::bind(self.addr, local_enr, sk, config).await?; - info!("Started discv4 at address:{:?}", socket_addr); + info!("Started discv4 at address:{:?}", self.addr); let mut discv4_updates = discv4_service.update_stream(); discv4_service.spawn(); @@ -57,7 +55,7 @@ impl Command { if self.v5 { info!("Starting discv5"); - let config = Config::builder(socket_addr).build(); + let config = Config::builder(self.addr).build(); let (_discv5, updates, _local_enr_discv5) = Discv5::start(&sk, config).await?; discv5_updates = Some(updates); }; diff --git a/crates/cli/commands/src/re_execute.rs b/crates/cli/commands/src/re_execute.rs index a555297488e..3b8ba305a42 100644 --- a/crates/cli/commands/src/re_execute.rs +++ b/crates/cli/commands/src/re_execute.rs @@ -4,14 +4,14 @@ use crate::common::{ AccessRights, CliComponentsBuilder, CliNodeComponents, CliNodeTypes, Environment, EnvironmentArgs, }; -use alloy_consensus::{BlockHeader, TxReceipt}; +use alloy_consensus::{transaction::TxHashRef, BlockHeader, TxReceipt}; use clap::Parser; use eyre::WrapErr; use reth_chainspec::{EthChainSpec, EthereumHardforks, Hardforks}; use reth_cli::chainspec::ChainSpecParser; use reth_consensus::FullConsensus; use reth_evm::{execute::Executor, ConfigureEvm}; -use reth_primitives_traits::{format_gas_throughput, BlockBody, GotExpected, SignedTransaction}; +use reth_primitives_traits::{format_gas_throughput, BlockBody, GotExpected}; use reth_provider::{ BlockNumReader, BlockReader, ChainSpecProvider, DatabaseProviderFactory, ReceiptProvider, StaticFileProviderFactory, TransactionVariant, diff --git a/crates/cli/commands/src/recover/mod.rs b/crates/cli/commands/src/recover/mod.rs deleted file mode 100644 index dde0d6c448f..00000000000 --- a/crates/cli/commands/src/recover/mod.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! `reth recover` command. - -use crate::common::CliNodeTypes; -use clap::{Parser, Subcommand}; -use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_cli::chainspec::ChainSpecParser; -use reth_cli_runner::CliContext; -use std::sync::Arc; - -mod storage_tries; - -/// `reth recover` command -#[derive(Debug, Parser)] -pub struct Command { - #[command(subcommand)] - command: Subcommands, -} - -/// `reth recover` subcommands -#[derive(Subcommand, Debug)] -pub enum Subcommands { - /// Recover the node by deleting dangling storage tries. - StorageTries(storage_tries::Command), -} - -impl> Command { - /// Execute `recover` command - pub async fn execute>( - self, - ctx: CliContext, - ) -> eyre::Result<()> { - match self.command { - Subcommands::StorageTries(command) => command.execute::(ctx).await, - } - } -} - -impl Command { - /// Returns the underlying chain being used to run this command - pub fn chain_spec(&self) -> Option<&Arc> { - match &self.command { - Subcommands::StorageTries(command) => command.chain_spec(), - } - } -} diff --git a/crates/cli/commands/src/recover/storage_tries.rs b/crates/cli/commands/src/recover/storage_tries.rs deleted file mode 100644 index 9974f2fd72c..00000000000 --- a/crates/cli/commands/src/recover/storage_tries.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::common::{AccessRights, CliNodeTypes, Environment, EnvironmentArgs}; -use alloy_consensus::BlockHeader; -use clap::Parser; -use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_cli::chainspec::ChainSpecParser; -use reth_cli_runner::CliContext; -use reth_db_api::{ - cursor::{DbCursorRO, DbDupCursorRW}, - tables, - transaction::DbTx, -}; -use reth_provider::{BlockNumReader, HeaderProvider, ProviderError}; -use reth_trie::StateRoot; -use reth_trie_db::DatabaseStateRoot; -use std::sync::Arc; -use tracing::*; - -/// `reth recover storage-tries` command -#[derive(Debug, Parser)] -pub struct Command { - #[command(flatten)] - env: EnvironmentArgs, -} - -impl> Command { - /// Execute `storage-tries` recovery command - pub async fn execute>( - self, - _ctx: CliContext, - ) -> eyre::Result<()> { - let Environment { provider_factory, .. } = self.env.init::(AccessRights::RW)?; - - let mut provider = provider_factory.provider_rw()?; - let best_block = provider.best_block_number()?; - let best_header = provider - .sealed_header(best_block)? - .ok_or_else(|| ProviderError::HeaderNotFound(best_block.into()))?; - - let mut deleted_tries = 0; - let tx_mut = provider.tx_mut(); - let mut hashed_account_cursor = tx_mut.cursor_read::()?; - let mut storage_trie_cursor = tx_mut.cursor_dup_read::()?; - let mut entry = storage_trie_cursor.first()?; - - info!(target: "reth::cli", "Starting pruning of storage tries"); - while let Some((hashed_address, _)) = entry { - if hashed_account_cursor.seek_exact(hashed_address)?.is_none() { - deleted_tries += 1; - storage_trie_cursor.delete_current_duplicates()?; - } - - entry = storage_trie_cursor.next()?; - } - - let state_root = StateRoot::from_tx(tx_mut).root()?; - if state_root != best_header.state_root() { - eyre::bail!( - "Recovery failed. Incorrect state root. Expected: {:?}. Received: {:?}", - best_header.state_root(), - state_root - ); - } - - provider.commit()?; - info!(target: "reth::cli", deleted = deleted_tries, "Finished recovery"); - - Ok(()) - } -} - -impl Command { - /// Returns the underlying chain being used to run this command - pub fn chain_spec(&self) -> Option<&Arc> { - Some(&self.env.chain) - } -} diff --git a/crates/cli/commands/src/stage/unwind.rs b/crates/cli/commands/src/stage/unwind.rs index 90e7c4fb06f..94aa5794173 100644 --- a/crates/cli/commands/src/stage/unwind.rs +++ b/crates/cli/commands/src/stage/unwind.rs @@ -82,6 +82,7 @@ impl> Command } else { info!(target: "reth::cli", ?target, "Executing a pipeline unwind."); } + info!(target: "reth::cli", prune_config=?config.prune, "Using prune settings"); // This will build an offline-only pipeline if the `offline` flag is enabled let mut pipeline = diff --git a/crates/cli/runner/src/lib.rs b/crates/cli/runner/src/lib.rs index 3060391d97e..d9456ec2a1c 100644 --- a/crates/cli/runner/src/lib.rs +++ b/crates/cli/runner/src/lib.rs @@ -11,7 +11,15 @@ //! Entrypoint for running commands. use reth_tasks::{TaskExecutor, TaskManager}; -use std::{future::Future, pin::pin, sync::mpsc, time::Duration}; +use std::{ + future::Future, + pin::pin, + sync::{ + atomic::{AtomicUsize, Ordering}, + mpsc, + }, + time::Duration, +}; use tracing::{debug, error, trace}; /// Executes CLI commands. @@ -99,8 +107,7 @@ impl CliRunner { F: Future>, E: Send + Sync + From + 'static, { - let tokio_runtime = tokio_runtime()?; - tokio_runtime.block_on(run_until_ctrl_c(fut))?; + self.tokio_runtime.block_on(run_until_ctrl_c(fut))?; Ok(()) } @@ -113,7 +120,7 @@ impl CliRunner { F: Future> + Send + 'static, E: Send + Sync + From + 'static, { - let tokio_runtime = tokio_runtime()?; + let tokio_runtime = self.tokio_runtime; let handle = tokio_runtime.handle().clone(); let fut = tokio_runtime.handle().spawn_blocking(move || handle.block_on(fut)); tokio_runtime @@ -160,7 +167,14 @@ pub struct CliContext { /// Creates a new default tokio multi-thread [Runtime](tokio::runtime::Runtime) with all features /// enabled pub fn tokio_runtime() -> Result { - tokio::runtime::Builder::new_multi_thread().enable_all().build() + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .thread_name_fn(|| { + static IDX: AtomicUsize = AtomicUsize::new(0); + let id = IDX.fetch_add(1, Ordering::Relaxed); + format!("tokio-{id}") + }) + .build() } /// Runs the given future to completion or until a critical task panicked. diff --git a/crates/cli/util/src/sigsegv_handler.rs b/crates/cli/util/src/sigsegv_handler.rs index b0a195391ff..dabbf866cee 100644 --- a/crates/cli/util/src/sigsegv_handler.rs +++ b/crates/cli/util/src/sigsegv_handler.rs @@ -7,7 +7,7 @@ use std::{ fmt, mem, ptr, }; -extern "C" { +unsafe extern "C" { fn backtrace_symbols_fd(buffer: *const *mut libc::c_void, size: libc::c_int, fd: libc::c_int); } diff --git a/crates/consensus/common/src/validation.rs b/crates/consensus/common/src/validation.rs index ff9d09df598..e14a3164279 100644 --- a/crates/consensus/common/src/validation.rs +++ b/crates/consensus/common/src/validation.rs @@ -1,16 +1,26 @@ //! Collection of methods for block validation. use alloy_consensus::{ - constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader as _, EMPTY_OMMER_ROOT_HASH, + constants::MAXIMUM_EXTRA_DATA_SIZE, BlockHeader as _, Transaction, EMPTY_OMMER_ROOT_HASH, }; use alloy_eips::{eip4844::DATA_GAS_PER_BLOB, eip7840::BlobParams}; use reth_chainspec::{EthChainSpec, EthereumHardfork, EthereumHardforks}; -use reth_consensus::ConsensusError; +use reth_consensus::{ConsensusError, TxGasLimitTooHighErr}; use reth_primitives_traits::{ - constants::MAXIMUM_GAS_LIMIT_BLOCK, Block, BlockBody, BlockHeader, GotExpected, SealedBlock, - SealedHeader, + constants::{ + GAS_LIMIT_BOUND_DIVISOR, MAXIMUM_GAS_LIMIT_BLOCK, MAX_TX_GAS_LIMIT_OSAKA, MINIMUM_GAS_LIMIT, + }, + transaction::TxHashRef, + Block, BlockBody, BlockHeader, GotExpected, SealedBlock, SealedHeader, }; +/// The maximum RLP length of a block, defined in [EIP-7934](https://eips.ethereum.org/EIPS/eip-7934). +/// +/// Calculated as `MAX_BLOCK_SIZE` - `SAFETY_MARGIN` where +/// `MAX_BLOCK_SIZE` = `10_485_760` +/// `SAFETY_MARGIN` = `2_097_152` +pub const MAX_RLP_BLOCK_SIZE: usize = 8_388_608; + /// Gas used needs to be less than gas limit. Gas used is going to be checked after execution. #[inline] pub fn validate_header_gas(header: &H) -> Result<(), ConsensusError> { @@ -146,6 +156,19 @@ where if let Err(error) = block.ensure_transaction_root_valid() { return Err(ConsensusError::BodyTransactionRootDiff(error.into())) } + // EIP-7825 validation + if chain_spec.is_osaka_active_at_timestamp(block.timestamp()) { + for tx in block.body().transactions() { + if tx.gas_limit() > MAX_TX_GAS_LIMIT_OSAKA { + return Err(TxGasLimitTooHighErr { + tx_hash: *tx.tx_hash(), + gas_limit: tx.gas_limit(), + max_allowed: MAX_TX_GAS_LIMIT_OSAKA, + } + .into()); + } + } + } Ok(()) } @@ -157,6 +180,7 @@ where /// information about the specific checks in [`validate_shanghai_withdrawals`]. /// * EIP-4844 blob gas validation, if cancun is active based on the given chainspec. See more /// information about the specific checks in [`validate_cancun_gas`]. +/// * EIP-7934 block size limit validation, if osaka is active based on the given chainspec. pub fn post_merge_hardfork_fields( block: &SealedBlock, chain_spec: &ChainSpec, @@ -186,6 +210,15 @@ where validate_cancun_gas(block)?; } + if chain_spec.is_osaka_active_at_timestamp(block.timestamp()) && + block.rlp_length() > MAX_RLP_BLOCK_SIZE + { + return Err(ConsensusError::BlockTooLarge { + rlp_length: block.rlp_length(), + max_rlp_length: MAX_RLP_BLOCK_SIZE, + }) + } + Ok(()) } @@ -312,6 +345,54 @@ pub fn validate_against_parent_timestamp( Ok(()) } +/// Validates gas limit against parent gas limit. +/// +/// The maximum allowable difference between self and parent gas limits is determined by the +/// parent's gas limit divided by the [`GAS_LIMIT_BOUND_DIVISOR`]. +#[inline] +pub fn validate_against_parent_gas_limit< + H: BlockHeader, + ChainSpec: EthChainSpec + EthereumHardforks, +>( + header: &SealedHeader, + parent: &SealedHeader, + chain_spec: &ChainSpec, +) -> Result<(), ConsensusError> { + // Determine the parent gas limit, considering elasticity multiplier on the London fork. + let parent_gas_limit = if !chain_spec.is_london_active_at_block(parent.number()) && + chain_spec.is_london_active_at_block(header.number()) + { + parent.gas_limit() * + chain_spec.base_fee_params_at_timestamp(header.timestamp()).elasticity_multiplier + as u64 + } else { + parent.gas_limit() + }; + + // Check for an increase in gas limit beyond the allowed threshold. + if header.gas_limit() > parent_gas_limit { + if header.gas_limit() - parent_gas_limit >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR { + return Err(ConsensusError::GasLimitInvalidIncrease { + parent_gas_limit, + child_gas_limit: header.gas_limit(), + }) + } + } + // Check for a decrease in gas limit beyond the allowed threshold. + else if parent_gas_limit - header.gas_limit() >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR { + return Err(ConsensusError::GasLimitInvalidDecrease { + parent_gas_limit, + child_gas_limit: header.gas_limit(), + }) + } + // Check if the self gas limit is below the minimum required limit. + else if header.gas_limit() < MINIMUM_GAS_LIMIT { + return Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: header.gas_limit() }) + } + + Ok(()) +} + /// Validates that the EIP-4844 header fields are correct with respect to the parent block. This /// ensures that the `blob_gas_used` and `excess_blob_gas` fields exist in the child header, and /// that the `excess_blob_gas` field matches the expected `excess_blob_gas` calculated from the @@ -335,8 +416,12 @@ pub fn validate_against_parent_4844( } let excess_blob_gas = header.excess_blob_gas().ok_or(ConsensusError::ExcessBlobGasMissing)?; - let expected_excess_blob_gas = - blob_params.next_block_excess_blob_gas(parent_excess_blob_gas, parent_blob_gas_used); + let parent_base_fee_per_gas = parent.base_fee_per_gas().unwrap_or(0); + let expected_excess_blob_gas = blob_params.next_block_excess_blob_gas_osaka( + parent_excess_blob_gas, + parent_blob_gas_used, + parent_base_fee_per_gas, + ); if expected_excess_blob_gas != excess_blob_gas { return Err(ConsensusError::ExcessBlobGasDiff { diff: GotExpected { got: excess_blob_gas, expected: expected_excess_blob_gas }, diff --git a/crates/consensus/consensus/src/lib.rs b/crates/consensus/consensus/src/lib.rs index 93babfe3a14..6dd7a0bcf53 100644 --- a/crates/consensus/consensus/src/lib.rs +++ b/crates/consensus/consensus/src/lib.rs @@ -11,7 +11,7 @@ extern crate alloc; -use alloc::{fmt::Debug, string::String, vec::Vec}; +use alloc::{boxed::Box, fmt::Debug, string::String, vec::Vec}; use alloy_consensus::Header; use alloy_primitives::{BlockHash, BlockNumber, Bloom, B256}; use reth_execution_types::BlockExecutionResult; @@ -395,6 +395,17 @@ pub enum ConsensusError { /// The block's timestamp. timestamp: u64, }, + /// Error when the block is too large. + #[error("block is too large: {rlp_length} > {max_rlp_length}")] + BlockTooLarge { + /// The actual RLP length of the block. + rlp_length: usize, + /// The maximum allowed RLP length. + max_rlp_length: usize, + }, + /// EIP-7825: Transaction gas limit exceeds maximum allowed + #[error(transparent)] + TransactionGasLimitTooHigh(Box), /// Other, likely an injected L2 error. #[error("{0}")] Other(String), @@ -413,7 +424,25 @@ impl From for ConsensusError { } } +impl From for ConsensusError { + fn from(value: TxGasLimitTooHighErr) -> Self { + Self::TransactionGasLimitTooHigh(Box::new(value)) + } +} + /// `HeaderConsensusError` combines a `ConsensusError` with the `SealedHeader` it relates to. #[derive(thiserror::Error, Debug)] #[error("Consensus error: {0}, Invalid header: {1:?}")] pub struct HeaderConsensusError(ConsensusError, SealedHeader); + +/// EIP-7825: Transaction gas limit exceeds maximum allowed +#[derive(thiserror::Error, Debug, Eq, PartialEq, Clone)] +#[error("transaction gas limit ({gas_limit}) is greater than the cap ({max_allowed})")] +pub struct TxGasLimitTooHighErr { + /// Hash of the transaction that violates the rule + pub tx_hash: B256, + /// The gas limit of the transaction + pub gas_limit: u64, + /// The maximum allowed gas limit + pub max_allowed: u64, +} diff --git a/crates/consensus/debug-client/Cargo.toml b/crates/consensus/debug-client/Cargo.toml index 5ff3735c33c..3783793a29f 100644 --- a/crates/consensus/debug-client/Cargo.toml +++ b/crates/consensus/debug-client/Cargo.toml @@ -20,6 +20,7 @@ reth-primitives-traits.workspace = true alloy-consensus = { workspace = true, features = ["serde"] } alloy-eips.workspace = true alloy-provider = { workspace = true, features = ["ws"] } +alloy-transport.workspace = true alloy-rpc-types-engine.workspace = true alloy-json-rpc.workspace = true alloy-primitives.workspace = true diff --git a/crates/consensus/debug-client/src/client.rs b/crates/consensus/debug-client/src/client.rs index 41074136e07..b77d7db94f4 100644 --- a/crates/consensus/debug-client/src/client.rs +++ b/crates/consensus/debug-client/src/client.rs @@ -84,7 +84,6 @@ where /// blocks. pub async fn run(self) { let mut previous_block_hashes = AllocRingBuffer::new(64); - let mut block_stream = { let (tx, rx) = mpsc::channel::(64); let block_provider = self.block_provider.clone(); diff --git a/crates/consensus/debug-client/src/providers/rpc.rs b/crates/consensus/debug-client/src/providers/rpc.rs index fe23c9ba79e..0c9dfbce7de 100644 --- a/crates/consensus/debug-client/src/providers/rpc.rs +++ b/crates/consensus/debug-client/src/providers/rpc.rs @@ -1,9 +1,9 @@ use crate::BlockProvider; -use alloy_consensus::BlockHeader; use alloy_provider::{Network, Provider, ProviderBuilder}; -use futures::StreamExt; +use alloy_transport::TransportResult; +use futures::{Stream, StreamExt}; use reth_node_api::Block; -use reth_tracing::tracing::warn; +use reth_tracing::tracing::{debug, warn}; use std::sync::Arc; use tokio::sync::mpsc::Sender; @@ -30,6 +30,28 @@ impl RpcBlockProvider { convert: Arc::new(convert), }) } + + /// Obtains a full block stream. + /// + /// This first attempts to obtain an `eth_subscribe` subscription, if that fails because the + /// connection is not a websocket, this falls back to poll based subscription. + async fn full_block_stream( + &self, + ) -> TransportResult>> { + // first try to obtain a regular subscription + match self.provider.subscribe_full_blocks().full().into_stream().await { + Ok(sub) => Ok(sub.left_stream()), + Err(err) => { + debug!( + target: "consensus::debug-client", + %err, + url=%self.url, + "Failed to establish block subscription", + ); + Ok(self.provider.watch_full_blocks().await?.full().into_stream().right_stream()) + } + } + } } impl BlockProvider for RpcBlockProvider @@ -39,22 +61,21 @@ where type Block = PrimitiveBlock; async fn subscribe_blocks(&self, tx: Sender) { - let mut stream = match self.provider.subscribe_blocks().await { - Ok(sub) => sub.into_stream(), - Err(err) => { - warn!( - target: "consensus::debug-client", - %err, - url=%self.url, - "Failed to subscribe to blocks", - ); - return; - } + let Ok(mut stream) = self.full_block_stream().await.inspect_err(|err| { + warn!( + target: "consensus::debug-client", + %err, + url=%self.url, + "Failed to subscribe to blocks", + ); + }) else { + return }; - while let Some(header) = stream.next().await { - match self.get_block(header.number()).await { + + while let Some(res) = stream.next().await { + match res { Ok(block) => { - if tx.send(block).await.is_err() { + if tx.send((self.convert)(block)).await.is_err() { // Channel closed. break; } diff --git a/crates/e2e-test-utils/Cargo.toml b/crates/e2e-test-utils/Cargo.toml index c29c94dd6a9..015732bd05d 100644 --- a/crates/e2e-test-utils/Cargo.toml +++ b/crates/e2e-test-utils/Cargo.toml @@ -16,7 +16,6 @@ reth-tracing.workspace = true reth-db = { workspace = true, features = ["test-utils"] } reth-network-api.workspace = true reth-network-p2p.workspace = true -reth-rpc-layer.workspace = true reth-rpc-server-types.workspace = true reth-rpc-builder.workspace = true reth-rpc-eth-api.workspace = true @@ -38,11 +37,7 @@ reth-ethereum-primitives.workspace = true reth-cli-commands.workspace = true reth-config.workspace = true reth-consensus.workspace = true -reth-evm.workspace = true -reth-static-file.workspace = true -reth-ethereum-consensus.workspace = true reth-primitives.workspace = true -reth-prune-types.workspace = true reth-db-common.workspace = true reth-primitives-traits.workspace = true @@ -64,7 +59,6 @@ alloy-rpc-types-engine.workspace = true alloy-network.workspace = true alloy-consensus = { workspace = true, features = ["kzg"] } alloy-provider = { workspace = true, features = ["reqwest"] } -alloy-genesis.workspace = true futures-util.workspace = true eyre.workspace = true diff --git a/crates/e2e-test-utils/src/lib.rs b/crates/e2e-test-utils/src/lib.rs index 0037197f261..a51b78ae654 100644 --- a/crates/e2e-test-utils/src/lib.rs +++ b/crates/e2e-test-utils/src/lib.rs @@ -96,10 +96,11 @@ where } // Connect last node with the first if there are more than two - if idx + 1 == num_nodes && num_nodes > 2 { - if let Some(first_node) = nodes.first_mut() { - node.connect(first_node).await; - } + if idx + 1 == num_nodes && + num_nodes > 2 && + let Some(first_node) = nodes.first_mut() + { + node.connect(first_node).await; } nodes.push(node); @@ -207,10 +208,11 @@ where } // Connect last node with the first if there are more than two - if idx + 1 == num_nodes && num_nodes > 2 { - if let Some(first_node) = nodes.first_mut() { - node.connect(first_node).await; - } + if idx + 1 == num_nodes && + num_nodes > 2 && + let Some(first_node) = nodes.first_mut() + { + node.connect(first_node).await; } } diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index 080304ca0c8..ad1f807b089 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -1,5 +1,5 @@ use crate::{network::NetworkTestContext, payload::PayloadTestContext, rpc::RpcTestContext}; -use alloy_consensus::BlockHeader; +use alloy_consensus::{transaction::TxHashRef, BlockHeader}; use alloy_eips::BlockId; use alloy_primitives::{BlockHash, BlockNumber, Bytes, Sealable, B256}; use alloy_rpc_types_engine::ForkchoiceState; @@ -14,11 +14,11 @@ use reth_node_api::{ PrimitivesTy, }; use reth_node_builder::{rpc::RethRpcAddOns, FullNode, NodeTypes}; -use reth_node_core::primitives::SignedTransaction; + use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; use reth_provider::{ BlockReader, BlockReaderIdExt, CanonStateNotificationStream, CanonStateSubscriptions, - StageCheckpointReader, + HeaderProvider, StageCheckpointReader, }; use reth_rpc_builder::auth::AuthServerHandle; use reth_rpc_eth_api::helpers::{EthApiSpec, EthTransactions, TraceExt}; @@ -150,19 +150,18 @@ where loop { tokio::time::sleep(std::time::Duration::from_millis(20)).await; - if !check && wait_finish_checkpoint { - if let Some(checkpoint) = - self.inner.provider.get_stage_checkpoint(StageId::Finish)? - { - if checkpoint.block_number >= number { - check = true - } - } + if !check && + wait_finish_checkpoint && + let Some(checkpoint) = + self.inner.provider.get_stage_checkpoint(StageId::Finish)? && + checkpoint.block_number >= number + { + check = true } if check { - if let Some(latest_block) = self.inner.provider.block_by_number(number)? { - assert_eq!(latest_block.header().hash_slow(), expected_block_hash); + if let Some(latest_header) = self.inner.provider.header_by_number(number)? { + assert_eq!(latest_header.hash_slow(), expected_block_hash); break } assert!( @@ -178,10 +177,10 @@ where pub async fn wait_unwind(&self, number: BlockNumber) -> eyre::Result<()> { loop { tokio::time::sleep(std::time::Duration::from_millis(10)).await; - if let Some(checkpoint) = self.inner.provider.get_stage_checkpoint(StageId::Headers)? { - if checkpoint.block_number == number { - break - } + if let Some(checkpoint) = self.inner.provider.get_stage_checkpoint(StageId::Headers)? && + checkpoint.block_number == number + { + break } } Ok(()) @@ -207,14 +206,13 @@ where // wait for the block to commit tokio::time::sleep(std::time::Duration::from_millis(20)).await; if let Some(latest_block) = - self.inner.provider.block_by_number_or_tag(BlockNumberOrTag::Latest)? + self.inner.provider.block_by_number_or_tag(BlockNumberOrTag::Latest)? && + latest_block.header().number() == block_number { - if latest_block.header().number() == block_number { - // make sure the block hash we submitted via FCU engine api is the new latest - // block using an RPC call - assert_eq!(latest_block.header().hash_slow(), block_hash); - break - } + // make sure the block hash we submitted via FCU engine api is the new latest + // block using an RPC call + assert_eq!(latest_block.header().hash_slow(), block_hash); + break } } Ok(()) diff --git a/crates/e2e-test-utils/src/setup_import.rs b/crates/e2e-test-utils/src/setup_import.rs index 8d435abd6c4..81e5a386aac 100644 --- a/crates/e2e-test-utils/src/setup_import.rs +++ b/crates/e2e-test-utils/src/setup_import.rs @@ -166,13 +166,10 @@ pub async fn setup_engine_with_chain_import( result.is_complete() ); - // The import counts genesis block in total_imported_blocks, so we expect - // total_imported_blocks to be total_decoded_blocks + 1 - let expected_imported = result.total_decoded_blocks + 1; // +1 for genesis - if result.total_imported_blocks != expected_imported { + if result.total_decoded_blocks != result.total_imported_blocks { debug!(target: "e2e::import", - "Import block count mismatch: expected {} (decoded {} + genesis), got {}", - expected_imported, result.total_decoded_blocks, result.total_imported_blocks + "Import block count mismatch: decoded {} != imported {}", + result.total_decoded_blocks, result.total_imported_blocks ); return Err(eyre::eyre!("Chain import block count mismatch for node {}", idx)); } @@ -351,7 +348,7 @@ mod tests { .unwrap(); assert_eq!(result.total_decoded_blocks, 5); - assert_eq!(result.total_imported_blocks, 6); // +1 for genesis + assert_eq!(result.total_imported_blocks, 5); // Verify stage checkpoints exist let provider = provider_factory.database_provider_ro().unwrap(); @@ -508,7 +505,7 @@ mod tests { // Verify the import was successful assert_eq!(result.total_decoded_blocks, 10); - assert_eq!(result.total_imported_blocks, 11); // +1 for genesis + assert_eq!(result.total_imported_blocks, 10); assert_eq!(result.total_decoded_txns, 0); assert_eq!(result.total_imported_txns, 0); diff --git a/crates/e2e-test-utils/src/testsuite/actions/custom_fcu.rs b/crates/e2e-test-utils/src/testsuite/actions/custom_fcu.rs new file mode 100644 index 00000000000..397947caef1 --- /dev/null +++ b/crates/e2e-test-utils/src/testsuite/actions/custom_fcu.rs @@ -0,0 +1,226 @@ +//! Custom forkchoice update actions for testing specific FCU scenarios. + +use crate::testsuite::{Action, Environment}; +use alloy_primitives::B256; +use alloy_rpc_types_engine::{ForkchoiceState, PayloadStatusEnum}; +use eyre::Result; +use futures_util::future::BoxFuture; +use reth_node_api::EngineTypes; +use reth_rpc_api::clients::EngineApiClient; +use std::marker::PhantomData; +use tracing::debug; + +/// Reference to a block for forkchoice update +#[derive(Debug, Clone)] +pub enum BlockReference { + /// Direct block hash + Hash(B256), + /// Tagged block reference + Tag(String), + /// Latest block on the active node + Latest, +} + +/// Helper function to resolve a block reference to a hash +pub fn resolve_block_reference( + reference: &BlockReference, + env: &Environment, +) -> Result { + match reference { + BlockReference::Hash(hash) => Ok(*hash), + BlockReference::Tag(tag) => { + let (block_info, _) = env + .block_registry + .get(tag) + .ok_or_else(|| eyre::eyre!("Block tag '{tag}' not found in registry"))?; + Ok(block_info.hash) + } + BlockReference::Latest => { + let block_info = env + .current_block_info() + .ok_or_else(|| eyre::eyre!("No current block information available"))?; + Ok(block_info.hash) + } + } +} + +/// Action to send a custom forkchoice update with specific finalized, safe, and head blocks +#[derive(Debug)] +pub struct SendForkchoiceUpdate { + /// The finalized block reference + pub finalized: BlockReference, + /// The safe block reference + pub safe: BlockReference, + /// The head block reference + pub head: BlockReference, + /// Expected payload status (None means accept any non-error) + pub expected_status: Option, + /// Node index to send to (None means active node) + pub node_idx: Option, + /// Tracks engine type + _phantom: PhantomData, +} + +impl SendForkchoiceUpdate { + /// Create a new custom forkchoice update action + pub const fn new( + finalized: BlockReference, + safe: BlockReference, + head: BlockReference, + ) -> Self { + Self { finalized, safe, head, expected_status: None, node_idx: None, _phantom: PhantomData } + } + + /// Set expected status for the FCU response + pub fn with_expected_status(mut self, status: PayloadStatusEnum) -> Self { + self.expected_status = Some(status); + self + } + + /// Set the target node index + pub const fn with_node_idx(mut self, idx: usize) -> Self { + self.node_idx = Some(idx); + self + } +} + +impl Action for SendForkchoiceUpdate +where + Engine: EngineTypes, +{ + fn execute<'a>(&'a mut self, env: &'a mut Environment) -> BoxFuture<'a, Result<()>> { + Box::pin(async move { + let finalized_hash = resolve_block_reference(&self.finalized, env)?; + let safe_hash = resolve_block_reference(&self.safe, env)?; + let head_hash = resolve_block_reference(&self.head, env)?; + + let fork_choice_state = ForkchoiceState { + head_block_hash: head_hash, + safe_block_hash: safe_hash, + finalized_block_hash: finalized_hash, + }; + + debug!( + "Sending FCU - finalized: {finalized_hash}, safe: {safe_hash}, head: {head_hash}" + ); + + let node_idx = self.node_idx.unwrap_or(env.active_node_idx); + if node_idx >= env.node_clients.len() { + return Err(eyre::eyre!("Node index {node_idx} out of bounds")); + } + + let engine = env.node_clients[node_idx].engine.http_client(); + let fcu_response = + EngineApiClient::::fork_choice_updated_v3(&engine, fork_choice_state, None) + .await?; + + debug!( + "Node {node_idx}: FCU response - status: {:?}, latest_valid_hash: {:?}", + fcu_response.payload_status.status, fcu_response.payload_status.latest_valid_hash + ); + + // If we have an expected status, validate it + if let Some(expected) = &self.expected_status { + match (&fcu_response.payload_status.status, expected) { + (PayloadStatusEnum::Valid, PayloadStatusEnum::Valid) => { + debug!("Node {node_idx}: FCU returned VALID as expected"); + } + ( + PayloadStatusEnum::Invalid { validation_error }, + PayloadStatusEnum::Invalid { .. }, + ) => { + debug!( + "Node {node_idx}: FCU returned INVALID as expected: {validation_error:?}" + ); + } + (PayloadStatusEnum::Syncing, PayloadStatusEnum::Syncing) => { + debug!("Node {node_idx}: FCU returned SYNCING as expected"); + } + (PayloadStatusEnum::Accepted, PayloadStatusEnum::Accepted) => { + debug!("Node {node_idx}: FCU returned ACCEPTED as expected"); + } + (actual, expected) => { + return Err(eyre::eyre!( + "Node {node_idx}: FCU status mismatch. Expected {expected:?}, got {actual:?}" + )); + } + } + } else { + // Just validate it's not an error + if matches!(fcu_response.payload_status.status, PayloadStatusEnum::Invalid { .. }) { + return Err(eyre::eyre!( + "Node {node_idx}: FCU returned unexpected INVALID status: {:?}", + fcu_response.payload_status.status + )); + } + } + + Ok(()) + }) + } +} + +/// Action to finalize a specific block with a given head +#[derive(Debug)] +pub struct FinalizeBlock { + /// Block to finalize + pub block_to_finalize: BlockReference, + /// Current head block (if None, uses the finalized block) + pub head: Option, + /// Node index to send to (None means active node) + pub node_idx: Option, + /// Tracks engine type + _phantom: PhantomData, +} + +impl FinalizeBlock { + /// Create a new finalize block action + pub const fn new(block_to_finalize: BlockReference) -> Self { + Self { block_to_finalize, head: None, node_idx: None, _phantom: PhantomData } + } + + /// Set the head block (if different from finalized) + pub fn with_head(mut self, head: BlockReference) -> Self { + self.head = Some(head); + self + } + + /// Set the target node index + pub const fn with_node_idx(mut self, idx: usize) -> Self { + self.node_idx = Some(idx); + self + } +} + +impl Action for FinalizeBlock +where + Engine: EngineTypes, +{ + fn execute<'a>(&'a mut self, env: &'a mut Environment) -> BoxFuture<'a, Result<()>> { + Box::pin(async move { + let finalized_hash = resolve_block_reference(&self.block_to_finalize, env)?; + let head_hash = if let Some(ref head_ref) = self.head { + resolve_block_reference(head_ref, env)? + } else { + finalized_hash + }; + + // Use SendForkchoiceUpdate to do the actual work + let mut fcu_action = SendForkchoiceUpdate::new( + BlockReference::Hash(finalized_hash), + BlockReference::Hash(finalized_hash), // safe = finalized + BlockReference::Hash(head_hash), + ); + + if let Some(idx) = self.node_idx { + fcu_action = fcu_action.with_node_idx(idx); + } + + fcu_action.execute(env).await?; + + debug!("Block {finalized_hash} successfully finalized with head at {head_hash}"); + + Ok(()) + }) + } +} diff --git a/crates/e2e-test-utils/src/testsuite/actions/mod.rs b/crates/e2e-test-utils/src/testsuite/actions/mod.rs index 58472618001..d4916265692 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/mod.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/mod.rs @@ -9,12 +9,14 @@ use reth_rpc_api::clients::EngineApiClient; use std::future::Future; use tracing::debug; +pub mod custom_fcu; pub mod engine_api; pub mod fork; pub mod node_ops; pub mod produce_blocks; pub mod reorg; +pub use custom_fcu::{BlockReference, FinalizeBlock, SendForkchoiceUpdate}; pub use engine_api::{ExpectedPayloadStatus, SendNewPayload, SendNewPayloads}; pub use fork::{CreateFork, ForkBase, SetForkBase, SetForkBaseFromBlockInfo, ValidateFork}; pub use node_ops::{ @@ -172,16 +174,13 @@ where ]; // if we're on a fork, validate it now that it's canonical - if let Ok(active_state) = env.active_node_state() { - if let Some(fork_base) = active_state.current_fork_base { - debug!( - "MakeCanonical: Adding fork validation from base block {}", - fork_base - ); - actions.push(Box::new(ValidateFork::new(fork_base))); - // clear the fork base since we're now canonical - env.active_node_state_mut()?.current_fork_base = None; - } + if let Ok(active_state) = env.active_node_state() && + let Some(fork_base) = active_state.current_fork_base + { + debug!("MakeCanonical: Adding fork validation from base block {}", fork_base); + actions.push(Box::new(ValidateFork::new(fork_base))); + // clear the fork base since we're now canonical + env.active_node_state_mut()?.current_fork_base = None; } let mut sequence = Sequence::new(actions); diff --git a/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs b/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs index f42951fc57b..a00ab5e8675 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/node_ops.rs @@ -195,15 +195,15 @@ where .copied() .ok_or_else(|| eyre::eyre!("Block tag '{}' not found in registry", self.tag))?; - if let Some(expected_node) = self.expected_node_idx { - if node_idx != expected_node { - return Err(eyre::eyre!( - "Block tag '{}' came from node {} but expected node {}", - self.tag, - node_idx, - expected_node - )); - } + if let Some(expected_node) = self.expected_node_idx && + node_idx != expected_node + { + return Err(eyre::eyre!( + "Block tag '{}' came from node {} but expected node {}", + self.tag, + node_idx, + expected_node + )); } debug!( diff --git a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs index c20b79d9ae4..9d2088c11a4 100644 --- a/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs +++ b/crates/e2e-test-utils/src/testsuite/actions/produce_blocks.rs @@ -590,12 +590,21 @@ where // at least one client passes all the check, save the header in Env if !accepted_check { accepted_check = true; - // save the header in Env - env.active_node_state_mut()?.latest_header_time = next_new_payload.timestamp; + // save the current block info in Env + env.set_current_block_info(BlockInfo { + hash: rpc_latest_header.hash, + number: rpc_latest_header.inner.number, + timestamp: rpc_latest_header.inner.timestamp, + })?; - // add it to header history + // align latest header time and forkchoice state with the accepted canonical + // head + env.active_node_state_mut()?.latest_header_time = + rpc_latest_header.inner.timestamp; env.active_node_state_mut()?.latest_fork_choice_state.head_block_hash = rpc_latest_header.hash; + + // update local copy for any further usage in this scope latest_block.hash = rpc_latest_header.hash; latest_block.number = rpc_latest_header.inner.number; } diff --git a/crates/e2e-test-utils/src/testsuite/setup.rs b/crates/e2e-test-utils/src/testsuite/setup.rs index a13518149f6..1425d534110 100644 --- a/crates/e2e-test-utils/src/testsuite/setup.rs +++ b/crates/e2e-test-utils/src/testsuite/setup.rs @@ -219,7 +219,7 @@ where let is_dev = self.is_dev; let node_count = self.network.node_count; - let attributes_generator = self.create_attributes_generator::(); + let attributes_generator = Self::create_static_attributes_generator::(); let result = setup_engine_with_connection::( node_count, @@ -304,10 +304,11 @@ where .await } - /// Create the attributes generator function - fn create_attributes_generator( - &self, - ) -> impl Fn(u64) -> <::Payload as PayloadTypes>::PayloadBuilderAttributes + Copy + /// Create a static attributes generator that doesn't capture any instance data + fn create_static_attributes_generator( + ) -> impl Fn(u64) -> <::Payload as PayloadTypes>::PayloadBuilderAttributes + + Copy + + use where N: NodeBuilderHelper, LocalPayloadAttributesBuilder: PayloadAttributesBuilder< diff --git a/crates/e2e-test-utils/tests/e2e-testsuite/main.rs b/crates/e2e-test-utils/tests/e2e-testsuite/main.rs index 96c976a44ca..5cd1bfe8c6c 100644 --- a/crates/e2e-test-utils/tests/e2e-testsuite/main.rs +++ b/crates/e2e-test-utils/tests/e2e-testsuite/main.rs @@ -89,11 +89,11 @@ async fn test_apply_with_import() -> Result<()> { ) .await; - if let Ok(Some(block)) = block_result { - if block.header.number == 10 { - debug!("Pipeline finished, block 10 is fully available"); - break; - } + if let Ok(Some(block)) = block_result && + block.header.number == 10 + { + debug!("Pipeline finished, block 10 is fully available"); + break; } if start.elapsed() > std::time::Duration::from_secs(10) { diff --git a/crates/engine/invalid-block-hooks/Cargo.toml b/crates/engine/invalid-block-hooks/Cargo.toml index 02b4b2c4460..8d4a469ee16 100644 --- a/crates/engine/invalid-block-hooks/Cargo.toml +++ b/crates/engine/invalid-block-hooks/Cargo.toml @@ -13,7 +13,6 @@ workspace = true [dependencies] # reth revm-bytecode.workspace = true -reth-chainspec.workspace = true revm-database.workspace = true reth-engine-primitives.workspace = true reth-evm.workspace = true diff --git a/crates/engine/invalid-block-hooks/src/witness.rs b/crates/engine/invalid-block-hooks/src/witness.rs index 7f37fd9c0f9..66d1084a698 100644 --- a/crates/engine/invalid-block-hooks/src/witness.rs +++ b/crates/engine/invalid-block-hooks/src/witness.rs @@ -145,10 +145,7 @@ where block: &RecoveredBlock, output: &BlockExecutionOutput, trie_updates: Option<(&TrieUpdates, B256)>, - ) -> eyre::Result<()> - where - N: NodePrimitives, - { + ) -> eyre::Result<()> { // TODO(alexey): unify with `DebugApi::debug_execution_witness` let mut executor = self.evm_config.batch_executor(StateProviderDatabase::new( @@ -159,7 +156,7 @@ where // Take the bundle state let mut db = executor.into_state(); - let mut bundle_state = db.take_bundle(); + let bundle_state = db.take_bundle(); // Initialize a map of preimages. let mut state_preimages = Vec::default(); @@ -251,20 +248,10 @@ where // The bundle state after re-execution should match the original one. // - // NOTE: This should not be needed if `Reverts` had a comparison method that sorted first, - // or otherwise did not care about order. + // Reverts now supports order-independent equality, so we can compare directly without + // sorting the reverts vectors. // - // See: https://github.com/bluealloy/revm/issues/1813 - let mut output = output.clone(); - for reverts in output.state.reverts.iter_mut() { - reverts.sort_by(|left, right| left.0.cmp(&right.0)); - } - - // We also have to sort the `bundle_state` reverts - for reverts in bundle_state.reverts.iter_mut() { - reverts.sort_by(|left, right| left.0.cmp(&right.0)); - } - + // See: https://github.com/bluealloy/revm/pull/1827 if bundle_state != output.state { let original_path = self.save_file( format!("{}_{}.bundle_state.original.json", block.number(), block.hash()), diff --git a/crates/engine/local/src/miner.rs b/crates/engine/local/src/miner.rs index 0b430782f1e..eb75afd358f 100644 --- a/crates/engine/local/src/miner.rs +++ b/crates/engine/local/src/miner.rs @@ -13,6 +13,7 @@ use reth_payload_primitives::{ use reth_provider::BlockReader; use reth_transaction_pool::TransactionPool; use std::{ + collections::VecDeque, future::Future, pin::Pin, task::{Context, Poll}, @@ -24,12 +25,14 @@ use tracing::error; /// A mining mode for the local dev engine. #[derive(Debug)] -pub enum MiningMode { +pub enum MiningMode { /// In this mode a block is built as soon as /// a valid transaction reaches the pool. /// If `max_transactions` is set, a block is built when that many transactions have /// accumulated. Instant { + /// The transaction pool. + pool: Pool, /// Stream of transaction notifications. rx: Fuse>, /// Maximum number of transactions to accumulate before mining a block. @@ -42,11 +45,11 @@ pub enum MiningMode { Interval(Interval), } -impl MiningMode { +impl MiningMode { /// Constructor for a [`MiningMode::Instant`] - pub fn instant(pool: Pool, max_transactions: Option) -> Self { + pub fn instant(pool: Pool, max_transactions: Option) -> Self { let rx = pool.pending_transactions_listener(); - Self::Instant { rx: ReceiverStream::new(rx).fuse(), max_transactions, accumulated: 0 } + Self::Instant { pool, rx: ReceiverStream::new(rx).fuse(), max_transactions, accumulated: 0 } } /// Constructor for a [`MiningMode::Interval`] @@ -56,15 +59,18 @@ impl MiningMode { } } -impl Future for MiningMode { +impl Future for MiningMode { type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); match this { - Self::Instant { rx, max_transactions, accumulated } => { + Self::Instant { pool, rx, max_transactions, accumulated } => { // Poll for new transaction notifications while let Poll::Ready(Some(_)) = rx.poll_next_unpin(cx) { + if pool.pending_and_queued_txn_count().0 == 0 { + continue; + } if let Some(max_tx) = max_transactions { *accumulated += 1; // If we've reached the max transactions threshold, mine a block @@ -91,32 +97,33 @@ impl Future for MiningMode { /// Local miner advancing the chain #[derive(Debug)] -pub struct LocalMiner { +pub struct LocalMiner { /// The payload attribute builder for the engine payload_attributes_builder: B, /// Sender for events to engine. to_engine: ConsensusEngineHandle, /// The mining mode for the engine - mode: MiningMode, + mode: MiningMode, /// The payload builder for the engine payload_builder: PayloadBuilderHandle, /// Timestamp for the next block. last_timestamp: u64, /// Stores latest mined blocks. - last_block_hashes: Vec, + last_block_hashes: VecDeque, } -impl LocalMiner +impl LocalMiner where T: PayloadTypes, B: PayloadAttributesBuilder<::PayloadAttributes>, + Pool: TransactionPool + Unpin, { /// Spawns a new [`LocalMiner`] with the given parameters. pub fn new( provider: impl BlockReader, payload_attributes_builder: B, to_engine: ConsensusEngineHandle, - mode: MiningMode, + mode: MiningMode, payload_builder: PayloadBuilderHandle, ) -> Self { let latest_header = @@ -128,7 +135,7 @@ where mode, payload_builder, last_timestamp: latest_header.timestamp(), - last_block_hashes: vec![latest_header.hash()], + last_block_hashes: VecDeque::from([latest_header.hash()]), } } @@ -156,7 +163,7 @@ where /// Returns current forkchoice state. fn forkchoice_state(&self) -> ForkchoiceState { ForkchoiceState { - head_block_hash: *self.last_block_hashes.last().expect("at least 1 block exists"), + head_block_hash: *self.last_block_hashes.back().expect("at least 1 block exists"), safe_block_hash: *self .last_block_hashes .get(self.last_block_hashes.len().saturating_sub(32)) @@ -170,13 +177,14 @@ where /// Sends a FCU to the engine. async fn update_forkchoice_state(&self) -> eyre::Result<()> { + let state = self.forkchoice_state(); let res = self .to_engine - .fork_choice_updated(self.forkchoice_state(), None, EngineApiMessageVersion::default()) + .fork_choice_updated(state, None, EngineApiMessageVersion::default()) .await?; if !res.is_valid() { - eyre::bail!("Invalid fork choice update") + eyre::bail!("Invalid fork choice update {state:?}: {res:?}") } Ok(()) @@ -224,11 +232,10 @@ where } self.last_timestamp = timestamp; - self.last_block_hashes.push(block.hash()); + self.last_block_hashes.push_back(block.hash()); // ensure we keep at most 64 blocks if self.last_block_hashes.len() > 64 { - self.last_block_hashes = - self.last_block_hashes.split_off(self.last_block_hashes.len() - 64); + self.last_block_hashes.pop_front(); } Ok(()) diff --git a/crates/engine/local/src/payload.rs b/crates/engine/local/src/payload.rs index 408ea2b8d05..34deaf3e10c 100644 --- a/crates/engine/local/src/payload.rs +++ b/crates/engine/local/src/payload.rs @@ -61,6 +61,7 @@ where no_tx_pool: None, gas_limit: None, eip_1559_params: None, + min_base_fee: None, } } } diff --git a/crates/engine/primitives/src/config.rs b/crates/engine/primitives/src/config.rs index ccff97bc064..3b838437598 100644 --- a/crates/engine/primitives/src/config.rs +++ b/crates/engine/primitives/src/config.rs @@ -4,7 +4,7 @@ pub const DEFAULT_PERSISTENCE_THRESHOLD: u64 = 2; /// How close to the canonical head we persist blocks. -pub const DEFAULT_MEMORY_BLOCK_BUFFER_TARGET: u64 = 2; +pub const DEFAULT_MEMORY_BLOCK_BUFFER_TARGET: u64 = 0; /// Default maximum concurrency for proof tasks pub const DEFAULT_MAX_PROOF_TASK_CONCURRENCY: u64 = 256; @@ -65,8 +65,8 @@ pub struct TreeConfig { always_compare_trie_updates: bool, /// Whether to disable cross-block caching and parallel prewarming. disable_caching_and_prewarming: bool, - /// Whether to enable the parallel sparse trie state root algorithm. - enable_parallel_sparse_trie: bool, + /// Whether to disable the parallel sparse trie state root algorithm. + disable_parallel_sparse_trie: bool, /// Whether to enable state provider metrics. state_provider_metrics: bool, /// Cross-block cache size in bytes. @@ -95,6 +95,8 @@ pub struct TreeConfig { /// where immediate payload regeneration is desired despite the head not changing or moving to /// an ancestor. always_process_payload_attributes_on_canonical_head: bool, + /// Whether to unwind canonical header to ancestor during forkchoice updates. + allow_unwind_canonical_header: bool, } impl Default for TreeConfig { @@ -108,7 +110,7 @@ impl Default for TreeConfig { legacy_state_root: false, always_compare_trie_updates: false, disable_caching_and_prewarming: false, - enable_parallel_sparse_trie: false, + disable_parallel_sparse_trie: false, state_provider_metrics: false, cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE, has_enough_parallelism: has_enough_parallelism(), @@ -117,6 +119,7 @@ impl Default for TreeConfig { precompile_cache_disabled: false, state_root_fallback: false, always_process_payload_attributes_on_canonical_head: false, + allow_unwind_canonical_header: false, } } } @@ -133,7 +136,7 @@ impl TreeConfig { legacy_state_root: bool, always_compare_trie_updates: bool, disable_caching_and_prewarming: bool, - enable_parallel_sparse_trie: bool, + disable_parallel_sparse_trie: bool, state_provider_metrics: bool, cross_block_cache_size: u64, has_enough_parallelism: bool, @@ -142,6 +145,7 @@ impl TreeConfig { precompile_cache_disabled: bool, state_root_fallback: bool, always_process_payload_attributes_on_canonical_head: bool, + allow_unwind_canonical_header: bool, ) -> Self { Self { persistence_threshold, @@ -152,7 +156,7 @@ impl TreeConfig { legacy_state_root, always_compare_trie_updates, disable_caching_and_prewarming, - enable_parallel_sparse_trie, + disable_parallel_sparse_trie, state_provider_metrics, cross_block_cache_size, has_enough_parallelism, @@ -161,6 +165,7 @@ impl TreeConfig { precompile_cache_disabled, state_root_fallback, always_process_payload_attributes_on_canonical_head, + allow_unwind_canonical_header, } } @@ -210,9 +215,9 @@ impl TreeConfig { self.state_provider_metrics } - /// Returns whether or not the parallel sparse trie is enabled. - pub const fn enable_parallel_sparse_trie(&self) -> bool { - self.enable_parallel_sparse_trie + /// Returns whether or not the parallel sparse trie is disabled. + pub const fn disable_parallel_sparse_trie(&self) -> bool { + self.disable_parallel_sparse_trie } /// Returns whether or not cross-block caching and parallel prewarming should be used. @@ -257,6 +262,11 @@ impl TreeConfig { self.always_process_payload_attributes_on_canonical_head } + /// Returns true if canonical header should be unwound to ancestor during forkchoice updates. + pub const fn unwind_canonical_header(&self) -> bool { + self.allow_unwind_canonical_header + } + /// Setter for persistence threshold. pub const fn with_persistence_threshold(mut self, persistence_threshold: u64) -> Self { self.persistence_threshold = persistence_threshold; @@ -339,12 +349,12 @@ impl TreeConfig { self } - /// Setter for using the parallel sparse trie - pub const fn with_enable_parallel_sparse_trie( + /// Setter for whether to disable the parallel sparse trie + pub const fn with_disable_parallel_sparse_trie( mut self, - enable_parallel_sparse_trie: bool, + disable_parallel_sparse_trie: bool, ) -> Self { - self.enable_parallel_sparse_trie = enable_parallel_sparse_trie; + self.disable_parallel_sparse_trie = disable_parallel_sparse_trie; self } @@ -375,6 +385,12 @@ impl TreeConfig { self } + /// Setter for whether to unwind canonical header to ancestor during forkchoice updates. + pub const fn with_unwind_canonical_header(mut self, unwind_canonical_header: bool) -> Self { + self.allow_unwind_canonical_header = unwind_canonical_header; + self + } + /// Whether or not to use state root task pub const fn use_state_root_task(&self) -> bool { self.has_enough_parallelism && !self.legacy_state_root diff --git a/crates/engine/primitives/src/event.rs b/crates/engine/primitives/src/event.rs index 7e45b5c73d3..1c74282cba5 100644 --- a/crates/engine/primitives/src/event.rs +++ b/crates/engine/primitives/src/event.rs @@ -90,7 +90,7 @@ pub enum ConsensusEngineLiveSyncProgress { DownloadingBlocks { /// The number of blocks remaining to download. remaining_blocks: u64, - /// The target block hash and number to download. + /// The target block hash to download. target: B256, }, } diff --git a/crates/engine/tree/Cargo.toml b/crates/engine/tree/Cargo.toml index c6e44e629a2..9be7d495763 100644 --- a/crates/engine/tree/Cargo.toml +++ b/crates/engine/tree/Cargo.toml @@ -18,6 +18,7 @@ reth-consensus.workspace = true reth-db.workspace = true reth-engine-primitives.workspace = true reth-errors.workspace = true +reth-execution-types.workspace = true reth-evm = { workspace = true, features = ["metrics"] } reth-network-p2p.workspace = true reth-payload-builder.workspace = true @@ -51,6 +52,7 @@ futures.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["rt", "rt-multi-thread", "sync", "macros"] } mini-moka = { workspace = true, features = ["sync"] } +smallvec.workspace = true # metrics metrics.workspace = true @@ -76,12 +78,12 @@ reth-chain-state = { workspace = true, features = ["test-utils"] } reth-chainspec.workspace = true reth-db-common.workspace = true reth-ethereum-consensus.workspace = true +metrics-util = { workspace = true, features = ["debugging"] } reth-ethereum-engine-primitives.workspace = true reth-evm = { workspace = true, features = ["test-utils"] } reth-exex-types.workspace = true reth-network-p2p = { workspace = true, features = ["test-utils"] } reth-prune-types.workspace = true -reth-rpc-convert.workspace = true reth-stages = { workspace = true, features = ["test-utils"] } reth-static-file.workspace = true reth-testing-utils.workspace = true diff --git a/crates/engine/tree/benches/state_root_task.rs b/crates/engine/tree/benches/state_root_task.rs index 6539ee9d9a4..9f61e62d2f9 100644 --- a/crates/engine/tree/benches/state_root_task.rs +++ b/crates/engine/tree/benches/state_root_task.rs @@ -42,13 +42,9 @@ struct BenchParams { fn create_bench_state_updates(params: &BenchParams) -> Vec { let mut runner = TestRunner::deterministic(); let mut rng = runner.rng().clone(); - let all_addresses: Vec
= (0..params.num_accounts) - .map(|_| { - // TODO: rand08 - Address::random() - }) - .collect(); - let mut updates = Vec::new(); + let all_addresses: Vec
= + (0..params.num_accounts).map(|_| Address::random_with(&mut rng)).collect(); + let mut updates = Vec::with_capacity(params.updates_per_account); for _ in 0..params.updates_per_account { let mut state_update = EvmState::default(); @@ -126,7 +122,7 @@ fn setup_provider( for update in state_updates { let provider_rw = factory.provider_rw()?; - let mut account_updates = Vec::new(); + let mut account_updates = Vec::with_capacity(update.len()); for (address, account) in update { // only process self-destructs if account exists, always process diff --git a/crates/engine/tree/src/download.rs b/crates/engine/tree/src/download.rs index 5d7d52af848..b7c147e4524 100644 --- a/crates/engine/tree/src/download.rs +++ b/crates/engine/tree/src/download.rs @@ -121,7 +121,7 @@ where self.download_full_block(hash); } else { trace!( - target: "consensus::engine", + target: "engine::download", ?hash, ?count, "start downloading full block range." @@ -152,7 +152,7 @@ where }); trace!( - target: "consensus::engine::sync", + target: "engine::download", ?hash, "Start downloading full block" ); @@ -213,7 +213,7 @@ where for idx in (0..self.inflight_full_block_requests.len()).rev() { let mut request = self.inflight_full_block_requests.swap_remove(idx); if let Poll::Ready(block) = request.poll_unpin(cx) { - trace!(target: "consensus::engine", block=?block.num_hash(), "Received single full block, buffering"); + trace!(target: "engine::download", block=?block.num_hash(), "Received single full block, buffering"); self.set_buffered_blocks.push(Reverse(block.into())); } else { // still pending @@ -225,7 +225,7 @@ where for idx in (0..self.inflight_block_range_requests.len()).rev() { let mut request = self.inflight_block_range_requests.swap_remove(idx); if let Poll::Ready(blocks) = request.poll_unpin(cx) { - trace!(target: "consensus::engine", len=?blocks.len(), first=?blocks.first().map(|b| b.num_hash()), last=?blocks.last().map(|b| b.num_hash()), "Received full block range, buffering"); + trace!(target: "engine::download", len=?blocks.len(), first=?blocks.first().map(|b| b.num_hash()), last=?blocks.last().map(|b| b.num_hash()), "Received full block range, buffering"); self.set_buffered_blocks.extend( blocks .into_iter() diff --git a/crates/engine/tree/src/tree/cached_state.rs b/crates/engine/tree/src/tree/cached_state.rs index bce9949564f..504a19fbbdb 100644 --- a/crates/engine/tree/src/tree/cached_state.rs +++ b/crates/engine/tree/src/tree/cached_state.rs @@ -1,4 +1,4 @@ -//! Implements a state provider that has a shared cache in front of it. +//! Execution cache implementation for block processing. use alloy_primitives::{Address, StorageKey, StorageValue, B256}; use metrics::Gauge; use mini_moka::sync::CacheBuilder; @@ -15,7 +15,7 @@ use reth_trie::{ MultiProofTargets, StorageMultiProof, StorageProof, TrieInput, }; use revm_primitives::map::DefaultHashBuilder; -use std::time::Duration; +use std::{sync::Arc, time::Duration}; use tracing::trace; pub(crate) type Cache = @@ -27,7 +27,7 @@ pub(crate) struct CachedStateProvider { state_provider: S, /// The caches used for the provider - caches: ProviderCaches, + caches: ExecutionCache, /// Metrics for the cached state provider metrics: CachedStateMetrics, @@ -37,11 +37,11 @@ impl CachedStateProvider where S: StateProvider, { - /// Creates a new [`CachedStateProvider`] from a [`ProviderCaches`], state provider, and + /// Creates a new [`CachedStateProvider`] from an [`ExecutionCache`], state provider, and /// [`CachedStateMetrics`]. pub(crate) const fn new_with_caches( state_provider: S, - caches: ProviderCaches, + caches: ExecutionCache, metrics: CachedStateMetrics, ) -> Self { Self { state_provider, caches, metrics } @@ -128,14 +128,14 @@ impl AccountReader for CachedStateProvider { } } -/// Represents the status of a storage slot in the cache +/// Represents the status of a storage slot in the cache. #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum SlotStatus { - /// The account's storage cache doesn't exist + /// The account's storage cache doesn't exist. NotCached, - /// The storage slot is empty (either not in cache or explicitly None) + /// The storage slot exists in cache and is empty (value is zero). Empty, - /// The storage slot has a value + /// The storage slot exists in cache and has a specific non-zero value. Value(StorageValue), } @@ -248,6 +248,18 @@ impl StorageRootProvider for CachedStateProvider { self.state_provider.storage_proof(address, slot, hashed_storage) } + /// Generate a storage multiproof for multiple storage slots. + /// + /// A **storage multiproof** is a cryptographic proof that can verify the values + /// of multiple storage slots for a single account in a single verification step. + /// Instead of generating separate proofs for each slot (which would be inefficient), + /// a multiproof bundles the necessary trie nodes to prove all requested slots. + /// + /// ## How it works: + /// 1. Takes an account address and a list of storage slot keys + /// 2. Traverses the account's storage trie to collect proof nodes + /// 3. Returns a [`StorageMultiProof`] containing the minimal set of trie nodes needed to verify + /// all the requested storage slots fn storage_multiproof( &self, address: Address, @@ -278,20 +290,25 @@ impl HashedPostStateProvider for CachedStateProvider } } -/// The set of caches that are used in the [`CachedStateProvider`]. +/// Execution cache used during block processing. +/// +/// Optimizes state access by maintaining in-memory copies of frequently accessed +/// accounts, storage slots, and bytecode. Works in conjunction with prewarming +/// to reduce database I/O during block execution. #[derive(Debug, Clone)] -pub(crate) struct ProviderCaches { - /// The cache for bytecode +pub(crate) struct ExecutionCache { + /// Cache for contract bytecode, keyed by code hash. code_cache: Cache>, - /// The cache for storage, organized hierarchically by account + /// Per-account storage cache: outer cache keyed by Address, inner cache tracks that account’s + /// storage slots. storage_cache: Cache, - /// The cache for basic accounts + /// Cache for basic account information (nonce, balance, code hash). account_cache: Cache>, } -impl ProviderCaches { +impl ExecutionCache { /// Get storage value from hierarchical cache. /// /// Returns a `SlotStatus` indicating whether: @@ -330,18 +347,24 @@ impl ProviderCaches { self.storage_cache.iter().map(|addr| addr.len()).sum() } - /// Inserts the [`BundleState`] entries into the cache. + /// Inserts the post-execution state changes into the cache. + /// + /// This method is called after transaction execution to update the cache with + /// the touched and modified state. The insertion order is critical: + /// + /// 1. Bytecodes: Insert contract code first + /// 2. Storage slots: Update storage values for each account + /// 3. Accounts: Update account info (nonce, balance, code hash) /// - /// Entries are inserted in the following order: - /// 1. Bytecodes - /// 2. Storage slots - /// 3. Accounts + /// ## Why This Order Matters /// - /// The order is important, because the access patterns are Account -> Bytecode and Account -> - /// Storage slot. If we update the account first, it may point to a code hash that doesn't have - /// the associated bytecode anywhere yet. + /// Account information references bytecode via code hash. If we update accounts + /// before bytecode, we might create cache entries pointing to non-existent code. + /// The current order ensures cache consistency. /// - /// Returns an error if the state can't be cached and should be discarded. + /// ## Error Handling + /// + /// Returns an error if the state updates are inconsistent and should be discarded. pub(crate) fn insert_state(&self, state_updates: &BundleState) -> Result<(), ()> { // Insert bytecodes for (code_hash, bytecode) in &state_updates.contracts { @@ -388,9 +411,9 @@ impl ProviderCaches { } } -/// A builder for [`ProviderCaches`]. +/// A builder for [`ExecutionCache`]. #[derive(Debug)] -pub(crate) struct ProviderCacheBuilder { +pub(crate) struct ExecutionCacheBuilder { /// Code cache entries code_cache_entries: u64, @@ -401,9 +424,9 @@ pub(crate) struct ProviderCacheBuilder { account_cache_entries: u64, } -impl ProviderCacheBuilder { - /// Build a [`ProviderCaches`] struct, so that provider caches can be easily cloned. - pub(crate) fn build_caches(self, total_cache_size: u64) -> ProviderCaches { +impl ExecutionCacheBuilder { + /// Build an [`ExecutionCache`] struct, so that execution caches can be easily cloned. + pub(crate) fn build_caches(self, total_cache_size: u64) -> ExecutionCache { let storage_cache_size = (total_cache_size * 8888) / 10000; // 88.88% of total let account_cache_size = (total_cache_size * 556) / 10000; // 5.56% of total let code_cache_size = (total_cache_size * 556) / 10000; // 5.56% of total @@ -464,11 +487,11 @@ impl ProviderCacheBuilder { .time_to_idle(TIME_TO_IDLE) .build_with_hasher(DefaultHashBuilder::default()); - ProviderCaches { code_cache, storage_cache, account_cache } + ExecutionCache { code_cache, storage_cache, account_cache } } } -impl Default for ProviderCacheBuilder { +impl Default for ExecutionCacheBuilder { fn default() -> Self { // With weigher and max_capacity in place, these numbers represent // the maximum number of entries that can be stored, not the actual @@ -493,20 +516,20 @@ pub(crate) struct SavedCache { hash: B256, /// The caches used for the provider. - caches: ProviderCaches, + caches: ExecutionCache, /// Metrics for the cached state provider metrics: CachedStateMetrics, + + /// A guard to track in-flight usage of this cache. + /// The cache is considered available if the strong count is 1. + usage_guard: Arc<()>, } impl SavedCache { /// Creates a new instance with the internals - pub(super) const fn new( - hash: B256, - caches: ProviderCaches, - metrics: CachedStateMetrics, - ) -> Self { - Self { hash, caches, metrics } + pub(super) fn new(hash: B256, caches: ExecutionCache, metrics: CachedStateMetrics) -> Self { + Self { hash, caches, metrics, usage_guard: Arc::new(()) } } /// Returns the hash for this cache @@ -515,16 +538,26 @@ impl SavedCache { } /// Splits the cache into its caches and metrics, consuming it. - pub(crate) fn split(self) -> (ProviderCaches, CachedStateMetrics) { + pub(crate) fn split(self) -> (ExecutionCache, CachedStateMetrics) { (self.caches, self.metrics) } - /// Returns the [`ProviderCaches`] belonging to the tracked hash. - pub(crate) const fn cache(&self) -> &ProviderCaches { + /// Returns true if the cache is available for use (no other tasks are currently using it). + pub(crate) fn is_available(&self) -> bool { + Arc::strong_count(&self.usage_guard) == 1 + } + + /// Returns the [`ExecutionCache`] belonging to the tracked hash. + pub(crate) const fn cache(&self) -> &ExecutionCache { &self.caches } - /// Updates the metrics for the [`ProviderCaches`]. + /// Returns the metrics associated with this cache. + pub(crate) const fn metrics(&self) -> &CachedStateMetrics { + &self.metrics + } + + /// Updates the metrics for the [`ExecutionCache`]. pub(crate) fn update_metrics(&self) { self.metrics.storage_cache_size.set(self.caches.total_storage_slots() as f64); self.metrics.account_cache_size.set(self.caches.account_cache.entry_count() as f64); @@ -532,10 +565,20 @@ impl SavedCache { } } -/// Cache for an account's storage slots +#[cfg(test)] +impl SavedCache { + fn clone_guard_for_test(&self) -> Arc<()> { + self.usage_guard.clone() + } +} + +/// Cache for an individual account's storage slots. +/// +/// This represents the second level of the hierarchical storage cache. +/// Each account gets its own `AccountStorageCache` to store accessed storage slots. #[derive(Debug, Clone)] pub(crate) struct AccountStorageCache { - /// The storage slots for this account + /// Map of storage keys to their cached values. slots: Cache>, } @@ -621,7 +664,7 @@ mod tests { unsafe impl GlobalAlloc for TrackingAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - let ret = self.inner.alloc(layout); + let ret = unsafe { self.inner.alloc(layout) }; if !ret.is_null() { self.allocated.fetch_add(layout.size(), Ordering::SeqCst); self.total_allocated.fetch_add(layout.size(), Ordering::SeqCst); @@ -631,7 +674,7 @@ mod tests { unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { self.allocated.fetch_sub(layout.size(), Ordering::SeqCst); - self.inner.dealloc(ptr, layout) + unsafe { self.inner.dealloc(ptr, layout) } } } } @@ -692,7 +735,7 @@ mod tests { let provider = MockEthProvider::default(); provider.extend_accounts(vec![(address, account)]); - let caches = ProviderCacheBuilder::default().build_caches(1000); + let caches = ExecutionCacheBuilder::default().build_caches(1000); let state_provider = CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed()); @@ -715,7 +758,7 @@ mod tests { let provider = MockEthProvider::default(); provider.extend_accounts(vec![(address, account)]); - let caches = ProviderCacheBuilder::default().build_caches(1000); + let caches = ExecutionCacheBuilder::default().build_caches(1000); let state_provider = CachedStateProvider::new_with_caches(provider, caches, CachedStateMetrics::zeroed()); @@ -733,7 +776,7 @@ mod tests { let storage_value = U256::from(1); // insert into caches directly - let caches = ProviderCacheBuilder::default().build_caches(1000); + let caches = ExecutionCacheBuilder::default().build_caches(1000); caches.insert_storage(address, storage_key, Some(storage_value)); // check that the storage is empty @@ -748,7 +791,7 @@ mod tests { let address = Address::random(); // just create empty caches - let caches = ProviderCacheBuilder::default().build_caches(1000); + let caches = ExecutionCacheBuilder::default().build_caches(1000); // check that the storage is empty let slot_status = caches.get_storage(&address, &storage_key); @@ -763,11 +806,52 @@ mod tests { let storage_key = StorageKey::random(); // insert into caches directly - let caches = ProviderCacheBuilder::default().build_caches(1000); + let caches = ExecutionCacheBuilder::default().build_caches(1000); caches.insert_storage(address, storage_key, None); // check that the storage is empty let slot_status = caches.get_storage(&address, &storage_key); assert_eq!(slot_status, SlotStatus::Empty); } + + // Tests for SavedCache locking mechanism + #[test] + fn test_saved_cache_is_available() { + let execution_cache = ExecutionCacheBuilder::default().build_caches(1000); + let cache = SavedCache::new(B256::ZERO, execution_cache, CachedStateMetrics::zeroed()); + + // Initially, the cache should be available (only one reference) + assert!(cache.is_available(), "Cache should be available initially"); + + // Clone the usage guard (simulating it being handed out) + let _guard = cache.clone_guard_for_test(); + + // Now the cache should not be available (two references) + assert!(!cache.is_available(), "Cache should not be available with active guard"); + } + + #[test] + fn test_saved_cache_multiple_references() { + let execution_cache = ExecutionCacheBuilder::default().build_caches(1000); + let cache = + SavedCache::new(B256::from([2u8; 32]), execution_cache, CachedStateMetrics::zeroed()); + + // Create multiple references to the usage guard + let guard1 = cache.clone_guard_for_test(); + let guard2 = cache.clone_guard_for_test(); + let guard3 = guard1.clone(); + + // Cache should not be available with multiple guards + assert!(!cache.is_available()); + + // Drop guards one by one + drop(guard1); + assert!(!cache.is_available()); // Still not available + + drop(guard2); + assert!(!cache.is_available()); // Still not available + + drop(guard3); + assert!(cache.is_available()); // Now available + } } diff --git a/crates/engine/tree/src/tree/metrics.rs b/crates/engine/tree/src/tree/metrics.rs index d2c4a85a76f..a5ebcf52547 100644 --- a/crates/engine/tree/src/tree/metrics.rs +++ b/crates/engine/tree/src/tree/metrics.rs @@ -1,9 +1,22 @@ -use reth_evm::metrics::ExecutorMetrics; +use crate::tree::MeteredStateHook; +use alloy_consensus::transaction::TxHashRef; +use alloy_evm::{ + block::{BlockExecutor, ExecutableTx}, + Evm, +}; +use core::borrow::BorrowMut; +use reth_errors::BlockExecutionError; +use reth_evm::{metrics::ExecutorMetrics, OnStateHook}; +use reth_execution_types::BlockExecutionOutput; use reth_metrics::{ metrics::{Counter, Gauge, Histogram}, Metrics, }; +use reth_primitives_traits::SignedTransaction; use reth_trie::updates::TrieUpdates; +use revm::database::{states::bundle_state::BundleRetention, State}; +use std::time::Instant; +use tracing::{debug_span, trace}; /// Metrics for the `EngineApi`. #[derive(Debug, Default)] @@ -18,6 +31,87 @@ pub(crate) struct EngineApiMetrics { pub tree: TreeMetrics, } +impl EngineApiMetrics { + /// Helper function for metered execution + fn metered(&self, f: F) -> R + where + F: FnOnce() -> (u64, R), + { + // Execute the block and record the elapsed time. + let execute_start = Instant::now(); + let (gas_used, output) = f(); + let execution_duration = execute_start.elapsed().as_secs_f64(); + + // Update gas metrics. + self.executor.gas_processed_total.increment(gas_used); + self.executor.gas_per_second.set(gas_used as f64 / execution_duration); + self.executor.gas_used_histogram.record(gas_used as f64); + self.executor.execution_histogram.record(execution_duration); + self.executor.execution_duration.set(execution_duration); + + output + } + + /// Execute the given block using the provided [`BlockExecutor`] and update metrics for the + /// execution. + /// + /// This method updates metrics for execution time, gas usage, and the number + /// of accounts, storage slots and bytecodes loaded and updated. + pub(crate) fn execute_metered( + &self, + executor: E, + transactions: impl Iterator, BlockExecutionError>>, + state_hook: Box, + ) -> Result, BlockExecutionError> + where + DB: alloy_evm::Database, + E: BlockExecutor>>, Transaction: SignedTransaction>, + { + // clone here is cheap, all the metrics are Option>. additionally + // they are globally registered so that the data recorded in the hook will + // be accessible. + let wrapper = MeteredStateHook { metrics: self.executor.clone(), inner_hook: state_hook }; + + let mut executor = executor.with_state_hook(Some(Box::new(wrapper))); + + let f = || { + executor.apply_pre_execution_changes()?; + for tx in transactions { + let tx = tx?; + let span = + debug_span!(target: "engine::tree", "execute_tx", tx_hash=?tx.tx().tx_hash()); + let _enter = span.enter(); + trace!(target: "engine::tree", "Executing transaction"); + executor.execute_transaction(tx)?; + } + executor.finish().map(|(evm, result)| (evm.into_db(), result)) + }; + + // Use metered to execute and track timing/gas metrics + let (mut db, result) = self.metered(|| { + let res = f(); + let gas_used = res.as_ref().map(|r| r.1.gas_used).unwrap_or(0); + (gas_used, res) + })?; + + // merge transitions into bundle state + db.borrow_mut().merge_transitions(BundleRetention::Reverts); + let output = BlockExecutionOutput { result, state: db.borrow_mut().take_bundle() }; + + // Update the metrics for the number of accounts, storage slots and bytecodes updated + let accounts = output.state.state.len(); + let storage_slots = + output.state.state.values().map(|account| account.storage.len()).sum::(); + let bytecodes = output.state.contracts.len(); + + self.executor.accounts_updated_histogram.record(accounts as f64); + self.executor.storage_slots_updated_histogram.record(storage_slots as f64); + self.executor.bytecodes_updated_histogram.record(bytecodes as f64); + + Ok(output) + } +} + /// Metrics for the entire blockchain tree #[derive(Metrics)] #[metrics(scope = "blockchain_tree")] @@ -58,7 +152,8 @@ pub(crate) struct EngineMetrics { pub(crate) failed_new_payload_response_deliveries: Counter, /// Tracks the how often we failed to deliver a forkchoice update response. pub(crate) failed_forkchoice_updated_response_deliveries: Counter, - // TODO add latency metrics + /// block insert duration + pub(crate) block_insert_total_duration: Histogram, } /// Metrics for non-execution related block validation. @@ -69,16 +164,22 @@ pub(crate) struct BlockValidationMetrics { pub(crate) state_root_storage_tries_updated_total: Counter, /// Total number of times the parallel state root computation fell back to regular. pub(crate) state_root_parallel_fallback_total: Counter, - /// Histogram of state root duration - pub(crate) state_root_histogram: Histogram, - /// Latest state root duration + /// Latest state root duration, ie the time spent blocked waiting for the state root. pub(crate) state_root_duration: Gauge, + /// Histogram for state root duration ie the time spent blocked waiting for the state root + pub(crate) state_root_histogram: Histogram, /// Trie input computation duration pub(crate) trie_input_duration: Histogram, /// Payload conversion and validation latency pub(crate) payload_validation_duration: Gauge, /// Histogram of payload validation latency pub(crate) payload_validation_histogram: Histogram, + /// Payload processor spawning duration + pub(crate) spawn_payload_processor: Histogram, + /// Post-execution validation duration + pub(crate) post_execution_validation_duration: Histogram, + /// Total duration of the new payload call + pub(crate) total_duration: Histogram, } impl BlockValidationMetrics { @@ -105,3 +206,232 @@ pub(crate) struct BlockBufferMetrics { /// Total blocks in the block buffer pub blocks: Gauge, } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_eips::eip7685::Requests; + use alloy_evm::block::StateChangeSource; + use alloy_primitives::{B256, U256}; + use metrics_util::debugging::{DebuggingRecorder, Snapshotter}; + use reth_ethereum_primitives::{Receipt, TransactionSigned}; + use reth_evm_ethereum::EthEvm; + use reth_execution_types::BlockExecutionResult; + use reth_primitives_traits::RecoveredBlock; + use revm::{ + context::result::{ExecutionResult, Output, ResultAndState, SuccessReason}, + database::State, + database_interface::EmptyDB, + inspector::NoOpInspector, + state::{Account, AccountInfo, AccountStatus, EvmState, EvmStorage, EvmStorageSlot}, + Context, MainBuilder, MainContext, + }; + use revm_primitives::Bytes; + use std::sync::mpsc; + + /// A simple mock executor for testing that doesn't require complex EVM setup + struct MockExecutor { + state: EvmState, + hook: Option>, + } + + impl MockExecutor { + fn new(state: EvmState) -> Self { + Self { state, hook: None } + } + } + + // Mock Evm type for testing + type MockEvm = EthEvm, NoOpInspector>; + + impl BlockExecutor for MockExecutor { + type Transaction = TransactionSigned; + type Receipt = Receipt; + type Evm = MockEvm; + + fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { + Ok(()) + } + + fn execute_transaction_without_commit( + &mut self, + _tx: impl ExecutableTx, + ) -> Result::HaltReason>, BlockExecutionError> { + // Call hook with our mock state for each transaction + if let Some(hook) = self.hook.as_mut() { + hook.on_state(StateChangeSource::Transaction(0), &self.state); + } + + Ok(ResultAndState::new( + ExecutionResult::Success { + reason: SuccessReason::Return, + gas_used: 1000, // Mock gas used + gas_refunded: 0, + logs: vec![], + output: Output::Call(Bytes::from(vec![])), + }, + Default::default(), + )) + } + + fn commit_transaction( + &mut self, + _output: ResultAndState<::HaltReason>, + _tx: impl ExecutableTx, + ) -> Result { + Ok(1000) + } + + fn finish( + self, + ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { + let Self { hook, state, .. } = self; + + // Call hook with our mock state + if let Some(mut hook) = hook { + hook.on_state(StateChangeSource::Transaction(0), &state); + } + + // Create a mock EVM + let db = State::builder() + .with_database(EmptyDB::default()) + .with_bundle_update() + .without_state_clear() + .build(); + let evm = EthEvm::new( + Context::mainnet().with_db(db).build_mainnet_with_inspector(NoOpInspector {}), + false, + ); + + // Return successful result like the original tests + Ok(( + evm, + BlockExecutionResult { + receipts: vec![], + requests: Requests::default(), + gas_used: 1000, + }, + )) + } + + fn set_state_hook(&mut self, hook: Option>) { + self.hook = hook; + } + + fn evm(&self) -> &Self::Evm { + panic!("Mock executor evm() not implemented") + } + + fn evm_mut(&mut self) -> &mut Self::Evm { + panic!("Mock executor evm_mut() not implemented") + } + } + + struct ChannelStateHook { + output: i32, + sender: mpsc::Sender, + } + + impl OnStateHook for ChannelStateHook { + fn on_state(&mut self, _source: StateChangeSource, _state: &EvmState) { + let _ = self.sender.send(self.output); + } + } + + fn setup_test_recorder() -> Snapshotter { + let recorder = DebuggingRecorder::new(); + let snapshotter = recorder.snapshotter(); + recorder.install().unwrap(); + snapshotter + } + + #[test] + fn test_executor_metrics_hook_called() { + let metrics = EngineApiMetrics::default(); + let input = RecoveredBlock::::default(); + + let (tx, rx) = mpsc::channel(); + let expected_output = 42; + let state_hook = Box::new(ChannelStateHook { sender: tx, output: expected_output }); + + let state = EvmState::default(); + let executor = MockExecutor::new(state); + + // This will fail to create the EVM but should still call the hook + let _result = metrics.execute_metered::<_, EmptyDB>( + executor, + input.clone_transactions_recovered().map(Ok::<_, BlockExecutionError>), + state_hook, + ); + + // Check if hook was called (it might not be if finish() fails early) + match rx.try_recv() { + Ok(actual_output) => assert_eq!(actual_output, expected_output), + Err(_) => { + // Hook wasn't called, which is expected if the mock fails early + // The test still validates that the code compiles and runs + } + } + } + + #[test] + fn test_executor_metrics_hook_metrics_recorded() { + let snapshotter = setup_test_recorder(); + let metrics = EngineApiMetrics::default(); + + // Pre-populate some metrics to ensure they exist + metrics.executor.gas_processed_total.increment(0); + metrics.executor.gas_per_second.set(0.0); + metrics.executor.gas_used_histogram.record(0.0); + + let input = RecoveredBlock::::default(); + + let (tx, _rx) = mpsc::channel(); + let state_hook = Box::new(ChannelStateHook { sender: tx, output: 42 }); + + // Create a state with some data + let state = { + let mut state = EvmState::default(); + let storage = + EvmStorage::from_iter([(U256::from(1), EvmStorageSlot::new(U256::from(2), 0))]); + state.insert( + Default::default(), + Account { + info: AccountInfo { + balance: U256::from(100), + nonce: 10, + code_hash: B256::random(), + code: Default::default(), + }, + storage, + status: AccountStatus::default(), + transaction_id: 0, + }, + ); + state + }; + + let executor = MockExecutor::new(state); + + // Execute (will fail but should still update some metrics) + let _result = metrics.execute_metered::<_, EmptyDB>( + executor, + input.clone_transactions_recovered().map(Ok::<_, BlockExecutionError>), + state_hook, + ); + + let snapshot = snapshotter.snapshot().into_vec(); + + // Verify that metrics were registered + let mut found_metrics = false; + for (key, _unit, _desc, _value) in snapshot { + let metric_name = key.key().name(); + if metric_name.starts_with("sync.execution") { + found_metrics = true; + break; + } + } + + assert!(found_metrics, "Expected to find sync.execution metrics"); + } +} diff --git a/crates/engine/tree/src/tree/mod.rs b/crates/engine/tree/src/tree/mod.rs index 929d5493df1..5faf8318e7f 100644 --- a/crates/engine/tree/src/tree/mod.rs +++ b/crates/engine/tree/src/tree/mod.rs @@ -7,6 +7,7 @@ use crate::{ }; use alloy_consensus::BlockHeader; use alloy_eips::{eip1898::BlockWithParent, merge::EPOCH_SLOTS, BlockNumHash, NumHash}; +use alloy_evm::block::StateChangeSource; use alloy_primitives::B256; use alloy_rpc_types_engine::{ ForkchoiceState, PayloadStatus, PayloadStatusEnum, PayloadValidationError, @@ -23,12 +24,12 @@ use reth_engine_primitives::{ ForkchoiceStateTracker, OnForkChoiceUpdated, }; use reth_errors::{ConsensusError, ProviderResult}; -use reth_evm::ConfigureEvm; +use reth_evm::{ConfigureEvm, OnStateHook}; use reth_payload_builder::PayloadBuilderHandle; use reth_payload_primitives::{ BuiltPayload, EngineApiMessageVersion, NewPayloadError, PayloadBuilderAttributes, PayloadTypes, }; -use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; +use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader}; use reth_provider::{ providers::ConsistentDbView, BlockNumReader, BlockReader, DBProvider, DatabaseProviderFactory, HashedPostStateProvider, ProviderError, StateProviderBox, StateProviderFactory, StateReader, @@ -38,6 +39,7 @@ use reth_revm::database::StateProviderDatabase; use reth_stages_api::ControlFlow; use reth_trie::{HashedPostState, TrieInput}; use reth_trie_db::DatabaseHashedPostState; +use revm::state::EvmState; use state::TreeState; use std::{ fmt::Debug, @@ -210,6 +212,28 @@ pub enum TreeAction { }, } +/// Wrapper struct that combines metrics and state hook +struct MeteredStateHook { + metrics: reth_evm::metrics::ExecutorMetrics, + inner_hook: Box, +} + +impl OnStateHook for MeteredStateHook { + fn on_state(&mut self, source: StateChangeSource, state: &EvmState) { + // Update the metrics for the number of accounts, storage slots and bytecodes loaded + let accounts = state.keys().len(); + let storage_slots = state.values().map(|account| account.storage.len()).sum::(); + let bytecodes = state.values().filter(|account| !account.info.is_empty_code_hash()).count(); + + self.metrics.accounts_loaded_histogram.record(accounts as f64); + self.metrics.storage_slots_loaded_histogram.record(storage_slots as f64); + self.metrics.bytecodes_loaded_histogram.record(bytecodes as f64); + + // Call the original state hook + self.inner_hook.on_state(source, state); + } +} + /// The engine API tree handler implementation. /// /// This type is responsible for processing engine API requests, maintaining the canonical state and @@ -389,7 +413,7 @@ where evm_config, ); let incoming = task.incoming_tx.clone(); - std::thread::Builder::new().name("Tree Task".to_string()).spawn(|| task.run()).unwrap(); + std::thread::Builder::new().name("Engine Task".to_string()).spawn(|| task.run()).unwrap(); (incoming, outgoing) } @@ -484,7 +508,8 @@ where trace!(target: "engine::tree", "invoked new payload"); self.metrics.engine.new_payload_messages.increment(1); - let validation_start = Instant::now(); + // start timing for the new payload process + let start = Instant::now(); // Ensures that the given payload does not violate any consensus rules that concern the // block's layout, like: @@ -513,10 +538,6 @@ where // This validation **MUST** be instantly run in all cases even during active sync process. let parent_hash = payload.parent_hash(); - self.metrics - .block_validation - .record_payload_validation(validation_start.elapsed().as_secs_f64()); - let num_hash = payload.num_hash(); let engine_event = ConsensusEngineEvent::BlockReceived(num_hash); self.emit_event(EngineApiEvent::BeaconConsensus(engine_event)); @@ -545,6 +566,8 @@ where let status = self.on_invalid_new_payload(block.into_sealed_block(), invalid)?; return Ok(TreeOutcome::new(status)) } + // record pre-execution phase duration + self.metrics.block_validation.record_payload_validation(start.elapsed().as_secs_f64()); let status = if self.backfill_sync_state.is_idle() { let mut latest_valid_hash = None; @@ -601,6 +624,9 @@ where } } + // record total newPayload duration + self.metrics.block_validation.total_duration.record(start.elapsed().as_secs_f64()); + Ok(outcome) } @@ -639,7 +665,7 @@ where warn!(target: "engine::tree", current_hash=?current_hash, "Sidechain block not found in TreeState"); // This should never happen as we're walking back a chain that should connect to // the canonical chain - return Ok(None); + return Ok(None) } } @@ -649,7 +675,7 @@ where new_chain.reverse(); // Simple extension of the current chain - return Ok(Some(NewCanonicalChain::Commit { new: new_chain })); + return Ok(Some(NewCanonicalChain::Commit { new: new_chain })) } // We have a reorg. Walk back both chains to find the fork point. @@ -666,7 +692,7 @@ where } else { // This shouldn't happen as we're walking back the canonical chain warn!(target: "engine::tree", current_hash=?old_hash, "Canonical block not found in TreeState"); - return Ok(None); + return Ok(None) } } @@ -682,7 +708,7 @@ where } else { // This shouldn't happen as we're walking back the canonical chain warn!(target: "engine::tree", current_hash=?old_hash, "Canonical block not found in TreeState"); - return Ok(None); + return Ok(None) } if let Some(block) = self.state.tree_state.executed_block_by_hash(current_hash).cloned() @@ -692,7 +718,7 @@ where } else { // This shouldn't happen as we've already walked this path warn!(target: "engine::tree", invalid_hash=?current_hash, "New chain block not found in TreeState"); - return Ok(None); + return Ok(None) } } new_chain.reverse(); @@ -701,6 +727,196 @@ where Ok(Some(NewCanonicalChain::Reorg { new: new_chain, old: old_chain })) } + /// Updates the latest block state to the specified canonical ancestor. + /// + /// This method ensures that the latest block tracks the given canonical header by resetting + /// + /// # Arguments + /// * `canonical_header` - The canonical header to set as the new head + /// + /// # Returns + /// * `ProviderResult<()>` - Ok(()) on success, error if state update fails + /// + /// Caution: This unwinds the canonical chain + fn update_latest_block_to_canonical_ancestor( + &mut self, + canonical_header: &SealedHeader, + ) -> ProviderResult<()> { + debug!(target: "engine::tree", head = ?canonical_header.num_hash(), "Update latest block to canonical ancestor"); + let current_head_number = self.state.tree_state.canonical_block_number(); + let new_head_number = canonical_header.number(); + let new_head_hash = canonical_header.hash(); + + // Update tree state with the new canonical head + self.state.tree_state.set_canonical_head(canonical_header.num_hash()); + + // Handle the state update based on whether this is an unwind scenario + if new_head_number < current_head_number { + debug!( + target: "engine::tree", + current_head = current_head_number, + new_head = new_head_number, + new_head_hash = ?new_head_hash, + "FCU unwind detected: reverting to canonical ancestor" + ); + + self.handle_canonical_chain_unwind(current_head_number, canonical_header) + } else { + debug!( + target: "engine::tree", + previous_head = current_head_number, + new_head = new_head_number, + new_head_hash = ?new_head_hash, + "Advancing latest block to canonical ancestor" + ); + self.handle_chain_advance_or_same_height(canonical_header) + } + } + + /// Handles chain unwind scenarios by collecting blocks to remove and performing an unwind back + /// to the canonical header + fn handle_canonical_chain_unwind( + &self, + current_head_number: u64, + canonical_header: &SealedHeader, + ) -> ProviderResult<()> { + let new_head_number = canonical_header.number(); + debug!( + target: "engine::tree", + from = current_head_number, + to = new_head_number, + "Handling unwind: collecting blocks to remove from in-memory state" + ); + + // Collect blocks that need to be removed from memory + let old_blocks = + self.collect_blocks_for_canonical_unwind(new_head_number, current_head_number); + + // Load and apply the canonical ancestor block + self.apply_canonical_ancestor_via_reorg(canonical_header, old_blocks) + } + + /// Collects blocks from memory that need to be removed during an unwind to a canonical block. + fn collect_blocks_for_canonical_unwind( + &self, + new_head_number: u64, + current_head_number: u64, + ) -> Vec> { + let mut old_blocks = Vec::new(); + + for block_num in (new_head_number + 1)..=current_head_number { + if let Some(block_state) = self.canonical_in_memory_state.state_by_number(block_num) { + let executed_block = block_state.block_ref().block.clone(); + old_blocks.push(executed_block); + debug!( + target: "engine::tree", + block_number = block_num, + "Collected block for removal from in-memory state" + ); + } + } + + if old_blocks.is_empty() { + debug!( + target: "engine::tree", + "No blocks found in memory to remove, will clear and reset state" + ); + } + + old_blocks + } + + /// Applies the canonical ancestor block via a reorg operation. + fn apply_canonical_ancestor_via_reorg( + &self, + canonical_header: &SealedHeader, + old_blocks: Vec>, + ) -> ProviderResult<()> { + let new_head_hash = canonical_header.hash(); + let new_head_number = canonical_header.number(); + + // Try to load the canonical ancestor's block + match self.canonical_block_by_hash(new_head_hash)? { + Some(executed_block) => { + let block_with_trie = ExecutedBlockWithTrieUpdates { + block: executed_block, + trie: ExecutedTrieUpdates::Missing, + }; + + // Perform the reorg to properly handle the unwind + self.canonical_in_memory_state.update_chain(NewCanonicalChain::Reorg { + new: vec![block_with_trie], + old: old_blocks, + }); + + // CRITICAL: Update the canonical head after the reorg + // This ensures get_canonical_head() returns the correct block + self.canonical_in_memory_state.set_canonical_head(canonical_header.clone()); + + debug!( + target: "engine::tree", + block_number = new_head_number, + block_hash = ?new_head_hash, + "Successfully loaded canonical ancestor into memory via reorg" + ); + } + None => { + // Fallback: update header only if block cannot be found + warn!( + target: "engine::tree", + block_hash = ?new_head_hash, + "Could not find canonical ancestor block, updating header only" + ); + self.canonical_in_memory_state.set_canonical_head(canonical_header.clone()); + } + } + + Ok(()) + } + + /// Handles chain advance or same height scenarios. + fn handle_chain_advance_or_same_height( + &self, + canonical_header: &SealedHeader, + ) -> ProviderResult<()> { + let new_head_number = canonical_header.number(); + let new_head_hash = canonical_header.hash(); + + // Update the canonical head header + self.canonical_in_memory_state.set_canonical_head(canonical_header.clone()); + + // Load the block into memory if it's not already present + self.ensure_block_in_memory(new_head_number, new_head_hash) + } + + /// Ensures a block is loaded into memory if not already present. + fn ensure_block_in_memory(&self, block_number: u64, block_hash: B256) -> ProviderResult<()> { + // Check if block is already in memory + if self.canonical_in_memory_state.state_by_number(block_number).is_some() { + return Ok(()); + } + + // Try to load the block from storage + if let Some(executed_block) = self.canonical_block_by_hash(block_hash)? { + let block_with_trie = ExecutedBlockWithTrieUpdates { + block: executed_block, + trie: ExecutedTrieUpdates::Missing, + }; + + self.canonical_in_memory_state + .update_chain(NewCanonicalChain::Commit { new: vec![block_with_trie] }); + + debug!( + target: "engine::tree", + block_number, + block_hash = ?block_hash, + "Added canonical block to in-memory state" + ); + } + + Ok(()) + } + /// Determines if the given block is part of a fork by checking that these /// conditions are true: /// * walking back from the target hash to verify that the target hash is not part of an @@ -826,13 +1042,13 @@ where // we still need to process payload attributes if the head is already canonical if let Some(attr) = attrs { let tip = self - .block_by_hash(self.state.tree_state.canonical_block_hash())? + .sealed_header_by_hash(self.state.tree_state.canonical_block_hash())? .ok_or_else(|| { // If we can't find the canonical block, then something is wrong and we need // to return an error ProviderError::HeaderNotFound(state.head_block_hash.into()) })?; - let updated = self.process_payload_attributes(attr, tip.header(), state, version); + let updated = self.process_payload_attributes(attr, &tip, state, version); return Ok(TreeOutcome::new(updated)) } @@ -844,9 +1060,8 @@ where if let Ok(Some(canonical_header)) = self.find_canonical_header(state.head_block_hash) { debug!(target: "engine::tree", head = canonical_header.number(), "fcu head block is already canonical"); - // For OpStack the proposers are allowed to reorg their own chain at will, so we need to - // always trigger a new payload job if requested. - // Also allow forcing this behavior via a config flag. + // For OpStack, or if explicitly configured, the proposers are allowed to reorg their + // own chain at will, so we need to always trigger a new payload job if requested. if self.engine_kind.is_opstack() || self.config.always_process_payload_attributes_on_canonical_head() { @@ -856,6 +1071,16 @@ where self.process_payload_attributes(attr, &canonical_header, state, version); return Ok(TreeOutcome::new(updated)) } + + // At this point, no alternative block has been triggered, so we need effectively + // unwind the _canonical_ chain to the FCU's head, which is part of the canonical + // chain. We need to update the latest block state to reflect the + // canonical ancestor. This ensures that state providers and the + // transaction pool operate with the correct chain state after + // forkchoice update processing. + if self.config.unwind_canonical_header() { + self.update_latest_block_to_canonical_ancestor(&canonical_header)?; + } } // 2. Client software MAY skip an update of the forkchoice state and MUST NOT begin a @@ -1347,6 +1572,9 @@ where /// `(last_persisted_number .. canonical_head - threshold]`. The expected /// order is oldest -> newest. /// + /// If any blocks are missing trie updates, all blocks are persisted, not taking `threshold` + /// into account. + /// /// For those blocks that didn't have the trie updates calculated, runs the state root /// calculation, and saves the trie updates. /// @@ -1361,13 +1589,31 @@ where let mut blocks_to_persist = Vec::new(); let mut current_hash = self.state.tree_state.canonical_block_hash(); let last_persisted_number = self.persistence_state.last_persisted_block.number; - let canonical_head_number = self.state.tree_state.canonical_block_number(); + let all_blocks_have_trie_updates = self + .state + .tree_state + .blocks_by_hash + .values() + .all(|block| block.trie_updates().is_some()); - let target_number = - canonical_head_number.saturating_sub(self.config.memory_block_buffer_target()); + let target_number = if all_blocks_have_trie_updates { + // Persist only up to block buffer target if all blocks have trie updates + canonical_head_number.saturating_sub(self.config.memory_block_buffer_target()) + } else { + // Persist all blocks if any block is missing trie updates + canonical_head_number + }; - debug!(target: "engine::tree", ?last_persisted_number, ?canonical_head_number, ?target_number, ?current_hash, "Returning canonical blocks to persist"); + debug!( + target: "engine::tree", + ?current_hash, + ?last_persisted_number, + ?canonical_head_number, + ?all_blocks_have_trie_updates, + ?target_number, + "Returning canonical blocks to persist" + ); while let Some(block) = self.state.tree_state.blocks_by_hash.get(¤t_hash) { if block.recovered_block().number() <= last_persisted_number { break; @@ -1484,42 +1730,21 @@ where })) } - /// Return sealed block from database or in-memory state by hash. + /// Return sealed block header from in-memory state or database by hash. fn sealed_header_by_hash( &self, hash: B256, ) -> ProviderResult>> { // check memory first - let block = self - .state - .tree_state - .block_by_hash(hash) - .map(|block| block.as_ref().clone_sealed_header()); + let header = self.state.tree_state.sealed_header_by_hash(&hash); - if block.is_some() { - Ok(block) + if header.is_some() { + Ok(header) } else { self.provider.sealed_header_by_hash(hash) } } - /// Return block from database or in-memory state by hash. - fn block_by_hash(&self, hash: B256) -> ProviderResult> { - // check database first - let mut block = self.provider.block_by_hash(hash)?; - if block.is_none() { - // Note: it's fine to return the unsealed block because the caller already has - // the hash - block = self - .state - .tree_state - .block_by_hash(hash) - // TODO: clone for compatibility. should we return an Arc here? - .map(|block| block.as_ref().clone().into_block()); - } - Ok(block) - } - /// Return the parent hash of the lowest buffered ancestor for the requested block, if there /// are any buffered ancestors. If there are no buffered ancestors, and the block itself does /// not exist in the buffer, this returns the hash that is passed in. @@ -1549,7 +1774,7 @@ where parent_hash: B256, ) -> ProviderResult> { // Check if parent exists in side chain or in canonical chain. - if self.block_by_hash(parent_hash)?.is_some() { + if self.sealed_header_by_hash(parent_hash)?.is_some() { return Ok(Some(parent_hash)) } @@ -1563,7 +1788,7 @@ where // If current_header is None, then the current_hash does not have an invalid // ancestor in the cache, check its presence in blockchain tree - if current_block.is_none() && self.block_by_hash(current_hash)?.is_some() { + if current_block.is_none() && self.sealed_header_by_hash(current_hash)?.is_some() { return Ok(Some(current_hash)) } } @@ -1576,10 +1801,10 @@ where fn prepare_invalid_response(&mut self, mut parent_hash: B256) -> ProviderResult { // Edge case: the `latestValid` field is the zero hash if the parent block is the terminal // PoW block, which we need to identify by looking at the parent's block difficulty - if let Some(parent) = self.block_by_hash(parent_hash)? { - if !parent.header().difficulty().is_zero() { - parent_hash = B256::ZERO; - } + if let Some(parent) = self.sealed_header_by_hash(parent_hash)? && + !parent.difficulty().is_zero() + { + parent_hash = B256::ZERO; } let valid_parent_hash = self.latest_valid_hash_for_invalid_payload(parent_hash)?; @@ -1745,62 +1970,65 @@ where let sync_target_state = self.state.forkchoice_state_tracker.sync_target_state(); // check if the downloaded block is the tracked finalized block - let mut exceeds_backfill_threshold = if let Some(buffered_finalized) = sync_target_state - .as_ref() - .and_then(|state| self.state.buffer.block(&state.finalized_block_hash)) - { - // if we have buffered the finalized block, we should check how far - // we're off - self.exceeds_backfill_run_threshold(canonical_tip_num, buffered_finalized.number()) - } else { - // check if the distance exceeds the threshold for backfill sync - self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number) - }; - - // If this is invoked after we downloaded a block we can check if this block is the - // finalized block - if let (Some(downloaded_block), Some(ref state)) = (downloaded_block, sync_target_state) { - if downloaded_block.hash == state.finalized_block_hash { - // we downloaded the finalized block and can now check how far we're off - exceeds_backfill_threshold = - self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number); - } - } + let exceeds_backfill_threshold = + match (downloaded_block.as_ref(), sync_target_state.as_ref()) { + // if we downloaded the finalized block we can now check how far we're off + (Some(downloaded_block), Some(state)) + if downloaded_block.hash == state.finalized_block_hash => + { + self.exceeds_backfill_run_threshold(canonical_tip_num, downloaded_block.number) + } + _ => match sync_target_state + .as_ref() + .and_then(|state| self.state.buffer.block(&state.finalized_block_hash)) + { + Some(buffered_finalized) => { + // if we have buffered the finalized block, we should check how far we're + // off + self.exceeds_backfill_run_threshold( + canonical_tip_num, + buffered_finalized.number(), + ) + } + None => { + // check if the distance exceeds the threshold for backfill sync + self.exceeds_backfill_run_threshold(canonical_tip_num, target_block_number) + } + }, + }; // if the number of missing blocks is greater than the max, trigger backfill - if exceeds_backfill_threshold { - if let Some(state) = sync_target_state { - // if we have already canonicalized the finalized block, we should skip backfill - match self.provider.header_by_hash_or_number(state.finalized_block_hash.into()) { - Err(err) => { - warn!(target: "engine::tree", %err, "Failed to get finalized block header"); + if exceeds_backfill_threshold && let Some(state) = sync_target_state { + // if we have already canonicalized the finalized block, we should skip backfill + match self.provider.header_by_hash_or_number(state.finalized_block_hash.into()) { + Err(err) => { + warn!(target: "engine::tree", %err, "Failed to get finalized block header"); + } + Ok(None) => { + // ensure the finalized block is known (not the zero hash) + if !state.finalized_block_hash.is_zero() { + // we don't have the block yet and the distance exceeds the allowed + // threshold + return Some(state.finalized_block_hash) } - Ok(None) => { - // ensure the finalized block is known (not the zero hash) - if !state.finalized_block_hash.is_zero() { - // we don't have the block yet and the distance exceeds the allowed - // threshold - return Some(state.finalized_block_hash) - } - // OPTIMISTIC SYNCING - // - // It can happen when the node is doing an - // optimistic sync, where the CL has no knowledge of the finalized hash, - // but is expecting the EL to sync as high - // as possible before finalizing. - // - // This usually doesn't happen on ETH mainnet since CLs use the more - // secure checkpoint syncing. - // - // However, optimism chains will do this. The risk of a reorg is however - // low. - debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target."); - return Some(state.head_block_hash) - } - Ok(Some(_)) => { - // we're fully synced to the finalized block - } + // OPTIMISTIC SYNCING + // + // It can happen when the node is doing an + // optimistic sync, where the CL has no knowledge of the finalized hash, + // but is expecting the EL to sync as high + // as possible before finalizing. + // + // This usually doesn't happen on ETH mainnet since CLs use the more + // secure checkpoint syncing. + // + // However, optimism chains will do this. The risk of a reorg is however + // low. + debug!(target: "engine::tree", hash=?state.head_block_hash, "Setting head hash as an optimistic backfill target."); + return Some(state.head_block_hash) + } + Ok(Some(_)) => { + // we're fully synced to the finalized block } } } @@ -2077,10 +2305,11 @@ where where Err: From>, { + let block_insert_start = Instant::now(); let block_num_hash = block_id.block; debug!(target: "engine::tree", block=?block_num_hash, parent = ?block_id.parent, "Inserting new block into tree"); - match self.block_by_hash(block_num_hash.hash) { + match self.sealed_header_by_hash(block_num_hash.hash) { Err(err) => { let block = convert_to_block(self, input)?; return Err(InsertBlockError::new(block.into_sealed_block(), err.into()).into()); @@ -2131,12 +2360,8 @@ where Ok(is_fork) => is_fork, }; - let ctx = TreeCtx::new( - &mut self.state, - &self.persistence_state, - &self.canonical_in_memory_state, - is_fork, - ); + let ctx = + TreeCtx::new(&mut self.state, &self.persistence_state, &self.canonical_in_memory_state); let start = Instant::now(); @@ -2161,6 +2386,10 @@ where }; self.emit_event(EngineApiEvent::BeaconConsensus(engine_event)); + self.metrics + .engine + .block_insert_total_duration + .record(block_insert_start.elapsed().as_secs_f64()); debug!(target: "engine::tree", block=?block_num_hash, "Finished inserting block"); Ok(InsertPayloadOk::Inserted(BlockStatus::Valid)) } @@ -2300,6 +2529,7 @@ where self.emit_event(EngineApiEvent::BeaconConsensus(ConsensusEngineEvent::InvalidBlock( Box::new(block), ))); + Ok(PayloadStatus::new( PayloadStatusEnum::Invalid { validation_error: validation_err.to_string() }, latest_valid_hash, diff --git a/crates/engine/tree/src/tree/payload_processor/configured_sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/configured_sparse_trie.rs index d59f14c796a..176cffcd8fa 100644 --- a/crates/engine/tree/src/tree/payload_processor/configured_sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/configured_sparse_trie.rs @@ -14,7 +14,7 @@ use std::borrow::Cow; /// This type allows runtime selection between different sparse trie implementations, /// providing flexibility in choosing the appropriate implementation based on workload /// characteristics. -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) enum ConfiguredSparseTrie { /// Serial implementation of the sparse trie. Serial(Box), diff --git a/crates/engine/tree/src/tree/payload_processor/executor.rs b/crates/engine/tree/src/tree/payload_processor/executor.rs index 3013c5e1c72..5d171f626fc 100644 --- a/crates/engine/tree/src/tree/payload_processor/executor.rs +++ b/crates/engine/tree/src/tree/payload_processor/executor.rs @@ -2,7 +2,10 @@ use rayon::ThreadPool as RayonPool; use std::{ - sync::{Arc, OnceLock}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, OnceLock, + }, time::Duration, }; use tokio::{ @@ -71,6 +74,7 @@ impl WorkloadExecutorInner { Handle::try_current().unwrap_or_else(|_| { // Create a new runtime if no runtime is available static RT: OnceLock = OnceLock::new(); + static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(0); let rt = RT.get_or_init(|| { Builder::new_multi_thread() @@ -82,6 +86,10 @@ impl WorkloadExecutorInner { // new block, and instead reuse the existing // threads. .thread_keep_alive(Duration::from_secs(15)) + .thread_name_fn(|| { + let id = THREAD_COUNTER.fetch_add(1, Ordering::Relaxed); + format!("tokio-payload-{id}") + }) .build() .unwrap() }); diff --git a/crates/engine/tree/src/tree/payload_processor/mod.rs b/crates/engine/tree/src/tree/payload_processor/mod.rs index 1133078978d..f1175ed57a1 100644 --- a/crates/engine/tree/src/tree/payload_processor/mod.rs +++ b/crates/engine/tree/src/tree/payload_processor/mod.rs @@ -1,7 +1,11 @@ //! Entrypoint for payload processing. +use super::precompile_cache::PrecompileCacheMap; use crate::tree::{ - cached_state::{CachedStateMetrics, ProviderCacheBuilder, ProviderCaches, SavedCache}, + cached_state::{ + CachedStateMetrics, ExecutionCache as StateExecutionCache, ExecutionCacheBuilder, + SavedCache, + }, payload_processor::{ prewarm::{PrewarmCacheTask, PrewarmContext, PrewarmTaskEvent}, sparse_trie::StateRootComputeOutcome, @@ -33,15 +37,15 @@ use reth_trie_parallel::{ }; use reth_trie_sparse::{ provider::{TrieNodeProvider, TrieNodeProviderFactory}, - ClearedSparseStateTrie, SerialSparseTrie, SparseStateTrie, SparseTrie, + ClearedSparseStateTrie, SparseStateTrie, SparseTrie, }; +use reth_trie_sparse_parallel::{ParallelSparseTrie, ParallelismThresholds}; use std::sync::{ atomic::AtomicBool, mpsc::{self, channel, Sender}, Arc, }; - -use super::precompile_cache::PrecompileCacheMap; +use tracing::{debug, instrument}; mod configured_sparse_trie; pub mod executor; @@ -51,6 +55,14 @@ pub mod sparse_trie; use configured_sparse_trie::ConfiguredSparseTrie; +/// Default parallelism thresholds to use with the [`ParallelSparseTrie`]. +/// +/// These values were determined by performing benchmarks using gradually increasing values to judge +/// the affects. Below 100 throughput would generally be equal or slightly less, while above 150 it +/// would deteriorate to the point where PST might as well not be used. +pub const PARALLEL_SPARSE_TRIE_PARALLELISM_THRESHOLDS: ParallelismThresholds = + ParallelismThresholds { min_revealed_nodes: 100, min_updated_nodes: 100 }; + /// Entrypoint for executing the payload. #[derive(Debug)] pub struct PayloadProcessor @@ -76,10 +88,12 @@ where /// A cleared `SparseStateTrie`, kept around to be reused for the state root computation so /// that allocations can be minimized. sparse_state_trie: Arc< - parking_lot::Mutex>>, + parking_lot::Mutex< + Option>, + >, >, - /// Whether to use the parallel sparse trie. - use_parallel_sparse_trie: bool, + /// Whether to disable the parallel sparse trie. + disable_parallel_sparse_trie: bool, /// A cleared trie input, kept around to be reused so allocations can be minimized. trie_input: Option, } @@ -107,7 +121,7 @@ where precompile_cache_map, sparse_state_trie: Arc::default(), trie_input: None, - use_parallel_sparse_trie: config.enable_parallel_sparse_trie(), + disable_parallel_sparse_trie: config.disable_parallel_sparse_trie(), } } } @@ -300,13 +314,14 @@ where transactions = mpsc::channel().1; } - let (cache, cache_metrics) = self.cache_for(env.parent_hash).split(); + let saved_cache = self.cache_for(env.parent_hash); + let cache = saved_cache.cache().clone(); + let cache_metrics = saved_cache.metrics().clone(); // configure prewarming let prewarm_ctx = PrewarmContext { env, evm_config: self.evm_config.clone(), - cache: cache.clone(), - cache_metrics: cache_metrics.clone(), + saved_cache, provider: provider_builder, metrics: PrewarmMetrics::default(), terminate_execution: Arc::new(AtomicBool::new(false)), @@ -341,11 +356,16 @@ where /// /// If the given hash is different then what is recently cached, then this will create a new /// instance. + #[instrument(target = "engine::caching", skip(self))] fn cache_for(&self, parent_hash: B256) -> SavedCache { - self.execution_cache.get_cache_for(parent_hash).unwrap_or_else(|| { - let cache = ProviderCacheBuilder::default().build_caches(self.cross_block_cache_size); + if let Some(cache) = self.execution_cache.get_cache_for(parent_hash) { + debug!("reusing execution cache"); + cache + } else { + debug!("creating new execution cache on cache miss"); + let cache = ExecutionCacheBuilder::default().build_caches(self.cross_block_cache_size); SavedCache::new(parent_hash, cache, CachedStateMetrics::zeroed()) - }) + } } /// Spawns the [`SparseTrieTask`] for this payload processor. @@ -363,21 +383,24 @@ where // there's none to reuse. let cleared_sparse_trie = Arc::clone(&self.sparse_state_trie); let sparse_state_trie = cleared_sparse_trie.lock().take().unwrap_or_else(|| { - let accounts_trie = if self.use_parallel_sparse_trie { - ConfiguredSparseTrie::Parallel(Default::default()) - } else { + let default_trie = SparseTrie::blind_from(if self.disable_parallel_sparse_trie { ConfiguredSparseTrie::Serial(Default::default()) - }; + } else { + ConfiguredSparseTrie::Parallel(Box::new( + ParallelSparseTrie::default() + .with_parallelism_thresholds(PARALLEL_SPARSE_TRIE_PARALLELISM_THRESHOLDS), + )) + }); ClearedSparseStateTrie::from_state_trie( SparseStateTrie::new() - .with_accounts_trie(SparseTrie::Blind(Some(Box::new(accounts_trie)))) + .with_accounts_trie(default_trie.clone()) + .with_default_storage_trie(default_trie) .with_updates(true), ) }); let task = - SparseTrieTask::<_, ConfiguredSparseTrie, SerialSparseTrie>::new_with_cleared_trie( - self.executor.clone(), + SparseTrieTask::<_, ConfiguredSparseTrie, ConfiguredSparseTrie>::new_with_cleared_trie( sparse_trie_rx, proof_task_handle, self.trie_metrics.clone(), @@ -438,10 +461,11 @@ impl PayloadHandle { } /// Returns a clone of the caches used by prewarming - pub(super) fn caches(&self) -> ProviderCaches { + pub(super) fn caches(&self) -> StateExecutionCache { self.prewarm_handle.cache.clone() } + /// Returns a clone of the cache metrics used by prewarming pub(super) fn cache_metrics(&self) -> CachedStateMetrics { self.prewarm_handle.cache_metrics.clone() } @@ -472,7 +496,7 @@ impl PayloadHandle { #[derive(Debug)] pub(crate) struct CacheTaskHandle { /// The shared cache the task operates with. - cache: ProviderCaches, + cache: StateExecutionCache, /// Metrics for the caches cache_metrics: CachedStateMetrics, /// Channel to the spawned prewarm task if any @@ -513,6 +537,24 @@ impl Drop for CacheTaskHandle { /// - Update cache upon successful payload execution /// /// This process assumes that payloads are received sequentially. +/// +/// ## Cache Safety +/// +/// **CRITICAL**: Cache update operations require exclusive access. All concurrent cache users +/// (such as prewarming tasks) must be terminated before calling `update_with_guard`, otherwise +/// the cache may be corrupted or cleared. +/// +/// ## Cache vs Prewarming Distinction +/// +/// **`ExecutionCache`**: +/// - Stores parent block's execution state after completion +/// - Used to fetch parent data for next block's execution +/// - Must be exclusively accessed during save operations +/// +/// **`PrewarmCacheTask`**: +/// - Speculatively loads accounts/storage that might be used in transaction execution +/// - Prepares data for state root proof computation +/// - Runs concurrently but must not interfere with cache saves #[derive(Clone, Debug, Default)] struct ExecutionCache { /// Guarded cloneable cache identified by a block hash. @@ -520,12 +562,17 @@ struct ExecutionCache { } impl ExecutionCache { - /// Returns the cache if the currently store cache is for the given `parent_hash` + /// Returns the cache for `parent_hash` if it's available for use. + /// + /// A cache is considered available when: + /// - It exists and matches the requested parent hash + /// - No other tasks are currently using it (checked via Arc reference count) pub(crate) fn get_cache_for(&self, parent_hash: B256) -> Option { let cache = self.inner.read(); cache .as_ref() - .and_then(|cache| (cache.executed_block_hash() == parent_hash).then(|| cache.clone())) + .filter(|c| c.executed_block_hash() == parent_hash && c.is_available()) + .cloned() } /// Clears the tracked cache @@ -534,9 +581,25 @@ impl ExecutionCache { self.inner.write().take(); } - /// Stores the provider cache - pub(crate) fn save_cache(&self, cache: SavedCache) { - self.inner.write().replace(cache); + /// Updates the cache with a closure that has exclusive access to the guard. + /// This ensures that all cache operations happen atomically. + /// + /// ## CRITICAL SAFETY REQUIREMENT + /// + /// **Before calling this method, you MUST ensure there are no other active cache users.** + /// This includes: + /// - No running [`PrewarmCacheTask`] instances that could write to the cache + /// - No concurrent transactions that might access the cached state + /// - All prewarming operations must be completed or cancelled + /// + /// Violating this requirement can result in cache corruption, incorrect state data, + /// and potential consensus failures. + pub(crate) fn update_with_guard(&self, update_fn: F) + where + F: FnOnce(&mut Option), + { + let mut guard = self.inner.write(); + update_fn(&mut guard); } } @@ -566,7 +629,9 @@ where #[cfg(test)] mod tests { + use super::ExecutionCache; use crate::tree::{ + cached_state::{CachedStateMetrics, ExecutionCacheBuilder, SavedCache}, payload_processor::{ evm_state_to_hashed_post_state, executor::WorkloadExecutor, PayloadProcessor, }, @@ -592,10 +657,81 @@ mod tests { use revm_state::{AccountInfo, AccountStatus, EvmState, EvmStorageSlot}; use std::sync::Arc; + fn make_saved_cache(hash: B256) -> SavedCache { + let execution_cache = ExecutionCacheBuilder::default().build_caches(1_000); + SavedCache::new(hash, execution_cache, CachedStateMetrics::zeroed()) + } + + #[test] + fn execution_cache_allows_single_checkout() { + let execution_cache = ExecutionCache::default(); + let hash = B256::from([1u8; 32]); + + execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(hash))); + + let first = execution_cache.get_cache_for(hash); + assert!(first.is_some(), "expected initial checkout to succeed"); + + let second = execution_cache.get_cache_for(hash); + assert!(second.is_none(), "second checkout should be blocked while guard is active"); + + drop(first); + + let third = execution_cache.get_cache_for(hash); + assert!(third.is_some(), "third checkout should succeed after guard is dropped"); + } + + #[test] + fn execution_cache_checkout_releases_on_drop() { + let execution_cache = ExecutionCache::default(); + let hash = B256::from([2u8; 32]); + + execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(hash))); + + { + let guard = execution_cache.get_cache_for(hash); + assert!(guard.is_some(), "expected checkout to succeed"); + // Guard dropped at end of scope + } + + let retry = execution_cache.get_cache_for(hash); + assert!(retry.is_some(), "checkout should succeed after guard drop"); + } + + #[test] + fn execution_cache_mismatch_parent_returns_none() { + let execution_cache = ExecutionCache::default(); + let hash = B256::from([3u8; 32]); + + execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(hash))); + + let miss = execution_cache.get_cache_for(B256::from([4u8; 32])); + assert!(miss.is_none(), "checkout should fail for different parent hash"); + } + + #[test] + fn execution_cache_update_after_release_succeeds() { + let execution_cache = ExecutionCache::default(); + let initial = B256::from([5u8; 32]); + + execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(initial))); + + let guard = + execution_cache.get_cache_for(initial).expect("expected initial checkout to succeed"); + + drop(guard); + + let updated = B256::from([6u8; 32]); + execution_cache.update_with_guard(|slot| *slot = Some(make_saved_cache(updated))); + + let new_checkout = execution_cache.get_cache_for(updated); + assert!(new_checkout.is_some(), "new checkout should succeed after release and update"); + } + fn create_mock_state_updates(num_accounts: usize, updates_per_account: usize) -> Vec { let mut rng = generators::rng(); let all_addresses: Vec
= (0..num_accounts).map(|_| rng.random()).collect(); - let mut updates = Vec::new(); + let mut updates = Vec::with_capacity(updates_per_account); for _ in 0..updates_per_account { let num_accounts_in_update = rng.random_range(1..=num_accounts); diff --git a/crates/engine/tree/src/tree/payload_processor/multiproof.rs b/crates/engine/tree/src/tree/payload_processor/multiproof.rs index 810f1a4fe60..7107dadad30 100644 --- a/crates/engine/tree/src/tree/payload_processor/multiproof.rs +++ b/crates/engine/tree/src/tree/payload_processor/multiproof.rs @@ -14,8 +14,9 @@ use reth_metrics::Metrics; use reth_provider::{providers::ConsistentDbView, BlockReader, DatabaseProviderFactory, FactoryTx}; use reth_revm::state::EvmState; use reth_trie::{ - prefix_set::TriePrefixSetsMut, updates::TrieUpdatesSorted, DecodedMultiProof, HashedPostState, - HashedPostStateSorted, HashedStorage, MultiProofTargets, TrieInput, + added_removed_keys::MultiAddedRemovedKeys, prefix_set::TriePrefixSetsMut, + updates::TrieUpdatesSorted, DecodedMultiProof, HashedPostState, HashedPostStateSorted, + HashedStorage, MultiProofTargets, TrieInput, }; use reth_trie_parallel::{proof::ParallelProof, proof_task::ProofTaskManagerHandle}; use std::{ @@ -301,6 +302,7 @@ struct StorageMultiproofInput { proof_targets: B256Set, proof_sequence_number: u64, state_root_message_sender: Sender, + multi_added_removed_keys: Arc, } impl StorageMultiproofInput { @@ -322,6 +324,7 @@ struct MultiproofInput { proof_targets: MultiProofTargets, proof_sequence_number: u64, state_root_message_sender: Sender, + multi_added_removed_keys: Option>, } impl MultiproofInput { @@ -432,6 +435,7 @@ where proof_targets, proof_sequence_number, state_root_message_sender, + multi_added_removed_keys, } = storage_multiproof_input; let storage_proof_task_handle = self.storage_proof_task_handle.clone(); @@ -455,6 +459,7 @@ where storage_proof_task_handle.clone(), ) .with_branch_node_masks(true) + .with_multi_added_removed_keys(Some(multi_added_removed_keys)) .decoded_storage_proof(hashed_address, proof_targets); let elapsed = start.elapsed(); trace!( @@ -502,6 +507,7 @@ where proof_targets, proof_sequence_number, state_root_message_sender, + multi_added_removed_keys, } = multiproof_input; let storage_proof_task_handle = self.storage_proof_task_handle.clone(); @@ -515,8 +521,10 @@ where ?proof_targets, account_targets, storage_targets, + ?source, "Starting multiproof calculation", ); + let start = Instant::now(); let result = ParallelProof::new( config.consistent_view, @@ -526,6 +534,7 @@ where storage_proof_task_handle.clone(), ) .with_branch_node_masks(true) + .with_multi_added_removed_keys(multi_added_removed_keys) .decoded_multiproof(proof_targets); let elapsed = start.elapsed(); trace!( @@ -628,6 +637,8 @@ pub(super) struct MultiProofTask { to_sparse_trie: Sender, /// Proof targets that have been already fetched. fetched_proof_targets: MultiProofTargets, + /// Tracks keys which have been added and removed throughout the entire block. + multi_added_removed_keys: MultiAddedRemovedKeys, /// Proof sequencing handler. proof_sequencer: ProofSequencer, /// Manages calculation of multiproofs. @@ -657,6 +668,7 @@ where tx, to_sparse_trie, fetched_proof_targets: Default::default(), + multi_added_removed_keys: MultiAddedRemovedKeys::new(), proof_sequencer: ProofSequencer::default(), multiproof_manager: MultiproofManager::new( executor, @@ -680,6 +692,14 @@ where let proof_targets = self.get_prefetch_proof_targets(targets); self.fetched_proof_targets.extend_ref(&proof_targets); + // Make sure all target accounts have an `AddedRemovedKeySet` in the + // [`MultiAddedRemovedKeys`]. Even if there are not any known removed keys for the account, + // we still want to optimistically fetch extension children for the leaf addition case. + self.multi_added_removed_keys.touch_accounts(proof_targets.keys().copied()); + + // Clone+Arc MultiAddedRemovedKeys for sharing with the spawned multiproof tasks + let multi_added_removed_keys = Arc::new(self.multi_added_removed_keys.clone()); + self.metrics.prefetch_proof_targets_accounts_histogram.record(proof_targets.len() as f64); self.metrics .prefetch_proof_targets_storages_histogram @@ -696,6 +716,7 @@ where proof_targets: proof_targets_chunk, proof_sequence_number: self.proof_sequencer.next_sequence(), state_root_message_sender: self.tx.clone(), + multi_added_removed_keys: Some(multi_added_removed_keys.clone()), } .into(), ); @@ -784,10 +805,14 @@ where /// Returns a number of proofs that were spawned. fn on_state_update(&mut self, source: StateChangeSource, update: EvmState) -> u64 { let hashed_state_update = evm_state_to_hashed_post_state(update); + + // Update removed keys based on the state update. + self.multi_added_removed_keys.update_with_state(&hashed_state_update); + // Split the state update into already fetched and not fetched according to the proof // targets. - let (fetched_state_update, not_fetched_state_update) = - hashed_state_update.partition_by_targets(&self.fetched_proof_targets); + let (fetched_state_update, not_fetched_state_update) = hashed_state_update + .partition_by_targets(&self.fetched_proof_targets, &self.multi_added_removed_keys); let mut state_updates = 0; // If there are any accounts or storage slots that we already fetched the proofs for, @@ -800,11 +825,15 @@ where state_updates += 1; } + // Clone+Arc MultiAddedRemovedKeys for sharing with the spawned multiproof tasks + let multi_added_removed_keys = Arc::new(self.multi_added_removed_keys.clone()); + // Process state updates in chunks. let mut chunks = 0; let mut spawned_proof_targets = MultiProofTargets::default(); for chunk in not_fetched_state_update.chunks(MULTIPROOF_TARGETS_CHUNK_SIZE) { - let proof_targets = get_proof_targets(&chunk, &self.fetched_proof_targets); + let proof_targets = + get_proof_targets(&chunk, &self.fetched_proof_targets, &multi_added_removed_keys); spawned_proof_targets.extend_ref(&proof_targets); self.multiproof_manager.spawn_or_queue( @@ -815,6 +844,7 @@ where proof_targets, proof_sequence_number: self.proof_sequencer.next_sequence(), state_root_message_sender: self.tx.clone(), + multi_added_removed_keys: Some(multi_added_removed_keys.clone()), } .into(), ); @@ -1044,10 +1074,8 @@ where Err(_) => { // this means our internal message channel is closed, which shouldn't happen // in normal operation since we hold both ends - error!( - target: "engine::root", - "Internal message channel closed unexpectedly" - ); + error!(target: "engine::root", "Internal message channel closed unexpectedly"); + return } } } @@ -1082,6 +1110,7 @@ where fn get_proof_targets( state_update: &HashedPostState, fetched_proof_targets: &MultiProofTargets, + multi_added_removed_keys: &MultiAddedRemovedKeys, ) -> MultiProofTargets { let mut targets = MultiProofTargets::default(); @@ -1095,10 +1124,14 @@ fn get_proof_targets( // then process storage slots for all accounts in the state update for (hashed_address, storage) in &state_update.storages { let fetched = fetched_proof_targets.get(hashed_address); + let storage_added_removed_keys = multi_added_removed_keys.get_storage(hashed_address); let mut changed_slots = storage .storage .keys() - .filter(|slot| !fetched.is_some_and(|f| f.contains(*slot))) + .filter(|slot| { + !fetched.is_some_and(|f| f.contains(*slot)) || + storage_added_removed_keys.is_some_and(|k| k.is_removed(slot)) + }) .peekable(); // If the storage is wiped, we still need to fetch the account proof. @@ -1264,7 +1297,7 @@ mod tests { let state = create_get_proof_targets_state(); let fetched = MultiProofTargets::default(); - let targets = get_proof_targets(&state, &fetched); + let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new()); // should return all accounts as targets since nothing was fetched before assert_eq!(targets.len(), state.accounts.len()); @@ -1278,7 +1311,7 @@ mod tests { let state = create_get_proof_targets_state(); let fetched = MultiProofTargets::default(); - let targets = get_proof_targets(&state, &fetched); + let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new()); // verify storage slots are included for accounts with storage for (addr, storage) in &state.storages { @@ -1306,7 +1339,7 @@ mod tests { // mark the account as already fetched fetched.insert(*fetched_addr, HashSet::default()); - let targets = get_proof_targets(&state, &fetched); + let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new()); // should not include the already fetched account since it has no storage updates assert!(!targets.contains_key(fetched_addr)); @@ -1326,7 +1359,7 @@ mod tests { fetched_slots.insert(fetched_slot); fetched.insert(*addr, fetched_slots); - let targets = get_proof_targets(&state, &fetched); + let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new()); // should not include the already fetched storage slot let target_slots = &targets[addr]; @@ -1339,7 +1372,7 @@ mod tests { let state = HashedPostState::default(); let fetched = MultiProofTargets::default(); - let targets = get_proof_targets(&state, &fetched); + let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new()); assert!(targets.is_empty()); } @@ -1366,7 +1399,7 @@ mod tests { fetched_slots.insert(slot1); fetched.insert(addr1, fetched_slots); - let targets = get_proof_targets(&state, &fetched); + let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new()); assert!(targets.contains_key(&addr2)); assert!(!targets[&addr1].contains(&slot1)); @@ -1392,7 +1425,7 @@ mod tests { assert!(!state.accounts.contains_key(&addr)); assert!(!fetched.contains_key(&addr)); - let targets = get_proof_targets(&state, &fetched); + let targets = get_proof_targets(&state, &fetched, &MultiAddedRemovedKeys::new()); // verify that we still get the storage slots for the unmodified account assert!(targets.contains_key(&addr)); @@ -1414,8 +1447,8 @@ mod tests { let addr2 = B256::random(); let slot1 = B256::random(); let slot2 = B256::random(); - targets.insert(addr1, vec![slot1].into_iter().collect()); - targets.insert(addr2, vec![slot2].into_iter().collect()); + targets.insert(addr1, std::iter::once(slot1).collect()); + targets.insert(addr2, std::iter::once(slot2).collect()); let prefetch_proof_targets = test_state_root_task.get_prefetch_proof_targets(targets.clone()); @@ -1427,7 +1460,7 @@ mod tests { // add a different addr and slot to fetched proof targets let addr3 = B256::random(); let slot3 = B256::random(); - test_state_root_task.fetched_proof_targets.insert(addr3, vec![slot3].into_iter().collect()); + test_state_root_task.fetched_proof_targets.insert(addr3, std::iter::once(slot3).collect()); let prefetch_proof_targets = test_state_root_task.get_prefetch_proof_targets(targets.clone()); @@ -1448,11 +1481,11 @@ mod tests { let addr2 = B256::random(); let slot1 = B256::random(); let slot2 = B256::random(); - targets.insert(addr1, vec![slot1].into_iter().collect()); - targets.insert(addr2, vec![slot2].into_iter().collect()); + targets.insert(addr1, std::iter::once(slot1).collect()); + targets.insert(addr2, std::iter::once(slot2).collect()); // add a subset of the first target to fetched proof targets - test_state_root_task.fetched_proof_targets.insert(addr1, vec![slot1].into_iter().collect()); + test_state_root_task.fetched_proof_targets.insert(addr1, std::iter::once(slot1).collect()); let prefetch_proof_targets = test_state_root_task.get_prefetch_proof_targets(targets.clone()); @@ -1475,12 +1508,119 @@ mod tests { assert!(prefetch_proof_targets.contains_key(&addr1)); assert_eq!( *prefetch_proof_targets.get(&addr1).unwrap(), - vec![slot3].into_iter().collect::() + std::iter::once(slot3).collect::() ); assert!(prefetch_proof_targets.contains_key(&addr2)); assert_eq!( *prefetch_proof_targets.get(&addr2).unwrap(), - vec![slot2].into_iter().collect::() + std::iter::once(slot2).collect::() ); } + + #[test] + fn test_get_proof_targets_with_removed_storage_keys() { + let mut state = HashedPostState::default(); + let mut fetched = MultiProofTargets::default(); + let mut multi_added_removed_keys = MultiAddedRemovedKeys::new(); + + let addr = B256::random(); + let slot1 = B256::random(); + let slot2 = B256::random(); + + // add account to state + state.accounts.insert(addr, Some(Default::default())); + + // add storage updates + let mut storage = HashedStorage::default(); + storage.storage.insert(slot1, U256::from(100)); + storage.storage.insert(slot2, U256::from(200)); + state.storages.insert(addr, storage); + + // mark slot1 as already fetched + let mut fetched_slots = HashSet::default(); + fetched_slots.insert(slot1); + fetched.insert(addr, fetched_slots); + + // update multi_added_removed_keys to mark slot1 as removed + let mut removed_state = HashedPostState::default(); + let mut removed_storage = HashedStorage::default(); + removed_storage.storage.insert(slot1, U256::ZERO); // U256::ZERO marks as removed + removed_state.storages.insert(addr, removed_storage); + multi_added_removed_keys.update_with_state(&removed_state); + + let targets = get_proof_targets(&state, &fetched, &multi_added_removed_keys); + + // slot1 should be included despite being fetched, because it's marked as removed + assert!(targets.contains_key(&addr)); + let target_slots = &targets[&addr]; + assert_eq!(target_slots.len(), 2); + assert!(target_slots.contains(&slot1)); // included because it's removed + assert!(target_slots.contains(&slot2)); // included because it's not fetched + } + + #[test] + fn test_get_proof_targets_with_wiped_storage() { + let mut state = HashedPostState::default(); + let fetched = MultiProofTargets::default(); + let multi_added_removed_keys = MultiAddedRemovedKeys::new(); + + let addr = B256::random(); + let slot1 = B256::random(); + + // add account to state + state.accounts.insert(addr, Some(Default::default())); + + // add wiped storage + let mut storage = HashedStorage::new(true); + storage.storage.insert(slot1, U256::from(100)); + state.storages.insert(addr, storage); + + let targets = get_proof_targets(&state, &fetched, &multi_added_removed_keys); + + // account should be included because storage is wiped and account wasn't fetched + assert!(targets.contains_key(&addr)); + let target_slots = &targets[&addr]; + assert_eq!(target_slots.len(), 1); + assert!(target_slots.contains(&slot1)); + } + + #[test] + fn test_get_proof_targets_removed_keys_not_in_state_update() { + let mut state = HashedPostState::default(); + let mut fetched = MultiProofTargets::default(); + let mut multi_added_removed_keys = MultiAddedRemovedKeys::new(); + + let addr = B256::random(); + let slot1 = B256::random(); + let slot2 = B256::random(); + let slot3 = B256::random(); + + // add account to state + state.accounts.insert(addr, Some(Default::default())); + + // add storage updates for slot1 and slot2 only + let mut storage = HashedStorage::default(); + storage.storage.insert(slot1, U256::from(100)); + storage.storage.insert(slot2, U256::from(200)); + state.storages.insert(addr, storage); + + // mark all slots as already fetched + let mut fetched_slots = HashSet::default(); + fetched_slots.insert(slot1); + fetched_slots.insert(slot2); + fetched_slots.insert(slot3); // slot3 is fetched but not in state update + fetched.insert(addr, fetched_slots); + + // mark slot3 as removed (even though it's not in the state update) + let mut removed_state = HashedPostState::default(); + let mut removed_storage = HashedStorage::default(); + removed_storage.storage.insert(slot3, U256::ZERO); + removed_state.storages.insert(addr, removed_storage); + multi_added_removed_keys.update_with_state(&removed_state); + + let targets = get_proof_targets(&state, &fetched, &multi_added_removed_keys); + + // only slots in the state update can be included, so slot3 should not appear + assert!(!targets.contains_key(&addr)); + } } diff --git a/crates/engine/tree/src/tree/payload_processor/prewarm.rs b/crates/engine/tree/src/tree/payload_processor/prewarm.rs index 112c24d5bc1..ca3aff3c37f 100644 --- a/crates/engine/tree/src/tree/payload_processor/prewarm.rs +++ b/crates/engine/tree/src/tree/payload_processor/prewarm.rs @@ -1,19 +1,32 @@ //! Caching and prewarming related functionality. +//! +//! Prewarming executes transactions in parallel before the actual block execution +//! to populate the execution cache with state that will likely be accessed during +//! block processing. +//! +//! ## How Prewarming Works +//! +//! 1. Incoming transactions are split into two streams: one for prewarming (executed in parallel) +//! and one for actual execution (executed sequentially) +//! 2. Prewarming tasks execute transactions in parallel using shared caches +//! 3. When actual block execution happens, it benefits from the warmed cache use crate::tree::{ - cached_state::{CachedStateMetrics, CachedStateProvider, ProviderCaches, SavedCache}, + cached_state::{CachedStateProvider, SavedCache}, payload_processor::{ - executor::WorkloadExecutor, multiproof::MultiProofMessage, ExecutionCache, + executor::WorkloadExecutor, multiproof::MultiProofMessage, + ExecutionCache as PayloadExecutionCache, }, precompile_cache::{CachedPrecompile, PrecompileCacheMap}, ExecutionEnv, StateProviderBuilder, }; +use alloy_consensus::transaction::TxHashRef; use alloy_evm::Database; use alloy_primitives::{keccak256, map::B256Set, B256}; -use metrics::{Gauge, Histogram}; +use metrics::{Counter, Gauge, Histogram}; use reth_evm::{execute::ExecutableTxFor, ConfigureEvm, Evm, EvmFor, SpecFor}; use reth_metrics::Metrics; -use reth_primitives_traits::{NodePrimitives, SignedTransaction}; +use reth_primitives_traits::NodePrimitives; use reth_provider::{BlockReader, StateProviderFactory, StateReader}; use reth_revm::{database::StateProviderDatabase, db::BundleState, state::EvmState}; use reth_trie::MultiProofTargets; @@ -39,7 +52,7 @@ where /// The executor used to spawn execution tasks. executor: WorkloadExecutor, /// Shared execution cache. - execution_cache: ExecutionCache, + execution_cache: PayloadExecutionCache, /// Context provided to execution tasks ctx: PrewarmContext, /// How many transactions should be executed in parallel @@ -59,7 +72,7 @@ where /// Initializes the task with the given transactions pending execution pub(super) fn new( executor: WorkloadExecutor, - execution_cache: ExecutionCache, + execution_cache: PayloadExecutionCache, ctx: PrewarmContext, to_multi_proof: Option>, ) -> (Self, Sender) { @@ -88,7 +101,7 @@ where let max_concurrency = self.max_concurrency; self.executor.spawn_blocking(move || { - let mut handles = Vec::new(); + let mut handles = Vec::with_capacity(max_concurrency); let (done_tx, done_rx) = mpsc::channel(); let mut executing = 0; while let Ok(executable) = pending.recv() { @@ -129,25 +142,47 @@ where } } - /// Save the state to the shared cache for the given block. + /// This method calls `ExecutionCache::update_with_guard` which requires exclusive access. + /// It should only be called after ensuring that: + /// 1. All prewarming tasks have completed execution + /// 2. No other concurrent operations are accessing the cache + /// + /// Saves the warmed caches back into the shared slot after prewarming completes. + /// + /// This consumes the `SavedCache` held by the task, which releases its usage guard and allows + /// the new, warmed cache to be inserted. + /// + /// This method is called from `run()` only after all execution tasks are complete. fn save_cache(self, state: BundleState) { let start = Instant::now(); - let cache = SavedCache::new( - self.ctx.env.hash, - self.ctx.cache.clone(), - self.ctx.cache_metrics.clone(), - ); - if cache.cache().insert_state(&state).is_err() { - return - } - cache.update_metrics(); + let Self { execution_cache, ctx: PrewarmContext { env, metrics, saved_cache, .. }, .. } = + self; + let hash = env.hash; + + // Perform all cache operations atomically under the lock + execution_cache.update_with_guard(|cached| { - debug!(target: "engine::caching", "Updated state caches"); + // consumes the `SavedCache` held by the prewarming task, which releases its usage guard + let (caches, cache_metrics) = saved_cache.split(); + let new_cache = SavedCache::new(hash, caches, cache_metrics); - // update the cache for the executed block - self.execution_cache.save_cache(cache); - self.ctx.metrics.cache_saving_duration.set(start.elapsed().as_secs_f64()); + // Insert state into cache while holding the lock + if new_cache.cache().insert_state(&state).is_err() { + // Clear the cache on error to prevent having a polluted cache + *cached = None; + debug!(target: "engine::caching", "cleared execution cache on update error"); + return; + } + + new_cache.update_metrics(); + debug!(target: "engine::caching", parent_hash=?new_cache.executed_block_hash(), "Updated execution cache"); + + // Replace the shared cache with the new one; the previous cache (if any) is dropped. + *cached = Some(new_cache); + }); + + metrics.cache_saving_duration.set(start.elapsed().as_secs_f64()); } /// Executes the task. @@ -175,6 +210,7 @@ where self.send_multi_proof_targets(proof_targets); } PrewarmTaskEvent::Terminate { block_output } => { + trace!(target: "engine::tree::prewarm", "Received termination signal"); final_block_output = Some(block_output); if finished_execution { @@ -183,6 +219,7 @@ where } } PrewarmTaskEvent::FinishedTxExecution { executed_transactions } => { + trace!(target: "engine::tree::prewarm", "Finished prewarm execution signal"); self.ctx.metrics.transactions.set(executed_transactions as f64); self.ctx.metrics.transactions_histogram.record(executed_transactions as f64); @@ -196,6 +233,8 @@ where } } + trace!(target: "engine::tree::prewarm", "Completed prewarm execution"); + // save caches and finish if let Some(Some(state)) = final_block_output { self.save_cache(state); @@ -212,8 +251,7 @@ where { pub(super) env: ExecutionEnv, pub(super) evm_config: Evm, - pub(super) cache: ProviderCaches, - pub(super) cache_metrics: CachedStateMetrics, + pub(super) saved_cache: SavedCache, /// Provider to obtain the state pub(super) provider: StateProviderBuilder, pub(super) metrics: PrewarmMetrics, @@ -235,8 +273,7 @@ where let Self { env, evm_config, - cache: caches, - cache_metrics, + saved_cache, provider, metrics, terminate_execution, @@ -257,6 +294,8 @@ where }; // Use the caches to create a new provider with caching + let caches = saved_cache.cache().clone(); + let cache_metrics = saved_cache.metrics().clone(); let state_provider = CachedStateProvider::new_with_caches(state_provider, caches, cache_metrics); @@ -293,8 +332,8 @@ where /// Returns `None` if executing the transactions failed to a non Revert error. /// Returns the touched+modified state of the transaction. /// - /// Note: Since here are no ordering guarantees this won't the state the txs produce when - /// executed sequentially. + /// Note: There are no ordering guarantees; this does not reflect the state produced by + /// sequential execution. fn transact_batch( self, txs: mpsc::Receiver>, @@ -317,13 +356,16 @@ where Ok(res) => res, Err(err) => { trace!( - target: "engine::tree", + target: "engine::tree::prewarm", %err, tx_hash=%tx.tx().tx_hash(), sender=%tx.signer(), "Error when executing prewarm transaction", ); - return + // Track transaction execution errors + metrics.transaction_errors.increment(1); + // skip error because we can ignore these errors and continue with the next tx + continue } }; metrics.execution_duration.record(start.elapsed()); @@ -413,4 +455,6 @@ pub(crate) struct PrewarmMetrics { pub(crate) prefetch_storage_targets: Histogram, /// A histogram of duration for cache saving pub(crate) cache_saving_duration: Gauge, + /// Counter for transaction execution errors during prewarming + pub(crate) transaction_errors: Counter, } diff --git a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs index 9879a2c58bf..bc0b6adfbc9 100644 --- a/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs +++ b/crates/engine/tree/src/tree/payload_processor/sparse_trie.rs @@ -1,9 +1,6 @@ //! Sparse Trie task related functionality. -use crate::tree::payload_processor::{ - executor::WorkloadExecutor, - multiproof::{MultiProofTaskMetrics, SparseTrieUpdate}, -}; +use crate::tree::payload_processor::multiproof::{MultiProofTaskMetrics, SparseTrieUpdate}; use alloy_primitives::B256; use rayon::iter::{ParallelBridge, ParallelIterator}; use reth_trie::{updates::TrieUpdates, Nibbles}; @@ -13,6 +10,7 @@ use reth_trie_sparse::{ provider::{TrieNodeProvider, TrieNodeProviderFactory}, ClearedSparseStateTrie, SerialSparseTrie, SparseStateTrie, SparseTrieInterface, }; +use smallvec::SmallVec; use std::{ sync::mpsc, time::{Duration, Instant}, @@ -26,9 +24,6 @@ where BPF::AccountNodeProvider: TrieNodeProvider + Send + Sync, BPF::StorageNodeProvider: TrieNodeProvider + Send + Sync, { - /// Executor used to spawn subtasks. - #[expect(unused)] // TODO use this for spawning trie tasks - pub(super) executor: WorkloadExecutor, /// Receives updates from the state root task. pub(super) updates: mpsc::Receiver, /// `SparseStateTrie` used for computing the state root. @@ -44,23 +39,16 @@ where BPF::AccountNodeProvider: TrieNodeProvider + Send + Sync, BPF::StorageNodeProvider: TrieNodeProvider + Send + Sync, A: SparseTrieInterface + Send + Sync + Default, - S: SparseTrieInterface + Send + Sync + Default, + S: SparseTrieInterface + Send + Sync + Default + Clone, { /// Creates a new sparse trie, pre-populating with a [`ClearedSparseStateTrie`]. pub(super) fn new_with_cleared_trie( - executor: WorkloadExecutor, updates: mpsc::Receiver, blinded_provider_factory: BPF, metrics: MultiProofTaskMetrics, sparse_state_trie: ClearedSparseStateTrie, ) -> Self { - Self { - executor, - updates, - metrics, - trie: sparse_state_trie.into_inner(), - blinded_provider_factory, - } + Self { updates, metrics, trie: sparse_state_trie.into_inner(), blinded_provider_factory } } /// Runs the sparse trie task to completion. @@ -152,7 +140,7 @@ where BPF::AccountNodeProvider: TrieNodeProvider + Send + Sync, BPF::StorageNodeProvider: TrieNodeProvider + Send + Sync, A: SparseTrieInterface + Send + Sync + Default, - S: SparseTrieInterface + Send + Sync + Default, + S: SparseTrieInterface + Send + Sync + Default + Clone, { trace!(target: "engine::root::sparse", "Updating sparse trie"); let started_at = Instant::now(); @@ -184,28 +172,52 @@ where trace!(target: "engine::root::sparse", "Wiping storage"); storage_trie.wipe()?; } + + // Defer leaf removals until after updates/additions, so that we don't delete an + // intermediate branch node during a removal and then re-add that branch back during a + // later leaf addition. This is an optimization, but also a requirement inherited from + // multiproof generating, which can't know the order that leaf operations happen in. + let mut removed_slots = SmallVec::<[Nibbles; 8]>::new(); + for (slot, value) in storage.storage { let slot_nibbles = Nibbles::unpack(slot); + if value.is_zero() { - trace!(target: "engine::root::sparse", ?slot, "Removing storage slot"); - storage_trie.remove_leaf(&slot_nibbles, &storage_provider)?; - } else { - trace!(target: "engine::root::sparse", ?slot, "Updating storage slot"); - storage_trie.update_leaf( - slot_nibbles, - alloy_rlp::encode_fixed_size(&value).to_vec(), - &storage_provider, - )?; + removed_slots.push(slot_nibbles); + continue; } + + trace!(target: "engine::root::sparse", ?slot_nibbles, "Updating storage slot"); + storage_trie.update_leaf( + slot_nibbles, + alloy_rlp::encode_fixed_size(&value).to_vec(), + &storage_provider, + )?; + } + + for slot_nibbles in removed_slots { + trace!(target: "engine::root::sparse", ?slot_nibbles, "Removing storage slot"); + storage_trie.remove_leaf(&slot_nibbles, &storage_provider)?; } storage_trie.root(); SparseStateTrieResult::Ok((address, storage_trie)) }) - .for_each_init(|| tx.clone(), |tx, result| tx.send(result).unwrap()); + .for_each_init( + || tx.clone(), + |tx, result| { + let _ = tx.send(result); + }, + ); drop(tx); + // Defer leaf removals until after updates/additions, so that we don't delete an intermediate + // branch node during a removal and then re-add that branch back during a later leaf addition. + // This is an optimization, but also a requirement inherited from multiproof generating, which + // can't know the order that leaf operations happen in. + let mut removed_accounts = Vec::new(); + // Update account storage roots for result in rx { let (address, storage_trie) = result?; @@ -215,18 +227,35 @@ where // If the account itself has an update, remove it from the state update and update in // one go instead of doing it down below. trace!(target: "engine::root::sparse", ?address, "Updating account and its storage root"); - trie.update_account(address, account.unwrap_or_default(), blinded_provider_factory)?; + if !trie.update_account( + address, + account.unwrap_or_default(), + blinded_provider_factory, + )? { + removed_accounts.push(address); + } } else if trie.is_account_revealed(address) { // Otherwise, if the account is revealed, only update its storage root. trace!(target: "engine::root::sparse", ?address, "Updating account storage root"); - trie.update_account_storage_root(address, blinded_provider_factory)?; + if !trie.update_account_storage_root(address, blinded_provider_factory)? { + removed_accounts.push(address); + } } } // Update accounts for (address, account) in state.accounts { trace!(target: "engine::root::sparse", ?address, "Updating account"); - trie.update_account(address, account.unwrap_or_default(), blinded_provider_factory)?; + if !trie.update_account(address, account.unwrap_or_default(), blinded_provider_factory)? { + removed_accounts.push(address); + } + } + + // Remove accounts + for address in removed_accounts { + trace!(target: "trie::sparse", ?address, "Removing account"); + let nibbles = Nibbles::unpack(address); + trie.remove_account_leaf(&nibbles, blinded_provider_factory)?; } let elapsed_before = started_at.elapsed(); diff --git a/crates/engine/tree/src/tree/payload_validator.rs b/crates/engine/tree/src/tree/payload_validator.rs index fd4a30a767d..22f31370725 100644 --- a/crates/engine/tree/src/tree/payload_validator.rs +++ b/crates/engine/tree/src/tree/payload_validator.rs @@ -35,16 +35,16 @@ use reth_primitives_traits::{ AlloyBlockHeader, BlockTy, GotExpected, NodePrimitives, RecoveredBlock, SealedHeader, }; use reth_provider::{ - BlockExecutionOutput, BlockNumReader, BlockReader, DBProvider, DatabaseProviderFactory, - ExecutionOutcome, HashedPostStateProvider, ProviderError, StateProvider, StateProviderFactory, - StateReader, StateRootProvider, + BlockExecutionOutput, BlockHashReader, BlockNumReader, BlockReader, DBProvider, + DatabaseProviderFactory, ExecutionOutcome, HashedPostStateProvider, HeaderProvider, + ProviderError, StateProvider, StateProviderFactory, StateReader, StateRootProvider, }; use reth_revm::db::State; use reth_trie::{updates::TrieUpdates, HashedPostState, KeccakKeyHasher, TrieInput}; use reth_trie_db::DatabaseHashedPostState; use reth_trie_parallel::root::{ParallelStateRoot, ParallelStateRootError}; use std::{collections::HashMap, sync::Arc, time::Instant}; -use tracing::{debug, error, info, trace, warn}; +use tracing::{debug, debug_span, error, info, trace, warn}; /// Context providing access to tree state during validation. /// @@ -57,8 +57,6 @@ pub struct TreeCtx<'a, N: NodePrimitives> { persistence: &'a PersistenceState, /// Reference to the canonical in-memory state canonical_in_memory_state: &'a CanonicalInMemoryState, - /// Whether the currently validated block is on a fork chain. - is_fork: bool, } impl<'a, N: NodePrimitives> std::fmt::Debug for TreeCtx<'a, N> { @@ -77,9 +75,8 @@ impl<'a, N: NodePrimitives> TreeCtx<'a, N> { state: &'a mut EngineApiTreeState, persistence: &'a PersistenceState, canonical_in_memory_state: &'a CanonicalInMemoryState, - is_fork: bool, ) -> Self { - Self { state, persistence, canonical_in_memory_state, is_fork } + Self { state, persistence, canonical_in_memory_state } } /// Returns a reference to the engine tree state @@ -102,11 +99,6 @@ impl<'a, N: NodePrimitives> TreeCtx<'a, N> { self.canonical_in_memory_state } - /// Returns whether the currently validated block is on a fork chain. - pub const fn is_fork(&self) -> bool { - self.is_fork - } - /// Determines the persisting kind for the given block based on persistence info. /// /// Based on the given header it returns whether any conflicting persistence operation is @@ -232,14 +224,14 @@ where pub fn evm_env_for>>( &self, input: &BlockOrPayload, - ) -> EvmEnvFor + ) -> Result, Evm::Error> where V: PayloadValidator, Evm: ConfigureEngineEvm, { match input { - BlockOrPayload::Payload(payload) => self.evm_config.evm_env_for_payload(payload), - BlockOrPayload::Block(block) => self.evm_config.evm_env(block.header()), + BlockOrPayload::Payload(payload) => Ok(self.evm_config.evm_env_for_payload(payload)), + BlockOrPayload::Block(block) => Ok(self.evm_config.evm_env(block.header())?), } } @@ -267,17 +259,59 @@ where pub fn execution_ctx_for<'a, T: PayloadTypes>>( &self, input: &'a BlockOrPayload, - ) -> ExecutionCtxFor<'a, Evm> + ) -> Result, Evm::Error> where V: PayloadValidator, Evm: ConfigureEngineEvm, { match input { - BlockOrPayload::Payload(payload) => self.evm_config.context_for_payload(payload), - BlockOrPayload::Block(block) => self.evm_config.context_for_block(block), + BlockOrPayload::Payload(payload) => Ok(self.evm_config.context_for_payload(payload)), + BlockOrPayload::Block(block) => Ok(self.evm_config.context_for_block(block)?), } } + /// Handles execution errors by checking if header validation errors should take precedence. + /// + /// When an execution error occurs, this function checks if there are any header validation + /// errors that should be reported instead, as header validation errors have higher priority. + fn handle_execution_error>>( + &self, + input: BlockOrPayload, + execution_err: InsertBlockErrorKind, + parent_block: &SealedHeader, + ) -> Result, InsertPayloadError> + where + V: PayloadValidator, + { + debug!( + target: "engine::tree", + ?execution_err, + block = ?input.num_hash(), + "Block execution failed, checking for header validation errors" + ); + + // If execution failed, we should first check if there are any header validation + // errors that take precedence over the execution error + let block = self.convert_to_block(input)?; + + // Validate block consensus rules which includes header validation + if let Err(consensus_err) = self.validate_block_inner(&block) { + // Header validation error takes precedence over execution error + return Err(InsertBlockError::new(block.into_sealed_block(), consensus_err.into()).into()) + } + + // Also validate against the parent + if let Err(consensus_err) = + self.consensus.validate_header_against_parent(block.sealed_header(), parent_block) + { + // Parent validation error takes precedence over execution error + return Err(InsertBlockError::new(block.into_sealed_block(), consensus_err.into()).into()) + } + + // No header validation errors, return the original execution error + Err(InsertBlockError::new(block.into_sealed_block(), execution_err).into()) + } + /// Validates a block that has already been converted from a payload. /// /// This method performs: @@ -301,7 +335,9 @@ where Ok(val) => val, Err(e) => { let block = self.convert_to_block(input)?; - return Err(InsertBlockError::new(block.into_sealed_block(), e.into()).into()) + return Err( + InsertBlockError::new(block.into_sealed_block(), e.into()).into() + ) } } }; @@ -334,7 +370,7 @@ where .into()) }; - let evm_env = self.evm_env_for(&input); + let evm_env = self.evm_env_for(&input).map_err(NewPayloadError::other)?; let env = ExecutionEnv { evm_env, hash: input.hash(), parent_hash: input.parent_hash() }; @@ -403,7 +439,8 @@ where // Use state root task only if prefix sets are empty, otherwise proof generation is too // expensive because it requires walking over the paths in the prefix set in every // proof. - if trie_input.prefix_sets.is_empty() { + let spawn_payload_processor_start = Instant::now(); + let handle = if trie_input.prefix_sets.is_empty() { self.payload_processor.spawn( env.clone(), txs, @@ -416,9 +453,25 @@ where debug!(target: "engine::tree", block=?block_num_hash, "Disabling state root task due to non-empty prefix sets"); use_state_root_task = false; self.payload_processor.spawn_cache_exclusive(env.clone(), txs, provider_builder) - } + }; + + // record prewarming initialization duration + self.metrics + .block_validation + .spawn_payload_processor + .record(spawn_payload_processor_start.elapsed().as_secs_f64()); + handle } else { - self.payload_processor.spawn_cache_exclusive(env.clone(), txs, provider_builder) + let prewarming_start = Instant::now(); + let handle = + self.payload_processor.spawn_cache_exclusive(env.clone(), txs, provider_builder); + + // Record prewarming initialization duration + self.metrics + .block_validation + .spawn_payload_processor + .record(prewarming_start.elapsed().as_secs_f64()); + handle }; // Use cached state provider before executing, used in execution after prewarming threads @@ -429,14 +482,17 @@ where handle.cache_metrics(), ); - let (output, execution_finish) = if self.config.state_provider_metrics() { + // Execute the block and handle any execution errors + let output = match if self.config.state_provider_metrics() { let state_provider = InstrumentedStateProvider::from_state_provider(&state_provider); - let (output, execution_finish) = - ensure_ok!(self.execute_block(&state_provider, env, &input, &mut handle)); + let result = self.execute_block(&state_provider, env, &input, &mut handle); state_provider.record_total_latency(); - (output, execution_finish) + result } else { - ensure_ok!(self.execute_block(&state_provider, env, &input, &mut handle)) + self.execute_block(&state_provider, env, &input, &mut handle) + } { + Ok(output) => output, + Err(err) => return self.handle_execution_error(input, err, &parent_block), }; // after executing the block we can stop executing transactions @@ -454,6 +510,7 @@ where }; } + let post_execution_start = Instant::now(); trace!(target: "engine::tree", block=?block_num_hash, "Validating block consensus"); // validate block consensus rules ensure_ok!(self.validate_block_inner(&block)); @@ -482,6 +539,12 @@ where return Err(InsertBlockError::new(block.into_sealed_block(), err.into()).into()) } + // record post-execution validation duration + self.metrics + .block_validation + .post_execution_validation_duration + .record(post_execution_start.elapsed().as_secs_f64()); + debug!(target: "engine::tree", block=?block_num_hash, "Calculating block state root"); let root_time = Instant::now(); @@ -495,7 +558,7 @@ where debug!(target: "engine::tree", block=?block_num_hash, "Using sparse trie state root algorithm"); match handle.state_root() { Ok(StateRootComputeOutcome { state_root, trie_updates }) => { - let elapsed = execution_finish.elapsed(); + let elapsed = root_time.elapsed(); info!(target: "engine::tree", ?state_root, ?elapsed, "State root task finished"); // we double check the state root here for good measure if state_root == block.header().state_root() { @@ -510,7 +573,7 @@ where } } Err(error) => { - debug!(target: "engine::tree", %error, "Background parallel state root computation failed"); + debug!(target: "engine::tree", %error, "State root task failed"); } } } else { @@ -530,15 +593,8 @@ where ); maybe_state_root = Some((result.0, result.1, root_time.elapsed())); } - Err(ParallelStateRootError::Provider(ProviderError::ConsistentView(error))) => { - debug!(target: "engine::tree", %error, "Parallel state root computation failed consistency check, falling back"); - } Err(error) => { - return Err(InsertBlockError::new( - block.into_sealed_block(), - InsertBlockErrorKind::Other(Box::new(error)), - ) - .into()) + debug!(target: "engine::tree", %error, "Parallel state root computation failed"); } } } @@ -589,9 +645,26 @@ where // terminate prewarming task with good state output handle.terminate_caching(Some(output.state.clone())); - // If the block is a fork, we don't save the trie updates, because they may be incorrect. + // If the block doesn't connect to the database tip, we don't save its trie updates, because + // they may be incorrect as they were calculated on top of the forked block. + // + // We also only save trie updates if all ancestors have trie updates, because otherwise the + // trie updates may be incorrect. + // // Instead, they will be recomputed on persistence. - let trie_updates = if ctx.is_fork() { + let connects_to_last_persisted = + ensure_ok!(self.block_connects_to_last_persisted(ctx, &block)); + let should_discard_trie_updates = + !connects_to_last_persisted || has_ancestors_with_missing_trie_updates; + debug!( + target: "engine::tree", + block = ?block_num_hash, + connects_to_last_persisted, + has_ancestors_with_missing_trie_updates, + should_discard_trie_updates, + "Checking if should discard trie updates" + ); + let trie_updates = if should_discard_trie_updates { ExecutedTrieUpdates::Missing } else { ExecutedTrieUpdates::Present(Arc::new(trie_output)) @@ -607,18 +680,17 @@ where }) } - /// Return sealed block from database or in-memory state by hash. + /// Return sealed block header from database or in-memory state by hash. fn sealed_header_by_hash( &self, hash: B256, state: &EngineApiTreeState, ) -> ProviderResult>> { // check memory first - let block = - state.tree_state.block_by_hash(hash).map(|block| block.as_ref().clone_sealed_header()); + let header = state.tree_state.sealed_header_by_hash(&hash); - if block.is_some() { - Ok(block) + if header.is_some() { + Ok(header) } else { self.provider.sealed_header_by_hash(hash) } @@ -647,7 +719,7 @@ where env: ExecutionEnv, input: &BlockOrPayload, handle: &mut PayloadHandle, Err>, - ) -> Result<(BlockExecutionOutput, Instant), InsertBlockErrorKind> + ) -> Result, InsertBlockErrorKind> where S: StateProvider, Err: core::error::Error + Send + Sync + 'static, @@ -656,7 +728,11 @@ where Evm: ConfigureEngineEvm, { let num_hash = NumHash::new(env.evm_env.block_env.number.to(), env.hash); - debug!(target: "engine::tree", block=?num_hash, "Executing block"); + + let span = debug_span!(target: "engine::tree", "execute_block", num = ?num_hash.number, hash = ?num_hash.hash); + let _enter = span.enter(); + debug!(target: "engine::tree", "Executing block"); + let mut db = State::builder() .with_database(StateProviderDatabase::new(&state_provider)) .with_bundle_update() @@ -664,7 +740,8 @@ where .build(); let evm = self.evm_config.evm_with_env(&mut db, env.evm_env.clone()); - let ctx = self.execution_ctx_for(input); + let ctx = + self.execution_ctx_for(input).map_err(|e| InsertBlockErrorKind::Other(Box::new(e)))?; let mut executor = self.evm_config.create_executor(evm, ctx); if !self.config.precompile_cache_disabled() { @@ -686,7 +763,7 @@ where let execution_start = Instant::now(); let state_hook = Box::new(handle.state_hook()); - let output = self.metrics.executor.execute_metered( + let output = self.metrics.execute_metered( executor, handle.iter_transactions().map(|res| res.map_err(BlockExecutionError::other)), state_hook, @@ -694,7 +771,7 @@ where let execution_finish = Instant::now(); let execution_time = execution_finish.duration_since(execution_start); debug!(target: "engine::tree", elapsed = ?execution_time, number=?num_hash.number, "Executed block"); - Ok((output, execution_finish)) + Ok(output) } /// Compute state root for the given hashed post state in parallel. @@ -727,6 +804,51 @@ where ParallelStateRoot::new(consistent_view, input).incremental_root_with_updates() } + /// Checks if the given block connects to the last persisted block, i.e. if the last persisted + /// block is the ancestor of the given block. + /// + /// This checks the database for the actual last persisted block, not [`PersistenceState`]. + fn block_connects_to_last_persisted( + &self, + ctx: TreeCtx<'_, N>, + block: &RecoveredBlock, + ) -> ProviderResult { + let provider = self.provider.database_provider_ro()?; + let last_persisted_block = provider.best_block_number()?; + let last_persisted_hash = provider + .block_hash(last_persisted_block)? + .ok_or(ProviderError::HeaderNotFound(last_persisted_block.into()))?; + let last_persisted = NumHash::new(last_persisted_block, last_persisted_hash); + + let parent_num_hash = |hash: B256| -> ProviderResult { + let parent_num_hash = + if let Some(header) = ctx.state().tree_state.sealed_header_by_hash(&hash) { + Some(header.parent_num_hash()) + } else { + provider.sealed_header_by_hash(hash)?.map(|header| header.parent_num_hash()) + }; + + parent_num_hash.ok_or(ProviderError::BlockHashNotFound(hash)) + }; + + let mut parent_block = block.parent_num_hash(); + while parent_block.number > last_persisted.number { + parent_block = parent_num_hash(parent_block.hash)?; + } + + let connects = parent_block == last_persisted; + + debug!( + target: "engine::tree", + num_hash = ?block.num_hash(), + ?last_persisted, + ?parent_block, + "Checking if block connects to last persisted block" + ); + + Ok(connects) + } + /// Check if the given block has any ancestors with missing trie updates. fn has_ancestors_with_missing_trie_updates( &self, @@ -790,7 +912,7 @@ where ) { if state.invalid_headers.get(&block.hash()).is_some() { // we already marked this block as invalid - return; + return } self.invalid_block_hook.on_invalid_block(parent_header, block, output, trie_updates); } diff --git a/crates/engine/tree/src/tree/persistence_state.rs b/crates/engine/tree/src/tree/persistence_state.rs index e7b4dc0ad19..bbb981a531a 100644 --- a/crates/engine/tree/src/tree/persistence_state.rs +++ b/crates/engine/tree/src/tree/persistence_state.rs @@ -1,3 +1,25 @@ +//! Persistence state management for background database operations. +//! +//! This module manages the state of background tasks that persist cached data +//! to the database. The persistence system works asynchronously to avoid blocking +//! block execution while ensuring data durability. +//! +//! ## Background Persistence +//! +//! The execution engine maintains an in-memory cache of state changes that need +//! to be persisted to disk. Rather than writing synchronously (which would slow +//! down block processing), persistence happens in background tasks. +//! +//! ## Persistence Actions +//! +//! - **Saving Blocks**: Persist newly executed blocks and their state changes +//! - **Removing Blocks**: Remove invalid blocks during chain reorganizations +//! +//! ## Coordination +//! +//! The [`PersistenceState`] tracks ongoing persistence operations and coordinates +//! between the main execution thread and background persistence workers. + use alloy_eips::BlockNumHash; use alloy_primitives::B256; use std::time::Instant; diff --git a/crates/engine/tree/src/tree/precompile_cache.rs b/crates/engine/tree/src/tree/precompile_cache.rs index cc3d173fb84..c88cb4bc720 100644 --- a/crates/engine/tree/src/tree/precompile_cache.rs +++ b/crates/engine/tree/src/tree/precompile_cache.rs @@ -3,7 +3,7 @@ use alloy_primitives::Bytes; use parking_lot::Mutex; use reth_evm::precompiles::{DynPrecompile, Precompile, PrecompileInput}; -use revm::precompile::{PrecompileOutput, PrecompileResult}; +use revm::precompile::{PrecompileId, PrecompileOutput, PrecompileResult}; use revm_primitives::Address; use schnellru::LruMap; use std::{ @@ -148,8 +148,12 @@ where spec_id: S, metrics: Option, ) -> DynPrecompile { + let precompile_id = precompile.precompile_id().clone(); let wrapped = Self::new(precompile, cache, spec_id, metrics); - move |input: PrecompileInput<'_>| -> PrecompileResult { wrapped.call(input) }.into() + (precompile_id, move |input: PrecompileInput<'_>| -> PrecompileResult { + wrapped.call(input) + }) + .into() } fn increment_by_one_precompile_cache_hits(&self) { @@ -181,6 +185,10 @@ impl Precompile for CachedPrecompile where S: Eq + Hash + std::fmt::Debug + Send + Sync + Clone + 'static, { + fn precompile_id(&self) -> &PrecompileId { + self.precompile.precompile_id() + } + fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult { let key = CacheKeyRef::new(self.spec_id.clone(), input.data); @@ -301,7 +309,7 @@ mod tests { let mut cache_map = PrecompileCacheMap::default(); // create the first precompile with a specific output - let precompile1: DynPrecompile = { + let precompile1: DynPrecompile = (PrecompileId::custom("custom"), { move |input: PrecompileInput<'_>| -> PrecompileResult { assert_eq!(input.data, input_data); @@ -311,11 +319,11 @@ mod tests { reverted: false, }) } - } - .into(); + }) + .into(); // create the second precompile with a different output - let precompile2: DynPrecompile = { + let precompile2: DynPrecompile = (PrecompileId::custom("custom"), { move |input: PrecompileInput<'_>| -> PrecompileResult { assert_eq!(input.data, input_data); @@ -325,8 +333,8 @@ mod tests { reverted: false, }) } - } - .into(); + }) + .into(); let wrapped_precompile1 = CachedPrecompile::wrap( precompile1, diff --git a/crates/engine/tree/src/tree/state.rs b/crates/engine/tree/src/tree/state.rs index 0fcc51d59e6..7db56030eaa 100644 --- a/crates/engine/tree/src/tree/state.rs +++ b/crates/engine/tree/src/tree/state.rs @@ -7,7 +7,7 @@ use alloy_primitives::{ BlockNumber, B256, }; use reth_chain_state::{EthPrimitives, ExecutedBlockWithTrieUpdates}; -use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives, SealedBlock}; +use reth_primitives_traits::{AlloyBlockHeader, NodePrimitives, SealedHeader}; use reth_trie::updates::TrieUpdates; use std::{ collections::{btree_map, hash_map, BTreeMap, VecDeque}, @@ -85,9 +85,12 @@ impl TreeState { self.blocks_by_hash.get(&hash) } - /// Returns the block by hash. - pub(crate) fn block_by_hash(&self, hash: B256) -> Option>> { - self.blocks_by_hash.get(&hash).map(|b| Arc::new(b.recovered_block().sealed_block().clone())) + /// Returns the sealed block header by hash. + pub(crate) fn sealed_header_by_hash( + &self, + hash: &B256, + ) -> Option> { + self.blocks_by_hash.get(hash).map(|b| b.sealed_block().sealed_header().clone()) } /// Returns all available blocks for the given hash that lead back to the canonical chain, from diff --git a/crates/engine/tree/src/tree/tests.rs b/crates/engine/tree/src/tree/tests.rs index aeef8616746..e3194c5a85a 100644 --- a/crates/engine/tree/src/tree/tests.rs +++ b/crates/engine/tree/src/tree/tests.rs @@ -1,12 +1,15 @@ use super::*; use crate::persistence::PersistenceAction; use alloy_consensus::Header; +use alloy_eips::eip1898::BlockWithParent; use alloy_primitives::{ map::{HashMap, HashSet}, Bytes, B256, }; use alloy_rlp::Decodable; -use alloy_rpc_types_engine::{ExecutionData, ExecutionPayloadSidecar, ExecutionPayloadV1}; +use alloy_rpc_types_engine::{ + ExecutionData, ExecutionPayloadSidecar, ExecutionPayloadV1, ForkchoiceState, +}; use assert_matches::assert_matches; use reth_chain_state::{test_utils::TestBlockBuilder, BlockState}; use reth_chainspec::{ChainSpec, HOLESKY, MAINNET}; @@ -23,6 +26,7 @@ use std::{ str::FromStr, sync::mpsc::{channel, Sender}, }; +use tokio::sync::oneshot; /// Mock engine validator for tests #[derive(Debug, Clone)] @@ -381,7 +385,7 @@ async fn test_tree_persist_blocks() { .collect(); let test_harness = TestHarness::new(chain_spec).with_blocks(blocks.clone()); std::thread::Builder::new() - .name("Tree Task".to_string()) + .name("Engine Task".to_string()) .spawn(|| test_harness.tree.run()) .unwrap(); @@ -759,7 +763,7 @@ async fn test_get_canonical_blocks_to_persist() { let fork_block_hash = fork_block.recovered_block().hash(); test_harness.tree.state.tree_state.insert_executed(fork_block); - assert!(test_harness.tree.state.tree_state.block_by_hash(fork_block_hash).is_some()); + assert!(test_harness.tree.state.tree_state.sealed_header_by_hash(&fork_block_hash).is_some()); let blocks_to_persist = test_harness.tree.get_canonical_blocks_to_persist().unwrap(); assert_eq!(blocks_to_persist.len(), expected_blocks_to_persist_length); @@ -867,3 +871,277 @@ async fn test_engine_tree_live_sync_transition_required_blocks_requested() { _ => panic!("Unexpected event: {event:#?}"), } } + +#[tokio::test] +async fn test_fcu_with_canonical_ancestor_updates_latest_block() { + // Test for issue where FCU with canonical ancestor doesn't update Latest block state + // This was causing "nonce too low" errors when discard_reorged_transactions is enabled + + reth_tracing::init_test_tracing(); + let chain_spec = MAINNET.clone(); + + // Create test harness + let mut test_harness = TestHarness::new(chain_spec.clone()); + + // Set engine kind to OpStack and enable unwind_canonical_header to ensure the fix is triggered + test_harness.tree.engine_kind = EngineApiKind::OpStack; + test_harness.tree.config = test_harness.tree.config.clone().with_unwind_canonical_header(true); + let mut test_block_builder = TestBlockBuilder::eth().with_chain_spec((*chain_spec).clone()); + + // Create a chain of blocks + let blocks: Vec<_> = test_block_builder.get_executed_blocks(1..5).collect(); + test_harness = test_harness.with_blocks(blocks.clone()); + + // Set block 4 as the current canonical head + let current_head = blocks[3].recovered_block().clone(); // Block 4 (0-indexed as blocks[3]) + let current_head_sealed = current_head.clone_sealed_header(); + test_harness.tree.state.tree_state.set_canonical_head(current_head.num_hash()); + test_harness.tree.canonical_in_memory_state.set_canonical_head(current_head_sealed); + + // Verify the current head is set correctly + assert_eq!(test_harness.tree.state.tree_state.canonical_block_number(), current_head.number()); + assert_eq!(test_harness.tree.state.tree_state.canonical_block_hash(), current_head.hash()); + + // Now perform FCU to a canonical ancestor (block 2) + let ancestor_block = blocks[1].recovered_block().clone(); // Block 2 (0-indexed as blocks[1]) + + // Send FCU to the canonical ancestor + let (tx, rx) = oneshot::channel(); + test_harness + .tree + .on_engine_message(FromEngine::Request( + BeaconEngineMessage::ForkchoiceUpdated { + state: ForkchoiceState { + head_block_hash: ancestor_block.hash(), + safe_block_hash: B256::ZERO, + finalized_block_hash: B256::ZERO, + }, + payload_attrs: None, + tx, + version: EngineApiMessageVersion::default(), + } + .into(), + )) + .unwrap(); + + // Verify FCU succeeds + let response = rx.await.unwrap().unwrap().await.unwrap(); + assert!(response.payload_status.is_valid()); + + // The critical test: verify that Latest block has been updated to the canonical ancestor + // Check tree state + assert_eq!( + test_harness.tree.state.tree_state.canonical_block_number(), + ancestor_block.number(), + "Tree state: Latest block number should be updated to canonical ancestor" + ); + assert_eq!( + test_harness.tree.state.tree_state.canonical_block_hash(), + ancestor_block.hash(), + "Tree state: Latest block hash should be updated to canonical ancestor" + ); + + // Also verify canonical in-memory state is synchronized + assert_eq!( + test_harness.tree.canonical_in_memory_state.get_canonical_head().number, + ancestor_block.number(), + "In-memory state: Latest block number should be updated to canonical ancestor" + ); + assert_eq!( + test_harness.tree.canonical_in_memory_state.get_canonical_head().hash(), + ancestor_block.hash(), + "In-memory state: Latest block hash should be updated to canonical ancestor" + ); +} + +/// Test that verifies the happy path where a new payload extends the canonical chain +#[test] +fn test_on_new_payload_canonical_insertion() { + reth_tracing::init_test_tracing(); + + // Use test data similar to test_disconnected_payload + let s = include_str!("../../test-data/holesky/1.rlp"); + let data = Bytes::from_str(s).unwrap(); + let block1 = Block::decode(&mut data.as_ref()).unwrap(); + let sealed1 = block1.seal_slow(); + let hash1 = sealed1.hash(); + let payload1 = ExecutionPayloadV1::from_block_unchecked(hash1, &sealed1.clone().into_block()); + + let mut test_harness = TestHarness::new(HOLESKY.clone()); + + // Case 1: Submit payload when NOT sync target head - should be syncing (disconnected) + let outcome1 = test_harness + .tree + .on_new_payload(ExecutionData { + payload: payload1.into(), + sidecar: ExecutionPayloadSidecar::none(), + }) + .unwrap(); + + // Since this is disconnected from genesis, it should be syncing + assert!(outcome1.outcome.is_syncing(), "Disconnected payload should be syncing"); + + // Verify no canonicalization event + assert!(outcome1.event.is_none(), "Should not trigger canonicalization when syncing"); + + // Ensure block is buffered (like test_disconnected_payload) + let buffered = test_harness.tree.state.buffer.block(&hash1).unwrap(); + assert_eq!(buffered.clone_sealed_block(), sealed1, "Block should be buffered"); +} + +/// Test that ensures payloads are rejected when linking to a known-invalid ancestor +#[test] +fn test_on_new_payload_invalid_ancestor() { + reth_tracing::init_test_tracing(); + + // Use Holesky test data + let mut test_harness = TestHarness::new(HOLESKY.clone()); + + // Read block 1 from test data + let s1 = include_str!("../../test-data/holesky/1.rlp"); + let data1 = Bytes::from_str(s1).unwrap(); + let block1 = Block::decode(&mut data1.as_ref()).unwrap(); + let sealed1 = block1.seal_slow(); + let hash1 = sealed1.hash(); + let parent1 = sealed1.parent_hash(); + + // Mark block 1 as invalid + test_harness + .tree + .state + .invalid_headers + .insert(BlockWithParent { block: sealed1.num_hash(), parent: parent1 }); + + // Read block 2 which has block 1 as parent + let s2 = include_str!("../../test-data/holesky/2.rlp"); + let data2 = Bytes::from_str(s2).unwrap(); + let block2 = Block::decode(&mut data2.as_ref()).unwrap(); + let sealed2 = block2.seal_slow(); + let hash2 = sealed2.hash(); + + // Verify block2's parent is block1 + assert_eq!(sealed2.parent_hash(), hash1, "Block 2 should have block 1 as parent"); + + let payload2 = ExecutionPayloadV1::from_block_unchecked(hash2, &sealed2.into_block()); + + // Submit payload 2 (child of invalid block 1) + let outcome = test_harness + .tree + .on_new_payload(ExecutionData { + payload: payload2.into(), + sidecar: ExecutionPayloadSidecar::none(), + }) + .unwrap(); + + // Verify response is INVALID + assert!( + outcome.outcome.is_invalid(), + "Payload should be invalid when parent is marked invalid" + ); + + // For invalid ancestors, the latest_valid_hash behavior varies + // We just verify it's marked as invalid + assert!( + outcome.outcome.latest_valid_hash.is_some() || outcome.outcome.latest_valid_hash.is_none(), + "Latest valid hash should be set appropriately for invalid ancestor" + ); + + // Verify block 2 is now also marked as invalid + assert!( + test_harness.tree.state.invalid_headers.get(&hash2).is_some(), + "Block should be added to invalid headers when parent is invalid" + ); +} + +/// Test that confirms payloads received during backfill sync are buffered and reported as syncing +#[test] +fn test_on_new_payload_backfill_buffering() { + reth_tracing::init_test_tracing(); + + // Use a test data file similar to test_holesky_payload + let s = include_str!("../../test-data/holesky/1.rlp"); + let data = Bytes::from_str(s).unwrap(); + let block = Block::decode(&mut data.as_ref()).unwrap(); + let sealed = block.seal_slow(); + let payload = + ExecutionPayloadV1::from_block_unchecked(sealed.hash(), &sealed.clone().into_block()); + + // Initialize test harness with backfill sync active + let mut test_harness = + TestHarness::new(HOLESKY.clone()).with_backfill_state(BackfillSyncState::Active); + + // Submit payload during backfill + let outcome = test_harness + .tree + .on_new_payload(ExecutionData { + payload: payload.into(), + sidecar: ExecutionPayloadSidecar::none(), + }) + .unwrap(); + + // Verify response is SYNCING + assert!(outcome.outcome.is_syncing(), "Payload should be syncing during backfill"); + + // Verify the block is present in the buffer + let hash = sealed.hash(); + let buffered_block = test_harness + .tree + .state + .buffer + .block(&hash) + .expect("Block should be buffered during backfill sync"); + + // Verify the buffered block matches what we submitted + assert_eq!( + buffered_block.clone_sealed_block(), + sealed, + "Buffered block should match submitted payload" + ); +} + +/// Test that captures the Engine-API rule where malformed payloads report latestValidHash = None +#[test] +fn test_on_new_payload_malformed_payload() { + reth_tracing::init_test_tracing(); + + let mut test_harness = TestHarness::new(HOLESKY.clone()); + + // Use test data + let s = include_str!("../../test-data/holesky/1.rlp"); + let data = Bytes::from_str(s).unwrap(); + let block = Block::decode(&mut data.as_ref()).unwrap(); + let sealed = block.seal_slow(); + + // Create a payload with incorrect block hash to trigger malformed validation + let mut payload = ExecutionPayloadV1::from_block_unchecked(sealed.hash(), &sealed.into_block()); + + // Corrupt the block hash - this makes the computed hash not match the provided hash + // This will cause ensure_well_formed_payload to fail + let wrong_hash = B256::random(); + payload.block_hash = wrong_hash; + + // Submit the malformed payload + let outcome = test_harness + .tree + .on_new_payload(ExecutionData { + payload: payload.into(), + sidecar: ExecutionPayloadSidecar::none(), + }) + .unwrap(); + + // For malformed payloads with incorrect hash, the current implementation + // returns SYNCING since it doesn't match computed hash + // This test captures the current behavior to prevent regression + assert!( + outcome.outcome.is_syncing() || outcome.outcome.is_invalid(), + "Malformed payload should be either syncing or invalid" + ); + + // If invalid, latestValidHash should be None per Engine API spec + if outcome.outcome.is_invalid() { + assert_eq!( + outcome.outcome.latest_valid_hash, None, + "Malformed payload must have latestValidHash = None when invalid" + ); + } +} diff --git a/crates/engine/tree/tests/e2e-testsuite/fcu_finalized_blocks.rs b/crates/engine/tree/tests/e2e-testsuite/fcu_finalized_blocks.rs new file mode 100644 index 00000000000..e7ec9ee8e68 --- /dev/null +++ b/crates/engine/tree/tests/e2e-testsuite/fcu_finalized_blocks.rs @@ -0,0 +1,79 @@ +//! E2E test for forkchoice update with finalized blocks. +//! +//! This test verifies the behavior when attempting to reorg behind a finalized block. + +use eyre::Result; +use reth_chainspec::{ChainSpecBuilder, MAINNET}; +use reth_e2e_test_utils::testsuite::{ + actions::{ + BlockReference, CaptureBlock, CreateFork, FinalizeBlock, MakeCanonical, ProduceBlocks, + SendForkchoiceUpdate, + }, + setup::{NetworkSetup, Setup}, + TestBuilder, +}; +use reth_engine_tree::tree::TreeConfig; +use reth_ethereum_engine_primitives::EthEngineTypes; +use reth_node_ethereum::EthereumNode; +use std::sync::Arc; + +/// Creates the standard setup for engine tree e2e tests. +fn default_engine_tree_setup() -> Setup { + Setup::default() + .with_chain_spec(Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis( + serde_json::from_str(include_str!( + "../../../../e2e-test-utils/src/testsuite/assets/genesis.json" + )) + .unwrap(), + ) + .cancun_activated() + .build(), + )) + .with_network(NetworkSetup::single_node()) + .with_tree_config( + TreeConfig::default().with_legacy_state_root(false).with_has_enough_parallelism(true), + ) +} + +/// This test: +/// 1. Creates a main chain and finalizes a block +/// 2. Creates a fork that branches BEFORE the finalized block +/// 3. Attempts to switch to that fork (which would require changing history behind finalized) +#[tokio::test] +async fn test_reorg_to_fork_behind_finalized() -> Result<()> { + reth_tracing::init_test_tracing(); + + let test = TestBuilder::new() + .with_setup(default_engine_tree_setup()) + // Build main chain: blocks 1-10 + .with_action(ProduceBlocks::::new(10)) + .with_action(MakeCanonical::new()) + // Capture blocks for the test + .with_action(CaptureBlock::new("block_5")) // Will be fork point + .with_action(CaptureBlock::new("block_7")) // Will be finalized + .with_action(CaptureBlock::new("block_10")) // Current head + // Create a fork from block 5 (before block 7 which will be finalized) + .with_action(CreateFork::::new(5, 5)) // Fork from block 5, add 5 blocks + .with_action(CaptureBlock::new("fork_tip")) + // Step 1: Finalize block 7 with head at block 10 + .with_action( + FinalizeBlock::::new(BlockReference::Tag("block_7".to_string())) + .with_head(BlockReference::Tag("block_10".to_string())), + ) + // Step 2: Attempt to reorg to a fork that doesn't contain the finalized block + .with_action( + SendForkchoiceUpdate::::new( + BlockReference::Tag("block_7".to_string()), // Keep finalized + BlockReference::Tag("fork_tip".to_string()), // New safe + BlockReference::Tag("fork_tip".to_string()), // New head + ) + .with_expected_status(alloy_rpc_types_engine::PayloadStatusEnum::Valid), + ); + + test.run::().await?; + + Ok(()) +} diff --git a/crates/engine/tree/tests/e2e-testsuite/main.rs b/crates/engine/tree/tests/e2e-testsuite/main.rs index 0b9162ab8c2..20d7e2d68e5 100644 --- a/crates/engine/tree/tests/e2e-testsuite/main.rs +++ b/crates/engine/tree/tests/e2e-testsuite/main.rs @@ -1,5 +1,7 @@ //! E2E test implementations using the e2e test framework for engine tree functionality. +mod fcu_finalized_blocks; + use eyre::Result; use reth_chainspec::{ChainSpecBuilder, MAINNET}; use reth_e2e_test_utils::testsuite::{ @@ -33,10 +35,7 @@ fn default_engine_tree_setup() -> Setup { )) .with_network(NetworkSetup::single_node()) .with_tree_config( - TreeConfig::default() - .with_legacy_state_root(false) - .with_has_enough_parallelism(true) - .with_enable_parallel_sparse_trie(true), + TreeConfig::default().with_legacy_state_root(false).with_has_enough_parallelism(true), ) } diff --git a/crates/engine/util/src/engine_store.rs b/crates/engine/util/src/engine_store.rs index 4f9ccb586ea..a79504db30e 100644 --- a/crates/engine/util/src/engine_store.rs +++ b/crates/engine/util/src/engine_store.rs @@ -140,10 +140,10 @@ where fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); let next = ready!(this.stream.poll_next_unpin(cx)); - if let Some(msg) = &next { - if let Err(error) = this.store.on_message(msg, SystemTime::now()) { - error!(target: "engine::stream::store", ?msg, %error, "Error handling Engine API message"); - } + if let Some(msg) = &next && + let Err(error) = this.store.on_message(msg, SystemTime::now()) + { + error!(target: "engine::stream::store", ?msg, %error, "Error handling Engine API message"); } Poll::Ready(next) } diff --git a/crates/engine/util/src/reorg.rs b/crates/engine/util/src/reorg.rs index 2b76a438589..7d84afc6d59 100644 --- a/crates/engine/util/src/reorg.rs +++ b/crates/engine/util/src/reorg.rs @@ -285,8 +285,8 @@ where .with_bundle_update() .build(); - let ctx = evm_config.context_for_block(&reorg_target); - let evm = evm_config.evm_for_block(&mut state, &reorg_target); + let ctx = evm_config.context_for_block(&reorg_target).map_err(RethError::other)?; + let evm = evm_config.evm_for_block(&mut state, &reorg_target).map_err(RethError::other)?; let mut builder = evm_config.create_block_builder(evm, &reorg_target_parent, ctx); builder.apply_pre_execution_changes()?; diff --git a/crates/era-downloader/src/client.rs b/crates/era-downloader/src/client.rs index bce670271a1..298248ff3e9 100644 --- a/crates/era-downloader/src/client.rs +++ b/crates/era-downloader/src/client.rs @@ -106,12 +106,11 @@ impl EraClient { if let Ok(mut dir) = fs::read_dir(&self.folder).await { while let Ok(Some(entry)) = dir.next_entry().await { - if let Some(name) = entry.file_name().to_str() { - if let Some(number) = self.file_name_to_number(name) { - if max.is_none() || matches!(max, Some(max) if number > max) { - max.replace(number + 1); - } - } + if let Some(name) = entry.file_name().to_str() && + let Some(number) = self.file_name_to_number(name) && + (max.is_none() || matches!(max, Some(max) if number > max)) + { + max.replace(number + 1); } } } @@ -125,14 +124,13 @@ impl EraClient { if let Ok(mut dir) = fs::read_dir(&self.folder).await { while let Ok(Some(entry)) = dir.next_entry().await { - if let Some(name) = entry.file_name().to_str() { - if let Some(number) = self.file_name_to_number(name) { - if number < index || number >= last { - eprintln!("Deleting file {}", entry.path().display()); - eprintln!("{number} < {index} || {number} > {last}"); - reth_fs_util::remove_file(entry.path())?; - } - } + if let Some(name) = entry.file_name().to_str() && + let Some(number) = self.file_name_to_number(name) && + (number < index || number >= last) + { + eprintln!("Deleting file {}", entry.path().display()); + eprintln!("{number} < {index} || {number} >= {last}"); + reth_fs_util::remove_file(entry.path())?; } } } @@ -208,12 +206,12 @@ impl EraClient { let mut writer = io::BufWriter::new(file); while let Some(line) = lines.next_line().await? { - if let Some(j) = line.find(".era1") { - if let Some(i) = line[..j].rfind(|c: char| !c.is_alphanumeric() && c != '-') { - let era = &line[i + 1..j + 5]; - writer.write_all(era.as_bytes()).await?; - writer.write_all(b"\n").await?; - } + if let Some(j) = line.find(".era1") && + let Some(i) = line[..j].rfind(|c: char| !c.is_alphanumeric() && c != '-') + { + let era = &line[i + 1..j + 5]; + writer.write_all(era.as_bytes()).await?; + writer.write_all(b"\n").await?; } } writer.flush().await?; diff --git a/crates/era-downloader/src/fs.rs b/crates/era-downloader/src/fs.rs index 17a2d46d26a..19532f01cff 100644 --- a/crates/era-downloader/src/fs.rs +++ b/crates/era-downloader/src/fs.rs @@ -17,16 +17,16 @@ pub fn read_dir( (|| { let path = entry?.path(); - if path.extension() == Some("era1".as_ref()) { - if let Some(last) = path.components().next_back() { - let str = last.as_os_str().to_string_lossy().to_string(); - let parts = str.split('-').collect::>(); + if path.extension() == Some("era1".as_ref()) && + let Some(last) = path.components().next_back() + { + let str = last.as_os_str().to_string_lossy().to_string(); + let parts = str.split('-').collect::>(); - if parts.len() == 3 { - let number = usize::from_str(parts[1])?; + if parts.len() == 3 { + let number = usize::from_str(parts[1])?; - return Ok(Some((number, path.into_boxed_path()))); - } + return Ok(Some((number, path.into_boxed_path()))); } } if path.file_name() == Some("checksums.txt".as_ref()) { diff --git a/crates/era-downloader/src/stream.rs b/crates/era-downloader/src/stream.rs index a488e098ab0..4e8a178e577 100644 --- a/crates/era-downloader/src/stream.rs +++ b/crates/era-downloader/src/stream.rs @@ -262,47 +262,47 @@ impl Stream for Starti self.fetch_file_list(); } - if self.state == State::FetchFileList { - if let Poll::Ready(result) = self.fetch_file_list.poll_unpin(cx) { - match result { - Ok(_) => self.delete_outside_range(), - Err(e) => { - self.fetch_file_list(); - - return Poll::Ready(Some(Box::pin(async move { Err(e) }))); - } + if self.state == State::FetchFileList && + let Poll::Ready(result) = self.fetch_file_list.poll_unpin(cx) + { + match result { + Ok(_) => self.delete_outside_range(), + Err(e) => { + self.fetch_file_list(); + + return Poll::Ready(Some(Box::pin(async move { Err(e) }))); } } } - if self.state == State::DeleteOutsideRange { - if let Poll::Ready(result) = self.delete_outside_range.poll_unpin(cx) { - match result { - Ok(_) => self.recover_index(), - Err(e) => { - self.delete_outside_range(); + if self.state == State::DeleteOutsideRange && + let Poll::Ready(result) = self.delete_outside_range.poll_unpin(cx) + { + match result { + Ok(_) => self.recover_index(), + Err(e) => { + self.delete_outside_range(); - return Poll::Ready(Some(Box::pin(async move { Err(e) }))); - } + return Poll::Ready(Some(Box::pin(async move { Err(e) }))); } } } - if self.state == State::RecoverIndex { - if let Poll::Ready(last) = self.recover_index.poll_unpin(cx) { - self.last = last; - self.count_files(); - } + if self.state == State::RecoverIndex && + let Poll::Ready(last) = self.recover_index.poll_unpin(cx) + { + self.last = last; + self.count_files(); } - if self.state == State::CountFiles { - if let Poll::Ready(downloaded) = self.files_count.poll_unpin(cx) { - let max_missing = self - .max_files - .saturating_sub(downloaded + self.downloading) - .max(self.last.unwrap_or_default().saturating_sub(self.index)); - self.state = State::Missing(max_missing); - } + if self.state == State::CountFiles && + let Poll::Ready(downloaded) = self.files_count.poll_unpin(cx) + { + let max_missing = self + .max_files + .saturating_sub(downloaded + self.downloading) + .max(self.last.unwrap_or_default().saturating_sub(self.index)); + self.state = State::Missing(max_missing); } if let State::Missing(max_missing) = self.state { @@ -316,18 +316,16 @@ impl Stream for Starti } } - if let State::NextUrl(max_missing) = self.state { - if let Poll::Ready(url) = self.next_url.poll_unpin(cx) { - self.state = State::Missing(max_missing - 1); + if let State::NextUrl(max_missing) = self.state && + let Poll::Ready(url) = self.next_url.poll_unpin(cx) + { + self.state = State::Missing(max_missing - 1); - return Poll::Ready(url.transpose().map(|url| -> DownloadFuture { - let mut client = self.client.clone(); + return Poll::Ready(url.transpose().map(|url| -> DownloadFuture { + let mut client = self.client.clone(); - Box::pin( - async move { client.download_to_file(url?).await.map(EraRemoteMeta::new) }, - ) - })); - } + Box::pin(async move { client.download_to_file(url?).await.map(EraRemoteMeta::new) }) + })); } Poll::Pending diff --git a/crates/era-utils/Cargo.toml b/crates/era-utils/Cargo.toml index 731a9bb9242..3363545faa0 100644 --- a/crates/era-utils/Cargo.toml +++ b/crates/era-utils/Cargo.toml @@ -13,14 +13,12 @@ exclude.workspace = true # alloy alloy-consensus.workspace = true alloy-primitives.workspace = true -alloy-rlp.workspace = true # reth reth-db-api.workspace = true reth-era.workspace = true reth-era-downloader.workspace = true reth-etl.workspace = true -reth-ethereum-primitives.workspace = true reth-fs-util.workspace = true reth-provider.workspace = true reth-stages-types.workspace = true @@ -43,7 +41,6 @@ reth-db-common.workspace = true # async tokio-util.workspace = true -futures.workspace = true bytes.workspace = true # http diff --git a/crates/era-utils/src/export.rs b/crates/era-utils/src/export.rs index 49909d80958..670a534ba01 100644 --- a/crates/era-utils/src/export.rs +++ b/crates/era-utils/src/export.rs @@ -8,6 +8,7 @@ use reth_era::{ e2s_types::IndexEntry, era1_file::Era1Writer, era1_types::{BlockIndex, Era1Id}, + era_file_ops::{EraFileId, StreamWriter}, execution_types::{ Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts, TotalDifficulty, MAX_BLOCKS_PER_ERA1, @@ -152,8 +153,8 @@ where let mut writer = Era1Writer::new(file); writer.write_version()?; - let mut offsets = Vec::::with_capacity(block_count); - let mut position = VERSION_ENTRY_SIZE as u64; + let mut offsets = Vec::::with_capacity(block_count); + let mut position = VERSION_ENTRY_SIZE as i64; let mut blocks_written = 0; let mut final_header_data = Vec::new(); @@ -178,7 +179,7 @@ where let body_size = compressed_body.data.len() + ENTRY_HEADER_SIZE; let receipts_size = compressed_receipts.data.len() + ENTRY_HEADER_SIZE; let difficulty_size = 32 + ENTRY_HEADER_SIZE; // U256 is 32 + 8 bytes header overhead - let total_size = (header_size + body_size + receipts_size + difficulty_size) as u64; + let total_size = (header_size + body_size + receipts_size + difficulty_size) as i64; let block_tuple = BlockTuple::new( compressed_header, diff --git a/crates/era-utils/src/history.rs b/crates/era-utils/src/history.rs index b3d2e0ed475..31ac8825dc2 100644 --- a/crates/era-utils/src/history.rs +++ b/crates/era-utils/src/history.rs @@ -10,6 +10,7 @@ use reth_db_api::{ use reth_era::{ e2s_types::E2sError, era1_file::{BlockTupleIterator, Era1Reader}, + era_file_ops::StreamReader, execution_types::BlockTuple, DecodeCompressed, }; @@ -301,10 +302,10 @@ where if number <= last_header_number { continue; } - if let Some(target) = target { - if number > target { - break; - } + if let Some(target) = target && + number > target + { + break; } let hash = header.hash_slow(); @@ -350,19 +351,18 @@ where // Database cursor for hash to number index let mut cursor_header_numbers = provider.tx_ref().cursor_write::>()?; - let mut first_sync = false; - // If we only have the genesis block hash, then we are at first sync, and we can remove it, // add it to the collector and use tx.append on all hashes. - if provider.tx_ref().entries::>()? == 1 { - if let Some((hash, block_number)) = cursor_header_numbers.last()? { - if block_number.value()? == 0 { - hash_collector.insert(hash.key()?, 0)?; - cursor_header_numbers.delete_current()?; - first_sync = true; - } - } - } + let first_sync = if provider.tx_ref().entries::>()? == 1 && + let Some((hash, block_number)) = cursor_header_numbers.last()? && + block_number.value()? == 0 + { + hash_collector.insert(hash.key()?, 0)?; + cursor_header_numbers.delete_current()?; + true + } else { + false + }; let interval = (total_headers / 10).max(8192); diff --git a/crates/era/src/e2s_types.rs b/crates/era/src/e2s_types.rs index 3e5681eb119..f14bfe56e86 100644 --- a/crates/era/src/e2s_types.rs +++ b/crates/era/src/e2s_types.rs @@ -173,13 +173,13 @@ pub trait IndexEntry: Sized { fn entry_type() -> [u8; 2]; /// Create a new instance with starting number and offsets - fn new(starting_number: u64, offsets: Vec) -> Self; + fn new(starting_number: u64, offsets: Vec) -> Self; /// Get the starting number - can be starting slot or block number for example fn starting_number(&self) -> u64; /// Get the offsets vector - fn offsets(&self) -> &[u64]; + fn offsets(&self) -> &[i64]; /// Convert to an [`Entry`] for storage in an e2store file /// Format: starting-number | offset1 | offset2 | ... | count @@ -193,7 +193,7 @@ pub trait IndexEntry: Sized { data.extend(self.offsets().iter().flat_map(|offset| offset.to_le_bytes())); // Encode count - 8 bytes again - let count = self.offsets().len() as u64; + let count = self.offsets().len() as i64; data.extend_from_slice(&count.to_le_bytes()); Entry::new(Self::entry_type(), data) @@ -219,7 +219,7 @@ pub trait IndexEntry: Sized { // Extract count from last 8 bytes let count_bytes = &entry.data[entry.data.len() - 8..]; - let count = u64::from_le_bytes( + let count = i64::from_le_bytes( count_bytes .try_into() .map_err(|_| E2sError::Ssz("Failed to read count bytes".to_string()))?, @@ -247,7 +247,7 @@ pub trait IndexEntry: Sized { let start = 8 + i * 8; let end = start + 8; let offset_bytes = &entry.data[start..end]; - let offset = u64::from_le_bytes( + let offset = i64::from_le_bytes( offset_bytes .try_into() .map_err(|_| E2sError::Ssz(format!("Failed to read offset {i} bytes")))?, diff --git a/crates/era/src/era1_file.rs b/crates/era/src/era1_file.rs index b665b481766..dc34ddef42b 100644 --- a/crates/era/src/era1_file.rs +++ b/crates/era/src/era1_file.rs @@ -9,6 +9,7 @@ use crate::{ e2s_file::{E2StoreReader, E2StoreWriter}, e2s_types::{E2sError, Entry, IndexEntry, Version}, era1_types::{BlockIndex, Era1Group, Era1Id, BLOCK_INDEX}, + era_file_ops::{EraFileFormat, FileReader, StreamReader, StreamWriter}, execution_types::{ self, Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts, TotalDifficulty, MAX_BLOCKS_PER_ERA1, @@ -19,7 +20,6 @@ use std::{ collections::VecDeque, fs::File, io::{Read, Seek, Write}, - path::Path, }; /// Era1 file interface @@ -35,12 +35,29 @@ pub struct Era1File { pub id: Era1Id, } -impl Era1File { +impl EraFileFormat for Era1File { + type EraGroup = Era1Group; + type Id = Era1Id; + /// Create a new [`Era1File`] - pub const fn new(group: Era1Group, id: Era1Id) -> Self { + fn new(group: Era1Group, id: Era1Id) -> Self { Self { version: Version, group, id } } + fn version(&self) -> &Version { + &self.version + } + + fn group(&self) -> &Self::EraGroup { + &self.group + } + + fn id(&self) -> &Self::Id { + &self.id + } +} + +impl Era1File { /// Get a block by its number, if present in this file pub fn get_block_by_number(&self, number: BlockNumber) -> Option<&BlockTuple> { let index = (number - self.group.block_index.starting_number()) as usize; @@ -155,20 +172,29 @@ impl BlockTupleIterator { } } -impl Era1Reader { +impl StreamReader for Era1Reader { + type File = Era1File; + type Iterator = BlockTupleIterator; + /// Create a new [`Era1Reader`] - pub fn new(reader: R) -> Self { + fn new(reader: R) -> Self { Self { reader: E2StoreReader::new(reader) } } /// Returns an iterator of [`BlockTuple`] streaming from `reader`. - pub fn iter(self) -> BlockTupleIterator { + fn iter(self) -> BlockTupleIterator { BlockTupleIterator::new(self.reader) } + fn read(self, network_name: String) -> Result { + self.read_and_assemble(network_name) + } +} + +impl Era1Reader { /// Reads and parses an Era1 file from the underlying reader, assembling all components /// into a complete [`Era1File`] with an [`Era1Id`] that includes the provided network name. - pub fn read(mut self, network_name: String) -> Result { + pub fn read_and_assemble(mut self, network_name: String) -> Result { // Validate version entry let _version_entry = match self.reader.read_version()? { Some(entry) if entry.is_version() => entry, @@ -224,17 +250,7 @@ impl Era1Reader { } } -impl Era1Reader { - /// Opens and reads an Era1 file from the given path - pub fn open>( - path: P, - network_name: impl Into, - ) -> Result { - let file = File::open(path).map_err(E2sError::Io)?; - let reader = Self::new(file); - reader.read(network_name.into()) - } -} +impl FileReader for Era1Reader {} /// Writer for Era1 files that builds on top of [`E2StoreWriter`] #[derive(Debug)] @@ -246,9 +262,11 @@ pub struct Era1Writer { has_written_block_index: bool, } -impl Era1Writer { +impl StreamWriter for Era1Writer { + type File = Era1File; + /// Create a new [`Era1Writer`] - pub fn new(writer: W) -> Self { + fn new(writer: W) -> Self { Self { writer: E2StoreWriter::new(writer), has_written_version: false, @@ -259,7 +277,7 @@ impl Era1Writer { } /// Write the version entry - pub fn write_version(&mut self) -> Result<(), E2sError> { + fn write_version(&mut self) -> Result<(), E2sError> { if self.has_written_version { return Ok(()); } @@ -270,7 +288,7 @@ impl Era1Writer { } /// Write a complete [`Era1File`] to the underlying writer - pub fn write_era1_file(&mut self, era1_file: &Era1File) -> Result<(), E2sError> { + fn write_file(&mut self, era1_file: &Era1File) -> Result<(), E2sError> { // Write version self.write_version()?; @@ -301,6 +319,13 @@ impl Era1Writer { Ok(()) } + /// Flush any buffered data to the underlying writer + fn flush(&mut self) -> Result<(), E2sError> { + self.writer.flush() + } +} + +impl Era1Writer { /// Write a single block tuple pub fn write_block( &mut self, @@ -337,27 +362,6 @@ impl Era1Writer { Ok(()) } - /// Write the accumulator - pub fn write_accumulator(&mut self, accumulator: &Accumulator) -> Result<(), E2sError> { - if !self.has_written_version { - self.write_version()?; - } - - if self.has_written_accumulator { - return Err(E2sError::Ssz("Accumulator already written".to_string())); - } - - if self.has_written_block_index { - return Err(E2sError::Ssz("Cannot write accumulator after block index".to_string())); - } - - let accumulator_entry = accumulator.to_entry(); - self.writer.write_entry(&accumulator_entry)?; - self.has_written_accumulator = true; - - Ok(()) - } - /// Write the block index pub fn write_block_index(&mut self, block_index: &BlockIndex) -> Result<(), E2sError> { if !self.has_written_version { @@ -375,39 +379,36 @@ impl Era1Writer { Ok(()) } - /// Flush any buffered data to the underlying writer - pub fn flush(&mut self) -> Result<(), E2sError> { - self.writer.flush() - } -} + /// Write the accumulator + pub fn write_accumulator(&mut self, accumulator: &Accumulator) -> Result<(), E2sError> { + if !self.has_written_version { + self.write_version()?; + } -impl Era1Writer { - /// Creates a new file at the specified path and writes the [`Era1File`] to it - pub fn create>(path: P, era1_file: &Era1File) -> Result<(), E2sError> { - let file = File::create(path).map_err(E2sError::Io)?; - let mut writer = Self::new(file); - writer.write_era1_file(era1_file)?; - Ok(()) - } + if self.has_written_accumulator { + return Err(E2sError::Ssz("Accumulator already written".to_string())); + } - /// Creates a new file in the specified directory with a filename derived from the - /// [`Era1File`]'s ID using the standardized Era1 file naming convention - pub fn create_with_id>( - directory: P, - era1_file: &Era1File, - ) -> Result<(), E2sError> { - let filename = era1_file.id.to_file_name(); - let path = directory.as_ref().join(filename); - Self::create(path, era1_file) + if self.has_written_block_index { + return Err(E2sError::Ssz("Cannot write accumulator after block index".to_string())); + } + + let accumulator_entry = accumulator.to_entry(); + self.writer.write_entry(&accumulator_entry)?; + self.has_written_accumulator = true; + Ok(()) } } #[cfg(test)] mod tests { use super::*; - use crate::execution_types::{ - Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts, - TotalDifficulty, + use crate::{ + era_file_ops::FileWriter, + execution_types::{ + Accumulator, BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts, + TotalDifficulty, + }, }; use alloy_primitives::{B256, U256}; use std::io::Cursor; @@ -446,7 +447,7 @@ mod tests { let mut offsets = Vec::with_capacity(block_count); for i in 0..block_count { - offsets.push(i as u64 * 100); + offsets.push(i as i64 * 100); } let block_index = BlockIndex::new(start_block, offsets); let group = Era1Group::new(blocks, accumulator, block_index); @@ -465,7 +466,7 @@ mod tests { let mut buffer = Vec::new(); { let mut writer = Era1Writer::new(&mut buffer); - writer.write_era1_file(&era1_file)?; + writer.write_file(&era1_file)?; } // Read back from memory buffer diff --git a/crates/era/src/era1_types.rs b/crates/era/src/era1_types.rs index 58f51b42419..821d34d86c4 100644 --- a/crates/era/src/era1_types.rs +++ b/crates/era/src/era1_types.rs @@ -4,6 +4,7 @@ use crate::{ e2s_types::{Entry, IndexEntry}, + era_file_ops::EraFileId, execution_types::{Accumulator, BlockTuple, MAX_BLOCKS_PER_ERA1}, }; use alloy_primitives::BlockNumber; @@ -56,12 +57,12 @@ pub struct BlockIndex { starting_number: BlockNumber, /// Offsets to data at each block number - offsets: Vec, + offsets: Vec, } impl BlockIndex { /// Get the offset for a specific block number - pub fn offset_for_block(&self, block_number: BlockNumber) -> Option { + pub fn offset_for_block(&self, block_number: BlockNumber) -> Option { if block_number < self.starting_number { return None; } @@ -72,7 +73,7 @@ impl BlockIndex { } impl IndexEntry for BlockIndex { - fn new(starting_number: u64, offsets: Vec) -> Self { + fn new(starting_number: u64, offsets: Vec) -> Self { Self { starting_number, offsets } } @@ -84,7 +85,7 @@ impl IndexEntry for BlockIndex { self.starting_number } - fn offsets(&self) -> &[u64] { + fn offsets(&self) -> &[i64] { &self.offsets } } @@ -122,11 +123,37 @@ impl Era1Id { self } + // Helper function to calculate the number of eras per era1 file, + // If the user can decide how many blocks per era1 file there are, we need to calculate it. + // Most of the time it should be 1, but it can never be more than 2 eras per file + // as there is a maximum of 8192 blocks per era1 file. + const fn calculate_era_count(&self, first_era: u64) -> u64 { + // Calculate the actual last block number in the range + let last_block = self.start_block + self.block_count as u64 - 1; + // Find which era the last block belongs to + let last_era = last_block / MAX_BLOCKS_PER_ERA1 as u64; + // Count how many eras we span + last_era - first_era + 1 + } +} + +impl EraFileId for Era1Id { + fn network_name(&self) -> &str { + &self.network_name + } + + fn start_number(&self) -> u64 { + self.start_block + } + + fn count(&self) -> u32 { + self.block_count + } /// Convert to file name following the era file naming: /// `---.era(1)` /// /// See also - pub fn to_file_name(&self) -> String { + fn to_file_name(&self) -> String { // Find which era the first block belongs to let era_number = self.start_block / MAX_BLOCKS_PER_ERA1 as u64; let era_count = self.calculate_era_count(era_number); @@ -141,19 +168,6 @@ impl Era1Id { format!("{}-{:05}-{:05}-00000000.era1", self.network_name, era_number, era_count) } } - - // Helper function to calculate the number of eras per era1 file, - // If the user can decide how many blocks per era1 file there are, we need to calculate it. - // Most of the time it should be 1, but it can never be more than 2 eras per file - // as there is a maximum of 8192 blocks per era1 file. - const fn calculate_era_count(&self, first_era: u64) -> u64 { - // Calculate the actual last block number in the range - let last_block = self.start_block + self.block_count as u64 - 1; - // Find which era the last block belongs to - let last_era = last_block / MAX_BLOCKS_PER_ERA1 as u64; - // Count how many eras we span - last_era - first_era + 1 - } } #[cfg(test)] diff --git a/crates/era/src/era_file_ops.rs b/crates/era/src/era_file_ops.rs new file mode 100644 index 00000000000..469d6b78351 --- /dev/null +++ b/crates/era/src/era_file_ops.rs @@ -0,0 +1,124 @@ +//! Represents reading and writing operations' era file + +use crate::{e2s_types::Version, E2sError}; +use std::{ + fs::File, + io::{Read, Seek, Write}, + path::Path, +}; + +/// Represents era file with generic content and identifier types +pub trait EraFileFormat: Sized { + /// Content group type + type EraGroup; + + /// The identifier type + type Id: EraFileId; + + /// Get the version + fn version(&self) -> &Version; + + /// Get the content group + fn group(&self) -> &Self::EraGroup; + + /// Get the file identifier + fn id(&self) -> &Self::Id; + + /// Create a new instance + fn new(group: Self::EraGroup, id: Self::Id) -> Self; +} + +/// Era file identifiers +pub trait EraFileId: Clone { + /// Convert to standardized file name + fn to_file_name(&self) -> String; + + /// Get the network name + fn network_name(&self) -> &str; + + /// Get the starting number (block or slot) + fn start_number(&self) -> u64; + + /// Get the count of items + fn count(&self) -> u32; +} + +/// [`StreamReader`] for reading era-format files +pub trait StreamReader: Sized { + /// The file type the reader produces + type File: EraFileFormat; + + /// The iterator type for streaming data + type Iterator; + + /// Create a new reader + fn new(reader: R) -> Self; + + /// Read and parse the complete file + fn read(self, network_name: String) -> Result; + + /// Get an iterator for streaming processing + fn iter(self) -> Self::Iterator; +} + +/// [`FileReader`] provides reading era file operations for era files +pub trait FileReader: StreamReader { + /// Opens and reads an era file from the given path + fn open>( + path: P, + network_name: impl Into, + ) -> Result { + let file = File::open(path).map_err(E2sError::Io)?; + let reader = Self::new(file); + reader.read(network_name.into()) + } +} + +/// [`StreamWriter`] for writing era-format files +pub trait StreamWriter: Sized { + /// The file type this writer handles + type File: EraFileFormat; + + /// Create a new writer + fn new(writer: W) -> Self; + + /// Writer version + fn write_version(&mut self) -> Result<(), E2sError>; + + /// Write a complete era file + fn write_file(&mut self, file: &Self::File) -> Result<(), E2sError>; + + /// Flush any buffered data + fn flush(&mut self) -> Result<(), E2sError>; +} + +/// [`StreamWriter`] provides writing file operations for era files +pub trait FileWriter { + /// Era file type the writer handles + type File: EraFileFormat; + + /// Creates a new file at the specified path and writes the era file to it + fn create>(path: P, file: &Self::File) -> Result<(), E2sError>; + + /// Creates a file in the directory using standardized era naming + fn create_with_id>(directory: P, file: &Self::File) -> Result<(), E2sError>; +} + +impl> FileWriter for T { + type File = T::File; + + /// Creates a new file at the specified path and writes the era file to it + fn create>(path: P, file: &Self::File) -> Result<(), E2sError> { + let file_handle = File::create(path).map_err(E2sError::Io)?; + let mut writer = Self::new(file_handle); + writer.write_file(file)?; + Ok(()) + } + + /// Creates a file in the directory using standardized era naming + fn create_with_id>(directory: P, file: &Self::File) -> Result<(), E2sError> { + let filename = file.id().to_file_name(); + let path = directory.as_ref().join(filename); + Self::create(path, file) + } +} diff --git a/crates/era/src/era_types.rs b/crates/era/src/era_types.rs index 7a3ed404839..a50b6f19281 100644 --- a/crates/era/src/era_types.rs +++ b/crates/era/src/era_types.rs @@ -79,12 +79,12 @@ pub struct SlotIndex { /// Offsets to data at each slot /// 0 indicates no data for that slot - pub offsets: Vec, + pub offsets: Vec, } impl SlotIndex { /// Create a new slot index - pub const fn new(starting_slot: u64, offsets: Vec) -> Self { + pub const fn new(starting_slot: u64, offsets: Vec) -> Self { Self { starting_slot, offsets } } @@ -94,7 +94,7 @@ impl SlotIndex { } /// Get the offset for a specific slot - pub fn get_offset(&self, slot_index: usize) -> Option { + pub fn get_offset(&self, slot_index: usize) -> Option { self.offsets.get(slot_index).copied() } @@ -105,7 +105,7 @@ impl SlotIndex { } impl IndexEntry for SlotIndex { - fn new(starting_number: u64, offsets: Vec) -> Self { + fn new(starting_number: u64, offsets: Vec) -> Self { Self { starting_slot: starting_number, offsets } } @@ -117,7 +117,7 @@ impl IndexEntry for SlotIndex { self.starting_slot } - fn offsets(&self) -> &[u64] { + fn offsets(&self) -> &[i64] { &self.offsets } } @@ -272,4 +272,18 @@ mod tests { assert_eq!(era_group.other_entries[1].entry_type, [0x02, 0x02]); assert_eq!(era_group.other_entries[1].data, vec![5, 6, 7, 8]); } + + #[test] + fn test_index_with_negative_offset() { + let mut data = Vec::new(); + data.extend_from_slice(&0u64.to_le_bytes()); + data.extend_from_slice(&(-1024i64).to_le_bytes()); + data.extend_from_slice(&0i64.to_le_bytes()); + data.extend_from_slice(&2i64.to_le_bytes()); + + let entry = Entry::new(SLOT_INDEX, data); + let index = SlotIndex::from_entry(&entry).unwrap(); + let parsed_offset = index.offsets[0]; + assert_eq!(parsed_offset, -1024); + } } diff --git a/crates/era/src/lib.rs b/crates/era/src/lib.rs index 45383e3eead..fd0596e9dfc 100644 --- a/crates/era/src/lib.rs +++ b/crates/era/src/lib.rs @@ -17,6 +17,7 @@ pub mod e2s_file; pub mod e2s_types; pub mod era1_file; pub mod era1_types; +pub mod era_file_ops; pub mod era_types; pub mod execution_types; #[cfg(test)] diff --git a/crates/era/tests/it/dd.rs b/crates/era/tests/it/dd.rs index 0c656a512f9..769a398d6ce 100644 --- a/crates/era/tests/it/dd.rs +++ b/crates/era/tests/it/dd.rs @@ -6,6 +6,7 @@ use alloy_primitives::U256; use reth_era::{ e2s_types::IndexEntry, era1_file::{Era1Reader, Era1Writer}, + era_file_ops::{StreamReader, StreamWriter}, execution_types::CompressedBody, }; use reth_ethereum_primitives::TransactionSigned; @@ -94,7 +95,7 @@ async fn test_mainnet_era1_only_file_decompression_and_decoding() -> eyre::Resul let mut buffer = Vec::new(); { let mut writer = Era1Writer::new(&mut buffer); - writer.write_era1_file(&file)?; + writer.write_file(&file)?; } // Read back from buffer diff --git a/crates/era/tests/it/main.rs b/crates/era/tests/it/main.rs index 17af9dc0015..611862aa8ea 100644 --- a/crates/era/tests/it/main.rs +++ b/crates/era/tests/it/main.rs @@ -10,6 +10,7 @@ use reqwest::{Client, Url}; use reth_era::{ e2s_types::E2sError, era1_file::{Era1File, Era1Reader}, + era_file_ops::FileReader, }; use reth_era_downloader::EraClient; use std::{ diff --git a/crates/era/tests/it/roundtrip.rs b/crates/era/tests/it/roundtrip.rs index 0689ef383e2..a78af341371 100644 --- a/crates/era/tests/it/roundtrip.rs +++ b/crates/era/tests/it/roundtrip.rs @@ -13,6 +13,7 @@ use reth_era::{ e2s_types::IndexEntry, era1_file::{Era1File, Era1Reader, Era1Writer}, era1_types::{Era1Group, Era1Id}, + era_file_ops::{EraFileFormat, StreamReader, StreamWriter}, execution_types::{ BlockTuple, CompressedBody, CompressedHeader, CompressedReceipts, TotalDifficulty, }, @@ -45,7 +46,7 @@ async fn test_file_roundtrip( let mut buffer = Vec::new(); { let mut writer = Era1Writer::new(&mut buffer); - writer.write_era1_file(&original_file)?; + writer.write_file(&original_file)?; } // Read back from buffer @@ -228,7 +229,7 @@ async fn test_file_roundtrip( Era1File::new(new_group, Era1Id::new(network, original_file.id.start_block, 1)); let mut writer = Era1Writer::new(&mut recompressed_buffer); - writer.write_era1_file(&new_file)?; + writer.write_file(&new_file)?; } let reader = Era1Reader::new(Cursor::new(&recompressed_buffer)); diff --git a/crates/errors/src/error.rs b/crates/errors/src/error.rs index 676a9c015d9..30a2ddd1315 100644 --- a/crates/errors/src/error.rs +++ b/crates/errors/src/error.rs @@ -61,9 +61,9 @@ mod size_asserts { }; } - static_assert_size!(RethError, 56); + static_assert_size!(RethError, 72); static_assert_size!(BlockExecutionError, 56); - static_assert_size!(ConsensusError, 48); + static_assert_size!(ConsensusError, 72); static_assert_size!(DatabaseError, 32); static_assert_size!(ProviderError, 48); } diff --git a/crates/ethereum/cli/Cargo.toml b/crates/ethereum/cli/Cargo.toml index 491d818eb92..01a7751e77b 100644 --- a/crates/ethereum/cli/Cargo.toml +++ b/crates/ethereum/cli/Cargo.toml @@ -21,6 +21,7 @@ reth-node-builder.workspace = true reth-node-core.workspace = true reth-node-ethereum.workspace = true reth-node-metrics.workspace = true +reth-rpc-server-types.workspace = true reth-tracing.workspace = true reth-node-api.workspace = true diff --git a/crates/ethereum/cli/src/app.rs b/crates/ethereum/cli/src/app.rs new file mode 100644 index 00000000000..e99dae2ac77 --- /dev/null +++ b/crates/ethereum/cli/src/app.rs @@ -0,0 +1,220 @@ +use crate::{interface::Commands, Cli}; +use eyre::{eyre, Result}; +use reth_chainspec::{ChainSpec, EthChainSpec, Hardforks}; +use reth_cli::chainspec::ChainSpecParser; +use reth_cli_commands::{ + common::{CliComponentsBuilder, CliHeader, CliNodeTypes}, + launcher::{FnLauncher, Launcher}, +}; +use reth_cli_runner::CliRunner; +use reth_db::DatabaseEnv; +use reth_node_api::NodePrimitives; +use reth_node_builder::{NodeBuilder, WithLaunchContext}; +use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig, EthereumNode}; +use reth_node_metrics::recorder::install_prometheus_recorder; +use reth_rpc_server_types::RpcModuleValidator; +use reth_tracing::{FileWorkerGuard, Layers}; +use std::{fmt, sync::Arc}; +use tracing::info; + +/// A wrapper around a parsed CLI that handles command execution. +#[derive(Debug)] +pub struct CliApp { + cli: Cli, + runner: Option, + layers: Option, + guard: Option, +} + +impl CliApp +where + C: ChainSpecParser, + Ext: clap::Args + fmt::Debug, + Rpc: RpcModuleValidator, +{ + pub(crate) fn new(cli: Cli) -> Self { + Self { cli, runner: None, layers: Some(Layers::new()), guard: None } + } + + /// Sets the runner for the CLI commander. + /// + /// This replaces any existing runner with the provided one. + pub fn set_runner(&mut self, runner: CliRunner) { + self.runner = Some(runner); + } + + /// Access to tracing layers. + /// + /// Returns a mutable reference to the tracing layers, or error + /// if tracing initialized and layers have detached already. + pub fn access_tracing_layers(&mut self) -> Result<&mut Layers> { + self.layers.as_mut().ok_or_else(|| eyre!("Tracing already initialized")) + } + + /// Execute the configured cli command. + /// + /// This accepts a closure that is used to launch the node via the + /// [`NodeCommand`](reth_cli_commands::node::NodeCommand). + pub fn run(self, launcher: impl Launcher) -> Result<()> + where + C: ChainSpecParser, + { + let components = |spec: Arc| { + (EthEvmConfig::ethereum(spec.clone()), Arc::new(EthBeaconConsensus::new(spec))) + }; + + self.run_with_components::(components, |builder, ext| async move { + launcher.entrypoint(builder, ext).await + }) + } + + /// Execute the configured cli command with the provided [`CliComponentsBuilder`]. + /// + /// This accepts a closure that is used to launch the node via the + /// [`NodeCommand`](reth_cli_commands::node::NodeCommand) and allows providing custom + /// components. + pub fn run_with_components( + mut self, + components: impl CliComponentsBuilder, + launcher: impl AsyncFnOnce( + WithLaunchContext, C::ChainSpec>>, + Ext, + ) -> Result<()>, + ) -> Result<()> + where + N: CliNodeTypes< + Primitives: NodePrimitives, + ChainSpec: Hardforks + EthChainSpec, + >, + C: ChainSpecParser, + { + let runner = match self.runner.take() { + Some(runner) => runner, + None => CliRunner::try_default_runtime()?, + }; + + // Add network name if available to the logs dir + if let Some(chain_spec) = self.cli.command.chain_spec() { + self.cli.logs.log_file_directory = + self.cli.logs.log_file_directory.join(chain_spec.chain().to_string()); + } + + self.init_tracing()?; + // Install the prometheus recorder to be sure to record all metrics + let _ = install_prometheus_recorder(); + + run_commands_with::(self.cli, runner, components, launcher) + } + + /// Initializes tracing with the configured options. + /// + /// If file logging is enabled, this function stores guard to the struct. + pub fn init_tracing(&mut self) -> Result<()> { + if self.guard.is_none() { + let layers = self.layers.take().unwrap_or_default(); + self.guard = self.cli.logs.init_tracing_with_layers(layers)?; + info!(target: "reth::cli", "Initialized tracing, debug log directory: {}", self.cli.logs.log_file_directory); + } + Ok(()) + } +} + +/// Run CLI commands with the provided runner, components and launcher. +/// This is the shared implementation used by both `CliApp` and Cli methods. +pub(crate) fn run_commands_with( + cli: Cli, + runner: CliRunner, + components: impl CliComponentsBuilder, + launcher: impl AsyncFnOnce( + WithLaunchContext, C::ChainSpec>>, + Ext, + ) -> Result<()>, +) -> Result<()> +where + C: ChainSpecParser, + Ext: clap::Args + fmt::Debug, + Rpc: RpcModuleValidator, + N: CliNodeTypes, ChainSpec: Hardforks>, +{ + match cli.command { + Commands::Node(command) => { + // Validate RPC modules using the configured validator + if let Some(http_api) = &command.rpc.http_api { + Rpc::validate_selection(http_api, "http.api").map_err(|e| eyre!("{e}"))?; + } + if let Some(ws_api) = &command.rpc.ws_api { + Rpc::validate_selection(ws_api, "ws.api").map_err(|e| eyre!("{e}"))?; + } + + runner.run_command_until_exit(|ctx| { + command.execute(ctx, FnLauncher::new::(launcher)) + }) + } + Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::()), + Commands::InitState(command) => runner.run_blocking_until_ctrl_c(command.execute::()), + Commands::Import(command) => { + runner.run_blocking_until_ctrl_c(command.execute::(components)) + } + Commands::ImportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::()), + Commands::ExportEra(command) => runner.run_blocking_until_ctrl_c(command.execute::()), + Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()), + Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::()), + Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::()), + Commands::Stage(command) => { + runner.run_command_until_exit(|ctx| command.execute::(ctx, components)) + } + Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::()), + Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), + Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::()), + #[cfg(feature = "dev")] + Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), + Commands::ReExecute(command) => runner.run_until_ctrl_c(command.execute::(components)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::chainspec::EthereumChainSpecParser; + use clap::Parser; + use reth_cli_commands::node::NoArgs; + + #[test] + fn test_cli_app_creation() { + let args = vec!["reth", "config"]; + let cli = Cli::::try_parse_from(args).unwrap(); + let app = cli.configure(); + + // Verify app is created correctly + assert!(app.runner.is_none()); + assert!(app.layers.is_some()); + assert!(app.guard.is_none()); + } + + #[test] + fn test_set_runner() { + let args = vec!["reth", "config"]; + let cli = Cli::::try_parse_from(args).unwrap(); + let mut app = cli.configure(); + + // Create and set a runner + if let Ok(runner) = CliRunner::try_default_runtime() { + app.set_runner(runner); + assert!(app.runner.is_some()); + } + } + + #[test] + fn test_access_tracing_layers() { + let args = vec!["reth", "config"]; + let cli = Cli::::try_parse_from(args).unwrap(); + let mut app = cli.configure(); + + // Should be able to access layers before initialization + assert!(app.access_tracing_layers().is_ok()); + + // After taking layers (simulating initialization), access should error + app.layers = None; + assert!(app.access_tracing_layers().is_err()); + } +} diff --git a/crates/ethereum/cli/src/interface.rs b/crates/ethereum/cli/src/interface.rs index 83b79abe811..c1d91c12c79 100644 --- a/crates/ethereum/cli/src/interface.rs +++ b/crates/ethereum/cli/src/interface.rs @@ -1,6 +1,9 @@ //! CLI definition and entrypoint to executable -use crate::chainspec::EthereumChainSpecParser; +use crate::{ + app::{run_commands_with, CliApp}, + chainspec::EthereumChainSpecParser, +}; use clap::{Parser, Subcommand}; use reth_chainspec::{ChainSpec, EthChainSpec, Hardforks}; use reth_cli::chainspec::ChainSpecParser; @@ -9,17 +12,17 @@ use reth_cli_commands::{ config_cmd, db, download, dump_genesis, export_era, import, import_era, init_cmd, init_state, launcher::FnLauncher, node::{self, NoArgs}, - p2p, prune, re_execute, recover, stage, + p2p, prune, re_execute, stage, }; use reth_cli_runner::CliRunner; use reth_db::DatabaseEnv; use reth_node_api::NodePrimitives; use reth_node_builder::{NodeBuilder, WithLaunchContext}; use reth_node_core::{args::LogArgs, version::version_metadata}; -use reth_node_ethereum::{consensus::EthBeaconConsensus, EthEvmConfig, EthereumNode}; use reth_node_metrics::recorder::install_prometheus_recorder; +use reth_rpc_server_types::{DefaultRpcModuleValidator, RpcModuleValidator}; use reth_tracing::FileWorkerGuard; -use std::{ffi::OsString, fmt, future::Future, sync::Arc}; +use std::{ffi::OsString, fmt, future::Future, marker::PhantomData, sync::Arc}; use tracing::info; /// The main reth cli interface. @@ -27,8 +30,11 @@ use tracing::info; /// This is the entrypoint to the executable. #[derive(Debug, Parser)] #[command(author, version =version_metadata().short_version.as_ref(), long_version = version_metadata().long_version.as_ref(), about = "Reth", long_about = None)] -pub struct Cli -{ +pub struct Cli< + C: ChainSpecParser = EthereumChainSpecParser, + Ext: clap::Args + fmt::Debug = NoArgs, + Rpc: RpcModuleValidator = DefaultRpcModuleValidator, +> { /// The command to run #[command(subcommand)] pub command: Commands, @@ -36,6 +42,10 @@ pub struct Cli, } impl Cli { @@ -54,7 +64,18 @@ impl Cli { } } -impl Cli { +impl Cli { + /// Configures the CLI and returns a [`CliApp`] instance. + /// + /// This method is used to prepare the CLI for execution by wrapping it in a + /// [`CliApp`] that can be further configured before running. + pub fn configure(self) -> CliApp + where + C: ChainSpecParser, + { + CliApp::new(self) + } + /// Execute the configured cli command. /// /// This accepts a closure that is used to launch the node via the @@ -152,15 +173,9 @@ impl Cli { Fut: Future>, C: ChainSpecParser, { - let components = |spec: Arc| { - (EthEvmConfig::ethereum(spec.clone()), EthBeaconConsensus::new(spec)) - }; - - self.with_runner_and_components::( - runner, - components, - async move |builder, ext| launcher(builder, ext).await, - ) + let mut app = self.configure(); + app.set_runner(runner); + app.run(FnLauncher::new::(async move |builder, ext| launcher(builder, ext).await)) } /// Execute the configured cli command with the provided [`CliRunner`] and @@ -189,41 +204,8 @@ impl Cli { // Install the prometheus recorder to be sure to record all metrics let _ = install_prometheus_recorder(); - match self.command { - Commands::Node(command) => runner.run_command_until_exit(|ctx| { - command.execute(ctx, FnLauncher::new::(launcher)) - }), - Commands::Init(command) => runner.run_blocking_until_ctrl_c(command.execute::()), - Commands::InitState(command) => { - runner.run_blocking_until_ctrl_c(command.execute::()) - } - Commands::Import(command) => { - runner.run_blocking_until_ctrl_c(command.execute::(components)) - } - Commands::ImportEra(command) => { - runner.run_blocking_until_ctrl_c(command.execute::()) - } - Commands::ExportEra(command) => { - runner.run_blocking_until_ctrl_c(command.execute::()) - } - Commands::DumpGenesis(command) => runner.run_blocking_until_ctrl_c(command.execute()), - Commands::Db(command) => runner.run_blocking_until_ctrl_c(command.execute::()), - Commands::Download(command) => runner.run_blocking_until_ctrl_c(command.execute::()), - Commands::Stage(command) => { - runner.run_command_until_exit(|ctx| command.execute::(ctx, components)) - } - Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::()), - #[cfg(feature = "dev")] - Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), - Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), - Commands::Recover(command) => { - runner.run_command_until_exit(|ctx| command.execute::(ctx)) - } - Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::()), - Commands::ReExecute(command) => { - runner.run_until_ctrl_c(command.execute::(components)) - } - } + // Use the shared standalone function to avoid duplication + run_commands_with::(self, runner, components, launcher) } /// Initializes tracing with the configured options. @@ -248,7 +230,7 @@ pub enum Commands { /// Initialize the database from a state dump file. #[command(name = "init-state")] InitState(init_state::InitStateCommand), - /// This syncs RLP encoded blocks from a file. + /// This syncs RLP encoded blocks from a file or files. #[command(name = "import")] Import(import::ImportCommand), /// This syncs ERA encoded blocks from a directory. @@ -261,7 +243,7 @@ pub enum Commands { DumpGenesis(dump_genesis::DumpGenesisCommand), /// Database debugging utilities #[command(name = "db")] - Db(db::Command), + Db(Box>), /// Download public node snapshots #[command(name = "download")] Download(download::DownloadCommand), @@ -270,7 +252,7 @@ pub enum Commands { Stage(stage::Command), /// P2P Debugging utilities #[command(name = "p2p")] - P2P(p2p::Command), + P2P(Box>), /// Generate Test Vectors #[cfg(feature = "dev")] #[command(name = "test-vectors")] @@ -278,9 +260,6 @@ pub enum Commands { /// Write config to stdout #[command(name = "config")] Config(config_cmd::Command), - /// Scripts for node recovery - #[command(name = "recover")] - Recover(recover::Command), /// Prune according to the configuration without any limits #[command(name = "prune")] Prune(prune::PruneCommand), @@ -307,7 +286,6 @@ impl Commands { #[cfg(feature = "dev")] Self::TestVectors(_) => None, Self::Config(_) => None, - Self::Recover(cmd) => cmd.chain_spec(), Self::Prune(cmd) => cmd.chain_spec(), Self::ReExecute(cmd) => cmd.chain_spec(), } @@ -417,4 +395,72 @@ mod tests { .unwrap(); assert!(reth.run(async move |_, _| Ok(())).is_ok()); } + + #[test] + fn test_rpc_module_validation() { + use reth_rpc_server_types::RethRpcModule; + + // Test that standard modules are accepted + let cli = + Cli::try_parse_args_from(["reth", "node", "--http.api", "eth,admin,debug"]).unwrap(); + + if let Commands::Node(command) = &cli.command { + if let Some(http_api) = &command.rpc.http_api { + // Should contain the expected modules + let modules = http_api.to_selection(); + assert!(modules.contains(&RethRpcModule::Eth)); + assert!(modules.contains(&RethRpcModule::Admin)); + assert!(modules.contains(&RethRpcModule::Debug)); + } else { + panic!("Expected http.api to be set"); + } + } else { + panic!("Expected Node command"); + } + + // Test that unknown modules are parsed as Other variant + let cli = + Cli::try_parse_args_from(["reth", "node", "--http.api", "eth,customrpc"]).unwrap(); + + if let Commands::Node(command) = &cli.command { + if let Some(http_api) = &command.rpc.http_api { + let modules = http_api.to_selection(); + assert!(modules.contains(&RethRpcModule::Eth)); + assert!(modules.contains(&RethRpcModule::Other("customrpc".to_string()))); + } else { + panic!("Expected http.api to be set"); + } + } else { + panic!("Expected Node command"); + } + } + + #[test] + fn test_rpc_module_unknown_rejected() { + use reth_cli_runner::CliRunner; + + // Test that unknown module names are rejected during validation + let cli = + Cli::try_parse_args_from(["reth", "node", "--http.api", "unknownmodule"]).unwrap(); + + // When we try to run the CLI with validation, it should fail + let runner = CliRunner::try_default_runtime().unwrap(); + let result = cli.with_runner(runner, |_, _| async { Ok(()) }); + + assert!(result.is_err()); + let err = result.unwrap_err(); + let err_msg = err.to_string(); + + // The error should mention it's an unknown module + assert!( + err_msg.contains("Unknown RPC module"), + "Error should mention unknown module: {}", + err_msg + ); + assert!( + err_msg.contains("'unknownmodule'"), + "Error should mention the module name: {}", + err_msg + ); + } } diff --git a/crates/ethereum/cli/src/lib.rs b/crates/ethereum/cli/src/lib.rs index 067d49d1682..4d882467dbe 100644 --- a/crates/ethereum/cli/src/lib.rs +++ b/crates/ethereum/cli/src/lib.rs @@ -8,10 +8,14 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +/// A configurable App on top of the cli parser. +pub mod app; /// Chain specification parser. pub mod chainspec; pub mod interface; -pub use interface::Cli; + +pub use app::CliApp; +pub use interface::{Cli, Commands}; #[cfg(test)] mod test { diff --git a/crates/ethereum/consensus/src/lib.rs b/crates/ethereum/consensus/src/lib.rs index 621a69f88b7..e9aceb2b3fc 100644 --- a/crates/ethereum/consensus/src/lib.rs +++ b/crates/ethereum/consensus/src/lib.rs @@ -18,13 +18,13 @@ use reth_chainspec::{EthChainSpec, EthereumHardforks}; use reth_consensus::{Consensus, ConsensusError, FullConsensus, HeaderValidator}; use reth_consensus_common::validation::{ validate_4844_header_standalone, validate_against_parent_4844, - validate_against_parent_eip1559_base_fee, validate_against_parent_hash_number, - validate_against_parent_timestamp, validate_block_pre_execution, validate_body_against_header, - validate_header_base_fee, validate_header_extra_data, validate_header_gas, + validate_against_parent_eip1559_base_fee, validate_against_parent_gas_limit, + validate_against_parent_hash_number, validate_against_parent_timestamp, + validate_block_pre_execution, validate_body_against_header, validate_header_base_fee, + validate_header_extra_data, validate_header_gas, }; use reth_execution_types::BlockExecutionResult; use reth_primitives_traits::{ - constants::{GAS_LIMIT_BOUND_DIVISOR, MINIMUM_GAS_LIMIT}, Block, BlockHeader, NodePrimitives, RecoveredBlock, SealedBlock, SealedHeader, }; @@ -46,53 +46,9 @@ impl EthBeaconConsensus Self { chain_spec } } - /// Checks the gas limit for consistency between parent and self headers. - /// - /// The maximum allowable difference between self and parent gas limits is determined by the - /// parent's gas limit divided by the [`GAS_LIMIT_BOUND_DIVISOR`]. - fn validate_against_parent_gas_limit( - &self, - header: &SealedHeader, - parent: &SealedHeader, - ) -> Result<(), ConsensusError> { - // Determine the parent gas limit, considering elasticity multiplier on the London fork. - let parent_gas_limit = if !self.chain_spec.is_london_active_at_block(parent.number()) && - self.chain_spec.is_london_active_at_block(header.number()) - { - parent.gas_limit() * - self.chain_spec - .base_fee_params_at_timestamp(header.timestamp()) - .elasticity_multiplier as u64 - } else { - parent.gas_limit() - }; - - // Check for an increase in gas limit beyond the allowed threshold. - if header.gas_limit() > parent_gas_limit { - if header.gas_limit() - parent_gas_limit >= parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR { - return Err(ConsensusError::GasLimitInvalidIncrease { - parent_gas_limit, - child_gas_limit: header.gas_limit(), - }) - } - } - // Check for a decrease in gas limit beyond the allowed threshold. - else if parent_gas_limit - header.gas_limit() >= - parent_gas_limit / GAS_LIMIT_BOUND_DIVISOR - { - return Err(ConsensusError::GasLimitInvalidDecrease { - parent_gas_limit, - child_gas_limit: header.gas_limit(), - }) - } - // Check if the self gas limit is below the minimum required limit. - else if header.gas_limit() < MINIMUM_GAS_LIMIT { - return Err(ConsensusError::GasLimitInvalidMinimum { - child_gas_limit: header.gas_limit(), - }) - } - - Ok(()) + /// Returns the chain spec associated with this consensus engine. + pub const fn chain_spec(&self) -> &Arc { + &self.chain_spec } } @@ -220,7 +176,7 @@ where validate_against_parent_timestamp(header.header(), parent.header())?; - self.validate_against_parent_gas_limit(header, parent)?; + validate_against_parent_gas_limit(header, parent, &self.chain_spec)?; validate_against_parent_eip1559_base_fee( header.header(), @@ -242,7 +198,11 @@ mod tests { use super::*; use alloy_primitives::B256; use reth_chainspec::{ChainSpec, ChainSpecBuilder}; - use reth_primitives_traits::proofs; + use reth_consensus_common::validation::validate_against_parent_gas_limit; + use reth_primitives_traits::{ + constants::{GAS_LIMIT_BOUND_DIVISOR, MINIMUM_GAS_LIMIT}, + proofs, + }; fn header_with_gas_limit(gas_limit: u64) -> SealedHeader { let header = reth_primitives_traits::Header { gas_limit, ..Default::default() }; @@ -255,8 +215,7 @@ mod tests { let child = header_with_gas_limit((parent.gas_limit + 5) as u64); assert_eq!( - EthBeaconConsensus::new(Arc::new(ChainSpec::default())) - .validate_against_parent_gas_limit(&child, &parent), + validate_against_parent_gas_limit(&child, &parent, &ChainSpec::default()), Ok(()) ); } @@ -267,8 +226,7 @@ mod tests { let child = header_with_gas_limit(MINIMUM_GAS_LIMIT - 1); assert_eq!( - EthBeaconConsensus::new(Arc::new(ChainSpec::default())) - .validate_against_parent_gas_limit(&child, &parent), + validate_against_parent_gas_limit(&child, &parent, &ChainSpec::default()), Err(ConsensusError::GasLimitInvalidMinimum { child_gas_limit: child.gas_limit as u64 }) ); } @@ -281,8 +239,7 @@ mod tests { ); assert_eq!( - EthBeaconConsensus::new(Arc::new(ChainSpec::default())) - .validate_against_parent_gas_limit(&child, &parent), + validate_against_parent_gas_limit(&child, &parent, &ChainSpec::default()), Err(ConsensusError::GasLimitInvalidIncrease { parent_gas_limit: parent.gas_limit, child_gas_limit: child.gas_limit, @@ -296,8 +253,7 @@ mod tests { let child = header_with_gas_limit(parent.gas_limit - 5); assert_eq!( - EthBeaconConsensus::new(Arc::new(ChainSpec::default())) - .validate_against_parent_gas_limit(&child, &parent), + validate_against_parent_gas_limit(&child, &parent, &ChainSpec::default()), Ok(()) ); } @@ -310,8 +266,7 @@ mod tests { ); assert_eq!( - EthBeaconConsensus::new(Arc::new(ChainSpec::default())) - .validate_against_parent_gas_limit(&child, &parent), + validate_against_parent_gas_limit(&child, &parent, &ChainSpec::default()), Err(ConsensusError::GasLimitInvalidDecrease { parent_gas_limit: parent.gas_limit, child_gas_limit: child.gas_limit, diff --git a/crates/ethereum/consensus/src/validation.rs b/crates/ethereum/consensus/src/validation.rs index 485828e6080..71affffeb0c 100644 --- a/crates/ethereum/consensus/src/validation.rs +++ b/crates/ethereum/consensus/src/validation.rs @@ -37,17 +37,19 @@ where // operation as hashing that is required for state root got calculated in every // transaction This was replaced with is_success flag. // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 - if chain_spec.is_byzantium_active_at_block(block.header().number()) { - if let Err(error) = - verify_receipts(block.header().receipts_root(), block.header().logs_bloom(), receipts) - { - let receipts = receipts - .iter() - .map(|r| Bytes::from(r.with_bloom_ref().encoded_2718())) - .collect::>(); - tracing::debug!(%error, ?receipts, "receipts verification failed"); - return Err(error) - } + if chain_spec.is_byzantium_active_at_block(block.header().number()) && + let Err(error) = verify_receipts( + block.header().receipts_root(), + block.header().logs_bloom(), + receipts, + ) + { + let receipts = receipts + .iter() + .map(|r| Bytes::from(r.with_bloom_ref().encoded_2718())) + .collect::>(); + tracing::debug!(%error, ?receipts, "receipts verification failed"); + return Err(error) } // Validate that the header requests hash matches the calculated requests hash diff --git a/crates/ethereum/engine-primitives/src/payload.rs b/crates/ethereum/engine-primitives/src/payload.rs index 444747716ee..45c1f6a31fa 100644 --- a/crates/ethereum/engine-primitives/src/payload.rs +++ b/crates/ethereum/engine-primitives/src/payload.rs @@ -15,9 +15,9 @@ use alloy_rpc_types_engine::{ ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId, }; use core::convert::Infallible; -use reth_ethereum_primitives::{Block, EthPrimitives}; +use reth_ethereum_primitives::EthPrimitives; use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes}; -use reth_primitives_traits::SealedBlock; +use reth_primitives_traits::{NodePrimitives, SealedBlock}; use crate::BuiltPayloadConversionError; @@ -27,11 +27,11 @@ use crate::BuiltPayloadConversionError; /// Therefore, the empty-block here is always available and full-block will be set/updated /// afterward. #[derive(Debug, Clone)] -pub struct EthBuiltPayload { +pub struct EthBuiltPayload { /// Identifier of the payload pub(crate) id: PayloadId, /// The built block - pub(crate) block: Arc>, + pub(crate) block: Arc>, /// The fees of the block pub(crate) fees: U256, /// The blobs, proofs, and commitments in the block. If the block is pre-cancun, this will be @@ -43,13 +43,13 @@ pub struct EthBuiltPayload { // === impl BuiltPayload === -impl EthBuiltPayload { +impl EthBuiltPayload { /// Initializes the payload with the given initial block /// /// Caution: This does not set any [`BlobSidecars`]. pub const fn new( id: PayloadId, - block: Arc>, + block: Arc>, fees: U256, requests: Option, ) -> Self { @@ -62,7 +62,7 @@ impl EthBuiltPayload { } /// Returns the built block(sealed) - pub fn block(&self) -> &SealedBlock { + pub fn block(&self) -> &SealedBlock { &self.block } @@ -81,7 +81,9 @@ impl EthBuiltPayload { self.sidecars = sidecars.into(); self } +} +impl EthBuiltPayload { /// Try converting built payload into [`ExecutionPayloadEnvelopeV3`]. /// /// Returns an error if the payload contains non EIP-4844 sidecar. @@ -158,10 +160,10 @@ impl EthBuiltPayload { } } -impl BuiltPayload for EthBuiltPayload { - type Primitives = EthPrimitives; +impl BuiltPayload for EthBuiltPayload { + type Primitives = N; - fn block(&self) -> &SealedBlock { + fn block(&self) -> &SealedBlock { &self.block } diff --git a/crates/ethereum/evm/src/build.rs b/crates/ethereum/evm/src/build.rs index 5e80ca9ba46..5f5e014d297 100644 --- a/crates/ethereum/evm/src/build.rs +++ b/crates/ethereum/evm/src/build.rs @@ -1,15 +1,15 @@ -use alloc::sync::Arc; +use alloc::{sync::Arc, vec::Vec}; use alloy_consensus::{ - proofs, Block, BlockBody, BlockHeader, Header, Transaction, TxReceipt, EMPTY_OMMER_ROOT_HASH, + proofs::{self, calculate_receipt_root}, + Block, BlockBody, BlockHeader, Header, Transaction, TxReceipt, EMPTY_OMMER_ROOT_HASH, }; use alloy_eips::merge::BEACON_NONCE; use alloy_evm::{block::BlockExecutorFactory, eth::EthBlockExecutionCtx}; use alloy_primitives::Bytes; use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_ethereum_primitives::{Receipt, TransactionSigned}; use reth_evm::execute::{BlockAssembler, BlockAssemblerInput, BlockExecutionError}; use reth_execution_types::BlockExecutionResult; -use reth_primitives_traits::logs_bloom; +use reth_primitives_traits::{logs_bloom, Receipt, SignedTransaction}; /// Block builder for Ethereum. #[derive(Debug, Clone)] @@ -31,17 +31,17 @@ impl BlockAssembler for EthBlockAssembler where F: for<'a> BlockExecutorFactory< ExecutionCtx<'a> = EthBlockExecutionCtx<'a>, - Transaction = TransactionSigned, - Receipt = Receipt, + Transaction: SignedTransaction, + Receipt: Receipt, >, ChainSpec: EthChainSpec + EthereumHardforks, { - type Block = Block; + type Block = Block; fn assemble_block( &self, input: BlockAssemblerInput<'_, '_, F>, - ) -> Result, BlockExecutionError> { + ) -> Result { let BlockAssemblerInput { evm_env, execution_ctx: ctx, @@ -55,7 +55,9 @@ where let timestamp = evm_env.block_env.timestamp.saturating_to(); let transactions_root = proofs::calculate_transaction_root(&transactions); - let receipts_root = Receipt::calculate_receipt_root_no_memo(receipts); + let receipts_root = calculate_receipt_root( + &receipts.iter().map(|r| r.with_bloom_ref()).collect::>(), + ); let logs_bloom = logs_bloom(receipts.iter().flat_map(|r| r.logs())); let withdrawals = self @@ -84,7 +86,10 @@ where } else { // for the first post-fork block, both parent.blob_gas_used and // parent.excess_blob_gas are evaluated as 0 - Some(alloy_eips::eip7840::BlobParams::cancun().next_block_excess_blob_gas(0, 0)) + Some( + alloy_eips::eip7840::BlobParams::cancun() + .next_block_excess_blob_gas_osaka(0, 0, 0), + ) }; } diff --git a/crates/ethereum/evm/src/config.rs b/crates/ethereum/evm/src/config.rs index 08f42540d08..f9c288f0674 100644 --- a/crates/ethereum/evm/src/config.rs +++ b/crates/ethereum/evm/src/config.rs @@ -1,262 +1,4 @@ -use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_ethereum_forks::{EthereumHardfork, Hardforks}; -use reth_primitives_traits::BlockHeader; -use revm::primitives::hardfork::SpecId; - -/// Map the latest active hardfork at the given header to a revm [`SpecId`]. -pub fn revm_spec(chain_spec: &C, header: &H) -> SpecId -where - C: EthereumHardforks + EthChainSpec + Hardforks, - H: BlockHeader, -{ - revm_spec_by_timestamp_and_block_number(chain_spec, header.timestamp(), header.number()) -} - -/// Map the latest active hardfork at the given timestamp or block number to a revm [`SpecId`]. -pub fn revm_spec_by_timestamp_and_block_number( - chain_spec: &C, - timestamp: u64, - block_number: u64, -) -> SpecId -where - C: EthereumHardforks + EthChainSpec + Hardforks, -{ - if chain_spec - .fork(EthereumHardfork::Osaka) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::OSAKA - } else if chain_spec - .fork(EthereumHardfork::Prague) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::PRAGUE - } else if chain_spec - .fork(EthereumHardfork::Cancun) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::CANCUN - } else if chain_spec - .fork(EthereumHardfork::Shanghai) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::SHANGHAI - } else if chain_spec.is_paris_active_at_block(block_number) { - SpecId::MERGE - } else if chain_spec - .fork(EthereumHardfork::London) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::LONDON - } else if chain_spec - .fork(EthereumHardfork::Berlin) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::BERLIN - } else if chain_spec - .fork(EthereumHardfork::Istanbul) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::ISTANBUL - } else if chain_spec - .fork(EthereumHardfork::Petersburg) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::PETERSBURG - } else if chain_spec - .fork(EthereumHardfork::Byzantium) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::BYZANTIUM - } else if chain_spec - .fork(EthereumHardfork::SpuriousDragon) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::SPURIOUS_DRAGON - } else if chain_spec - .fork(EthereumHardfork::Tangerine) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::TANGERINE - } else if chain_spec - .fork(EthereumHardfork::Homestead) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::HOMESTEAD - } else if chain_spec - .fork(EthereumHardfork::Frontier) - .active_at_timestamp_or_number(timestamp, block_number) - { - SpecId::FRONTIER - } else { - panic!( - "invalid hardfork chainspec: expected at least one hardfork, got {}", - chain_spec.display_hardforks() - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::U256; - use alloy_consensus::Header; - use reth_chainspec::{ChainSpecBuilder, MAINNET}; - - #[test] - fn test_revm_spec_by_timestamp() { - assert_eq!( - revm_spec_by_timestamp_and_block_number( - &ChainSpecBuilder::mainnet().cancun_activated().build(), - 0, - 0 - ), - SpecId::CANCUN - ); - assert_eq!( - revm_spec_by_timestamp_and_block_number( - &ChainSpecBuilder::mainnet().shanghai_activated().build(), - 0, - 0 - ), - SpecId::SHANGHAI - ); - let mainnet = ChainSpecBuilder::mainnet().build(); - assert_eq!( - revm_spec_by_timestamp_and_block_number(&mainnet, 0, mainnet.paris_block().unwrap()), - SpecId::MERGE - ); - } - - #[test] - fn test_to_revm_spec() { - assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().cancun_activated().build(), &Header::default()), - SpecId::CANCUN - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().shanghai_activated().build(), - &Header::default() - ), - SpecId::SHANGHAI - ); - assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().paris_activated().build(), &Header::default()), - SpecId::MERGE - ); - assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().london_activated().build(), &Header::default()), - SpecId::LONDON - ); - assert_eq!( - revm_spec(&ChainSpecBuilder::mainnet().berlin_activated().build(), &Header::default()), - SpecId::BERLIN - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().istanbul_activated().build(), - &Header::default() - ), - SpecId::ISTANBUL - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().petersburg_activated().build(), - &Header::default() - ), - SpecId::PETERSBURG - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().byzantium_activated().build(), - &Header::default() - ), - SpecId::BYZANTIUM - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().spurious_dragon_activated().build(), - &Header::default() - ), - SpecId::SPURIOUS_DRAGON - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().tangerine_whistle_activated().build(), - &Header::default() - ), - SpecId::TANGERINE - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().homestead_activated().build(), - &Header::default() - ), - SpecId::HOMESTEAD - ); - assert_eq!( - revm_spec( - &ChainSpecBuilder::mainnet().frontier_activated().build(), - &Header::default() - ), - SpecId::FRONTIER - ); - } - - #[test] - fn test_eth_spec() { - assert_eq!( - revm_spec(&*MAINNET, &Header { timestamp: 1710338135, ..Default::default() }), - SpecId::CANCUN - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { timestamp: 1681338455, ..Default::default() }), - SpecId::SHANGHAI - ); - - assert_eq!( - revm_spec( - &*MAINNET, - &Header { difficulty: U256::from(10_u128), number: 15537394, ..Default::default() } - ), - SpecId::MERGE - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 15537394 - 10, ..Default::default() }), - SpecId::LONDON - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 12244000 + 10, ..Default::default() }), - SpecId::BERLIN - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 12244000 - 10, ..Default::default() }), - SpecId::ISTANBUL - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 7280000 + 10, ..Default::default() }), - SpecId::PETERSBURG - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 7280000 - 10, ..Default::default() }), - SpecId::BYZANTIUM - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 2675000 + 10, ..Default::default() }), - SpecId::SPURIOUS_DRAGON - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 2675000 - 10, ..Default::default() }), - SpecId::TANGERINE - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 1150000 + 10, ..Default::default() }), - SpecId::HOMESTEAD - ); - assert_eq!( - revm_spec(&*MAINNET, &Header { number: 1150000 - 10, ..Default::default() }), - SpecId::FRONTIER - ); - } -} +pub use alloy_evm::{ + spec as revm_spec, + spec_by_timestamp_and_block_number as revm_spec_by_timestamp_and_block_number, +}; diff --git a/crates/ethereum/evm/src/lib.rs b/crates/ethereum/evm/src/lib.rs index ae7defc328a..7aa5a788f2e 100644 --- a/crates/ethereum/evm/src/lib.rs +++ b/crates/ethereum/evm/src/lib.rs @@ -28,13 +28,15 @@ use alloy_evm::{ use alloy_primitives::{Bytes, U256}; use alloy_rpc_types_engine::ExecutionData; use core::{convert::Infallible, fmt::Debug}; -use reth_chainspec::{ChainSpec, EthChainSpec, MAINNET}; +use reth_chainspec::{ChainSpec, EthChainSpec, EthereumHardforks, MAINNET}; use reth_ethereum_primitives::{Block, EthPrimitives, TransactionSigned}; use reth_evm::{ precompiles::PrecompilesMap, ConfigureEngineEvm, ConfigureEvm, EvmEnv, EvmEnvFor, EvmFactory, ExecutableTxIterator, ExecutionCtxFor, NextBlockEnvAttributes, TransactionEnv, }; -use reth_primitives_traits::{SealedBlock, SealedHeader, SignedTransaction, TxTy}; +use reth_primitives_traits::{ + constants::MAX_TX_GAS_LIMIT_OSAKA, SealedBlock, SealedHeader, SignedTransaction, TxTy, +}; use reth_storage_errors::any::AnyError; use revm::{ context::{BlockEnv, CfgEnv}, @@ -152,7 +154,7 @@ where &self.block_assembler } - fn evm_env(&self, header: &Header) -> EvmEnv { + fn evm_env(&self, header: &Header) -> Result { let blob_params = self.chain_spec().blob_params_at_timestamp(header.timestamp); let spec = config::revm_spec(self.chain_spec(), header); @@ -164,6 +166,10 @@ where cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx); } + if self.chain_spec().is_osaka_active_at_timestamp(header.timestamp) { + cfg_env.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA); + } + // derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current // blobparams let blob_excess_gas_and_price = @@ -183,7 +189,7 @@ where blob_excess_gas_and_price, }; - EvmEnv { cfg_env, block_env } + Ok(EvmEnv { cfg_env, block_env }) } fn next_evm_env( @@ -208,6 +214,10 @@ where cfg.set_max_blobs_per_tx(blob_params.max_blobs_per_tx); } + if self.chain_spec().is_osaka_active_at_timestamp(attributes.timestamp) { + cfg.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA); + } + // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is // cancun now, we need to set the excess blob gas to the default value(0) let blob_excess_gas_and_price = parent @@ -255,26 +265,29 @@ where Ok((cfg, block_env).into()) } - fn context_for_block<'a>(&self, block: &'a SealedBlock) -> EthBlockExecutionCtx<'a> { - EthBlockExecutionCtx { + fn context_for_block<'a>( + &self, + block: &'a SealedBlock, + ) -> Result, Self::Error> { + Ok(EthBlockExecutionCtx { 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(Cow::Borrowed), - } + }) } fn context_for_next_block( &self, parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, - ) -> EthBlockExecutionCtx<'_> { - EthBlockExecutionCtx { + ) -> Result, Self::Error> { + Ok(EthBlockExecutionCtx { parent_hash: parent.hash(), parent_beacon_block_root: attributes.parent_beacon_block_root, ommers: &[], withdrawals: attributes.withdrawals.map(Cow::Owned), - } + }) } } @@ -310,19 +323,21 @@ where cfg_env.set_max_blobs_per_tx(blob_params.max_blobs_per_tx); } + if self.chain_spec().is_osaka_active_at_timestamp(timestamp) { + cfg_env.tx_gas_limit_cap = Some(MAX_TX_GAS_LIMIT_OSAKA); + } + // derive the EIP-4844 blob fees from the header's `excess_blob_gas` and the current // blobparams let blob_excess_gas_and_price = - payload.payload.as_v3().map(|v3| v3.excess_blob_gas).zip(blob_params).map( - |(excess_blob_gas, params)| { - let blob_gasprice = params.calc_blob_fee(excess_blob_gas); - BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } - }, - ); + payload.payload.excess_blob_gas().zip(blob_params).map(|(excess_blob_gas, params)| { + let blob_gasprice = params.calc_blob_fee(excess_blob_gas); + BlobExcessGasAndPrice { excess_blob_gas, blob_gasprice } + }); let block_env = BlockEnv { number: U256::from(block_number), - beneficiary: payload.payload.as_v1().fee_recipient, + beneficiary: payload.payload.fee_recipient(), timestamp: U256::from(timestamp), difficulty: if spec >= SpecId::MERGE { U256::ZERO @@ -330,8 +345,8 @@ where payload.payload.as_v1().prev_randao.into() }, prevrandao: (spec >= SpecId::MERGE).then(|| payload.payload.as_v1().prev_randao), - gas_limit: payload.payload.as_v1().gas_limit, - basefee: payload.payload.as_v1().base_fee_per_gas.to(), + gas_limit: payload.payload.gas_limit(), + basefee: payload.payload.saturated_base_fee_per_gas(), blob_excess_gas_and_price, }; @@ -343,10 +358,7 @@ where parent_hash: payload.parent_hash(), parent_beacon_block_root: payload.sidecar.parent_beacon_block_root(), ommers: &[], - withdrawals: payload - .payload - .as_v2() - .map(|v2| Cow::Owned(v2.withdrawals.clone().into())), + withdrawals: payload.payload.withdrawals().map(|w| Cow::Owned(w.clone().into())), } } @@ -392,7 +404,7 @@ mod tests { // Use the `EthEvmConfig` to fill the `cfg_env` and `block_env` based on the ChainSpec, // Header, and total difficulty let EvmEnv { cfg_env, .. } = - EthEvmConfig::new(Arc::new(chain_spec.clone())).evm_env(&header); + EthEvmConfig::new(Arc::new(chain_spec.clone())).evm_env(&header).unwrap(); // Assert that the chain ID in the `cfg_env` is correctly set to the chain ID of the // ChainSpec diff --git a/crates/ethereum/evm/src/test_utils.rs b/crates/ethereum/evm/src/test_utils.rs index a4b3090aa8b..bbcf323a626 100644 --- a/crates/ethereum/evm/src/test_utils.rs +++ b/crates/ethereum/evm/src/test_utils.rs @@ -1,14 +1,15 @@ use crate::EthEvmConfig; -use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use alloc::{boxed::Box, sync::Arc, vec, vec::Vec}; use alloy_consensus::Header; 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, CommitChanges, + BlockExecutionError, BlockExecutor, BlockExecutorFactory, BlockExecutorFor, ExecutableTx, }, eth::{EthBlockExecutionCtx, EthEvmContext}, ConfigureEngineEvm, ConfigureEvm, Database, EthEvm, EthEvmFactory, Evm, EvmEnvFor, EvmFactory, @@ -17,7 +18,7 @@ use reth_evm::{ use reth_execution_types::{BlockExecutionResult, ExecutionOutcome}; use reth_primitives_traits::{BlockTy, SealedBlock, SealedHeader}; use revm::{ - context::result::{ExecutionResult, HaltReason}, + context::result::{ExecutionResult, Output, ResultAndState, SuccessReason}, database::State, Inspector, }; @@ -88,12 +89,28 @@ impl<'a, DB: Database, I: Inspector>>> BlockExec Ok(()) } - fn execute_transaction_with_commit_condition( + fn execute_transaction_without_commit( &mut self, - _tx: impl alloy_evm::block::ExecutableTx, - _f: impl FnOnce(&ExecutionResult) -> CommitChanges, - ) -> Result, BlockExecutionError> { - Ok(Some(0)) + _tx: impl ExecutableTx, + ) -> Result::HaltReason>, BlockExecutionError> { + Ok(ResultAndState::new( + ExecutionResult::Success { + reason: SuccessReason::Return, + gas_used: 0, + gas_refunded: 0, + logs: vec![], + output: Output::Call(Bytes::from(vec![])), + }, + Default::default(), + )) + } + + fn commit_transaction( + &mut self, + _output: ResultAndState<::HaltReason>, + _tx: impl ExecutableTx, + ) -> Result { + Ok(0) } fn finish( @@ -143,7 +160,7 @@ impl ConfigureEvm for MockEvmConfig { self.inner.block_assembler() } - fn evm_env(&self, header: &Header) -> EvmEnvFor { + fn evm_env(&self, header: &Header) -> Result, Self::Error> { self.inner.evm_env(header) } @@ -158,7 +175,7 @@ impl ConfigureEvm for MockEvmConfig { fn context_for_block<'a>( &self, block: &'a SealedBlock>, - ) -> reth_evm::ExecutionCtxFor<'a, Self> { + ) -> Result, Self::Error> { self.inner.context_for_block(block) } @@ -166,7 +183,7 @@ impl ConfigureEvm for MockEvmConfig { &self, parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, - ) -> reth_evm::ExecutionCtxFor<'_, Self> { + ) -> Result, Self::Error> { self.inner.context_for_next_block(parent, attributes) } } diff --git a/crates/ethereum/node/src/node.rs b/crates/ethereum/node/src/node.rs index 0962d8f6f21..089353f6b73 100644 --- a/crates/ethereum/node/src/node.rs +++ b/crates/ethereum/node/src/node.rs @@ -15,7 +15,7 @@ use reth_ethereum_engine_primitives::{ use reth_ethereum_primitives::{EthPrimitives, TransactionSigned}; use reth_evm::{ eth::spec::EthExecutorSpec, ConfigureEvm, EvmFactory, EvmFactoryFor, NextBlockEnvAttributes, - TxEnvFor, + SpecFor, TxEnvFor, }; use reth_network::{primitives::BasicNetworkPrimitives, NetworkHandle, PeersInfo}; use reth_node_api::{ @@ -44,7 +44,11 @@ use reth_rpc::{ use reth_rpc_api::servers::BlockSubmissionValidationApiServer; use reth_rpc_builder::{config::RethRpcServerConfig, middleware::RethRpcMiddleware}; use reth_rpc_eth_api::{ - helpers::pending_block::BuildPendingEnv, RpcConvert, RpcTypes, SignableTxRequest, + helpers::{ + config::{EthConfigApiServer, EthConfigHandler}, + pending_block::BuildPendingEnv, + }, + RpcConvert, RpcTypes, SignableTxRequest, }; use reth_rpc_eth_types::{error::FromEvmError, EthApiError}; use reth_rpc_server_types::RethRpcModule; @@ -149,7 +153,7 @@ impl Default for EthereumEthApiBuilder { impl EthApiBuilder for EthereumEthApiBuilder where N: FullNodeComponents< - Types: NodeTypes, + Types: NodeTypes, Evm: ConfigureEvm>>, >, NetworkT: RpcTypes>>, @@ -158,6 +162,7 @@ where TxEnv = TxEnvFor, Error = EthApiError, Network = NetworkT, + Spec = SpecFor, >, EthApiError: FromEvmError, { @@ -267,7 +272,7 @@ impl NodeAddOns where N: FullNodeComponents< Types: NodeTypes< - ChainSpec: EthChainSpec + EthereumHardforks, + ChainSpec: Hardforks + EthereumHardforks, Primitives = EthPrimitives, Payload: EngineTypes, >, @@ -296,6 +301,9 @@ where Arc::new(EthereumEngineValidator::new(ctx.config.chain.clone())), ); + let eth_config = + EthConfigHandler::new(ctx.node.provider().clone(), ctx.node.evm_config().clone()); + self.inner .launch_add_ons_with(ctx, move |container| { container.modules.merge_if_module_configured( @@ -303,6 +311,10 @@ where validation_api.into_rpc(), )?; + container + .modules + .merge_if_module_configured(RethRpcModule::Eth, eth_config.into_rpc())?; + Ok(()) }) .await @@ -313,7 +325,7 @@ impl RethRpcAddOns for EthereumAddOns, >, @@ -333,7 +345,8 @@ where } } -impl EngineValidatorAddOn for EthereumAddOns +impl EngineValidatorAddOn + for EthereumAddOns where N: FullNodeComponents< Types: NodeTypes< @@ -349,6 +362,7 @@ where EVB: EngineValidatorBuilder, EthApiError: FromEvmError, EvmFactoryFor: EvmFactory, + RpcMiddleware: Send, { type ValidatorBuilder = EVB; diff --git a/crates/ethereum/node/tests/e2e/dev.rs b/crates/ethereum/node/tests/e2e/dev.rs index ad214b04fe0..5ccd74ecb24 100644 --- a/crates/ethereum/node/tests/e2e/dev.rs +++ b/crates/ethereum/node/tests/e2e/dev.rs @@ -1,17 +1,14 @@ use alloy_eips::eip2718::Encodable2718; use alloy_genesis::Genesis; -use alloy_primitives::{b256, hex}; +use alloy_primitives::{b256, hex, Address}; use futures::StreamExt; use reth_chainspec::ChainSpec; use reth_node_api::{BlockBody, FullNodeComponents, FullNodePrimitives, NodeTypes}; -use reth_node_builder::{ - rpc::RethRpcAddOns, DebugNodeLauncher, EngineNodeLauncher, FullNode, NodeBuilder, NodeConfig, - NodeHandle, -}; +use reth_node_builder::{rpc::RethRpcAddOns, FullNode, NodeBuilder, NodeConfig, NodeHandle}; use reth_node_core::args::DevArgs; use reth_node_ethereum::{node::EthereumAddOns, EthereumNode}; use reth_provider::{providers::BlockchainProvider, CanonStateSubscriptions}; -use reth_rpc_eth_api::helpers::EthTransactions; +use reth_rpc_eth_api::{helpers::EthTransactions, EthApiServer}; use reth_tasks::TaskManager; use std::sync::Arc; @@ -29,23 +26,58 @@ async fn can_run_dev_node() -> eyre::Result<()> { .with_types_and_provider::>() .with_components(EthereumNode::components()) .with_add_ons(EthereumAddOns::default()) - .launch_with_fn(|builder| { - let engine_launcher = EngineNodeLauncher::new( - builder.task_executor().clone(), - builder.config().datadir(), - Default::default(), - ); - let launcher = DebugNodeLauncher::new(engine_launcher); - builder.launch_with(launcher) + .launch_with_debug_capabilities() + .await?; + + assert_chain_advances(&node).await; + + Ok(()) +} + +#[tokio::test] +async fn can_run_dev_node_custom_attributes() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + let tasks = TaskManager::current(); + let exec = tasks.executor(); + + let node_config = NodeConfig::test() + .with_chain(custom_chain()) + .with_dev(DevArgs { dev: true, ..Default::default() }); + let fee_recipient = Address::random(); + let NodeHandle { node, .. } = NodeBuilder::new(node_config.clone()) + .testing_node(exec.clone()) + .with_types_and_provider::>() + .with_components(EthereumNode::components()) + .with_add_ons(EthereumAddOns::default()) + .launch_with_debug_capabilities() + .map_debug_payload_attributes(move |mut attributes| { + attributes.suggested_fee_recipient = fee_recipient; + attributes }) .await?; - assert_chain_advances(node).await; + assert_chain_advances(&node).await; + + assert!( + node.rpc_registry.eth_api().balance(fee_recipient, Default::default()).await.unwrap() > 0 + ); + + assert!( + node.rpc_registry + .eth_api() + .block_by_number(Default::default(), false) + .await + .unwrap() + .unwrap() + .header + .beneficiary == + fee_recipient + ); Ok(()) } -async fn assert_chain_advances(node: FullNode) +async fn assert_chain_advances(node: &FullNode) where N: FullNodeComponents, AddOns: RethRpcAddOns, diff --git a/crates/ethereum/node/tests/e2e/rpc.rs b/crates/ethereum/node/tests/e2e/rpc.rs index ea49d8b3c8e..f040f44dfd8 100644 --- a/crates/ethereum/node/tests/e2e/rpc.rs +++ b/crates/ethereum/node/tests/e2e/rpc.rs @@ -1,5 +1,5 @@ use crate::utils::eth_payload_attributes; -use alloy_eips::eip2718::Encodable2718; +use alloy_eips::{eip2718::Encodable2718, eip7910::EthConfig}; use alloy_primitives::{Address, B256, U256}; use alloy_provider::{network::EthereumWallet, Provider, ProviderBuilder, SendableTx}; use alloy_rpc_types_beacon::relay::{ @@ -13,7 +13,10 @@ use reth_chainspec::{ChainSpecBuilder, EthChainSpec, MAINNET}; use reth_e2e_test_utils::setup_engine; use reth_node_ethereum::EthereumNode; use reth_payload_primitives::BuiltPayload; -use std::sync::Arc; +use std::{ + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; alloy_sol_types::sol! { #[sol(rpc, bytecode = "6080604052348015600f57600080fd5b5060405160db38038060db833981016040819052602a91607a565b60005b818110156074576040805143602082015290810182905260009060600160408051601f19818403018152919052805160209091012080555080606d816092565b915050602d565b505060b8565b600060208284031215608b57600080fd5b5051919050565b60006001820160b157634e487b7160e01b600052601160045260246000fd5b5060010190565b60168060c56000396000f3fe6080604052600080fdfea164736f6c6343000810000a")] @@ -62,10 +65,12 @@ async fn test_fee_history() -> eyre::Result<()> { let genesis_base_fee = chain_spec.initial_base_fee().unwrap() as u128; let expected_first_base_fee = genesis_base_fee - - genesis_base_fee / chain_spec.base_fee_params_at_block(0).max_change_denominator; + genesis_base_fee / + chain_spec + .base_fee_params_at_timestamp(chain_spec.genesis_timestamp()) + .max_change_denominator; assert_eq!(fee_history.base_fee_per_gas[0], genesis_base_fee); assert_eq!(fee_history.base_fee_per_gas[1], expected_first_base_fee,); - // Spend some gas let builder = GasWaster::deploy_builder(&provider, U256::from(500)).send().await?; node.advance_block().await?; @@ -280,3 +285,47 @@ async fn test_flashbots_validate_v4() -> eyre::Result<()> { .is_err()); Ok(()) } + +#[tokio::test] +async fn test_eth_config() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + + let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + let prague_timestamp = 10; + let osaka_timestamp = timestamp + 10000000; + + let chain_spec = Arc::new( + ChainSpecBuilder::default() + .chain(MAINNET.chain) + .genesis(serde_json::from_str(include_str!("../assets/genesis.json")).unwrap()) + .cancun_activated() + .with_prague_at(prague_timestamp) + .with_osaka_at(osaka_timestamp) + .build(), + ); + + let (mut nodes, _tasks, wallet) = setup_engine::( + 1, + chain_spec.clone(), + false, + Default::default(), + eth_payload_attributes, + ) + .await?; + let mut node = nodes.pop().unwrap(); + let provider = ProviderBuilder::new() + .wallet(EthereumWallet::new(wallet.wallet_gen().swap_remove(0))) + .connect_http(node.rpc_url()); + + let _ = provider.send_transaction(TransactionRequest::default().to(Address::ZERO)).await?; + node.advance_block().await?; + + let config = provider.client().request_noparams::("eth_config").await?; + + assert_eq!(config.last.unwrap().activation_time, osaka_timestamp); + assert_eq!(config.current.activation_time, prague_timestamp); + assert_eq!(config.next.unwrap().activation_time, osaka_timestamp); + + Ok(()) +} diff --git a/crates/ethereum/payload/Cargo.toml b/crates/ethereum/payload/Cargo.toml index 0907147ca4e..42d159fb844 100644 --- a/crates/ethereum/payload/Cargo.toml +++ b/crates/ethereum/payload/Cargo.toml @@ -13,6 +13,7 @@ workspace = true [dependencies] # reth +reth-consensus-common.workspace = true reth-ethereum-primitives.workspace = true reth-primitives-traits.workspace = true reth-revm.workspace = true @@ -29,6 +30,7 @@ reth-chainspec.workspace = true reth-payload-validator.workspace = true # ethereum +alloy-rlp.workspace = true revm.workspace = true alloy-rpc-types-engine.workspace = true diff --git a/crates/ethereum/payload/src/lib.rs b/crates/ethereum/payload/src/lib.rs index 603e7ab74e5..e3eed6b2265 100644 --- a/crates/ethereum/payload/src/lib.rs +++ b/crates/ethereum/payload/src/lib.rs @@ -11,12 +11,14 @@ use alloy_consensus::Transaction; use alloy_primitives::U256; +use alloy_rlp::Encodable; use reth_basic_payload_builder::{ is_better_payload, BuildArguments, BuildOutcome, MissingPayloadBehaviour, PayloadBuilder, PayloadConfig, }; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; -use reth_errors::{BlockExecutionError, BlockValidationError}; +use reth_consensus_common::validation::MAX_RLP_BLOCK_SIZE; +use reth_errors::{BlockExecutionError, BlockValidationError, ConsensusError}; use reth_ethereum_primitives::{EthPrimitives, TransactionSigned}; use reth_evm::{ execute::{BlockBuilder, BlockBuilderOutcome}, @@ -193,11 +195,14 @@ where let mut blob_sidecars = BlobSidecars::Empty; let mut block_blob_count = 0; + let mut block_transactions_rlp_length = 0; let blob_params = chain_spec.blob_params_at_timestamp(attributes.timestamp); let max_blob_count = blob_params.as_ref().map(|params| params.max_blob_count).unwrap_or_default(); + let is_osaka = chain_spec.is_osaka_active_at_timestamp(attributes.timestamp); + while let Some(pool_tx) = best_txs.next() { // ensure we still have capacity for this transaction if cumulative_gas_used + pool_tx.gas_limit() > block_gas_limit { @@ -219,6 +224,22 @@ where // convert tx to a signed transaction let tx = pool_tx.to_consensus(); + let estimated_block_size_with_tx = block_transactions_rlp_length + + tx.inner().length() + + attributes.withdrawals().length() + + 1024; // 1Kb of overhead for the block header + + if is_osaka && estimated_block_size_with_tx > MAX_RLP_BLOCK_SIZE { + best_txs.mark_invalid( + &pool_tx, + InvalidPoolTransactionError::OversizedData( + estimated_block_size_with_tx, + MAX_RLP_BLOCK_SIZE, + ), + ); + continue; + } + // There's only limited amount of blob space available per block, so we need to check if // the EIP-4844 can still fit in the block let mut blob_tx_sidecar = None; @@ -250,7 +271,7 @@ where break 'sidecar Err(Eip4844PoolTransactionError::MissingEip4844BlobSidecar) }; - if chain_spec.is_osaka_active_at_timestamp(attributes.timestamp) { + if is_osaka { if sidecar.is_eip7594() { Ok(sidecar) } else { @@ -307,6 +328,8 @@ where } } + block_transactions_rlp_length += tx.inner().length(); + // update and add to total fees let miner_fee = tx.effective_tip_per_gas(base_fee).expect("fee is always valid; execution succeeded"); @@ -336,6 +359,13 @@ where let sealed_block = Arc::new(block.sealed_block().clone()); debug!(target: "payload_builder", id=%attributes.id, sealed_block_header = ?sealed_block.sealed_header(), "sealed built block"); + if is_osaka && sealed_block.rlp_length() > MAX_RLP_BLOCK_SIZE { + return Err(PayloadBuilderError::other(ConsensusError::BlockTooLarge { + rlp_length: sealed_block.rlp_length(), + max_rlp_length: MAX_RLP_BLOCK_SIZE, + })); + } + let payload = EthBuiltPayload::new(attributes.id, sealed_block, total_fees, requests) // add blob sidecars from the executed txs .with_sidecars(blob_sidecars); diff --git a/crates/ethereum/primitives/Cargo.toml b/crates/ethereum/primitives/Cargo.toml index b99f2d34e58..efa8b945f95 100644 --- a/crates/ethereum/primitives/Cargo.toml +++ b/crates/ethereum/primitives/Cargo.toml @@ -21,6 +21,8 @@ reth-zstd-compressors = { workspace = true, optional = true } alloy-eips = { workspace = true, features = ["k256"] } alloy-primitives.workspace = true alloy-consensus = { workspace = true, features = ["serde"] } +alloy-serde = { workspace = true, optional = true } +alloy-rpc-types-eth = { workspace = true, optional = true } alloy-rlp.workspace = true # misc @@ -41,6 +43,7 @@ reth-codecs = { workspace = true, features = ["test-utils"] } reth-zstd-compressors.workspace = true secp256k1 = { workspace = true, features = ["rand"] } alloy-consensus = { workspace = true, features = ["serde", "arbitrary"] } +serde_json.workspace = true [features] default = ["std"] @@ -59,6 +62,9 @@ std = [ "derive_more/std", "serde_with?/std", "secp256k1/std", + "alloy-rpc-types-eth?/std", + "alloy-serde?/std", + "serde_json/std", ] reth-codec = [ "std", @@ -74,15 +80,19 @@ arbitrary = [ "reth-codecs?/arbitrary", "reth-primitives-traits/arbitrary", "alloy-eips/arbitrary", + "alloy-rpc-types-eth?/arbitrary", + "alloy-serde?/arbitrary", ] serde-bincode-compat = [ "dep:serde_with", "alloy-consensus/serde-bincode-compat", "alloy-eips/serde-bincode-compat", "reth-primitives-traits/serde-bincode-compat", + "alloy-rpc-types-eth?/serde-bincode-compat", ] serde = [ "dep:serde", + "dep:alloy-serde", "alloy-consensus/serde", "alloy-eips/serde", "alloy-primitives/serde", @@ -91,4 +101,6 @@ serde = [ "rand/serde", "rand_08/serde", "secp256k1/serde", + "alloy-rpc-types-eth?/serde", ] +rpc = ["dep:alloy-rpc-types-eth"] diff --git a/crates/ethereum/primitives/src/receipt.rs b/crates/ethereum/primitives/src/receipt.rs index 4d2d231ac45..ba66f6e0b83 100644 --- a/crates/ethereum/primitives/src/receipt.rs +++ b/crates/ethereum/primitives/src/receipt.rs @@ -2,7 +2,7 @@ use core::fmt::Debug; use alloc::vec::Vec; use alloy_consensus::{ - Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt, + Eip2718EncodableReceipt, Eip658Value, ReceiptEnvelope, ReceiptWithBloom, RlpDecodableReceipt, RlpEncodableReceipt, TxReceipt, TxType, Typed2718, }; use alloy_eips::{ @@ -41,23 +41,48 @@ impl TxTy for T where { } +/// Raw ethereum receipt. +pub type Receipt = EthereumReceipt; + +#[cfg(feature = "rpc")] +/// Receipt representation for RPC. +pub type RpcReceipt = EthereumReceipt; + /// Typed ethereum transaction receipt. /// Receipt containing result of transaction execution. #[derive(Clone, Debug, PartialEq, Eq, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "reth-codec", reth_codecs::add_arbitrary_tests(compact, rlp))] -pub struct Receipt { +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct EthereumReceipt { /// Receipt type. + #[cfg_attr(feature = "serde", serde(rename = "type"))] pub tx_type: T, /// If transaction is executed successfully. /// /// This is the `statusCode` + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity", rename = "status"))] pub success: bool, /// Gas used + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] pub cumulative_gas_used: u64, /// Log send from contracts. - pub logs: Vec, + pub logs: Vec, +} + +#[cfg(feature = "rpc")] +impl Receipt { + /// Converts the logs of the receipt to RPC logs. + pub fn into_rpc( + self, + next_log_index: usize, + meta: alloy_consensus::transaction::TransactionMeta, + ) -> RpcReceipt { + let Self { tx_type, success, cumulative_gas_used, logs } = self; + let logs = alloy_rpc_types_eth::Log::collect_for_receipt(next_log_index, meta, logs); + RpcReceipt { tx_type, success, cumulative_gas_used, logs } + } } impl Receipt { @@ -260,8 +285,12 @@ impl Decodable for Receipt { } } -impl TxReceipt for Receipt { - type Log = Log; +impl TxReceipt for EthereumReceipt +where + T: TxTy, + L: Send + Sync + Clone + Debug + Eq + AsRef, +{ + type Log = L; fn status_or_post_state(&self) -> Eip658Value { self.success.into() @@ -272,18 +301,18 @@ impl TxReceipt for Receipt { } fn bloom(&self) -> Bloom { - alloy_primitives::logs_bloom(self.logs()) + alloy_primitives::logs_bloom(self.logs.iter().map(|l| l.as_ref())) } fn cumulative_gas_used(&self) -> u64 { self.cumulative_gas_used } - fn logs(&self) -> &[Log] { + fn logs(&self) -> &[L] { &self.logs } - fn into_logs(self) -> Vec { + fn into_logs(self) -> Vec { self.logs } } @@ -309,11 +338,11 @@ impl InMemorySize for Receipt { } } -impl From> for Receipt +impl From> for Receipt where T: Into, { - fn from(value: alloy_consensus::ReceiptEnvelope) -> Self { + fn from(value: ReceiptEnvelope) -> Self { let value = value.into_primitives_receipt(); Self { tx_type: value.tx_type(), @@ -324,8 +353,8 @@ where } } -impl From> for alloy_consensus::Receipt { - fn from(value: Receipt) -> Self { +impl From> for alloy_consensus::Receipt { + fn from(value: EthereumReceipt) -> Self { Self { status: value.success.into(), cumulative_gas_used: value.cumulative_gas_used, @@ -334,8 +363,11 @@ impl From> for alloy_consensus::Receipt { } } -impl From> for alloy_consensus::ReceiptEnvelope { - fn from(value: Receipt) -> Self { +impl From> for ReceiptEnvelope +where + L: Send + Sync + Clone + Debug + Eq + AsRef, +{ + fn from(value: EthereumReceipt) -> Self { let tx_type = value.tx_type; let receipt = value.into_with_bloom().map_receipt(Into::into); match tx_type { @@ -624,6 +656,7 @@ mod tests { pub(crate) type Block = alloy_consensus::Block; #[test] + #[cfg(feature = "reth-codec")] fn test_decode_receipt() { reth_codecs::test_utils::test_decode::>(&hex!( "c428b52ffd23fc42696156b10200f034792b6a94c3850215c2fef7aea361a0c31b79d9a32652eefc0d4e2e730036061cff7344b6fc6132b50cda0ed810a991ae58ef013150c12b2522533cb3b3a8b19b7786a8b5ff1d3cdc84225e22b02def168c8858df" @@ -824,4 +857,20 @@ mod tests { b256!("0xfe70ae4a136d98944951b2123859698d59ad251a381abc9960fa81cae3d0d4a0") ); } + + // Ensures that reth and alloy receipts encode to the same JSON + #[test] + #[cfg(feature = "rpc")] + fn test_receipt_serde() { + let input = r#"{"status":"0x1","cumulativeGasUsed":"0x175cc0e","logs":[{"address":"0xa18b9ca2a78660d44ab38ae72e72b18792ffe413","topics":["0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925","0x000000000000000000000000e7e7d8006cbff47bc6ac2dabf592c98e97502708","0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d"],"data":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","blockHash":"0xbf9e6a368a399f996a0f0b27cab4191c028c3c99f5f76ea08a5b70b961475fcb","blockNumber":"0x164b59f","blockTimestamp":"0x68c9a713","transactionHash":"0x533aa9e57865675bb94f41aa2895c0ac81eee69686c77af16149c301e19805f1","transactionIndex":"0x14d","logIndex":"0x238","removed":false}],"logsBloom":"0x00000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000400000040000000000000004000000000000000000000000000000000000000000000020000000000000000000000000080000000000000000000000000200000020000000000000000000000000000000000000000000000000000000000000020000010000000000000000000000000000000000000000000000000000000000000","type":"0x2","transactionHash":"0x533aa9e57865675bb94f41aa2895c0ac81eee69686c77af16149c301e19805f1","transactionIndex":"0x14d","blockHash":"0xbf9e6a368a399f996a0f0b27cab4191c028c3c99f5f76ea08a5b70b961475fcb","blockNumber":"0x164b59f","gasUsed":"0xb607","effectiveGasPrice":"0x4a3ee768","from":"0xe7e7d8006cbff47bc6ac2dabf592c98e97502708","to":"0xa18b9ca2a78660d44ab38ae72e72b18792ffe413","contractAddress":null}"#; + let receipt: RpcReceipt = serde_json::from_str(input).unwrap(); + let envelope: ReceiptEnvelope = + serde_json::from_str(input).unwrap(); + + assert_eq!(envelope, receipt.clone().into()); + + let json_envelope = serde_json::to_value(&envelope).unwrap(); + let json_receipt = serde_json::to_value(receipt.into_with_bloom()).unwrap(); + assert_eq!(json_envelope, json_receipt); + } } diff --git a/crates/ethereum/primitives/src/transaction.rs b/crates/ethereum/primitives/src/transaction.rs index c6de2521a03..f2ec4ad9cdf 100644 --- a/crates/ethereum/primitives/src/transaction.rs +++ b/crates/ethereum/primitives/src/transaction.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use alloy_consensus::{ - transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignerRecoverable}, + transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignerRecoverable, TxHashRef}, EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip4844, TxEip7702, TxLegacy, TxType, Typed2718, }; @@ -658,12 +658,14 @@ impl SignerRecoverable for TransactionSigned { } } -impl SignedTransaction for TransactionSigned { +impl TxHashRef for TransactionSigned { fn tx_hash(&self) -> &TxHash { self.hash.get_or_init(|| self.recalculate_hash()) } } +impl SignedTransaction for TransactionSigned {} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/ethereum/reth/Cargo.toml b/crates/ethereum/reth/Cargo.toml index fef17491b77..959b7c1b65f 100644 --- a/crates/ethereum/reth/Cargo.toml +++ b/crates/ethereum/reth/Cargo.toml @@ -124,12 +124,14 @@ node = [ "provider", "consensus", "evm", + "network", "node-api", "dep:reth-node-ethereum", "dep:reth-node-builder", "dep:reth-engine-local", "rpc", "trie-db", + "pool", ] pool = ["dep:reth-transaction-pool"] rpc = [ diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index b7515ccc408..4bc8ef06dbb 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -36,7 +36,6 @@ metrics = { workspace = true, optional = true } [dev-dependencies] reth-ethereum-primitives.workspace = true reth-ethereum-forks.workspace = true -metrics-util = { workspace = true, features = ["debugging"] } [features] default = ["std"] diff --git a/crates/evm/evm/src/execute.rs b/crates/evm/evm/src/execute.rs index 49e442a2b7c..fb58a65ef98 100644 --- a/crates/evm/evm/src/execute.rs +++ b/crates/evm/evm/src/execute.rs @@ -199,6 +199,32 @@ pub struct BlockAssemblerInput<'a, 'b, F: BlockExecutorFactory, H = Header> { pub state_root: B256, } +impl<'a, 'b, F: BlockExecutorFactory, H> BlockAssemblerInput<'a, 'b, F, H> { + /// Creates a new [`BlockAssemblerInput`]. + #[expect(clippy::too_many_arguments)] + pub fn new( + evm_env: EvmEnv<::Spec>, + execution_ctx: F::ExecutionCtx<'a>, + parent: &'a SealedHeader, + transactions: Vec, + output: &'b BlockExecutionResult, + bundle_state: &'a BundleState, + state_provider: &'b dyn StateProvider, + state_root: B256, + ) -> Self { + Self { + evm_env, + execution_ctx, + parent, + transactions, + output, + bundle_state, + state_provider, + state_root, + } + } +} + /// A type that knows how to assemble a block from execution results. /// /// The [`BlockAssembler`] is the final step in block production. After transactions @@ -392,6 +418,23 @@ impl ExecutorTx for Recovered ExecutorTx + for WithTxEnv<<::Evm as Evm>::Tx, T> +where + T: ExecutorTx, + Executor: BlockExecutor, + <::Evm as Evm>::Tx: Clone, + Self: RecoveredTx, +{ + fn as_executable(&self) -> impl ExecutableTx { + self + } + + fn into_recovered(self) -> Recovered { + self.tx.into_recovered() + } +} + impl<'a, F, DB, Executor, Builder, N> BlockBuilder for BasicBlockBuilder<'a, F, Executor, Builder, N> where @@ -516,6 +559,7 @@ where let result = self .strategy_factory .executor_for_block(&mut self.db, block) + .map_err(BlockExecutionError::other)? .execute_block(block.transactions_recovered())?; self.db.merge_transitions(BundleRetention::Reverts); @@ -534,6 +578,7 @@ where let result = self .strategy_factory .executor_for_block(&mut self.db, block) + .map_err(BlockExecutionError::other)? .with_state_hook(Some(Box::new(state_hook))) .execute_block(block.transactions_recovered())?; diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index dc47fecb9c2..3130971d8b1 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -219,7 +219,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { fn block_assembler(&self) -> &Self::BlockAssembler; /// Creates a new [`EvmEnv`] for the given header. - fn evm_env(&self, header: &HeaderTy) -> EvmEnvFor; + fn evm_env(&self, header: &HeaderTy) -> Result, Self::Error>; /// Returns the configured [`EvmEnv`] for `parent + 1` block. /// @@ -246,7 +246,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { fn context_for_block<'a>( &self, block: &'a SealedBlock>, - ) -> ExecutionCtxFor<'a, Self>; + ) -> Result, Self::Error>; /// Returns the configured [`BlockExecutorFactory::ExecutionCtx`] for `parent + 1` /// block. @@ -254,7 +254,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { &self, parent: &SealedHeader>, attributes: Self::NextBlockEnvCtx, - ) -> ExecutionCtxFor<'_, Self>; + ) -> Result, Self::Error>; /// Returns a [`TxEnv`] from a transaction and [`Address`]. fn tx_env(&self, transaction: impl IntoTxEnv>) -> TxEnvFor { @@ -285,9 +285,9 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { &self, db: DB, header: &HeaderTy, - ) -> EvmFor { - let evm_env = self.evm_env(header); - self.evm_with_env(db, evm_env) + ) -> Result, Self::Error> { + let evm_env = self.evm_env(header)?; + Ok(self.evm_with_env(db, evm_env)) } /// Returns a new EVM with the given database configured with the given environment settings, @@ -327,10 +327,10 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { &'a self, db: &'a mut State, block: &'a SealedBlock<::Block>, - ) -> impl BlockExecutorFor<'a, Self::BlockExecutorFactory, DB> { - let evm = self.evm_for_block(db, block.header()); - let ctx = self.context_for_block(block); - self.create_executor(evm, ctx) + ) -> Result, Self::Error> { + let evm = self.evm_for_block(db, block.header())?; + let ctx = self.context_for_block(block)?; + Ok(self.create_executor(evm, ctx)) } /// Creates a [`BlockBuilder`]. Should be used when building a new block. @@ -407,7 +407,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin { ) -> Result, Self::Error> { let evm_env = self.next_evm_env(parent, &attributes)?; let evm = self.evm_with_env(db, evm_env); - let ctx = self.context_for_next_block(parent, attributes); + let ctx = self.context_for_next_block(parent, attributes)?; Ok(self.create_block_builder(evm, parent, ctx)) } diff --git a/crates/evm/evm/src/metrics.rs b/crates/evm/evm/src/metrics.rs index a80b0cb0692..3fa02c32654 100644 --- a/crates/evm/evm/src/metrics.rs +++ b/crates/evm/evm/src/metrics.rs @@ -1,47 +1,10 @@ //! Executor metrics. -//! -//! Block processing related to syncing should take care to update the metrics by using either -//! [`ExecutorMetrics::execute_metered`] or [`ExecutorMetrics::metered_one`]. -use crate::{Database, OnStateHook}; use alloy_consensus::BlockHeader; -use alloy_evm::{ - block::{BlockExecutor, ExecutableTx, StateChangeSource}, - Evm, -}; -use core::borrow::BorrowMut; use metrics::{Counter, Gauge, Histogram}; -use reth_execution_errors::BlockExecutionError; -use reth_execution_types::BlockExecutionOutput; use reth_metrics::Metrics; -use reth_primitives_traits::RecoveredBlock; -use revm::{ - database::{states::bundle_state::BundleRetention, State}, - state::EvmState, -}; +use reth_primitives_traits::{Block, RecoveredBlock}; use std::time::Instant; -/// Wrapper struct that combines metrics and state hook -struct MeteredStateHook { - metrics: ExecutorMetrics, - inner_hook: Box, -} - -impl OnStateHook for MeteredStateHook { - fn on_state(&mut self, source: StateChangeSource, state: &EvmState) { - // Update the metrics for the number of accounts, storage slots and bytecodes loaded - let accounts = state.keys().len(); - let storage_slots = state.values().map(|account| account.storage.len()).sum::(); - let bytecodes = state.values().filter(|account| !account.info.is_empty_code_hash()).count(); - - self.metrics.accounts_loaded_histogram.record(accounts as f64); - self.metrics.storage_slots_loaded_histogram.record(storage_slots as f64); - self.metrics.bytecodes_loaded_histogram.record(bytecodes as f64); - - // Call the original state hook - self.inner_hook.on_state(source, state); - } -} - /// Executor metrics. // TODO(onbjerg): add sload/sstore #[derive(Metrics, Clone)] @@ -75,6 +38,7 @@ pub struct ExecutorMetrics { } impl ExecutorMetrics { + /// Helper function for metered execution fn metered(&self, f: F) -> R where F: FnOnce() -> (u64, R), @@ -94,259 +58,64 @@ impl ExecutorMetrics { output } - /// Execute the given block using the provided [`BlockExecutor`] and update metrics for the - /// execution. + /// Execute a block and update basic gas/timing metrics. /// - /// Compared to [`Self::metered_one`], this method additionally updates metrics for the number - /// of accounts, storage slots and bytecodes loaded and updated. - /// Execute the given block using the provided [`BlockExecutor`] and update metrics for the - /// execution. - pub fn execute_metered( - &self, - executor: E, - transactions: impl Iterator, BlockExecutionError>>, - state_hook: Box, - ) -> Result, BlockExecutionError> - where - DB: Database, - E: BlockExecutor>>>, - { - // clone here is cheap, all the metrics are Option>. additionally - // they are globally registered so that the data recorded in the hook will - // be accessible. - let wrapper = MeteredStateHook { metrics: self.clone(), inner_hook: state_hook }; - - let mut executor = executor.with_state_hook(Some(Box::new(wrapper))); - - let f = || { - executor.apply_pre_execution_changes()?; - for tx in transactions { - executor.execute_transaction(tx?)?; - } - executor.finish().map(|(evm, result)| (evm.into_db(), result)) - }; - - // Use metered to execute and track timing/gas metrics - let (mut db, result) = self.metered(|| { - let res = f(); - let gas_used = res.as_ref().map(|r| r.1.gas_used).unwrap_or(0); - (gas_used, res) - })?; - - // merge transactions into bundle state - db.borrow_mut().merge_transitions(BundleRetention::Reverts); - let output = BlockExecutionOutput { result, state: db.borrow_mut().take_bundle() }; - - // Update the metrics for the number of accounts, storage slots and bytecodes updated - let accounts = output.state.state.len(); - let storage_slots = - output.state.state.values().map(|account| account.storage.len()).sum::(); - let bytecodes = output.state.contracts.len(); - - self.accounts_updated_histogram.record(accounts as f64); - self.storage_slots_updated_histogram.record(storage_slots as f64); - self.bytecodes_updated_histogram.record(bytecodes as f64); - - Ok(output) - } - - /// Execute the given block and update metrics for the execution. - pub fn metered_one(&self, input: &RecoveredBlock, f: F) -> R + /// This is a simple helper that tracks execution time and gas usage. + /// For more complex metrics tracking (like state changes), use the + /// metered execution functions in the engine/tree module. + pub fn metered_one(&self, block: &RecoveredBlock, f: F) -> R where F: FnOnce(&RecoveredBlock) -> R, - B: reth_primitives_traits::Block, + B: Block, + B::Header: BlockHeader, { - self.metered(|| (input.header().gas_used(), f(input))) + self.metered(|| (block.header().gas_used(), f(block))) } } #[cfg(test)] mod tests { use super::*; - use alloy_eips::eip7685::Requests; - use alloy_evm::{block::CommitChanges, EthEvm}; - use alloy_primitives::{B256, U256}; - use metrics_util::debugging::{DebugValue, DebuggingRecorder, Snapshotter}; - use reth_ethereum_primitives::{Receipt, TransactionSigned}; - use reth_execution_types::BlockExecutionResult; - use revm::{ - context::result::ExecutionResult, - database::State, - database_interface::EmptyDB, - inspector::NoOpInspector, - state::{Account, AccountInfo, AccountStatus, EvmStorage, EvmStorageSlot}, - Context, MainBuilder, MainContext, - }; - use std::sync::mpsc; - - /// A mock executor that simulates state changes - struct MockExecutor { - state: EvmState, - hook: Option>, - evm: EthEvm, NoOpInspector>, - } - - impl MockExecutor { - fn new(state: EvmState) -> Self { - let db = State::builder() - .with_database(EmptyDB::default()) - .with_bundle_update() - .without_state_clear() - .build(); - let evm = EthEvm::new( - Context::mainnet().with_db(db).build_mainnet_with_inspector(NoOpInspector {}), - false, - ); - Self { state, hook: None, evm } - } - } - - impl BlockExecutor for MockExecutor { - type Transaction = TransactionSigned; - type Receipt = Receipt; - type Evm = EthEvm, NoOpInspector>; - - fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> { - Ok(()) - } - - fn execute_transaction_with_commit_condition( - &mut self, - _tx: impl alloy_evm::block::ExecutableTx, - _f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, - ) -> Result, BlockExecutionError> { - Ok(Some(0)) - } - - fn finish( - self, - ) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { - let Self { evm, hook, .. } = self; - - // Call hook with our mock state - if let Some(mut hook) = hook { - hook.on_state(StateChangeSource::Transaction(0), &self.state); - } - - Ok(( - evm, - BlockExecutionResult { - receipts: vec![], - requests: Requests::default(), - gas_used: 0, - }, - )) - } - - fn set_state_hook(&mut self, hook: Option>) { - self.hook = hook; - } - - fn evm(&self) -> &Self::Evm { - &self.evm - } - - fn evm_mut(&mut self) -> &mut Self::Evm { - &mut self.evm - } - } - - struct ChannelStateHook { - output: i32, - sender: mpsc::Sender, - } - - impl OnStateHook for ChannelStateHook { - fn on_state(&mut self, _source: StateChangeSource, _state: &EvmState) { - let _ = self.sender.send(self.output); - } - } - - fn setup_test_recorder() -> Snapshotter { - let recorder = DebuggingRecorder::new(); - let snapshotter = recorder.snapshotter(); - recorder.install().unwrap(); - snapshotter + use alloy_consensus::Header; + use alloy_primitives::B256; + use reth_ethereum_primitives::Block; + use reth_primitives_traits::Block as BlockTrait; + + fn create_test_block_with_gas(gas_used: u64) -> RecoveredBlock { + let header = Header { gas_used, ..Default::default() }; + let block = Block { header, body: Default::default() }; + // Use a dummy hash for testing + let hash = B256::default(); + let sealed = block.seal_unchecked(hash); + RecoveredBlock::new_sealed(sealed, Default::default()) } #[test] - fn test_executor_metrics_hook_metrics_recorded() { - let snapshotter = setup_test_recorder(); + fn test_metered_one_updates_metrics() { let metrics = ExecutorMetrics::default(); - let input = RecoveredBlock::::default(); - - let (tx, _rx) = mpsc::channel(); - let expected_output = 42; - let state_hook = Box::new(ChannelStateHook { sender: tx, output: expected_output }); - - let state = { - let mut state = EvmState::default(); - let storage = - EvmStorage::from_iter([(U256::from(1), EvmStorageSlot::new(U256::from(2), 0))]); - state.insert( - Default::default(), - Account { - info: AccountInfo { - balance: U256::from(100), - nonce: 10, - code_hash: B256::random(), - code: Default::default(), - }, - storage, - status: AccountStatus::default(), - transaction_id: 0, - }, - ); - state - }; - let executor = MockExecutor::new(state); - let _result = metrics - .execute_metered::<_, EmptyDB>( - executor, - input.clone_transactions_recovered().map(Ok::<_, BlockExecutionError>), - state_hook, - ) - .unwrap(); + let block = create_test_block_with_gas(1000); - let snapshot = snapshotter.snapshot().into_vec(); + // Execute with metered_one + let result = metrics.metered_one(&block, |b| { + // Simulate some work + std::thread::sleep(std::time::Duration::from_millis(10)); + b.header().gas_used() + }); - for metric in snapshot { - let metric_name = metric.0.key().name(); - if metric_name == "sync.execution.accounts_loaded_histogram" || - metric_name == "sync.execution.storage_slots_loaded_histogram" || - metric_name == "sync.execution.bytecodes_loaded_histogram" - { - if let DebugValue::Histogram(vs) = metric.3 { - assert!( - vs.iter().any(|v| v.into_inner() > 0.0), - "metric {metric_name} not recorded" - ); - } - } - } + // Verify result + assert_eq!(result, 1000); } #[test] - fn test_executor_metrics_hook_called() { + fn test_metered_helper_tracks_timing() { let metrics = ExecutorMetrics::default(); - let input = RecoveredBlock::::default(); - - let (tx, rx) = mpsc::channel(); - let expected_output = 42; - let state_hook = Box::new(ChannelStateHook { sender: tx, output: expected_output }); - - let state = EvmState::default(); - let executor = MockExecutor::new(state); - let _result = metrics - .execute_metered::<_, EmptyDB>( - executor, - input.clone_transactions_recovered().map(Ok::<_, BlockExecutionError>), - state_hook, - ) - .unwrap(); + let result = metrics.metered(|| { + // Simulate some work + std::thread::sleep(std::time::Duration::from_millis(10)); + (500, "test_result") + }); - let actual_output = rx.try_recv().unwrap(); - assert_eq!(actual_output, expected_output); + assert_eq!(result, "test_result"); } } diff --git a/crates/evm/evm/src/noop.rs b/crates/evm/evm/src/noop.rs index 64cc403819b..1125650a9cb 100644 --- a/crates/evm/evm/src/noop.rs +++ b/crates/evm/evm/src/noop.rs @@ -43,7 +43,7 @@ where self.inner().block_assembler() } - fn evm_env(&self, header: &HeaderTy) -> EvmEnvFor { + fn evm_env(&self, header: &HeaderTy) -> Result, Self::Error> { self.inner().evm_env(header) } @@ -58,7 +58,7 @@ where fn context_for_block<'a>( &self, block: &'a SealedBlock>, - ) -> crate::ExecutionCtxFor<'a, Self> { + ) -> Result, Self::Error> { self.inner().context_for_block(block) } @@ -66,7 +66,7 @@ where &self, parent: &SealedHeader>, attributes: Self::NextBlockEnvCtx, - ) -> crate::ExecutionCtxFor<'_, Self> { + ) -> Result, Self::Error> { self.inner().context_for_next_block(parent, attributes) } } diff --git a/crates/exex/exex/src/backfill/test_utils.rs b/crates/exex/exex/src/backfill/test_utils.rs index 0485257fa2e..a3d82428822 100644 --- a/crates/exex/exex/src/backfill/test_utils.rs +++ b/crates/exex/exex/src/backfill/test_utils.rs @@ -82,7 +82,6 @@ where vec![block.clone()], &execution_outcome, Default::default(), - Default::default(), )?; provider_rw.commit()?; @@ -216,7 +215,6 @@ where vec![block1.clone(), block2.clone()], &execution_outcome, Default::default(), - Default::default(), )?; provider_rw.commit()?; diff --git a/crates/exex/exex/src/manager.rs b/crates/exex/exex/src/manager.rs index d5006dd9f19..d69d04c0e39 100644 --- a/crates/exex/exex/src/manager.rs +++ b/crates/exex/exex/src/manager.rs @@ -501,11 +501,11 @@ where .next_notification_id .checked_sub(this.min_id) .expect("exex expected notification ID outside the manager's range"); - if let Some(notification) = this.buffer.get(notification_index) { - if let Poll::Ready(Err(err)) = exex.send(cx, notification) { - // The channel was closed, which is irrecoverable for the manager - return Poll::Ready(Err(err.into())) - } + if let Some(notification) = this.buffer.get(notification_index) && + let Poll::Ready(Err(err)) = exex.send(cx, notification) + { + // The channel was closed, which is irrecoverable for the manager + return Poll::Ready(Err(err.into())) } min_id = min_id.min(exex.next_notification_id); this.exex_handles.push(exex); diff --git a/crates/gas-station/Cargo.toml b/crates/gas-station/Cargo.toml new file mode 100644 index 00000000000..cc72cfb0e64 --- /dev/null +++ b/crates/gas-station/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "reth-gas-station" +version = "0.0.1" +authors = ["LightLink "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[lib] +name = "reth_gas_station" +path = "src/lib.rs" + +[dependencies] +alloy-primitives = { workspace = true } +alloy-rlp = { workspace = true } +reth-storage-api = { workspace = true } +thiserror = { workspace = true } +revm-database.workspace = true +reth-primitives-traits.workspace = true + +[dev-dependencies] +hex = "0.4" + diff --git a/crates/gas-station/src/lib.rs b/crates/gas-station/src/lib.rs new file mode 100644 index 00000000000..e45a6e42b17 --- /dev/null +++ b/crates/gas-station/src/lib.rs @@ -0,0 +1,231 @@ +use alloy_primitives::{address, b256, keccak256, Address, B256, U256}; +use reth_storage_api::StateProvider; +use reth_primitives_traits::transaction::gasless_error::GaslessValidationError; + +#[derive(Clone, Debug)] // lets us clone (.clone()) and print debug info ("{:?}") +pub struct GasStationConfig { + pub enabled: bool, + pub address: Address, +} + +/// predeploy local for GasStation by default +pub const GAS_STATION_PREDEPLOY: Address = address!("0x4300000000000000000000000000000000000001"); + +impl Default for GasStationConfig { + fn default() -> Self { + // Set it as disabled by default + // TODO: make it enabled by default?? idk. + Self { enabled: true, address: GAS_STATION_PREDEPLOY } + } +} + +/// Result of keccak256(abi.encode(uint256(keccak256("gasstation.main")) - 1)) & ~bytes32(uint256(0xff)); +pub const GAS_STATION_STORAGE_LOCATION: B256 = + b256!("0x64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000"); + +/// Storage slots used by the GasStation contract for a given `to` (recipient) +/// and optional `from`. +#[derive(Clone, Debug)] +pub struct GasStationStorageSlots { + /// Struct base slot (has registered/active packed). + pub registered_slot: B256, + /// TODO REMOVE THIS. + pub active_slot: B256, + /// Slot for `credits` balance. + pub credits_slot: B256, + /// Base slot for nested `whitelist` mapping. + pub nested_whitelist_map_base_slot: B256, + /// Slot for `whitelistEnabled` flag. + pub whitelist_enabled_slot: B256, + /// Slot for `singleUseEnabled` flag. + pub single_use_enabled_slot: B256, + /// Base slot for nested `usedAddresses` mapping. + pub used_addresses_map_base_slot: B256, +} + +/// calculates the storage slot hashes for a specific registered contract within the GasStation's `contracts` mapping. +/// it returns the base slot for the struct (holding packed fields), the slot for credits, +/// the slot for whitelistEnabled, and the base slot for the nested whitelist mapping. +pub fn calculate_gas_station_slots(registered_contract_address: Address) -> GasStationStorageSlots { + // The 'contracts' mapping is at offset 1 from the storage location + // (dao is at offset 0, contracts is at offset 1) + let contracts_map_slot = U256::from_be_bytes(GAS_STATION_STORAGE_LOCATION.0) + U256::from(1); + + // Calculate the base slot for the struct entry in the mapping + // - left pad the address to 32 bytes + let mut key_padded = [0u8; 32]; + key_padded[12..].copy_from_slice(registered_contract_address.as_slice()); // Left-pad 20-byte address to 32 bytes + // - I expect this is left padded because big endian etc + let map_slot_padded = contracts_map_slot.to_be_bytes::<32>(); + // - keccak256(append(keyPadded, mapSlotPadded...)) + let combined = [key_padded, map_slot_padded].concat(); + let struct_base_slot_hash = keccak256(combined); + + // Calculate subsequent slots by adding offsets to the base slot hash + // New struct layout: bool registered, bool active, address admin (all packed in slot 0) + // uint256 credits (slot 1), bool whitelistEnabled (slot 2), mapping whitelist (slot 3) + // bool singleUseEnabled (slot 4), mapping usedAddresses (slot 5) + let struct_base_slot_u256 = U256::from_be_bytes(struct_base_slot_hash.0); + + // Slot for 'credits' (offset 1 from base - after the packed bools and address) + let credits_slot_u256 = struct_base_slot_u256 + U256::from(1); + let credit_slot_hash = B256::from_slice(&credits_slot_u256.to_be_bytes::<32>()); + + // Slot for 'whitelistEnabled' (offset 2 from base) + let whitelist_enabled_slot_u256 = struct_base_slot_u256 + U256::from(2); + let whitelist_enabled_slot_hash = B256::from_slice(&whitelist_enabled_slot_u256.to_be_bytes::<32>()); + + // Base slot for the nested 'whitelist' mapping (offset 3 from base) + let nested_whitelist_map_base_slot_u256 = struct_base_slot_u256 + U256::from(3); + let nested_whitelist_map_base_slot_hash = B256::from_slice(&nested_whitelist_map_base_slot_u256.to_be_bytes::<32>()); + + // Slot for 'singleUseEnabled' (offset 4 from base) + let single_use_enabled_slot_u256 = struct_base_slot_u256 + U256::from(4); + let single_use_enabled_slot_hash = B256::from_slice(&single_use_enabled_slot_u256.to_be_bytes::<32>()); + + // Base slot for the nested 'usedAddresses' mapping (offset 5 from base) + let used_addresses_map_base_slot_u256 = struct_base_slot_u256 + U256::from(5); + let used_addresses_map_base_slot_hash = B256::from_slice(&used_addresses_map_base_slot_u256.to_be_bytes::<32>()); + + GasStationStorageSlots { + registered_slot: struct_base_slot_hash, + active_slot: struct_base_slot_hash, + credits_slot: credit_slot_hash, + whitelist_enabled_slot: whitelist_enabled_slot_hash, + single_use_enabled_slot: single_use_enabled_slot_hash, + nested_whitelist_map_base_slot: nested_whitelist_map_base_slot_hash, + used_addresses_map_base_slot: used_addresses_map_base_slot_hash, + } +} + +/// Computes the storage slot hash for a nested mapping +pub fn calculate_nested_mapping_slot(key: Address, base_slot: B256) -> B256 { + // Left-pad the address to 32 bytes + let mut key_padded = [0u8; 32]; + key_padded[12..].copy_from_slice(key.as_slice()); // Left-pad 20-byte address to 32 bytes + + // The base_slot is already 32 bytes (B256) + let map_base_slot_padded = base_slot.0; + + // Combine: key first, then base slot + let combined = [key_padded, map_base_slot_padded].concat(); + keccak256(combined) +} + +#[derive(Clone, Debug)] +pub struct GaslessValidation { + pub available_credits: U256, + pub required_credits: U256, + pub slots: GasStationStorageSlots, +} + +/// A provider of pending credit usage, ... used by the txpool. +pub trait PendingCreditUsageProvider { + fn pending_credits_for_destination(&self, destination: &Address) -> U256; +} + +/// In-memory pending credit usage map keyed by destination address. +#[derive(Default, Debug)] +pub struct PendingCreditUsageMap { + inner: std::collections::HashMap, +} + +impl PendingCreditUsageMap { + pub fn new() -> Self { + Self { inner: Default::default() } + } + pub fn add_usage(&mut self, destination: Address, amount: U256) { + let entry = self.inner.entry(destination).or_insert(U256::ZERO); + *entry = *entry + amount; + } + pub fn remove_usage(&mut self, destination: Address, amount: U256) { + let entry = self.inner.entry(destination).or_insert(U256::ZERO); + *entry = entry.saturating_sub(amount); + } +} + +impl PendingCreditUsageProvider for PendingCreditUsageMap { + fn pending_credits_for_destination(&self, destination: &Address) -> U256 { + self.inner.get(destination).copied().unwrap_or(U256::ZERO) + } +} + +/// Validates a gasless transaction against on-chain gas station storage and pending usage. +pub fn validate_gasless_tx( + cfg: &GasStationConfig, + state: &SP, + to: Address, + from: Address, + gas_limit: u64, + pending_provider: Option<&dyn PendingCreditUsageProvider>, +) -> Result { + if !cfg.enabled { + return Err(GaslessValidationError::Disabled); + } + if cfg.address.is_zero() { + return Err(GaslessValidationError::NoAddress); + } + + // 1. compute slots + let slots = calculate_gas_station_slots(to); + + // 2. read a storage slot + // - helper to read a storage slot at gas station address + let read_slot = + |slot: B256| -> Option { state.storage(cfg.address, slot.into()).ok().flatten() }; + + // -> read GaslessContract.registered + let registered_slot = read_slot(slots.registered_slot); + let registered = registered_slot.unwrap_or_default() != U256::ZERO; + if !registered { + return Err(GaslessValidationError::NotRegistered); + } + + // -> read GaslessContract.active + let active = read_slot(slots.active_slot).unwrap_or_default() != U256::ZERO; + if !active { + return Err(GaslessValidationError::Inactive); + } + + // 3. read credits + let available_credits = read_slot(slots.credits_slot).unwrap_or_default(); + + // 4. calculate required credits + let mut required = U256::from(gas_limit); + // Include pool pending usage if provided + if let Some(p) = pending_provider { + required = required + p.pending_credits_for_destination(&to); + } + + // 5. check if we have enough credits + if available_credits < required { + return Err(GaslessValidationError::InsufficientCredits { + available: available_credits, + needed: required, + }); + } + + // 6. check whitelist + let whitelist_enabled = + read_slot(slots.whitelist_enabled_slot).unwrap_or_default() != U256::ZERO; + if whitelist_enabled { + let whitelist_slot = calculate_nested_mapping_slot(from, slots.nested_whitelist_map_base_slot); + let whitelist_status = read_slot(whitelist_slot).unwrap_or_default() != U256::ZERO; + if !whitelist_status { + return Err(GaslessValidationError::NotWhitelisted); + } + } + + // 7. check for single-use + let single_use_enabled = + read_slot(slots.single_use_enabled_slot).unwrap_or_default() != U256::ZERO; + if single_use_enabled { + let used_addresses_slot = calculate_nested_mapping_slot(from, slots.used_addresses_map_base_slot); + let used_addresses_status = read_slot(used_addresses_slot).unwrap_or_default() != U256::ZERO; + if used_addresses_status { + return Err(GaslessValidationError::SingleUseConsumed); + } + } + + Ok(GaslessValidation { available_credits, required_credits: required, slots }) +} diff --git a/crates/net/banlist/src/lib.rs b/crates/net/banlist/src/lib.rs index 29cf8eb76a4..9aa90fe7d97 100644 --- a/crates/net/banlist/src/lib.rs +++ b/crates/net/banlist/src/lib.rs @@ -59,11 +59,11 @@ impl BanList { pub fn evict_peers(&mut self, now: Instant) -> Vec { let mut evicted = Vec::new(); self.banned_peers.retain(|peer, until| { - if let Some(until) = until { - if now > *until { - evicted.push(*peer); - return false - } + if let Some(until) = until && + now > *until + { + evicted.push(*peer); + return false } true }); @@ -74,11 +74,11 @@ impl BanList { pub fn evict_ips(&mut self, now: Instant) -> Vec { let mut evicted = Vec::new(); self.banned_ips.retain(|peer, until| { - if let Some(until) = until { - if now > *until { - evicted.push(*peer); - return false - } + if let Some(until) = until && + now > *until + { + evicted.push(*peer); + return false } true }); diff --git a/crates/net/discv4/src/lib.rs b/crates/net/discv4/src/lib.rs index 976ade1728f..426a8f3e0fb 100644 --- a/crates/net/discv4/src/lib.rs +++ b/crates/net/discv4/src/lib.rs @@ -627,10 +627,10 @@ impl Discv4Service { /// Sets the external Ip to the configured external IP if [`NatResolver::ExternalIp`]. fn resolve_external_ip(&mut self) { - if let Some(r) = &self.resolve_external_ip_interval { - if let Some(external_ip) = r.resolver().as_external_ip() { - self.set_external_ip_addr(external_ip); - } + if let Some(r) = &self.resolve_external_ip_interval && + let Some(external_ip) = r.resolver().as_external_ip() + { + self.set_external_ip_addr(external_ip); } } @@ -904,10 +904,10 @@ impl Discv4Service { /// Check if the peer has an active bond. fn has_bond(&self, remote_id: PeerId, remote_ip: IpAddr) -> bool { - if let Some(timestamp) = self.received_pongs.last_pong(remote_id, remote_ip) { - if timestamp.elapsed() < self.config.bond_expiration { - return true - } + if let Some(timestamp) = self.received_pongs.last_pong(remote_id, remote_ip) && + timestamp.elapsed() < self.config.bond_expiration + { + return true } false } @@ -3048,12 +3048,11 @@ mod tests { loop { tokio::select! { Some(update) = updates.next() => { - if let DiscoveryUpdate::Added(record) = update { - if record.id == peerid_1 { + if let DiscoveryUpdate::Added(record) = update + && record.id == peerid_1 { bootnode_appeared = true; break; } - } } _ = &mut timeout => break, } diff --git a/crates/net/discv5/src/config.rs b/crates/net/discv5/src/config.rs index ef89e72da57..c5677544416 100644 --- a/crates/net/discv5/src/config.rs +++ b/crates/net/discv5/src/config.rs @@ -152,10 +152,10 @@ impl ConfigBuilder { /// Adds a comma-separated list of enodes, serialized unsigned node records, to boot nodes. pub fn add_serialized_unsigned_boot_nodes(mut self, enodes: &[&str]) -> Self { for node in enodes { - if let Ok(node) = node.parse() { - if let Ok(node) = BootNode::from_unsigned(node) { - self.bootstrap_nodes.insert(node); - } + if let Ok(node) = node.parse() && + let Ok(node) = BootNode::from_unsigned(node) + { + self.bootstrap_nodes.insert(node); } } @@ -411,14 +411,14 @@ pub fn discv5_sockets_wrt_rlpx_addr( let discv5_socket_ipv6 = discv5_addr_ipv6.map(|ip| SocketAddrV6::new(ip, discv5_port_ipv6, 0, 0)); - if let Some(discv5_addr) = discv5_addr_ipv4 { - if discv5_addr != rlpx_addr { - debug!(target: "net::discv5", - %discv5_addr, - %rlpx_addr, - "Overwriting discv5 IPv4 address with RLPx IPv4 address, limited to one advertised IP address per IP version" - ); - } + if let Some(discv5_addr) = discv5_addr_ipv4 && + discv5_addr != rlpx_addr + { + debug!(target: "net::discv5", + %discv5_addr, + %rlpx_addr, + "Overwriting discv5 IPv4 address with RLPx IPv4 address, limited to one advertised IP address per IP version" + ); } // overwrite discv5 ipv4 addr with RLPx address. this is since there is no @@ -430,14 +430,14 @@ pub fn discv5_sockets_wrt_rlpx_addr( let discv5_socket_ipv4 = discv5_addr_ipv4.map(|ip| SocketAddrV4::new(ip, discv5_port_ipv4)); - if let Some(discv5_addr) = discv5_addr_ipv6 { - if discv5_addr != rlpx_addr { - debug!(target: "net::discv5", - %discv5_addr, - %rlpx_addr, - "Overwriting discv5 IPv6 address with RLPx IPv6 address, limited to one advertised IP address per IP version" - ); - } + if let Some(discv5_addr) = discv5_addr_ipv6 && + discv5_addr != rlpx_addr + { + debug!(target: "net::discv5", + %discv5_addr, + %rlpx_addr, + "Overwriting discv5 IPv6 address with RLPx IPv6 address, limited to one advertised IP address per IP version" + ); } // overwrite discv5 ipv6 addr with RLPx address. this is since there is no diff --git a/crates/net/dns/Cargo.toml b/crates/net/dns/Cargo.toml index ee827cef398..8a04d1517d0 100644 --- a/crates/net/dns/Cargo.toml +++ b/crates/net/dns/Cargo.toml @@ -43,6 +43,7 @@ serde_with = { workspace = true, optional = true } reth-chainspec.workspace = true alloy-rlp.workspace = true alloy-chains.workspace = true +secp256k1 = { workspace = true, features = ["rand"] } tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] } reth-tracing.workspace = true rand.workspace = true diff --git a/crates/net/dns/src/query.rs b/crates/net/dns/src/query.rs index edf387ec5c6..f64551f42f1 100644 --- a/crates/net/dns/src/query.rs +++ b/crates/net/dns/src/query.rs @@ -80,12 +80,12 @@ impl QueryPool { // queue in new queries if we have capacity 'queries: while self.active_queries.len() < self.rate_limit.limit() as usize { - if self.rate_limit.poll_ready(cx).is_ready() { - if let Some(query) = self.queued_queries.pop_front() { - self.rate_limit.tick(); - self.active_queries.push(query); - continue 'queries - } + if self.rate_limit.poll_ready(cx).is_ready() && + let Some(query) = self.queued_queries.pop_front() + { + self.rate_limit.tick(); + self.active_queries.push(query); + continue 'queries } break } diff --git a/crates/net/downloaders/src/headers/reverse_headers.rs b/crates/net/downloaders/src/headers/reverse_headers.rs index a0876ea216d..cb6b36c9ff9 100644 --- a/crates/net/downloaders/src/headers/reverse_headers.rs +++ b/crates/net/downloaders/src/headers/reverse_headers.rs @@ -172,19 +172,16 @@ where /// /// Returns `None` if no more requests are required. fn next_request(&mut self) -> Option { - if let Some(local_head) = self.local_block_number() { - if self.next_request_block_number > local_head { - let request = calc_next_request( - local_head, - self.next_request_block_number, - self.request_limit, - ); - // need to shift the tracked request block number based on the number of requested - // headers so follow-up requests will use that as start. - self.next_request_block_number -= request.limit; - - return Some(request) - } + if let Some(local_head) = self.local_block_number() && + self.next_request_block_number > local_head + { + let request = + calc_next_request(local_head, self.next_request_block_number, self.request_limit); + // need to shift the tracked request block number based on the number of requested + // headers so follow-up requests will use that as start. + self.next_request_block_number -= request.limit; + + return Some(request) } None diff --git a/crates/net/eth-wire-types/src/broadcast.rs b/crates/net/eth-wire-types/src/broadcast.rs index 15e7bb70eba..1900cf004aa 100644 --- a/crates/net/eth-wire-types/src/broadcast.rs +++ b/crates/net/eth-wire-types/src/broadcast.rs @@ -801,7 +801,7 @@ pub struct BlockRangeUpdate { #[cfg(test)] mod tests { use super::*; - use alloy_consensus::Typed2718; + use alloy_consensus::{transaction::TxHashRef, Typed2718}; use alloy_eips::eip2718::Encodable2718; use alloy_primitives::{b256, hex, Signature, U256}; use reth_ethereum_primitives::{Transaction, TransactionSigned}; diff --git a/crates/net/eth-wire-types/src/header.rs b/crates/net/eth-wire-types/src/header.rs index 402212fda8c..986fbb006df 100644 --- a/crates/net/eth-wire-types/src/header.rs +++ b/crates/net/eth-wire-types/src/header.rs @@ -88,7 +88,7 @@ impl From for bool { mod tests { use super::*; use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH, EMPTY_ROOT_HASH}; - use alloy_primitives::{address, b256, bloom, bytes, hex, Address, Bytes, B256, U256}; + use alloy_primitives::{address, b256, bloom, bytes, hex, Bytes, B256, U256}; use alloy_rlp::{Decodable, Encodable}; use std::str::FromStr; @@ -121,8 +121,7 @@ mod tests { #[test] fn test_eip1559_block_header_hash() { let expected_hash = - B256::from_str("6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f") - .unwrap(); + b256!("0x6a251c7c3c5dca7b42407a3752ff48f3bbca1fab7f9868371d9918daf1988d1f"); let header = Header { parent_hash: b256!("0xe0a94a7a3c9617401586b1a27025d2d9671332d22d540e0af72b069170380f2a"), ommers_hash: EMPTY_OMMER_ROOT_HASH, @@ -181,8 +180,7 @@ mod tests { // make sure the hash matches let expected_hash = - B256::from_str("8c2f2af15b7b563b6ab1e09bed0e9caade7ed730aec98b70a993597a797579a9") - .unwrap(); + b256!("0x8c2f2af15b7b563b6ab1e09bed0e9caade7ed730aec98b70a993597a797579a9"); assert_eq!(header.hash_slow(), expected_hash); } @@ -197,7 +195,7 @@ mod tests { "18db39e19931515b30b16b3a92c292398039e31d6c267111529c3f2ba0a26c17", ) .unwrap(), - beneficiary: Address::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap(), + beneficiary: address!("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"), state_root: B256::from_str( "95efce3d6972874ca8b531b233b7a1d1ff0a56f08b20c8f1b89bef1b001194a5", ) @@ -217,18 +215,16 @@ mod tests { extra_data: Bytes::from_str("42").unwrap(), mix_hash: EMPTY_ROOT_HASH, base_fee_per_gas: Some(0x09), - withdrawals_root: Some( - B256::from_str("27f166f1d7c789251299535cb176ba34116e44894476a7886fe5d73d9be5c973") - .unwrap(), - ), + withdrawals_root: Some(b256!( + "0x27f166f1d7c789251299535cb176ba34116e44894476a7886fe5d73d9be5c973" + )), ..Default::default() }; let header =
::decode(&mut data.as_slice()).unwrap(); assert_eq!(header, expected); let expected_hash = - B256::from_str("85fdec94c534fa0a1534720f167b899d1fc268925c71c0cbf5aaa213483f5a69") - .unwrap(); + b256!("0x85fdec94c534fa0a1534720f167b899d1fc268925c71c0cbf5aaa213483f5a69"); assert_eq!(header.hash_slow(), expected_hash); } @@ -244,7 +240,7 @@ mod tests { ) .unwrap(), ommers_hash: EMPTY_OMMER_ROOT_HASH, - beneficiary: Address::from_str("2adc25665018aa1fe0e6bc666dac8fc2697ff9ba").unwrap(), + beneficiary: address!("0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"), state_root: B256::from_str( "3c837fc158e3e93eafcaf2e658a02f5d8f99abc9f1c4c66cdea96c0ca26406ae", ) diff --git a/crates/net/eth-wire-types/src/message.rs b/crates/net/eth-wire-types/src/message.rs index 0e54b86222f..5f36115204b 100644 --- a/crates/net/eth-wire-types/src/message.rs +++ b/crates/net/eth-wire-types/src/message.rs @@ -500,6 +500,15 @@ impl EthMessageID { Self::Receipts.to_u8() } } + + /// Returns the total number of message types for the given version. + /// + /// This is used for message ID multiplexing. + /// + /// + pub const fn message_count(version: EthVersion) -> u8 { + Self::max(version) + 1 + } } impl Encodable for EthMessageID { diff --git a/crates/net/eth-wire-types/src/status.rs b/crates/net/eth-wire-types/src/status.rs index 8f90058639c..db363695c32 100644 --- a/crates/net/eth-wire-types/src/status.rs +++ b/crates/net/eth-wire-types/src/status.rs @@ -461,7 +461,7 @@ mod tests { use alloy_consensus::constants::MAINNET_GENESIS_HASH; use alloy_genesis::Genesis; use alloy_hardforks::{EthereumHardfork, ForkHash, ForkId, Head}; - use alloy_primitives::{hex, B256, U256}; + use alloy_primitives::{b256, hex, B256, U256}; use alloy_rlp::{Decodable, Encodable}; use rand::Rng; use reth_chainspec::{Chain, ChainSpec, ForkCondition, NamedChain}; @@ -516,10 +516,7 @@ mod tests { .chain(Chain::mainnet()) .genesis(MAINNET_GENESIS_HASH) .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 }) - .blockhash( - B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d") - .unwrap(), - ) + .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")) .earliest_block(Some(1)) .latest_block(Some(2)) .total_difficulty(None) @@ -538,10 +535,7 @@ mod tests { .chain(Chain::sepolia()) .genesis(MAINNET_GENESIS_HASH) .forkid(ForkId { hash: ForkHash([0xaa, 0xbb, 0xcc, 0xdd]), next: 0 }) - .blockhash( - B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d") - .unwrap(), - ) + .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")) .total_difficulty(Some(U256::from(42u64))) .earliest_block(None) .latest_block(None) @@ -578,10 +572,7 @@ mod tests { .chain(Chain::from_named(NamedChain::Mainnet)) .genesis(MAINNET_GENESIS_HASH) .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 }) - .blockhash( - B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d") - .unwrap(), - ) + .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")) .earliest_block(Some(15_537_394)) .latest_block(Some(18_000_000)) .build() @@ -617,10 +608,7 @@ mod tests { .forkid(ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 0 }) .earliest_block(Some(15_537_394)) .latest_block(Some(18_000_000)) - .blockhash( - B256::from_str("feb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d") - .unwrap(), - ) + .blockhash(b256!("0xfeb27336ca7923f8fab3bd617fcb6e75841538f71c1bcfc267d7838489d9e13d")) .build() .into_message(); diff --git a/crates/net/eth-wire-types/src/version.rs b/crates/net/eth-wire-types/src/version.rs index 7b461aec89d..1b4b1f30bec 100644 --- a/crates/net/eth-wire-types/src/version.rs +++ b/crates/net/eth-wire-types/src/version.rs @@ -36,19 +36,6 @@ impl EthVersion { /// All known eth versions pub const ALL_VERSIONS: &'static [Self] = &[Self::Eth69, Self::Eth68, Self::Eth67, Self::Eth66]; - /// Returns the total number of messages the protocol version supports. - pub const fn total_messages(&self) -> u8 { - match self { - Self::Eth66 => 15, - Self::Eth67 | Self::Eth68 => { - // eth/67,68 are eth/66 minus GetNodeData and NodeData messages - 13 - } - // eth69 is both eth67 and eth68 minus NewBlockHashes and NewBlock + BlockRangeUpdate - Self::Eth69 => 12, - } - } - /// Returns true if the version is eth/66 pub const fn is_eth66(&self) -> bool { matches!(self, Self::Eth66) @@ -262,12 +249,4 @@ mod tests { assert_eq!(result, expected); } } - - #[test] - fn test_eth_version_total_messages() { - assert_eq!(EthVersion::Eth66.total_messages(), 15); - assert_eq!(EthVersion::Eth67.total_messages(), 13); - assert_eq!(EthVersion::Eth68.total_messages(), 13); - assert_eq!(EthVersion::Eth69.total_messages(), 12); - } } diff --git a/crates/net/eth-wire/src/capability.rs b/crates/net/eth-wire/src/capability.rs index a716fcea6e2..9b706a02cf9 100644 --- a/crates/net/eth-wire/src/capability.rs +++ b/crates/net/eth-wire/src/capability.rs @@ -134,7 +134,7 @@ impl SharedCapability { /// Returns the number of protocol messages supported by this capability. pub const fn num_messages(&self) -> u8 { match self { - Self::Eth { version, .. } => EthMessageID::max(*version) + 1, + Self::Eth { version, .. } => EthMessageID::message_count(*version), Self::UnknownCapability { messages, .. } => *messages, } } diff --git a/crates/net/eth-wire/src/eth_snap_stream.rs b/crates/net/eth-wire/src/eth_snap_stream.rs index 82260186593..43b91a7fd50 100644 --- a/crates/net/eth-wire/src/eth_snap_stream.rs +++ b/crates/net/eth-wire/src/eth_snap_stream.rs @@ -238,15 +238,15 @@ where } } else if message_id > EthMessageID::max(self.eth_version) && message_id <= - EthMessageID::max(self.eth_version) + 1 + SnapMessageId::TrieNodes as u8 + EthMessageID::message_count(self.eth_version) + SnapMessageId::TrieNodes as u8 { // Checks for multiplexed snap message IDs : // - message_id > EthMessageID::max() : ensures it's not an eth message - // - message_id <= EthMessageID::max() + 1 + snap_max : ensures it's within valid snap - // range + // - message_id <= EthMessageID::message_count() + snap_max : ensures it's within valid + // snap range // Message IDs are assigned lexicographically during capability negotiation // So real_snap_id = multiplexed_id - num_eth_messages - let adjusted_message_id = message_id - (EthMessageID::max(self.eth_version) + 1); + let adjusted_message_id = message_id - EthMessageID::message_count(self.eth_version); let mut buf = &bytes[1..]; match SnapProtocolMessage::decode(adjusted_message_id, &mut buf) { @@ -276,7 +276,7 @@ where let encoded = message.encode(); let message_id = encoded[0]; - let adjusted_id = message_id + EthMessageID::max(self.eth_version) + 1; + let adjusted_id = message_id + EthMessageID::message_count(self.eth_version); let mut adjusted = Vec::with_capacity(encoded.len()); adjusted.push(adjusted_id); diff --git a/crates/net/eth-wire/src/handshake.rs b/crates/net/eth-wire/src/handshake.rs index 91596971d00..8d412c349ee 100644 --- a/crates/net/eth-wire/src/handshake.rs +++ b/crates/net/eth-wire/src/handshake.rs @@ -178,19 +178,19 @@ where .into()); } - // Ensure total difficulty is reasonable - if let StatusMessage::Legacy(s) = status { - if s.total_difficulty.bit_len() > 160 { - unauth - .disconnect(DisconnectReason::ProtocolBreach) - .await - .map_err(EthStreamError::from)?; - return Err(EthHandshakeError::TotalDifficultyBitLenTooLarge { - got: s.total_difficulty.bit_len(), - maximum: 160, - } - .into()); + // Ensure peer's total difficulty is reasonable + if let StatusMessage::Legacy(s) = their_status_message && + s.total_difficulty.bit_len() > 160 + { + unauth + .disconnect(DisconnectReason::ProtocolBreach) + .await + .map_err(EthStreamError::from)?; + return Err(EthHandshakeError::TotalDifficultyBitLenTooLarge { + got: s.total_difficulty.bit_len(), + maximum: 160, } + .into()); } // Fork validation diff --git a/crates/net/eth-wire/src/multiplex.rs b/crates/net/eth-wire/src/multiplex.rs index d44f5ea7eb4..9eb4f15f0bc 100644 --- a/crates/net/eth-wire/src/multiplex.rs +++ b/crates/net/eth-wire/src/multiplex.rs @@ -13,15 +13,17 @@ use std::{ future::Future, io, pin::{pin, Pin}, + sync::Arc, task::{ready, Context, Poll}, }; use crate::{ capability::{SharedCapabilities, SharedCapability, UnsupportedCapabilityError}, errors::{EthStreamError, P2PStreamError}, + handshake::EthRlpxHandshake, p2pstream::DisconnectP2P, - CanDisconnect, Capability, DisconnectReason, EthStream, P2PStream, UnauthedEthStream, - UnifiedStatus, + CanDisconnect, Capability, DisconnectReason, EthStream, P2PStream, UnifiedStatus, + HANDSHAKE_TIMEOUT, }; use bytes::{Bytes, BytesMut}; use futures::{Sink, SinkExt, Stream, StreamExt, TryStream, TryStreamExt}; @@ -135,7 +137,7 @@ impl RlpxProtocolMultiplexer { /// This accepts a closure that does a handshake with the remote peer and returns a tuple of the /// primary stream and extra data. /// - /// See also [`UnauthedEthStream::handshake`] + /// See also [`UnauthedEthStream::handshake`](crate::UnauthedEthStream) pub async fn into_satellite_stream_with_tuple_handshake( mut self, cap: &Capability, @@ -167,6 +169,7 @@ impl RlpxProtocolMultiplexer { // complete loop { tokio::select! { + biased; Some(Ok(msg)) = self.inner.conn.next() => { // Ensure the message belongs to the primary protocol let Some(offset) = msg.first().copied() @@ -188,6 +191,10 @@ impl RlpxProtocolMultiplexer { Some(msg) = from_primary.recv() => { self.inner.conn.send(msg).await.map_err(Into::into)?; } + // Poll all subprotocols for new messages + msg = ProtocolsPoller::new(&mut self.inner.protocols) => { + self.inner.conn.send(msg.map_err(Into::into)?).await.map_err(Into::into)?; + } res = &mut f => { let (st, extra) = res?; return Ok((RlpxSatelliteStream { @@ -205,22 +212,28 @@ impl RlpxProtocolMultiplexer { } /// Converts this multiplexer into a [`RlpxSatelliteStream`] with eth protocol as the given - /// primary protocol. + /// primary protocol and the handshake implementation. pub async fn into_eth_satellite_stream( self, status: UnifiedStatus, fork_filter: ForkFilter, + handshake: Arc, ) -> Result<(RlpxSatelliteStream>, UnifiedStatus), EthStreamError> where St: Stream> + Sink + Unpin, { let eth_cap = self.inner.conn.shared_capabilities().eth_version()?; - self.into_satellite_stream_with_tuple_handshake( - &Capability::eth(eth_cap), - move |proxy| async move { - UnauthedEthStream::new(proxy).handshake(status, fork_filter).await - }, - ) + self.into_satellite_stream_with_tuple_handshake(&Capability::eth(eth_cap), move |proxy| { + let handshake = handshake.clone(); + async move { + let mut unauth = UnauthProxy { inner: proxy }; + let their_status = handshake + .handshake(&mut unauth, status, fork_filter, HANDSHAKE_TIMEOUT) + .await?; + let eth_stream = EthStream::new(eth_cap, unauth.into_inner()); + Ok((eth_stream, their_status)) + } + }) .await } } @@ -377,6 +390,57 @@ impl CanDisconnect for ProtocolProxy { } } +/// Adapter so the injected `EthRlpxHandshake` can run over a multiplexed `ProtocolProxy` +/// using the same error type expectations (`P2PStreamError`). +#[derive(Debug)] +struct UnauthProxy { + inner: ProtocolProxy, +} + +impl UnauthProxy { + fn into_inner(self) -> ProtocolProxy { + self.inner + } +} + +impl Stream for UnauthProxy { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_next_unpin(cx).map(|opt| opt.map(|res| res.map_err(P2PStreamError::from))) + } +} + +impl Sink for UnauthProxy { + type Error = P2PStreamError; + + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready_unpin(cx).map_err(P2PStreamError::from) + } + + fn start_send(mut self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> { + self.inner.start_send_unpin(item).map_err(P2PStreamError::from) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_flush_unpin(cx).map_err(P2PStreamError::from) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_close_unpin(cx).map_err(P2PStreamError::from) + } +} + +impl CanDisconnect for UnauthProxy { + fn disconnect( + &mut self, + reason: DisconnectReason, + ) -> Pin>::Error>> + Send + '_>> { + let fut = self.inner.disconnect(reason); + Box::pin(async move { fut.await.map_err(P2PStreamError::from) }) + } +} + /// A connection channel to receive _`non_empty`_ messages for the negotiated protocol. /// /// This is a [Stream] that returns raw bytes of the received messages for this protocol. @@ -666,15 +730,56 @@ impl fmt::Debug for ProtocolStream { } } +/// Helper to poll multiple protocol streams in a `tokio::select`! branch +struct ProtocolsPoller<'a> { + protocols: &'a mut Vec, +} + +impl<'a> ProtocolsPoller<'a> { + const fn new(protocols: &'a mut Vec) -> Self { + Self { protocols } + } +} + +impl<'a> Future for ProtocolsPoller<'a> { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Process protocols in reverse order, like the existing pattern + for idx in (0..self.protocols.len()).rev() { + let mut proto = self.protocols.swap_remove(idx); + match proto.poll_next_unpin(cx) { + Poll::Ready(Some(Err(err))) => { + self.protocols.push(proto); + return Poll::Ready(Err(P2PStreamError::from(err))) + } + Poll::Ready(Some(Ok(msg))) => { + // Got a message, put protocol back and return the message + self.protocols.push(proto); + return Poll::Ready(Ok(msg)); + } + _ => { + // push it back because we still want to complete the handshake first + self.protocols.push(proto); + } + } + } + + // All protocols processed, nothing ready + Poll::Pending + } +} + #[cfg(test)] mod tests { use super::*; use crate::{ + handshake::EthHandshake, test_utils::{ connect_passthrough, eth_handshake, eth_hello, proto::{test_hello, TestProtoMessage}, }, - UnauthedP2PStream, + UnauthedEthStream, UnauthedP2PStream, }; use reth_eth_wire_types::EthNetworkPrimitives; use tokio::{net::TcpListener, sync::oneshot}; @@ -736,7 +841,11 @@ mod tests { let (conn, _) = UnauthedP2PStream::new(stream).handshake(server_hello).await.unwrap(); let (mut st, _their_status) = RlpxProtocolMultiplexer::new(conn) - .into_eth_satellite_stream::(other_status, other_fork_filter) + .into_eth_satellite_stream::( + other_status, + other_fork_filter, + Arc::new(EthHandshake::default()), + ) .await .unwrap(); @@ -767,7 +876,11 @@ mod tests { let conn = connect_passthrough(local_addr, test_hello().0).await; let (mut st, _their_status) = RlpxProtocolMultiplexer::new(conn) - .into_eth_satellite_stream::(status, fork_filter) + .into_eth_satellite_stream::( + status, + fork_filter, + Arc::new(EthHandshake::default()), + ) .await .unwrap(); diff --git a/crates/net/eth-wire/src/protocol.rs b/crates/net/eth-wire/src/protocol.rs index 3ba36ed3ab0..16ec62b7cd7 100644 --- a/crates/net/eth-wire/src/protocol.rs +++ b/crates/net/eth-wire/src/protocol.rs @@ -1,6 +1,6 @@ //! A Protocol defines a P2P subprotocol in an `RLPx` connection -use crate::{Capability, EthVersion}; +use crate::{Capability, EthMessageID, EthVersion}; /// Type that represents a [Capability] and the number of messages it uses. /// @@ -26,7 +26,7 @@ impl Protocol { /// Returns the corresponding eth capability for the given version. pub const fn eth(version: EthVersion) -> Self { let cap = Capability::eth(version); - let messages = version.total_messages(); + let messages = EthMessageID::message_count(version); Self::new(cap, messages) } @@ -71,3 +71,18 @@ pub(crate) struct ProtoVersion { /// Version of the protocol pub(crate) version: usize, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_protocol_eth_message_count() { + // Test that Protocol::eth() returns correct message counts for each version + // This ensures that EthMessageID::message_count() produces the expected results + assert_eq!(Protocol::eth(EthVersion::Eth66).messages(), 17); + assert_eq!(Protocol::eth(EthVersion::Eth67).messages(), 17); + assert_eq!(Protocol::eth(EthVersion::Eth68).messages(), 17); + assert_eq!(Protocol::eth(EthVersion::Eth69).messages(), 18); + } +} diff --git a/crates/net/network-api/src/lib.rs b/crates/net/network-api/src/lib.rs index 58fe2c124e8..4c71f168608 100644 --- a/crates/net/network-api/src/lib.rs +++ b/crates/net/network-api/src/lib.rs @@ -192,7 +192,7 @@ pub trait Peers: PeersInfo { /// Disconnect an existing connection to the given peer using the provided reason fn disconnect_peer_with_reason(&self, peer: PeerId, reason: DisconnectReason); - /// Connect to the given peer. NOTE: if the maximum number out outbound sessions is reached, + /// Connect to the given peer. NOTE: if the maximum number of outbound sessions is reached, /// this won't do anything. See `reth_network::SessionManager::dial_outbound`. fn connect_peer(&self, peer: PeerId, tcp_addr: SocketAddr) { self.connect_peer_kind(peer, PeerKind::Static, tcp_addr, None) diff --git a/crates/net/network-api/src/noop.rs b/crates/net/network-api/src/noop.rs index c650db0afc4..2aaa0093568 100644 --- a/crates/net/network-api/src/noop.rs +++ b/crates/net/network-api/src/noop.rs @@ -31,6 +31,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream; #[derive(Debug, Clone)] #[non_exhaustive] pub struct NoopNetwork { + chain_id: u64, peers_handle: PeersHandle, _marker: PhantomData, } @@ -40,15 +41,23 @@ impl NoopNetwork { pub fn new() -> Self { let (tx, _) = mpsc::unbounded_channel(); - Self { peers_handle: PeersHandle::new(tx), _marker: PhantomData } + Self { + chain_id: 1, // mainnet + peers_handle: PeersHandle::new(tx), + _marker: PhantomData, + } + } + + /// Creates a new [`NoopNetwork`] from an existing one but with a new chain id. + pub const fn with_chain_id(mut self, chain_id: u64) -> Self { + self.chain_id = chain_id; + self } } impl Default for NoopNetwork { fn default() -> Self { - let (tx, _) = mpsc::unbounded_channel(); - - Self { peers_handle: PeersHandle::new(tx), _marker: PhantomData } + Self::new() } } @@ -77,8 +86,7 @@ where } fn chain_id(&self) -> u64 { - // mainnet - 1 + self.chain_id } fn is_syncing(&self) -> bool { diff --git a/crates/net/network-types/src/peers/mod.rs b/crates/net/network-types/src/peers/mod.rs index 5e998c87904..f3529875018 100644 --- a/crates/net/network-types/src/peers/mod.rs +++ b/crates/net/network-types/src/peers/mod.rs @@ -8,7 +8,7 @@ pub use config::{ConnectionsConfig, PeersConfig}; pub use reputation::{Reputation, ReputationChange, ReputationChangeKind, ReputationChangeWeights}; use alloy_eip2124::ForkId; -use tracing::debug; +use tracing::trace; use crate::{ is_banned_reputation, PeerAddr, PeerConnectionState, PeerKind, ReputationChangeOutcome, @@ -92,7 +92,7 @@ impl Peer { // we add reputation since negative reputation change decrease total reputation self.reputation = previous.saturating_add(reputation); - debug!(target: "net::peers", reputation=%self.reputation, banned=%self.is_banned(), ?kind, "applied reputation change"); + trace!(target: "net::peers", reputation=%self.reputation, banned=%self.is_banned(), ?kind, "applied reputation change"); if self.state.is_connected() && self.is_banned() { self.state.disconnect(); diff --git a/crates/net/network/Cargo.toml b/crates/net/network/Cargo.toml index 84fa656234d..54902ef4788 100644 --- a/crates/net/network/Cargo.toml +++ b/crates/net/network/Cargo.toml @@ -92,7 +92,6 @@ reth-transaction-pool = { workspace = true, features = ["test-utils"] } alloy-genesis.workspace = true # misc -tempfile.workspace = true url.workspace = true secp256k1 = { workspace = true, features = ["rand"] } diff --git a/crates/net/network/src/cache.rs b/crates/net/network/src/cache.rs index a06d7dcd69f..2c1ea15792c 100644 --- a/crates/net/network/src/cache.rs +++ b/crates/net/network/src/cache.rs @@ -1,9 +1,10 @@ //! Network cache support +use alloy_primitives::map::DefaultHashBuilder; use core::hash::BuildHasher; use derive_more::{Deref, DerefMut}; use itertools::Itertools; -use schnellru::{ByLength, Limiter, RandomState, Unlimited}; +use schnellru::{ByLength, Limiter, Unlimited}; use std::{fmt, hash::Hash}; /// A minimal LRU cache based on a [`LruMap`](schnellru::LruMap) with limited capacity. @@ -133,9 +134,10 @@ where } } -/// Wrapper of [`schnellru::LruMap`] that implements [`fmt::Debug`]. +/// Wrapper of [`schnellru::LruMap`] that implements [`fmt::Debug`] and with the common hash +/// builder. #[derive(Deref, DerefMut, Default)] -pub struct LruMap(schnellru::LruMap) +pub struct LruMap(schnellru::LruMap) where K: Hash + PartialEq, L: Limiter, @@ -171,7 +173,7 @@ where { /// Returns a new cache with default limiter and hash builder. pub fn new(max_length: u32) -> Self { - Self(schnellru::LruMap::new(ByLength::new(max_length))) + Self(schnellru::LruMap::with_hasher(ByLength::new(max_length), Default::default())) } } @@ -181,7 +183,7 @@ where { /// Returns a new cache with [`Unlimited`] limiter and default hash builder. pub fn new_unlimited() -> Self { - Self(schnellru::LruMap::new(Unlimited)) + Self(schnellru::LruMap::with_hasher(Unlimited, Default::default())) } } diff --git a/crates/net/network/src/config.rs b/crates/net/network/src/config.rs index cb04541a020..8e8d11fe69d 100644 --- a/crates/net/network/src/config.rs +++ b/crates/net/network/src/config.rs @@ -6,6 +6,7 @@ use crate::{ transactions::TransactionsManagerConfig, NetworkHandle, NetworkManager, }; +use alloy_primitives::B256; use reth_chainspec::{ChainSpecProvider, EthChainSpec, Hardforks}; use reth_discv4::{Discv4Config, Discv4ConfigBuilder, NatResolver, DEFAULT_DISCOVERY_ADDRESS}; use reth_discv5::NetworkStackId; @@ -24,7 +25,10 @@ use secp256k1::SECP256K1; use std::{collections::HashSet, net::SocketAddr, sync::Arc}; // re-export for convenience -use crate::protocol::{IntoRlpxSubProtocol, RlpxSubProtocols}; +use crate::{ + protocol::{IntoRlpxSubProtocol, RlpxSubProtocols}, + transactions::TransactionPropagationMode, +}; pub use secp256k1::SecretKey; /// Convenience function to create a new random [`SecretKey`] @@ -90,6 +94,9 @@ pub struct NetworkConfig { /// This can be overridden to support custom handshake logic via the /// [`NetworkConfigBuilder`]. pub handshake: Arc, + /// List of block hashes to check for required blocks. + /// If non-empty, peers that don't have these blocks will be filtered out. + pub required_block_hashes: Vec, } // === impl NetworkConfig === @@ -217,6 +224,8 @@ pub struct NetworkConfigBuilder { /// The Ethereum P2P handshake, see also: /// . handshake: Arc, + /// List of block hashes to check for required blocks. + required_block_hashes: Vec, } impl NetworkConfigBuilder { @@ -257,6 +266,7 @@ impl NetworkConfigBuilder { transactions_manager_config: Default::default(), nat: None, handshake: Arc::new(EthHandshake::default()), + required_block_hashes: Vec::new(), } } @@ -346,6 +356,12 @@ impl NetworkConfigBuilder { self } + /// Configures the propagation mode for the transaction manager. + pub const fn transaction_propagation_mode(mut self, mode: TransactionPropagationMode) -> Self { + self.transactions_manager_config.propagation_mode = mode; + self + } + /// Sets the discovery and listener address /// /// This is a convenience function for both [`NetworkConfigBuilder::listener_addr`] and @@ -535,6 +551,12 @@ impl NetworkConfigBuilder { self } + /// Sets the required block hashes for peer filtering. + pub fn required_block_hashes(mut self, hashes: Vec) -> Self { + self.required_block_hashes = hashes; + self + } + /// Sets the block import type. pub fn block_import(mut self, block_import: Box>) -> Self { self.block_import = Some(block_import); @@ -597,6 +619,7 @@ impl NetworkConfigBuilder { transactions_manager_config, nat, handshake, + required_block_hashes, } = self; let head = head.unwrap_or_else(|| Head { @@ -633,13 +656,11 @@ impl NetworkConfigBuilder { // If default DNS config is used then we add the known dns network to bootstrap from if let Some(dns_networks) = - dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) + dns_discovery_config.as_mut().and_then(|c| c.bootstrap_dns_networks.as_mut()) && + dns_networks.is_empty() && + let Some(link) = chain_spec.chain().public_dns_network_protocol() { - if dns_networks.is_empty() { - if let Some(link) = chain_spec.chain().public_dns_network_protocol() { - dns_networks.insert(link.parse().expect("is valid DNS link entry")); - } - } + dns_networks.insert(link.parse().expect("is valid DNS link entry")); } NetworkConfig { @@ -665,6 +686,7 @@ impl NetworkConfigBuilder { transactions_manager_config, nat, handshake, + required_block_hashes, } } } diff --git a/crates/net/network/src/discovery.rs b/crates/net/network/src/discovery.rs index 5809380aa8a..6b95b1e3a63 100644 --- a/crates/net/network/src/discovery.rs +++ b/crates/net/network/src/discovery.rs @@ -267,12 +267,11 @@ impl Discovery { while let Some(Poll::Ready(Some(update))) = self.discv5_updates.as_mut().map(|updates| updates.poll_next_unpin(cx)) { - if let Some(discv5) = self.discv5.as_mut() { - if let Some(DiscoveredPeer { node_record, fork_id }) = + if let Some(discv5) = self.discv5.as_mut() && + let Some(DiscoveredPeer { node_record, fork_id }) = discv5.on_discv5_update(update) - { - self.on_node_record_update(node_record, fork_id); - } + { + self.on_node_record_update(node_record, fork_id); } } diff --git a/crates/net/network/src/fetch/mod.rs b/crates/net/network/src/fetch/mod.rs index ece4fb626a4..6c14e994008 100644 --- a/crates/net/network/src/fetch/mod.rs +++ b/crates/net/network/src/fetch/mod.rs @@ -116,12 +116,12 @@ impl StateFetcher { /// /// Returns `true` if this a newer block pub(crate) fn update_peer_block(&mut self, peer_id: &PeerId, hash: B256, number: u64) -> bool { - if let Some(peer) = self.peers.get_mut(peer_id) { - if number > peer.best_number { - peer.best_hash = hash; - peer.best_number = number; - return true - } + if let Some(peer) = self.peers.get_mut(peer_id) && + number > peer.best_number + { + peer.best_hash = hash; + peer.best_number = number; + return true } false } diff --git a/crates/net/network/src/lib.rs b/crates/net/network/src/lib.rs index a6de95512f8..66108c398a7 100644 --- a/crates/net/network/src/lib.rs +++ b/crates/net/network/src/lib.rs @@ -140,6 +140,7 @@ mod listener; mod manager; mod metrics; mod network; +mod required_block_filter; mod session; mod state; mod swarm; diff --git a/crates/net/network/src/manager.rs b/crates/net/network/src/manager.rs index ce8cda2b259..c0a2934df75 100644 --- a/crates/net/network/src/manager.rs +++ b/crates/net/network/src/manager.rs @@ -29,6 +29,7 @@ use crate::{ peers::PeersManager, poll_nested_stream_with_budget, protocol::IntoRlpxSubProtocol, + required_block_filter::RequiredBlockFilter, session::SessionManager, state::NetworkState, swarm::{Swarm, SwarmEvent}, @@ -250,6 +251,7 @@ impl NetworkManager { transactions_manager_config: _, nat, handshake, + required_block_hashes, } = config; let peers_manager = PeersManager::new(peers_config); @@ -335,6 +337,12 @@ impl NetworkManager { nat, ); + // Spawn required block peer filter if configured + if !required_block_hashes.is_empty() { + let filter = RequiredBlockFilter::new(handle.clone(), required_block_hashes); + filter.spawn(); + } + Ok(Self { swarm, handle, diff --git a/crates/net/network/src/message.rs b/crates/net/network/src/message.rs index 7b489d2ffac..115939b1616 100644 --- a/crates/net/network/src/message.rs +++ b/crates/net/network/src/message.rs @@ -108,6 +108,10 @@ pub enum PeerResponse { response: oneshot::Receiver>>, }, /// Represents a response to a request for receipts. + /// + /// This is a variant of `Receipts` that was introduced in `eth/69`. + /// The difference is that this variant does not require the inclusion of bloom filters in the + /// response, making it more lightweight. Receipts69 { /// The receiver channel for the response to a receipts request. response: oneshot::Receiver>>, diff --git a/crates/net/network/src/peers.rs b/crates/net/network/src/peers.rs index d851a461ccc..39a8e11a267 100644 --- a/crates/net/network/src/peers.rs +++ b/crates/net/network/src/peers.rs @@ -382,14 +382,15 @@ impl PeersManager { /// Bans the peer temporarily with the configured ban timeout fn ban_peer(&mut self, peer_id: PeerId) { - let mut ban_duration = self.ban_duration; - if let Some(peer) = self.peers.get(&peer_id) { - if peer.is_trusted() || peer.is_static() { - // For misbehaving trusted or static peers, we provide a bit more leeway when - // penalizing them. - ban_duration = self.backoff_durations.low / 2; - } - } + let ban_duration = if let Some(peer) = self.peers.get(&peer_id) && + (peer.is_trusted() || peer.is_static()) + { + // For misbehaving trusted or static peers, we provide a bit more leeway when + // penalizing them. + self.backoff_durations.low / 2 + } else { + self.ban_duration + }; self.ban_list.ban_peer_until(peer_id, std::time::Instant::now() + ban_duration); self.queued_actions.push_back(PeerAction::BanPeer { peer_id }); @@ -804,7 +805,7 @@ impl PeersManager { } } - /// Connect to the given peer. NOTE: if the maximum number out outbound sessions is reached, + /// Connect to the given peer. NOTE: if the maximum number of outbound sessions is reached, /// this won't do anything. See `reth_network::SessionManager::dial_outbound`. #[cfg_attr(not(test), expect(dead_code))] pub(crate) fn add_and_connect( diff --git a/crates/net/network/src/required_block_filter.rs b/crates/net/network/src/required_block_filter.rs new file mode 100644 index 00000000000..9c831e2f5d2 --- /dev/null +++ b/crates/net/network/src/required_block_filter.rs @@ -0,0 +1,179 @@ +//! Required block peer filtering implementation. +//! +//! This module provides functionality to filter out peers that don't have +//! specific required blocks (primarily used for shadowfork testing). + +use alloy_primitives::B256; +use futures::StreamExt; +use reth_eth_wire_types::{GetBlockHeaders, HeadersDirection}; +use reth_network_api::{ + NetworkEvent, NetworkEventListenerProvider, PeerRequest, Peers, ReputationChangeKind, +}; +use tokio::sync::oneshot; +use tracing::{debug, info, trace}; + +/// Task that filters peers based on required block hashes. +/// +/// This task listens for new peer sessions and checks if they have the required +/// block hashes. Peers that don't have these blocks are banned. +pub struct RequiredBlockFilter { + /// Network handle for listening to events and managing peer reputation. + network: N, + /// List of block hashes that peers must have to be considered valid. + block_hashes: Vec, +} + +impl RequiredBlockFilter +where + N: NetworkEventListenerProvider + Peers + Clone + Send + Sync + 'static, +{ + /// Creates a new required block peer filter. + pub const fn new(network: N, block_hashes: Vec) -> Self { + Self { network, block_hashes } + } + + /// Spawns the required block peer filter task. + /// + /// This task will run indefinitely, monitoring new peer sessions and filtering + /// out peers that don't have the required blocks. + pub fn spawn(self) { + if self.block_hashes.is_empty() { + debug!(target: "net::filter", "No required block hashes configured, skipping peer filtering"); + return; + } + + info!(target: "net::filter", "Starting required block peer filter with {} block hashes", self.block_hashes.len()); + + tokio::spawn(async move { + self.run().await; + }); + } + + /// Main loop for the required block peer filter. + async fn run(self) { + let mut event_stream = self.network.event_listener(); + + while let Some(event) = event_stream.next().await { + if let NetworkEvent::ActivePeerSession { info, messages } = event { + let peer_id = info.peer_id; + debug!(target: "net::filter", "New peer session established: {}", peer_id); + + // Spawn a task to check this peer's blocks + let network = self.network.clone(); + let block_hashes = self.block_hashes.clone(); + + tokio::spawn(async move { + Self::check_peer_blocks(network, peer_id, messages, block_hashes).await; + }); + } + } + } + + /// Checks if a peer has the required blocks and bans them if not. + async fn check_peer_blocks( + network: N, + peer_id: reth_network_api::PeerId, + messages: reth_network_api::PeerRequestSender>, + block_hashes: Vec, + ) { + for block_hash in block_hashes { + trace!(target: "net::filter", "Checking if peer {} has block {}", peer_id, block_hash); + + // Create a request for block headers + let request = GetBlockHeaders { + start_block: block_hash.into(), + limit: 1, + skip: 0, + direction: HeadersDirection::Rising, + }; + + let (tx, rx) = oneshot::channel(); + let peer_request = PeerRequest::GetBlockHeaders { request, response: tx }; + + // Send the request to the peer + if let Err(e) = messages.try_send(peer_request) { + debug!(target: "net::filter", "Failed to send block header request to peer {}: {:?}", peer_id, e); + continue; + } + + // Wait for the response + let response = match rx.await { + Ok(response) => response, + Err(e) => { + debug!( + target: "net::filter", + "Channel error getting block {} from peer {}: {:?}", + block_hash, peer_id, e + ); + continue; + } + }; + + let headers = match response { + Ok(headers) => headers, + Err(e) => { + debug!(target: "net::filter", "Error getting block {} from peer {}: {:?}", block_hash, peer_id, e); + // Ban the peer if they fail to respond properly + network.reputation_change(peer_id, ReputationChangeKind::BadProtocol); + return; + } + }; + + if headers.0.is_empty() { + info!( + target: "net::filter", + "Peer {} does not have required block {}, banning", + peer_id, block_hash + ); + network.reputation_change(peer_id, ReputationChangeKind::BadProtocol); + return; // No need to check more blocks if one is missing + } + + trace!(target: "net::filter", "Peer {} has required block {}", peer_id, block_hash); + } + + debug!(target: "net::filter", "Peer {} has all required blocks", peer_id); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::{b256, B256}; + use reth_network_api::noop::NoopNetwork; + + #[test] + fn test_required_block_filter_creation() { + let network = NoopNetwork::default(); + let block_hashes = vec![ + b256!("0x1111111111111111111111111111111111111111111111111111111111111111"), + b256!("0x2222222222222222222222222222222222222222222222222222222222222222"), + ]; + + let filter = RequiredBlockFilter::new(network, block_hashes.clone()); + assert_eq!(filter.block_hashes.len(), 2); + assert_eq!(filter.block_hashes, block_hashes); + } + + #[test] + fn test_required_block_filter_empty_hashes_does_not_spawn() { + let network = NoopNetwork::default(); + let block_hashes = vec![]; + + let filter = RequiredBlockFilter::new(network, block_hashes); + // This should not panic and should exit early when spawn is called + filter.spawn(); + } + + #[tokio::test] + async fn test_required_block_filter_with_mock_peer() { + // This test would require a more complex setup with mock network components + // For now, we ensure the basic structure is correct + let network = NoopNetwork::default(); + let block_hashes = vec![B256::default()]; + + let filter = RequiredBlockFilter::new(network, block_hashes); + // Verify the filter can be created and basic properties are set + assert_eq!(filter.block_hashes.len(), 1); + } +} diff --git a/crates/net/network/src/session/active.rs b/crates/net/network/src/session/active.rs index 827c4bfb190..48aadd05e02 100644 --- a/crates/net/network/src/session/active.rs +++ b/crates/net/network/src/session/active.rs @@ -277,9 +277,7 @@ impl ActiveSession { on_response!(resp, GetReceipts) } EthMessage::Receipts69(resp) => { - // TODO: remove mandatory blooms - let resp = resp.map(|receipts| receipts.into_with_bloom()); - on_response!(resp, GetReceipts) + on_response!(resp, GetReceipts69) } EthMessage::BlockRangeUpdate(msg) => { // Validate that earliest <= latest according to the spec @@ -740,11 +738,11 @@ impl Future for ActiveSession { while this.internal_request_timeout_interval.poll_tick(cx).is_ready() { // check for timed out requests - if this.check_timed_out_requests(Instant::now()) { - if let Poll::Ready(Ok(_)) = this.to_session_manager.poll_reserve(cx) { - let msg = ActiveSessionMessage::ProtocolBreach { peer_id: this.remote_peer_id }; - this.pending_message_to_session = Some(msg); - } + if this.check_timed_out_requests(Instant::now()) && + let Poll::Ready(Ok(_)) = this.to_session_manager.poll_reserve(cx) + { + let msg = ActiveSessionMessage::ProtocolBreach { peer_id: this.remote_peer_id }; + this.pending_message_to_session = Some(msg); } } diff --git a/crates/net/network/src/session/counter.rs b/crates/net/network/src/session/counter.rs index 215c7279d1b..db9bd16cda9 100644 --- a/crates/net/network/src/session/counter.rs +++ b/crates/net/network/src/session/counter.rs @@ -80,10 +80,10 @@ impl SessionCounter { } const fn ensure(current: u32, limit: Option) -> Result<(), ExceedsSessionLimit> { - if let Some(limit) = limit { - if current >= limit { - return Err(ExceedsSessionLimit(limit)) - } + if let Some(limit) = limit && + current >= limit + { + return Err(ExceedsSessionLimit(limit)) } Ok(()) } diff --git a/crates/net/network/src/session/mod.rs b/crates/net/network/src/session/mod.rs index e94376948c6..c6bdb198b1d 100644 --- a/crates/net/network/src/session/mod.rs +++ b/crates/net/network/src/session/mod.rs @@ -1150,18 +1150,20 @@ async fn authenticate_stream( .ok(); } - let (multiplex_stream, their_status) = - match multiplex_stream.into_eth_satellite_stream(status, fork_filter).await { - Ok((multiplex_stream, their_status)) => (multiplex_stream, their_status), - Err(err) => { - return PendingSessionEvent::Disconnected { - remote_addr, - session_id, - direction, - error: Some(PendingSessionHandshakeError::Eth(err)), - } + let (multiplex_stream, their_status) = match multiplex_stream + .into_eth_satellite_stream(status, fork_filter, handshake) + .await + { + Ok((multiplex_stream, their_status)) => (multiplex_stream, their_status), + Err(err) => { + return PendingSessionEvent::Disconnected { + remote_addr, + session_id, + direction, + error: Some(PendingSessionHandshakeError::Eth(err)), } - }; + } + }; (multiplex_stream.into(), their_status) }; diff --git a/crates/net/network/src/transactions/config.rs b/crates/net/network/src/transactions/config.rs index e2d90e324fb..c34bbecd77b 100644 --- a/crates/net/network/src/transactions/config.rs +++ b/crates/net/network/src/transactions/config.rs @@ -11,6 +11,7 @@ use crate::transactions::constants::tx_fetcher::{ }; use alloy_primitives::B256; use derive_more::{Constructor, Display}; + use reth_eth_wire::NetworkPrimitives; use reth_ethereum_primitives::TxType; @@ -38,7 +39,7 @@ impl Default for TransactionsManagerConfig { } /// Determines how new pending transactions are propagated to other peers in full. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TransactionPropagationMode { /// Send full transactions to sqrt of current peers. @@ -60,6 +61,26 @@ impl TransactionPropagationMode { } } } +impl FromStr for TransactionPropagationMode { + type Err = String; + + fn from_str(s: &str) -> Result { + let s = s.to_lowercase(); + match s.as_str() { + "sqrt" => Ok(Self::Sqrt), + "all" => Ok(Self::All), + s => { + if let Some(num) = s.strip_prefix("max:") { + num.parse::() + .map(TransactionPropagationMode::Max) + .map_err(|_| format!("Invalid number for Max variant: {num}")) + } else { + Err(format!("Invalid transaction propagation mode: {s}")) + } + } + } + } +} /// Configuration for fetching transactions. #[derive(Debug, Constructor, Clone)] @@ -255,3 +276,60 @@ where /// Type alias for `TypedRelaxedFilter`. This filter accepts known Ethereum transaction types and /// ignores unknown ones without penalizing the peer. pub type RelaxedEthAnnouncementFilter = TypedRelaxedFilter; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_transaction_propagation_mode_from_str() { + // Test "sqrt" variant + assert_eq!( + TransactionPropagationMode::from_str("sqrt").unwrap(), + TransactionPropagationMode::Sqrt + ); + assert_eq!( + TransactionPropagationMode::from_str("SQRT").unwrap(), + TransactionPropagationMode::Sqrt + ); + assert_eq!( + TransactionPropagationMode::from_str("Sqrt").unwrap(), + TransactionPropagationMode::Sqrt + ); + + // Test "all" variant + assert_eq!( + TransactionPropagationMode::from_str("all").unwrap(), + TransactionPropagationMode::All + ); + assert_eq!( + TransactionPropagationMode::from_str("ALL").unwrap(), + TransactionPropagationMode::All + ); + assert_eq!( + TransactionPropagationMode::from_str("All").unwrap(), + TransactionPropagationMode::All + ); + + // Test "max:N" variant + assert_eq!( + TransactionPropagationMode::from_str("max:10").unwrap(), + TransactionPropagationMode::Max(10) + ); + assert_eq!( + TransactionPropagationMode::from_str("MAX:42").unwrap(), + TransactionPropagationMode::Max(42) + ); + assert_eq!( + TransactionPropagationMode::from_str("Max:100").unwrap(), + TransactionPropagationMode::Max(100) + ); + + // Test invalid inputs + assert!(TransactionPropagationMode::from_str("invalid").is_err()); + assert!(TransactionPropagationMode::from_str("max:not_a_number").is_err()); + assert!(TransactionPropagationMode::from_str("max:").is_err()); + assert!(TransactionPropagationMode::from_str("max").is_err()); + assert!(TransactionPropagationMode::from_str("").is_err()); + } +} diff --git a/crates/net/network/src/transactions/mod.rs b/crates/net/network/src/transactions/mod.rs index 48f9e81295d..eca7535ec9f 100644 --- a/crates/net/network/src/transactions/mod.rs +++ b/crates/net/network/src/transactions/mod.rs @@ -1,5 +1,7 @@ //! Transactions management for the p2p network. +use alloy_consensus::transaction::TxHashRef; + /// Aggregation on configurable parameters for [`TransactionsManager`]. pub mod config; /// Default and spec'd bounds. @@ -695,12 +697,11 @@ impl } }; - if is_eth68_message { - if let Some((actual_ty_byte, _)) = *metadata_ref_mut { - if let Ok(parsed_tx_type) = TxType::try_from(actual_ty_byte) { - tx_types_counter.increase_by_tx_type(parsed_tx_type); - } - } + if is_eth68_message && + let Some((actual_ty_byte, _)) = *metadata_ref_mut && + let Ok(parsed_tx_type) = TxType::try_from(actual_ty_byte) + { + tx_types_counter.increase_by_tx_type(parsed_tx_type); } let decision = self @@ -757,7 +758,7 @@ impl trace!(target: "net::tx::propagation", peer_id=format!("{peer_id:#}"), - hashes_len=valid_announcement_data.iter().count(), + hashes_len=valid_announcement_data.len(), hashes=?valid_announcement_data.keys().collect::>(), msg_version=%valid_announcement_data.msg_version(), client_version=%client, diff --git a/crates/net/network/tests/it/requests.rs b/crates/net/network/tests/it/requests.rs index 3f95ed7d23f..ac9b1a6dcac 100644 --- a/crates/net/network/tests/it/requests.rs +++ b/crates/net/network/tests/it/requests.rs @@ -508,7 +508,7 @@ async fn test_eth69_get_receipts() { let (tx, rx) = oneshot::channel(); handle0.send_request( *handle1.peer_id(), - reth_network::PeerRequest::GetReceipts { + reth_network::PeerRequest::GetReceipts69 { request: reth_eth_wire::GetReceipts(vec![block_hash]), response: tx, }, @@ -521,9 +521,8 @@ async fn test_eth69_get_receipts() { }; assert_eq!(receipts_response.0.len(), 1); assert_eq!(receipts_response.0[0].len(), 2); - // When using GetReceipts request with ETH69 peers, the response should still include bloom - // filters The protocol version handling is done at a lower level - assert_eq!(receipts_response.0[0][0].receipt.cumulative_gas_used, 21000); - assert_eq!(receipts_response.0[0][1].receipt.cumulative_gas_used, 42000); + // ETH69 receipts do not include bloom filters - verify the structure + assert_eq!(receipts_response.0[0][0].cumulative_gas_used, 21000); + assert_eq!(receipts_response.0[0][1].cumulative_gas_used, 42000); } } diff --git a/crates/net/p2p/src/bodies/response.rs b/crates/net/p2p/src/bodies/response.rs index 20287a4b450..772fe6cbbd3 100644 --- a/crates/net/p2p/src/bodies/response.rs +++ b/crates/net/p2p/src/bodies/response.rs @@ -22,7 +22,7 @@ where } } - /// Return the reference to the response header + /// Return the difficulty of the response header pub fn difficulty(&self) -> U256 { match self { Self::Full(block) => block.difficulty(), diff --git a/crates/net/p2p/src/full_block.rs b/crates/net/p2p/src/full_block.rs index 8dbf3ce5690..06128c6b542 100644 --- a/crates/net/p2p/src/full_block.rs +++ b/crates/net/p2p/src/full_block.rs @@ -280,18 +280,18 @@ where Client: BlockClient, { fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { - if let Some(fut) = Pin::new(&mut self.header).as_pin_mut() { - if let Poll::Ready(res) = fut.poll(cx) { - self.header = None; - return Poll::Ready(ResponseResult::Header(res)) - } + if let Some(fut) = Pin::new(&mut self.header).as_pin_mut() && + let Poll::Ready(res) = fut.poll(cx) + { + self.header = None; + return Poll::Ready(ResponseResult::Header(res)) } - if let Some(fut) = Pin::new(&mut self.body).as_pin_mut() { - if let Poll::Ready(res) = fut.poll(cx) { - self.body = None; - return Poll::Ready(ResponseResult::Body(res)) - } + if let Some(fut) = Pin::new(&mut self.body).as_pin_mut() && + let Poll::Ready(res) = fut.poll(cx) + { + self.body = None; + return Poll::Ready(ResponseResult::Body(res)) } Poll::Pending @@ -621,18 +621,18 @@ where &mut self, cx: &mut Context<'_>, ) -> Poll> { - if let Some(fut) = Pin::new(&mut self.headers).as_pin_mut() { - if let Poll::Ready(res) = fut.poll(cx) { - self.headers = None; - return Poll::Ready(RangeResponseResult::Header(res)) - } + if let Some(fut) = Pin::new(&mut self.headers).as_pin_mut() && + let Poll::Ready(res) = fut.poll(cx) + { + self.headers = None; + return Poll::Ready(RangeResponseResult::Header(res)) } - if let Some(fut) = Pin::new(&mut self.bodies).as_pin_mut() { - if let Poll::Ready(res) = fut.poll(cx) { - self.bodies = None; - return Poll::Ready(RangeResponseResult::Body(res)) - } + if let Some(fut) = Pin::new(&mut self.bodies).as_pin_mut() && + let Poll::Ready(res) = fut.poll(cx) + { + self.bodies = None; + return Poll::Ready(RangeResponseResult::Body(res)) } Poll::Pending diff --git a/crates/net/peers/src/node_record.rs b/crates/net/peers/src/node_record.rs index d9f10ebdbe7..0b1ef38b3dd 100644 --- a/crates/net/peers/src/node_record.rs +++ b/crates/net/peers/src/node_record.rs @@ -63,11 +63,11 @@ impl NodeRecord { /// See also [`std::net::Ipv6Addr::to_ipv4_mapped`] pub fn convert_ipv4_mapped(&mut self) -> bool { // convert IPv4 mapped IPv6 address - if let IpAddr::V6(v6) = self.address { - if let Some(v4) = v6.to_ipv4_mapped() { - self.address = v4.into(); - return true - } + if let IpAddr::V6(v6) = self.address && + let Some(v4) = v6.to_ipv4_mapped() + { + self.address = v4.into(); + return true } false } diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index f0c62f3252a..c1224d35e5a 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -19,7 +19,7 @@ reth-cli-util.workspace = true reth-config.workspace = true reth-consensus-debug-client.workspace = true reth-consensus.workspace = true -reth-db = { workspace = true, features = ["mdbx"], optional = true } +reth-db = { workspace = true, features = ["mdbx"] } reth-db-api.workspace = true reth-db-common.workspace = true reth-downloaders.workspace = true @@ -97,7 +97,6 @@ reth-evm-ethereum = { workspace = true, features = ["test-utils"] } default = [] js-tracer = ["reth-rpc/js-tracer"] test-utils = [ - "dep:reth-db", "reth-db/test-utils", "reth-chain-state/test-utils", "reth-chainspec/test-utils", @@ -117,7 +116,7 @@ test-utils = [ "reth-primitives-traits/test-utils", ] op = [ - "reth-db?/op", + "reth-db/op", "reth-db-api/op", "reth-engine-local/op", "reth-evm/op", diff --git a/crates/node/builder/src/builder/mod.rs b/crates/node/builder/src/builder/mod.rs index 118ead96ee9..fb22a82795e 100644 --- a/crates/node/builder/src/builder/mod.rs +++ b/crates/node/builder/src/builder/mod.rs @@ -662,9 +662,9 @@ where /// /// This is equivalent to [`WithLaunchContext::launch`], but will enable the debugging features, /// if they are configured. - pub async fn launch_with_debug_capabilities( + pub fn launch_with_debug_capabilities( self, - ) -> eyre::Result<>>::Node> + ) -> >>::Future where T::Types: DebugNode>, DebugNodeLauncher: LaunchNode>, @@ -678,7 +678,7 @@ where builder.config.datadir(), engine_tree_config, )); - builder.launch_with(launcher).await + builder.launch_with(launcher) } /// Returns an [`EngineNodeLauncher`] that can be used to launch the node with engine API diff --git a/crates/node/builder/src/builder/states.rs b/crates/node/builder/src/builder/states.rs index bbb3e250917..f60b56d57e7 100644 --- a/crates/node/builder/src/builder/states.rs +++ b/crates/node/builder/src/builder/states.rs @@ -251,11 +251,11 @@ where AO: RethRpcAddOns>, { /// Launches the node with the given launcher. - pub async fn launch_with(self, launcher: L) -> eyre::Result + pub fn launch_with(self, launcher: L) -> L::Future where L: LaunchNode, { - launcher.launch_node(self).await + launcher.launch_node(self) } /// Sets the hook that is run once the rpc server is started. diff --git a/crates/node/builder/src/components/builder.rs b/crates/node/builder/src/components/builder.rs index 2fcafeb4e91..c54cc0e37f1 100644 --- a/crates/node/builder/src/components/builder.rs +++ b/crates/node/builder/src/components/builder.rs @@ -7,6 +7,7 @@ use crate::{ }, BuilderContext, ConfigureEvm, FullNodeTypes, }; +use reth_chainspec::EthChainSpec; use reth_consensus::{noop::NoopConsensus, ConsensusError, FullConsensus}; use reth_network::{types::NetPrimitivesFor, EthNetworkPrimitives, NetworkPrimitives}; use reth_network_api::{noop::NoopNetwork, FullNetwork}; @@ -493,6 +494,13 @@ impl Default for NoopTransactionPoolBuilder { #[derive(Debug, Clone)] pub struct NoopNetworkBuilder(PhantomData); +impl NoopNetworkBuilder { + /// Returns the instance with ethereum types. + pub fn eth() -> Self { + Self::default() + } +} + impl NetworkBuilder for NoopNetworkBuilder where N: FullNodeTypes, @@ -508,10 +516,10 @@ where async fn build_network( self, - _ctx: &BuilderContext, + ctx: &BuilderContext, _pool: Pool, ) -> eyre::Result { - Ok(NoopNetwork::new()) + Ok(NoopNetwork::new().with_chain_id(ctx.chain_spec().chain_id())) } } diff --git a/crates/node/builder/src/components/pool.rs b/crates/node/builder/src/components/pool.rs index 2d431831ee3..f3e5bad4b26 100644 --- a/crates/node/builder/src/components/pool.rs +++ b/crates/node/builder/src/components/pool.rs @@ -128,7 +128,7 @@ impl<'a, Node: FullNodeTypes, V> TxPoolBuilder<'a, Node, V> { impl<'a, Node: FullNodeTypes, V> TxPoolBuilder<'a, Node, TransactionValidationTaskExecutor> where - V: TransactionValidator + Clone + 'static, + V: TransactionValidator + 'static, V::Transaction: PoolTransaction> + reth_transaction_pool::EthPoolTransaction, { diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index 15278818c74..e2bca822528 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -312,7 +312,7 @@ impl LaunchContextWith> { &self.attachment.right } - /// Get a mutable reference to the right value. + /// Get a mutable reference to the left value. pub const fn left_mut(&mut self) -> &mut L { &mut self.attachment.left } @@ -442,7 +442,10 @@ impl LaunchContextWith MiningMode { + pub fn dev_mining_mode(&self, pool: Pool) -> MiningMode + where + Pool: TransactionPool + Unpin, + { if let Some(interval) = self.node_config().dev.block_time { MiningMode::interval(interval) } else { @@ -953,20 +956,24 @@ where where T: FullNodeTypes, { - if self.node_config().pruning.bodies_pre_merge { - if let Some(merge_block) = - self.chain_spec().ethereum_fork_activation(EthereumHardfork::Paris).block_number() - { - // Ensure we only expire transactions after we synced past the merge block. - let Some(latest) = self.blockchain_db().latest_header()? else { return Ok(()) }; - if latest.number() > merge_block { - let provider = self.blockchain_db().static_file_provider(); - if provider.get_lowest_transaction_static_file_block() < Some(merge_block) { - info!(target: "reth::cli", merge_block, "Expiring pre-merge transactions"); - provider.delete_transactions_below(merge_block)?; - } else { - debug!(target: "reth::cli", merge_block, "No pre-merge transactions to expire"); - } + if self.node_config().pruning.bodies_pre_merge && + let Some(merge_block) = self + .chain_spec() + .ethereum_fork_activation(EthereumHardfork::Paris) + .block_number() + { + // Ensure we only expire transactions after we synced past the merge block. + let Some(latest) = self.blockchain_db().latest_header()? else { return Ok(()) }; + if latest.number() > merge_block { + let provider = self.blockchain_db().static_file_provider(); + if provider + .get_lowest_transaction_static_file_block() + .is_some_and(|lowest| lowest < merge_block) + { + info!(target: "reth::cli", merge_block, "Expiring pre-merge transactions"); + provider.delete_transactions_below(merge_block)?; + } else { + debug!(target: "reth::cli", merge_block, "No pre-merge transactions to expire"); } } } @@ -1116,9 +1123,9 @@ impl Attached { &self.right } - /// Get a mutable reference to the right value. - pub const fn left_mut(&mut self) -> &mut R { - &mut self.right + /// Get a mutable reference to the left value. + pub const fn left_mut(&mut self) -> &mut L { + &mut self.left } /// Get a mutable reference to the right value. diff --git a/crates/node/builder/src/launch/debug.rs b/crates/node/builder/src/launch/debug.rs index bbe6a7a6b72..f5e9745cddc 100644 --- a/crates/node/builder/src/launch/debug.rs +++ b/crates/node/builder/src/launch/debug.rs @@ -1,12 +1,19 @@ use super::LaunchNode; use crate::{rpc::RethRpcAddOns, EngineNodeLauncher, Node, NodeHandle}; +use alloy_consensus::transaction::Either; use alloy_provider::network::AnyNetwork; use jsonrpsee::core::{DeserializeOwned, Serialize}; use reth_chainspec::EthChainSpec; use reth_consensus_debug_client::{DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider}; use reth_engine_local::LocalMiner; -use reth_node_api::{BlockTy, FullNodeComponents, PayloadAttributesBuilder, PayloadTypes}; -use std::sync::Arc; +use reth_node_api::{ + BlockTy, FullNodeComponents, PayloadAttrTy, PayloadAttributesBuilder, PayloadTypes, +}; +use std::{ + future::{Future, IntoFuture}, + pin::Pin, + sync::Arc, +}; use tracing::info; /// [`Node`] extension with support for debugging utilities. @@ -79,8 +86,8 @@ pub trait DebugNode: Node { /// ## RPC Consensus Client /// /// When `--debug.rpc-consensus-ws ` is provided, the launcher will: -/// - Connect to an external RPC `WebSocket` endpoint -/// - Fetch blocks from that endpoint +/// - Connect to an external RPC endpoint (`WebSocket` or HTTP) +/// - Fetch blocks from that endpoint (using subscriptions for `WebSocket`, polling for HTTP) /// - Submit them to the local engine for execution /// - Useful for testing engine behavior with real network data /// @@ -104,23 +111,61 @@ impl DebugNodeLauncher { } } -impl LaunchNode for DebugNodeLauncher +/// Future for the [`DebugNodeLauncher`]. +#[expect(missing_debug_implementations, clippy::type_complexity)] +pub struct DebugNodeLauncherFuture +where + N: FullNodeComponents>, +{ + inner: L, + target: Target, + local_payload_attributes_builder: + Option>>>, + map_attributes: + Option) -> PayloadAttrTy + Send + Sync>>, +} + +impl DebugNodeLauncherFuture where N: FullNodeComponents>, AddOns: RethRpcAddOns, L: LaunchNode>, { - type Node = NodeHandle; + pub fn with_payload_attributes_builder( + self, + builder: impl PayloadAttributesBuilder>, + ) -> Self { + Self { + inner: self.inner, + target: self.target, + local_payload_attributes_builder: Some(Box::new(builder)), + map_attributes: None, + } + } + + pub fn map_debug_payload_attributes( + self, + f: impl Fn(PayloadAttrTy) -> PayloadAttrTy + Send + Sync + 'static, + ) -> Self { + Self { + inner: self.inner, + target: self.target, + local_payload_attributes_builder: None, + map_attributes: Some(Box::new(f)), + } + } + + async fn launch_node(self) -> eyre::Result> { + let Self { inner, target, local_payload_attributes_builder, map_attributes } = self; - async fn launch_node(self, target: Target) -> eyre::Result { - let handle = self.inner.launch_node(target).await?; + let handle = inner.launch_node(target).await?; let config = &handle.node.config; - if let Some(ws_url) = config.debug.rpc_consensus_ws.clone() { - info!(target: "reth::cli", "Using RPC WebSocket consensus client: {}", ws_url); + if let Some(url) = config.debug.rpc_consensus_url.clone() { + info!(target: "reth::cli", "Using RPC consensus client: {}", url); let block_provider = - RpcBlockProvider::::new(ws_url.as_str(), |block_response| { + RpcBlockProvider::::new(url.as_str(), |block_response| { let json = serde_json::to_value(block_response) .expect("Block serialization cannot fail"); let rpc_block = @@ -179,11 +224,23 @@ where let pool = handle.node.pool.clone(); let payload_builder_handle = handle.node.payload_builder_handle.clone(); + let builder = if let Some(builder) = local_payload_attributes_builder { + Either::Left(builder) + } else { + let local = N::Types::local_payload_attributes_builder(&chain_spec); + let builder = if let Some(f) = map_attributes { + Either::Left(move |block_number| f(local.build(block_number))) + } else { + Either::Right(local) + }; + Either::Right(builder) + }; + let dev_mining_mode = handle.node.config.dev_mining_mode(pool); handle.node.task_executor.spawn_critical("local engine", async move { LocalMiner::new( blockchain_db, - N::Types::local_payload_attributes_builder(&chain_spec), + builder, beacon_engine_handle, dev_mining_mode, payload_builder_handle, @@ -196,3 +253,38 @@ where Ok(handle) } } + +impl IntoFuture for DebugNodeLauncherFuture +where + Target: Send + 'static, + N: FullNodeComponents>, + AddOns: RethRpcAddOns + 'static, + L: LaunchNode> + 'static, +{ + type Output = eyre::Result>; + type IntoFuture = Pin>> + Send>>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(self.launch_node()) + } +} + +impl LaunchNode for DebugNodeLauncher +where + Target: Send + 'static, + N: FullNodeComponents>, + AddOns: RethRpcAddOns + 'static, + L: LaunchNode> + 'static, +{ + type Node = NodeHandle; + type Future = DebugNodeLauncherFuture; + + fn launch_node(self, target: Target) -> Self::Future { + DebugNodeLauncherFuture { + inner: self.inner, + target, + local_payload_attributes_builder: None, + map_attributes: None, + } + } +} diff --git a/crates/node/builder/src/launch/engine.rs b/crates/node/builder/src/launch/engine.rs index 9c2576c8a2c..5f6c54afc96 100644 --- a/crates/node/builder/src/launch/engine.rs +++ b/crates/node/builder/src/launch/engine.rs @@ -11,7 +11,6 @@ use crate::{ use alloy_consensus::BlockHeader; use futures::{stream_select, StreamExt}; use reth_chainspec::{EthChainSpec, EthereumHardforks}; -use reth_db_api::{database_metrics::DatabaseMetrics, Database}; use reth_engine_service::service::{ChainEvent, EngineService}; use reth_engine_tree::{ engine::{EngineApiRequest, EngineRequestHandler}, @@ -37,7 +36,7 @@ use reth_provider::{ use reth_tasks::TaskExecutor; use reth_tokio_util::EventSender; use reth_tracing::tracing::{debug, error, info}; -use std::sync::Arc; +use std::{future::Future, pin::Pin, sync::Arc}; use tokio::sync::{mpsc::unbounded_channel, oneshot}; use tokio_stream::wrappers::UnboundedReceiverStream; @@ -61,27 +60,22 @@ impl EngineNodeLauncher { ) -> Self { Self { ctx: LaunchContext::new(task_executor, data_dir), engine_tree_config } } -} -impl LaunchNode> for EngineNodeLauncher -where - Types: NodeTypesForProvider + NodeTypes, - DB: Database + DatabaseMetrics + Clone + Unpin + 'static, - T: FullNodeTypes< - Types = Types, - DB = DB, - Provider = BlockchainProvider>, - >, - CB: NodeComponentsBuilder, - AO: RethRpcAddOns> - + EngineValidatorAddOn>, -{ - type Node = NodeHandle, AO>; - - async fn launch_node( + async fn launch_node( self, target: NodeBuilderWithComponents, - ) -> eyre::Result { + ) -> eyre::Result, AO>> + where + T: FullNodeTypes< + Types: NodeTypesForProvider, + Provider = BlockchainProvider< + NodeTypesWithDBAdapter<::Types, ::DB>, + >, + >, + CB: NodeComponentsBuilder, + AO: RethRpcAddOns> + + EngineValidatorAddOn>, + { let Self { ctx, engine_tree_config } = self; let NodeBuilderWithComponents { adapter: NodeTypesAdapter { database }, @@ -112,7 +106,7 @@ where debug!(target: "reth::cli", chain=%this.chain_id(), genesis=?this.genesis_hash(), "Initializing genesis"); }) .with_genesis()? - .inspect(|this: &LaunchContextWith, _>>| { + .inspect(|this: &LaunchContextWith::ChainSpec>, _>>| { info!(target: "reth::cli", "\n{}", this.chain_spec().display_hardforks()); }) .with_metrics_task() @@ -368,3 +362,24 @@ where Ok(handle) } } + +impl LaunchNode> for EngineNodeLauncher +where + T: FullNodeTypes< + Types: NodeTypesForProvider, + Provider = BlockchainProvider< + NodeTypesWithDBAdapter<::Types, ::DB>, + >, + >, + CB: NodeComponentsBuilder + 'static, + AO: RethRpcAddOns> + + EngineValidatorAddOn> + + 'static, +{ + type Node = NodeHandle, AO>; + type Future = Pin> + Send>>; + + fn launch_node(self, target: NodeBuilderWithComponents) -> Self::Future { + Box::pin(self.launch_node(target)) + } +} diff --git a/crates/node/builder/src/launch/mod.rs b/crates/node/builder/src/launch/mod.rs index 30ae2cd49ea..cc6b1927d82 100644 --- a/crates/node/builder/src/launch/mod.rs +++ b/crates/node/builder/src/launch/mod.rs @@ -10,7 +10,7 @@ pub(crate) mod engine; pub use common::LaunchContext; pub use exex::ExExLauncher; -use std::future::Future; +use std::future::IntoFuture; /// A general purpose trait that launches a new node of any kind. /// @@ -21,22 +21,26 @@ use std::future::Future; /// /// See also [`EngineNodeLauncher`](crate::EngineNodeLauncher) and /// [`NodeBuilderWithComponents::launch_with`](crate::NodeBuilderWithComponents) -pub trait LaunchNode { +pub trait LaunchNode: Send { /// The node type that is created. type Node; + /// The future type that is returned. + type Future: IntoFuture, IntoFuture: Send>; + /// Create and return a new node asynchronously. - fn launch_node(self, target: Target) -> impl Future>; + fn launch_node(self, target: Target) -> Self::Future; } impl LaunchNode for F where F: FnOnce(Target) -> Fut + Send, - Fut: Future> + Send, + Fut: IntoFuture, IntoFuture: Send> + Send, { type Node = Node; + type Future = Fut; - fn launch_node(self, target: Target) -> impl Future> { + fn launch_node(self, target: Target) -> Self::Future { self(target) } } diff --git a/crates/node/builder/src/node.rs b/crates/node/builder/src/node.rs index 42f023fc4ee..ca44ad9523d 100644 --- a/crates/node/builder/src/node.rs +++ b/crates/node/builder/src/node.rs @@ -1,7 +1,11 @@ +use reth_db::DatabaseEnv; // re-export the node api types pub use reth_node_api::{FullNodeTypes, NodeTypes}; -use crate::{components::NodeComponentsBuilder, rpc::RethRpcAddOns, NodeAdapter, NodeAddOns}; +use crate::{ + components::NodeComponentsBuilder, rpc::RethRpcAddOns, NodeAdapter, NodeAddOns, NodeHandle, + RethFullAdapter, +}; use reth_node_api::{EngineTypes, FullNodeComponents, PayloadTypes}; use reth_node_core::{ dirs::{ChainPath, DataDirPath}, @@ -208,3 +212,11 @@ impl> DerefMut for FullNode> = + FullNode>, >>::AddOns>; + +/// Helper type alias to define [`NodeHandle`] for a given [`Node`]. +pub type NodeHandleFor> = + NodeHandle>, >>::AddOns>; diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index ddf2611c548..70adcc83d69 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -12,7 +12,7 @@ use alloy_rpc_types::engine::ClientVersionV1; use alloy_rpc_types_engine::ExecutionData; use jsonrpsee::{core::middleware::layer::Either, RpcModule}; use reth_chain_state::CanonStateSubscriptions; -use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks, Hardforks}; use reth_node_api::{ AddOnsContext, BlockTy, EngineApiValidator, EngineTypes, FullNodeComponents, FullNodeTypes, NodeAddOns, NodeTypes, PayloadTypes, PayloadValidator, PrimitivesTy, TreeConfig, @@ -362,6 +362,29 @@ where } } +impl RpcHandle { + /// Returns the RPC server handles. + pub const fn rpc_server_handles(&self) -> &RethRpcServerHandles { + &self.rpc_server_handles + } + + /// Returns the consensus engine handle. + /// + /// This handle can be used to interact with the engine service directly. + pub const fn consensus_engine_handle( + &self, + ) -> &ConsensusEngineHandle<::Payload> { + &self.beacon_engine_handle + } + + /// Returns the consensus engine events sender. + pub const fn consensus_engine_events( + &self, + ) -> &EventSender::Primitives>> { + &self.engine_events + } +} + /// Handle returned when only the regular RPC server (HTTP/WS/IPC) is launched. /// /// This handle provides access to the RPC server endpoints and registry, but does not @@ -379,6 +402,29 @@ pub struct RpcServerOnlyHandle { pub engine_handle: ConsensusEngineHandle<::Payload>, } +impl RpcServerOnlyHandle { + /// Returns the RPC server handle. + pub const fn rpc_server_handle(&self) -> &RpcServerHandle { + &self.rpc_server_handle + } + + /// Returns the consensus engine handle. + /// + /// This handle can be used to interact with the engine service directly. + pub const fn consensus_engine_handle( + &self, + ) -> &ConsensusEngineHandle<::Payload> { + &self.engine_handle + } + + /// Returns the consensus engine events sender. + pub const fn consensus_engine_events( + &self, + ) -> &EventSender::Primitives>> { + &self.engine_events + } +} + /// Handle returned when only the authenticated Engine API server is launched. /// /// This handle provides access to the Engine API server and registry, but does not @@ -396,6 +442,24 @@ pub struct AuthServerOnlyHandle { pub engine_handle: ConsensusEngineHandle<::Payload>, } +impl AuthServerOnlyHandle { + /// Returns the consensus engine handle. + /// + /// This handle can be used to interact with the engine service directly. + pub const fn consensus_engine_handle( + &self, + ) -> &ConsensusEngineHandle<::Payload> { + &self.engine_handle + } + + /// Returns the consensus engine events sender. + pub const fn consensus_engine_events( + &self, + ) -> &EventSender::Primitives>> { + &self.engine_events + } +} + /// Internal context struct for RPC setup shared between different launch methods struct RpcSetupContext<'a, Node: FullNodeComponents, EthApi: EthApiTypes> { node: Node, @@ -1074,7 +1138,9 @@ pub struct EthApiCtx<'a, N: FullNodeTypes> { pub cache: EthStateCache>, } -impl<'a, N: FullNodeComponents>> EthApiCtx<'a, N> { +impl<'a, N: FullNodeComponents>> + EthApiCtx<'a, N> +{ /// Provides a [`EthApiBuilder`] with preconfigured config and components. pub fn eth_api_builder(self) -> reth_rpc::EthApiBuilder> { reth_rpc::EthApiBuilder::new_with_components(self.components.clone()) @@ -1088,6 +1154,7 @@ impl<'a, N: FullNodeComponents>> .gas_oracle_config(self.config.gas_oracle) .max_batch_size(self.config.max_batch_size) .pending_block_kind(self.config.pending_block_kind) + .raw_tx_forwarder(self.config.raw_tx_forwarder) } } @@ -1116,13 +1183,15 @@ pub trait EngineValidatorAddOn: Send { fn engine_validator_builder(&self) -> Self::ValidatorBuilder; } -impl EngineValidatorAddOn for RpcAddOns +impl EngineValidatorAddOn + for RpcAddOns where N: FullNodeComponents, EthB: EthApiBuilder, PVB: Send, EB: EngineApiBuilder, EVB: EngineValidatorBuilder, + RpcMiddleware: Send, { type ValidatorBuilder = EVB; diff --git a/crates/node/core/src/args/benchmark_args.rs b/crates/node/core/src/args/benchmark_args.rs index 0f2a2b2d68c..2865054ded1 100644 --- a/crates/node/core/src/args/benchmark_args.rs +++ b/crates/node/core/src/args/benchmark_args.rs @@ -15,6 +15,12 @@ pub struct BenchmarkArgs { #[arg(long, verbatim_doc_comment)] pub to: Option, + /// Number of blocks to advance from the current head block. + /// When specified, automatically sets --from to current head + 1 and --to to current head + + /// advance. Cannot be used together with explicit --from and --to arguments. + #[arg(long, conflicts_with_all = &["from", "to"], verbatim_doc_comment)] + pub advance: Option, + /// Path to a JWT secret to use for the authenticated engine-API RPC server. /// /// This will perform JWT authentication for all requests to the given engine RPC url. diff --git a/crates/node/core/src/args/debug.rs b/crates/node/core/src/args/debug.rs index fdd08243a77..13d7685b055 100644 --- a/crates/node/core/src/args/debug.rs +++ b/crates/node/core/src/args/debug.rs @@ -32,19 +32,23 @@ pub struct DebugArgs { long = "debug.etherscan", help_heading = "Debug", conflicts_with = "tip", - conflicts_with = "rpc_consensus_ws", + conflicts_with = "rpc_consensus_url", value_name = "ETHERSCAN_API_URL" )] pub etherscan: Option>, - /// Runs a fake consensus client using blocks fetched from an RPC `WebSocket` endpoint. + /// Runs a fake consensus client using blocks fetched from an RPC endpoint. + /// Supports both HTTP and `WebSocket` endpoints - `WebSocket` endpoints will use + /// subscriptions, while HTTP endpoints will poll for new blocks. #[arg( - long = "debug.rpc-consensus-ws", + long = "debug.rpc-consensus-url", + alias = "debug.rpc-consensus-ws", help_heading = "Debug", conflicts_with = "tip", - conflicts_with = "etherscan" + conflicts_with = "etherscan", + value_name = "RPC_URL" )] - pub rpc_consensus_ws: Option, + pub rpc_consensus_url: Option, /// If provided, the engine will skip `n` consecutive FCUs. #[arg(long = "debug.skip-fcu", help_heading = "Debug")] @@ -106,7 +110,7 @@ impl Default for DebugArgs { tip: None, max_block: None, etherscan: None, - rpc_consensus_ws: None, + rpc_consensus_url: None, skip_fcu: None, skip_new_payload: None, reorg_frequency: None, diff --git a/crates/node/core/src/args/engine.rs b/crates/node/core/src/args/engine.rs index 64829c4c064..6e86db4417e 100644 --- a/crates/node/core/src/args/engine.rs +++ b/crates/node/core/src/args/engine.rs @@ -34,10 +34,16 @@ pub struct EngineArgs { #[arg(long = "engine.disable-caching-and-prewarming")] pub caching_and_prewarming_disabled: bool, - /// Enable the parallel sparse trie in the engine. - #[arg(long = "engine.parallel-sparse-trie", default_value = "false")] + /// CAUTION: This CLI flag has no effect anymore, use --engine.disable-parallel-sparse-trie + /// if you want to disable usage of the `ParallelSparseTrie`. + #[deprecated] + #[arg(long = "engine.parallel-sparse-trie", default_value = "true", hide = true)] pub parallel_sparse_trie_enabled: bool, + /// Disable the parallel sparse trie in the engine. + #[arg(long = "engine.disable-parallel-sparse-trie", default_value = "false")] + pub parallel_sparse_trie_disabled: bool, + /// Enable state provider latency metrics. This allows the engine to collect and report stats /// about how long state provider calls took during execution, but this does introduce slight /// overhead to state provider calls. @@ -89,6 +95,11 @@ pub struct EngineArgs { default_value = "false" )] pub always_process_payload_attributes_on_canonical_head: bool, + + /// Allow unwinding canonical header to ancestor during forkchoice updates. + /// See `TreeConfig::unwind_canonical_header` for more details. + #[arg(long = "engine.allow-unwind-canonical-header", default_value = "false")] + pub allow_unwind_canonical_header: bool, } #[allow(deprecated)] @@ -101,7 +112,8 @@ impl Default for EngineArgs { state_root_task_compare_updates: false, caching_and_prewarming_enabled: true, caching_and_prewarming_disabled: false, - parallel_sparse_trie_enabled: false, + parallel_sparse_trie_enabled: true, + parallel_sparse_trie_disabled: false, state_provider_metrics: false, cross_block_cache_size: DEFAULT_CROSS_BLOCK_CACHE_SIZE_MB, accept_execution_requests_hash: false, @@ -111,6 +123,7 @@ impl Default for EngineArgs { precompile_cache_disabled: false, state_root_fallback: false, always_process_payload_attributes_on_canonical_head: false, + allow_unwind_canonical_header: false, } } } @@ -123,7 +136,7 @@ impl EngineArgs { .with_memory_block_buffer_target(self.memory_block_buffer_target) .with_legacy_state_root(self.legacy_state_root_task_enabled) .without_caching_and_prewarming(self.caching_and_prewarming_disabled) - .with_enable_parallel_sparse_trie(self.parallel_sparse_trie_enabled) + .with_disable_parallel_sparse_trie(self.parallel_sparse_trie_disabled) .with_state_provider_metrics(self.state_provider_metrics) .with_always_compare_trie_updates(self.state_root_task_compare_updates) .with_cross_block_cache_size(self.cross_block_cache_size * 1024 * 1024) @@ -134,6 +147,7 @@ impl EngineArgs { .with_always_process_payload_attributes_on_canonical_head( self.always_process_payload_attributes_on_canonical_head, ) + .with_unwind_canonical_header(self.allow_unwind_canonical_header) } } diff --git a/crates/node/core/src/args/gas_price_oracle.rs b/crates/node/core/src/args/gas_price_oracle.rs index b7a704cdf55..5d675d4dc1a 100644 --- a/crates/node/core/src/args/gas_price_oracle.rs +++ b/crates/node/core/src/args/gas_price_oracle.rs @@ -25,17 +25,22 @@ pub struct GasPriceOracleArgs { /// The percentile of gas prices to use for the estimate #[arg(long = "gpo.percentile", default_value_t = DEFAULT_GAS_PRICE_PERCENTILE)] pub percentile: u32, + + /// The default gas price to use if there are no blocks to use + #[arg(long = "gpo.default-suggested-fee")] + pub default_suggested_fee: Option, } impl GasPriceOracleArgs { /// Returns a [`GasPriceOracleConfig`] from the arguments. pub fn gas_price_oracle_config(&self) -> GasPriceOracleConfig { - let Self { blocks, ignore_price, max_price, percentile } = self; + let Self { blocks, ignore_price, max_price, percentile, default_suggested_fee } = self; GasPriceOracleConfig { max_price: Some(U256::from(*max_price)), ignore_price: Some(U256::from(*ignore_price)), percentile: *percentile, blocks: *blocks, + default_suggested_fee: *default_suggested_fee, ..Default::default() } } @@ -48,6 +53,7 @@ impl Default for GasPriceOracleArgs { ignore_price: DEFAULT_IGNORE_GAS_PRICE.to(), max_price: DEFAULT_MAX_GAS_PRICE.to(), percentile: DEFAULT_GAS_PRICE_PERCENTILE, + default_suggested_fee: None, } } } @@ -73,6 +79,7 @@ mod tests { ignore_price: DEFAULT_IGNORE_GAS_PRICE.to(), max_price: DEFAULT_MAX_GAS_PRICE.to(), percentile: DEFAULT_GAS_PRICE_PERCENTILE, + default_suggested_fee: None, } ); } diff --git a/crates/node/core/src/args/log.rs b/crates/node/core/src/args/log.rs index 099bd063915..1236984fac0 100644 --- a/crates/node/core/src/args/log.rs +++ b/crates/node/core/src/args/log.rs @@ -35,6 +35,10 @@ pub struct LogArgs { #[arg(long = "log.file.directory", value_name = "PATH", global = true, default_value_t)] pub log_file_directory: PlatformPath, + /// The prefix name of the log files. + #[arg(long = "log.file.name", value_name = "NAME", global = true, default_value = "reth.log")] + pub log_file_name: String, + /// The maximum size (in MB) of one log file. #[arg(long = "log.file.max-size", value_name = "SIZE", global = true, default_value_t = 200)] pub log_file_max_size: u64, @@ -86,6 +90,7 @@ impl LogArgs { fn file_info(&self) -> FileInfo { FileInfo::new( self.log_file_directory.clone().into(), + self.log_file_name.clone(), self.log_file_max_size * MB_TO_BYTES, self.log_file_max_files, ) diff --git a/crates/node/core/src/args/network.rs b/crates/node/core/src/args/network.rs index 78bac7eebb9..a32f14edd41 100644 --- a/crates/node/core/src/args/network.rs +++ b/crates/node/core/src/args/network.rs @@ -1,5 +1,6 @@ //! clap [Args](clap::Args) for network related arguments. +use alloy_primitives::B256; use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, ops::Not, @@ -28,7 +29,7 @@ use reth_network::{ DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER, }, }, - TransactionFetcherConfig, TransactionsManagerConfig, + TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig, DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ, SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE, }, @@ -167,6 +168,22 @@ pub struct NetworkArgs { /// personal nodes, though providers should always opt to enable this flag. #[arg(long = "disable-tx-gossip")] pub disable_tx_gossip: bool, + + /// Sets the transaction propagation mode by determining how new pending transactions are + /// propagated to other peers in full. + /// + /// Examples: sqrt, all, max:10 + #[arg( + long = "tx-propagation-mode", + default_value = "sqrt", + help = "Transaction propagation mode (sqrt, all, max:)" + )] + pub propagation_mode: TransactionPropagationMode, + + /// Comma separated list of required block hashes. + /// Peers that don't have these blocks will be filtered out. + #[arg(long = "required-block-hashes", value_delimiter = ',')] + pub required_block_hashes: Vec, } impl NetworkArgs { @@ -198,7 +215,7 @@ impl NetworkArgs { }) } /// Configures and returns a `TransactionsManagerConfig` based on the current settings. - pub fn transactions_manager_config(&self) -> TransactionsManagerConfig { + pub const fn transactions_manager_config(&self) -> TransactionsManagerConfig { TransactionsManagerConfig { transaction_fetcher_config: TransactionFetcherConfig::new( self.max_concurrent_tx_requests, @@ -208,7 +225,7 @@ impl NetworkArgs { self.max_capacity_cache_txns_pending_fetch, ), max_transactions_seen_by_peer_history: self.max_seen_tx_history, - propagation_mode: Default::default(), + propagation_mode: self.propagation_mode, } } @@ -279,6 +296,7 @@ impl NetworkArgs { self.discovery.port, )) .disable_tx_gossip(self.disable_tx_gossip) + .required_block_hashes(self.required_block_hashes.clone()) } /// If `no_persist_peers` is false then this returns the path to the persistent peers file path. @@ -351,6 +369,8 @@ impl Default for NetworkArgs { net_if: None, tx_propagation_policy: TransactionPropagationKind::default(), disable_tx_gossip: false, + propagation_mode: TransactionPropagationMode::Sqrt, + required_block_hashes: vec![], } } } @@ -638,4 +658,30 @@ mod tests { assert_eq!(args, default_args); } + + #[test] + fn parse_required_block_hashes() { + let args = CommandParser::::parse_from([ + "reth", + "--required-block-hashes", + "0x1111111111111111111111111111111111111111111111111111111111111111,0x2222222222222222222222222222222222222222222222222222222222222222", + ]) + .args; + + assert_eq!(args.required_block_hashes.len(), 2); + assert_eq!( + args.required_block_hashes[0].to_string(), + "0x1111111111111111111111111111111111111111111111111111111111111111" + ); + assert_eq!( + args.required_block_hashes[1].to_string(), + "0x2222222222222222222222222222222222222222222222222222222222222222" + ); + } + + #[test] + fn parse_empty_required_block_hashes() { + let args = CommandParser::::parse_from(["reth"]).args; + assert!(args.required_block_hashes.is_empty()); + } } diff --git a/crates/node/core/src/args/pruning.rs b/crates/node/core/src/args/pruning.rs index 5dbbafc7c67..e96245350fd 100644 --- a/crates/node/core/src/args/pruning.rs +++ b/crates/node/core/src/args/pruning.rs @@ -111,7 +111,7 @@ impl PruningArgs { where ChainSpec: EthereumHardforks, { - // Initialise with a default prune configuration. + // Initialize with a default prune configuration. let mut config = PruneConfig::default(); // If --full is set, use full node defaults. diff --git a/crates/node/core/src/args/rpc_server.rs b/crates/node/core/src/args/rpc_server.rs index afcfd7f7262..58a1c388e4e 100644 --- a/crates/node/core/src/args/rpc_server.rs +++ b/crates/node/core/src/args/rpc_server.rs @@ -1,12 +1,9 @@ //! clap [Args](clap::Args) for RPC related arguments. -use std::{ - collections::HashSet, - ffi::OsStr, - net::{IpAddr, Ipv4Addr}, - path::PathBuf, +use crate::args::{ + types::{MaxU32, ZeroAsNoneU64}, + GasPriceOracleArgs, RpcStateCacheArgs, }; - use alloy_primitives::Address; use alloy_rpc_types_engine::JwtSecret; use clap::{ @@ -14,14 +11,17 @@ use clap::{ Arg, Args, Command, }; use rand::Rng; -use reth_cli_util::parse_ether_value; +use reth_cli_util::{parse_duration_from_secs_or_ms, parse_ether_value}; use reth_rpc_eth_types::builder::config::PendingBlockKind; use reth_rpc_server_types::{constants, RethRpcModule, RpcModuleSelection}; - -use crate::args::{ - types::{MaxU32, ZeroAsNoneU64}, - GasPriceOracleArgs, RpcStateCacheArgs, +use std::{ + collections::HashSet, + ffi::OsStr, + net::{IpAddr, Ipv4Addr}, + path::PathBuf, + time::Duration, }; +use url::Url; use super::types::MaxOr; @@ -227,6 +227,10 @@ pub struct RpcServerArgs { #[arg(long = "rpc.pending-block", default_value = "full", value_name = "KIND")] pub rpc_pending_block: PendingBlockKind, + /// Endpoint to forward transactions to. + #[arg(long = "rpc.forwarder", alias = "rpc-forwarder", value_name = "FORWARDER")] + pub rpc_forwarder: Option, + /// Path to file containing disallowed addresses, json-encoded list of strings. Block /// validation API will reject blocks containing transactions from these addresses. #[arg(long = "builder.disallow", value_name = "PATH", value_parser = reth_cli_util::parsers::read_json_from_file::>)] @@ -239,6 +243,15 @@ pub struct RpcServerArgs { /// Gas price oracle configuration. #[command(flatten)] pub gas_price_oracle: GasPriceOracleArgs, + + /// Timeout for `send_raw_transaction_sync` RPC method. + #[arg( + long = "rpc.send-raw-transaction-sync-timeout", + value_name = "SECONDS", + default_value = "30s", + value_parser = parse_duration_from_secs_or_ms, + )] + pub rpc_send_raw_transaction_sync_timeout: Duration, } impl RpcServerArgs { @@ -260,12 +273,25 @@ impl RpcServerArgs { self } + /// Configures modules for WS-RPC server. + pub fn with_ws_api(mut self, ws_api: RpcModuleSelection) -> Self { + self.ws_api = Some(ws_api); + self + } + /// Enables the Auth IPC pub const fn with_auth_ipc(mut self) -> Self { self.auth_ipc = true; self } + /// Configures modules for both the HTTP-RPC server and WS-RPC server. + /// + /// This is the same as calling both [`Self::with_http_api`] and [`Self::with_ws_api`]. + pub fn with_api(self, api: RpcModuleSelection) -> Self { + self.with_http_api(api.clone()).with_ws_api(api) + } + /// Change rpc port numbers based on the instance number, if provided. /// * The `auth_port` is scaled by a factor of `instance * 100` /// * The `http_port` is scaled by a factor of `-instance` @@ -333,6 +359,20 @@ impl RpcServerArgs { self = self.with_ipc_random_path(); self } + + /// Apply a function to the args. + pub fn apply(self, f: F) -> Self + where + F: FnOnce(Self) -> Self, + { + f(self) + } + + /// Configures the timeout for send raw transaction sync. + pub const fn with_send_raw_transaction_sync_timeout(mut self, timeout: Duration) -> Self { + self.rpc_send_raw_transaction_sync_timeout = timeout; + self + } } impl Default for RpcServerArgs { @@ -375,12 +415,15 @@ impl Default for RpcServerArgs { gas_price_oracle: GasPriceOracleArgs::default(), rpc_state_cache: RpcStateCacheArgs::default(), rpc_proof_permits: constants::DEFAULT_PROOF_PERMITS, + rpc_forwarder: None, builder_disallow: Default::default(), + rpc_send_raw_transaction_sync_timeout: + constants::RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS, } } } -/// clap value parser for [`RpcModuleSelection`]. +/// clap value parser for [`RpcModuleSelection`] with configurable validation. #[derive(Clone, Debug, Default)] #[non_exhaustive] struct RpcModuleSelectionValueParser; @@ -391,23 +434,20 @@ impl TypedValueParser for RpcModuleSelectionValueParser { fn parse_ref( &self, _cmd: &Command, - arg: Option<&Arg>, + _arg: Option<&Arg>, value: &OsStr, ) -> Result { let val = value.to_str().ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?; - val.parse::().map_err(|err| { - let arg = arg.map(|a| a.to_string()).unwrap_or_else(|| "...".to_owned()); - let possible_values = RethRpcModule::all_variant_names().to_vec().join(","); - let msg = format!( - "Invalid value '{val}' for {arg}: {err}.\n [possible values: {possible_values}]" - ); - clap::Error::raw(clap::error::ErrorKind::InvalidValue, msg) - }) + // This will now accept any module name, creating Other(name) for unknowns + Ok(val + .parse::() + .expect("RpcModuleSelection parsing cannot fail with Other variant")) } fn possible_values(&self) -> Option + '_>> { - let values = RethRpcModule::all_variant_names().iter().map(PossibleValue::new); + // Only show standard modules in help text (excludes "other") + let values = RethRpcModule::standard_variant_names().map(PossibleValue::new); Some(Box::new(values)) } } diff --git a/crates/node/core/src/args/txpool.rs b/crates/node/core/src/args/txpool.rs index 164fc83d4b1..2ab604be168 100644 --- a/crates/node/core/src/args/txpool.rs +++ b/crates/node/core/src/args/txpool.rs @@ -138,6 +138,24 @@ pub struct TxPoolArgs { pub max_batch_size: usize, } +impl TxPoolArgs { + /// Sets the minimal protocol base fee to 0, effectively disabling checks that enforce that a + /// transaction's fee must be higher than the [`MIN_PROTOCOL_BASE_FEE`] which is the lowest + /// value the ethereum EIP-1559 base fee can reach. + pub const fn with_disabled_protocol_base_fee(self) -> Self { + self.with_protocol_base_fee(0) + } + + /// Configures the minimal protocol base fee that should be enforced. + /// + /// Ethereum's EIP-1559 base fee can't drop below [`MIN_PROTOCOL_BASE_FEE`] hence this is + /// enforced by default in the pool. + pub const fn with_protocol_base_fee(mut self, protocol_base_fee: u64) -> Self { + self.minimal_protocol_basefee = protocol_base_fee; + self + } +} + impl Default for TxPoolArgs { fn default() -> Self { Self { @@ -212,6 +230,7 @@ impl RethTransactionPoolConfig for TxPoolArgs { new_tx_listener_buffer_size: self.new_tx_listener_buffer_size, max_new_pending_txs_notifications: self.max_new_pending_txs_notifications, max_queued_lifetime: self.max_queued_lifetime, + ..Default::default() } } diff --git a/crates/node/core/src/node_config.rs b/crates/node/core/src/node_config.rs index 66a5b2b5153..96fa8cc8dfa 100644 --- a/crates/node/core/src/node_config.rs +++ b/crates/node/core/src/node_config.rs @@ -494,7 +494,10 @@ impl NodeConfig { } /// Returns the [`MiningMode`] intended for --dev mode. - pub fn dev_mining_mode(&self, pool: impl TransactionPool) -> MiningMode { + pub fn dev_mining_mode(&self, pool: Pool) -> MiningMode + where + Pool: TransactionPool + Unpin, + { if let Some(interval) = self.dev.block_time { MiningMode::interval(interval) } else { diff --git a/crates/node/core/src/version.rs b/crates/node/core/src/version.rs index 85a6077709f..9953aea2390 100644 --- a/crates/node/core/src/version.rs +++ b/crates/node/core/src/version.rs @@ -108,13 +108,13 @@ pub fn version_metadata() -> &'static RethCliVersionConsts { pub fn default_reth_version_metadata() -> RethCliVersionConsts { RethCliVersionConsts { name_client: Cow::Borrowed("Reth"), - cargo_pkg_version: Cow::Owned(env!("CARGO_PKG_VERSION").to_string()), - vergen_git_sha_long: Cow::Owned(env!("VERGEN_GIT_SHA").to_string()), - vergen_git_sha: Cow::Owned(env!("VERGEN_GIT_SHA_SHORT").to_string()), - vergen_build_timestamp: Cow::Owned(env!("VERGEN_BUILD_TIMESTAMP").to_string()), - vergen_cargo_target_triple: Cow::Owned(env!("VERGEN_CARGO_TARGET_TRIPLE").to_string()), - vergen_cargo_features: Cow::Owned(env!("VERGEN_CARGO_FEATURES").to_string()), - short_version: Cow::Owned(env!("RETH_SHORT_VERSION").to_string()), + cargo_pkg_version: Cow::Borrowed(env!("CARGO_PKG_VERSION")), + vergen_git_sha_long: Cow::Borrowed(env!("VERGEN_GIT_SHA")), + vergen_git_sha: Cow::Borrowed(env!("VERGEN_GIT_SHA_SHORT")), + vergen_build_timestamp: Cow::Borrowed(env!("VERGEN_BUILD_TIMESTAMP")), + vergen_cargo_target_triple: Cow::Borrowed(env!("VERGEN_CARGO_TARGET_TRIPLE")), + vergen_cargo_features: Cow::Borrowed(env!("VERGEN_CARGO_FEATURES")), + short_version: Cow::Borrowed(env!("RETH_SHORT_VERSION")), long_version: Cow::Owned(format!( "{}\n{}\n{}\n{}\n{}", env!("RETH_LONG_VERSION_0"), @@ -124,8 +124,8 @@ pub fn default_reth_version_metadata() -> RethCliVersionConsts { env!("RETH_LONG_VERSION_4"), )), - build_profile_name: Cow::Owned(env!("RETH_BUILD_PROFILE").to_string()), - p2p_client_version: Cow::Owned(env!("RETH_P2P_CLIENT_VERSION").to_string()), + build_profile_name: Cow::Borrowed(env!("RETH_BUILD_PROFILE")), + p2p_client_version: Cow::Borrowed(env!("RETH_P2P_CLIENT_VERSION")), extra_data: Cow::Owned(default_extra_data()), } } diff --git a/crates/node/ethstats/src/ethstats.rs b/crates/node/ethstats/src/ethstats.rs index aea8a160fc0..b9fe5e47272 100644 --- a/crates/node/ethstats/src/ethstats.rs +++ b/crates/node/ethstats/src/ethstats.rs @@ -181,14 +181,14 @@ where let response = timeout(READ_TIMEOUT, conn.read_json()).await.map_err(|_| EthStatsError::Timeout)??; - if let Some(ack) = response.get("emit") { - if ack.get(0) == Some(&Value::String("ready".to_string())) { - info!( - target: "ethstats", - "Login successful to EthStats server as node_id {}", self.credentials.node_id - ); - return Ok(()); - } + if let Some(ack) = response.get("emit") && + ack.get(0) == Some(&Value::String("ready".to_string())) + { + info!( + target: "ethstats", + "Login successful to EthStats server as node_id {}", self.credentials.node_id + ); + return Ok(()); } debug!(target: "ethstats", "Login failed: Unauthorized or unexpected login response"); @@ -595,10 +595,10 @@ where tokio::spawn(async move { loop { let head = canonical_stream.next().await; - if let Some(head) = head { - if head_tx.send(head).await.is_err() { - break; - } + if let Some(head) = head && + head_tx.send(head).await.is_err() + { + break; } } @@ -681,10 +681,10 @@ where /// Attempts to close the connection cleanly and logs any errors /// that occur during the process. async fn disconnect(&self) { - if let Some(conn) = self.conn.write().await.take() { - if let Err(e) = conn.close().await { - debug!(target: "ethstats", "Error closing connection: {}", e); - } + if let Some(conn) = self.conn.write().await.take() && + let Err(e) = conn.close().await + { + debug!(target: "ethstats", "Error closing connection: {}", e); } } @@ -733,16 +733,13 @@ mod tests { // Handle ping while let Some(Ok(msg)) = ws_stream.next().await { - if let Message::Text(text) = msg { - if text.contains("node-ping") { - let pong = json!({ - "emit": ["node-pong", {"id": "test-node"}] - }); - ws_stream - .send(Message::Text(Utf8Bytes::from(pong.to_string()))) - .await - .unwrap(); - } + if let Message::Text(text) = msg && + text.contains("node-ping") + { + let pong = json!({ + "emit": ["node-pong", {"id": "test-node"}] + }); + ws_stream.send(Message::Text(Utf8Bytes::from(pong.to_string()))).await.unwrap(); } } }); diff --git a/crates/node/events/src/node.rs b/crates/node/events/src/node.rs index c0a698a31db..24500eee400 100644 --- a/crates/node/events/src/node.rs +++ b/crates/node/events/src/node.rs @@ -249,6 +249,10 @@ impl NodeState { } ConsensusEngineEvent::CanonicalBlockAdded(executed, elapsed) => { let block = executed.sealed_block(); + let mut full = block.gas_used() as f64 * 100.0 / block.gas_limit() as f64; + if full.is_nan() { + full = 0.0; + } info!( number=block.number(), hash=?block.hash(), @@ -257,7 +261,7 @@ impl NodeState { gas_used=%format_gas(block.gas_used()), gas_throughput=%format_gas_throughput(block.gas_used(), elapsed), gas_limit=%format_gas(block.gas_limit()), - full=%format!("{:.1}%", block.gas_used() as f64 * 100.0 / block.gas_limit() as f64), + full=%format!("{:.1}%", full), base_fee=%format!("{:.2}Gwei", block.base_fee_per_gas().unwrap_or(0) as f64 / GWEI_TO_WEI as f64), blobs=block.blob_gas_used().unwrap_or(0) / alloy_eips::eip4844::DATA_GAS_PER_BLOB, excess_blobs=block.excess_blob_gas().unwrap_or(0) / alloy_eips::eip4844::DATA_GAS_PER_BLOB, diff --git a/crates/optimism/bin/src/main.rs b/crates/optimism/bin/src/main.rs index e1567ef1c9b..b8f87ac77ef 100644 --- a/crates/optimism/bin/src/main.rs +++ b/crates/optimism/bin/src/main.rs @@ -13,7 +13,9 @@ fn main() { // Enable backtraces unless a RUST_BACKTRACE value has already been explicitly provided. if std::env::var_os("RUST_BACKTRACE").is_none() { - std::env::set_var("RUST_BACKTRACE", "1"); + unsafe { + std::env::set_var("RUST_BACKTRACE", "1"); + } } if let Err(err) = diff --git a/crates/optimism/chainspec/res/genesis/dev.json b/crates/optimism/chainspec/res/genesis/dev.json index ed0522167b0..01718fd8b8c 100644 --- a/crates/optimism/chainspec/res/genesis/dev.json +++ b/crates/optimism/chainspec/res/genesis/dev.json @@ -1 +1,224 @@ -{"nonce":"0x0","timestamp":"0x6490fdd2","extraData":"0x","gasLimit":"0x1c9c380","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","stateRoot":"0x5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494","alloc":{"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266":{"balance":"0xD3C21BCECCEDA1000000"},"0x70997970C51812dc3A010C7d01b50e0d17dc79C8":{"balance":"0xD3C21BCECCEDA1000000"},"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC":{"balance":"0xD3C21BCECCEDA1000000"},"0x90F79bf6EB2c4f870365E785982E1f101E93b906":{"balance":"0xD3C21BCECCEDA1000000"},"0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65":{"balance":"0xD3C21BCECCEDA1000000"},"0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc":{"balance":"0xD3C21BCECCEDA1000000"},"0x976EA74026E726554dB657fA54763abd0C3a0aa9":{"balance":"0xD3C21BCECCEDA1000000"},"0x14dC79964da2C08b23698B3D3cc7Ca32193d9955":{"balance":"0xD3C21BCECCEDA1000000"},"0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f":{"balance":"0xD3C21BCECCEDA1000000"},"0xa0Ee7A142d267C1f36714E4a8F75612F20a79720":{"balance":"0xD3C21BCECCEDA1000000"},"0xBcd4042DE499D14e55001CcbB24a551F3b954096":{"balance":"0xD3C21BCECCEDA1000000"},"0x71bE63f3384f5fb98995898A86B02Fb2426c5788":{"balance":"0xD3C21BCECCEDA1000000"},"0xFABB0ac9d68B0B445fB7357272Ff202C5651694a":{"balance":"0xD3C21BCECCEDA1000000"},"0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec":{"balance":"0xD3C21BCECCEDA1000000"},"0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097":{"balance":"0xD3C21BCECCEDA1000000"},"0xcd3B766CCDd6AE721141F452C550Ca635964ce71":{"balance":"0xD3C21BCECCEDA1000000"},"0x2546BcD3c84621e976D8185a91A922aE77ECEc30":{"balance":"0xD3C21BCECCEDA1000000"},"0xbDA5747bFD65F08deb54cb465eB87D40e51B197E":{"balance":"0xD3C21BCECCEDA1000000"},"0xdD2FD4581271e230360230F9337D5c0430Bf44C0":{"balance":"0xD3C21BCECCEDA1000000"},"0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199":{"balance":"0xD3C21BCECCEDA1000000"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"} \ No newline at end of file +{ + "nonce": "0x0", + "timestamp": "0x6490fdd2", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "stateRoot": "0x5eb6e371a698b8d68f665192350ffcecbbbf322916f4b51bd79bb6887da3f494", + "alloc": { + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x70997970C51812dc3A010C7d01b50e0d17dc79C8": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x90F79bf6EB2c4f870365E785982E1f101E93b906": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x976EA74026E726554dB657fA54763abd0C3a0aa9": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x14dC79964da2C08b23698B3D3cc7Ca32193d9955": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xBcd4042DE499D14e55001CcbB24a551F3b954096": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x71bE63f3384f5fb98995898A86B02Fb2426c5788": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xFABB0ac9d68B0B445fB7357272Ff202C5651694a": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x1CBd3b2770909D4e10f157cABC84C7264073C9Ec": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdF3e18d64BC6A983f673Ab319CCaE4f1a57C7097": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xcd3B766CCDd6AE721141F452C550Ca635964ce71": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x2546BcD3c84621e976D8185a91A922aE77ECEc30": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xbDA5747bFD65F08deb54cb465eB87D40e51B197E": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0xdD2FD4581271e230360230F9337D5c0430Bf44C0": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x8626f6940E2eb28930eFb4CeF49B2d1F2C9C1199": { + "balance": "0xD3C21BCECCEDA1000000" + }, + "0x4300000000000000000000000000000000000001": { + "code": "0x60806040526004361061005e5760003560e01c80635c60da1b116100435780635c60da1b146100be5780638f283970146100f8578063f851a440146101185761006d565b80633659cfe6146100755780634f1ef286146100955761006d565b3661006d5761006b61012d565b005b61006b61012d565b34801561008157600080fd5b5061006b6100903660046106dd565b610224565b6100a86100a33660046106f8565b610296565b6040516100b5919061077b565b60405180910390f35b3480156100ca57600080fd5b506100d3610419565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100b5565b34801561010457600080fd5b5061006b6101133660046106dd565b6104b0565b34801561012457600080fd5b506100d3610517565b60006101577f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b905073ffffffffffffffffffffffffffffffffffffffff8116610201576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f50726f78793a20696d706c656d656e746174696f6e206e6f7420696e6974696160448201527f6c697a656400000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3660008037600080366000845af43d6000803e8061021e573d6000fd5b503d6000f35b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061027d575033155b1561028e5761028b816105a3565b50565b61028b61012d565b60606102c07fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614806102f7575033155b1561040a57610305846105a3565b6000808573ffffffffffffffffffffffffffffffffffffffff16858560405161032f9291906107ee565b600060405180830381855af49150503d806000811461036a576040519150601f19603f3d011682016040523d82523d6000602084013e61036f565b606091505b509150915081610401576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f50726f78793a2064656c656761746563616c6c20746f206e657720696d706c6560448201527f6d656e746174696f6e20636f6e7472616374206661696c65640000000000000060648201526084016101f8565b91506104129050565b61041261012d565b9392505050565b60006104437fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061047a575033155b156104a557507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5490565b6104ad61012d565b90565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610509575033155b1561028e5761028b8161060c565b60006105417fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161480610578575033155b156104a557507fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81815560405173ffffffffffffffffffffffffffffffffffffffff8316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a25050565b60006106367fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035490565b7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61038381556040805173ffffffffffffffffffffffffffffffffffffffff80851682528616602082015292935090917f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f910160405180910390a1505050565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d857600080fd5b919050565b6000602082840312156106ef57600080fd5b610412826106b4565b60008060006040848603121561070d57600080fd5b610716846106b4565b9250602084013567ffffffffffffffff8082111561073357600080fd5b818601915086601f83011261074757600080fd5b81358181111561075657600080fd5b87602082850101111561076857600080fd5b6020830194508093505050509250925092565b600060208083528351808285015260005b818110156107a85785810183015185820160400152820161078c565b818111156107ba576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b818382376000910190815291905056fea164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30001", + "0x64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266", + "0x64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e003": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x0000000000000000000000004200000000000000000000000000000000000018" + }, + "balance": "0x0" + }, + "4e59b44847b379578588920ca78fbf26c0b4956c": { + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "balance": "0x0", + "nonce": "0x1" + }, + "5ff137d4b0fdcd49dca30c7cf57e578a026d2789": { + "code": "0x60806040526004361015610023575b361561001957600080fd5b610021615531565b005b60003560e01c80630396cb60146101b35780630bd28e3b146101aa5780631b2e01b8146101a15780631d732756146101985780631fad948c1461018f578063205c28781461018657806335567e1a1461017d5780634b1d7cf5146101745780635287ce121461016b57806370a08231146101625780638f41ec5a14610159578063957122ab146101505780639b249f6914610147578063a61935311461013e578063b760faf914610135578063bb9fe6bf1461012c578063c23a5cea14610123578063d6383f941461011a578063ee219423146101115763fc7e286d0361000e5761010c611bcd565b61000e565b5061010c6119b5565b5061010c61184d565b5061010c6116b4565b5061010c611536565b5061010c6114f7565b5061010c6114d6565b5061010c611337565b5061010c611164565b5061010c611129565b5061010c6110a4565b5061010c610f54565b5061010c610bf8565b5061010c610b33565b5061010c610994565b5061010c6108ba565b5061010c6106e7565b5061010c610467565b5061010c610385565b5060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043563ffffffff8116808203610359576103547fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c01916102716102413373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b9161024d811515615697565b61026a610261600185015463ffffffff1690565b63ffffffff1690565b11156156fc565b54926103366dffffffffffffffffffffffffffff946102f461029834888460781c166121d5565b966102a4881515615761565b6102b0818911156157c6565b6102d4816102bc6105ec565b941684906dffffffffffffffffffffffffffff169052565b6001602084015287166dffffffffffffffffffffffffffff166040830152565b63ffffffff83166060820152600060808201526103313373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b61582b565b6040805194855263ffffffff90911660208501523393918291820190565b0390a2005b600080fd5b6024359077ffffffffffffffffffffffffffffffffffffffffffffffff8216820361035957565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043577ffffffffffffffffffffffffffffffffffffffffffffffff81168103610359576104149033600052600160205260406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b61041e8154612491565b9055005b73ffffffffffffffffffffffffffffffffffffffff81160361035957565b6024359061044d82610422565b565b60c4359061044d82610422565b359061044d82610422565b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760206104fc6004356104a881610422565b73ffffffffffffffffffffffffffffffffffffffff6104c561035e565b91166000526001835260406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b54604051908152f35b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60a0810190811067ffffffffffffffff82111761055157604052565b610559610505565b604052565b610100810190811067ffffffffffffffff82111761055157604052565b67ffffffffffffffff811161055157604052565b6060810190811067ffffffffffffffff82111761055157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761055157604052565b6040519061044d82610535565b6040519060c0820182811067ffffffffffffffff82111761055157604052565b604051906040820182811067ffffffffffffffff82111761055157604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610675575b01160190565b61067d610505565b61066f565b92919261068e82610639565b9161069c60405193846105ab565b829481845281830111610359578281602093846000960137010152565b9181601f840112156103595782359167ffffffffffffffff8311610359576020838186019501011161035957565b5034610359576101c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595767ffffffffffffffff60043581811161035957366023820112156103595761074a903690602481600401359101610682565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc36016101808112610359576101006040519161078783610535565b12610359576040516107988161055e565b6107a0610440565b815260443560208201526064356040820152608435606082015260a43560808201526107ca61044f565b60a082015260e43560c08201526101043560e082015281526101243560208201526101443560408201526101643560608201526101843560808201526101a4359182116103595761083e9261082661082e9336906004016106b9565b9290916128b1565b6040519081529081906020820190565b0390f35b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126103595760043567ffffffffffffffff9283821161035957806023830112156103595781600401359384116103595760248460051b830101116103595760240191906024356108b781610422565b90565b5034610359576108c936610842565b6108d4929192611e3a565b6108dd83611d2d565b60005b84811061095d57506000927fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f9728480a183915b85831061092d576109238585611ed7565b6100216001600255565b909193600190610953610941878987611dec565b61094b8886611dca565b51908861233f565b0194019190610912565b8061098b610984610972600194869896611dca565b5161097e848a88611dec565b84613448565b9083612f30565b019290926108e0565b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576004356109d081610422565b6024359060009133835282602052604083206dffffffffffffffffffffffffffff81541692838311610ad557848373ffffffffffffffffffffffffffffffffffffffff829593610a788496610a3f610a2c8798610ad29c6121c0565b6dffffffffffffffffffffffffffff1690565b6dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b6040805173ffffffffffffffffffffffffffffffffffffffff831681526020810185905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb91a2165af1610acc611ea7565b50615ba2565b80f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f206c61726765000000000000006044820152fd5b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576020600435610b7181610422565b73ffffffffffffffffffffffffffffffffffffffff610b8e61035e565b911660005260018252610bc98160406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006040519260401b16178152f35b503461035957610c0736610842565b610c0f611e3a565b6000805b838210610df657610c249150611d2d565b7fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972600080a16000805b848110610d5c57505060008093815b818110610c9357610923868660007f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d8180a2611ed7565b610cf7610ca182848a6124cb565b610ccc610cb3610cb36020840161256d565b73ffffffffffffffffffffffffffffffffffffffff1690565b7f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d600080a280612519565b906000915b808310610d1457505050610d0f90612491565b610c5c565b90919497610d4f610d49610d5592610d438c8b610d3c82610d368e8b8d611dec565b92611dca565b519161233f565b906121d5565b99612491565b95612491565b9190610cfc565b610d678186886124cb565b6020610d7f610d768380612519565b9290930161256d565b9173ffffffffffffffffffffffffffffffffffffffff60009316905b828410610db45750505050610daf90612491565b610c4d565b90919294610d4f81610de985610de2610dd0610dee968d611dca565b51610ddc8c8b8a611dec565b85613448565b908b613148565b612491565b929190610d9b565b610e018285876124cb565b90610e0c8280612519565b92610e1c610cb36020830161256d565b9173ffffffffffffffffffffffffffffffffffffffff8316610e416001821415612577565b610e62575b505050610e5c91610e56916121d5565b91612491565b90610c13565b909592610e7b6040999693999895989788810190611fc8565b92908a3b156103595789938b918a5193849283927fe3563a4f00000000000000000000000000000000000000000000000000000000845260049e8f850193610ec294612711565b03815a93600094fa9081610f3b575b50610f255786517f86a9f75000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8a16818a0190815281906020010390fd5b0390fd5b9497509295509093509181610e56610e5c610e46565b80610f48610f4e9261057b565b8061111e565b38610ed1565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595761083e73ffffffffffffffffffffffffffffffffffffffff600435610fa881610422565b608060409283928351610fba81610535565b60009381858093528260208201528287820152826060820152015216815280602052209061104965ffffffffffff6001835194610ff686610535565b80546dffffffffffffffffffffffffffff8082168852607082901c60ff161515602089015260789190911c1685870152015463ffffffff8116606086015260201c16608084019065ffffffffffff169052565b5191829182919091608065ffffffffffff8160a08401956dffffffffffffffffffffffffffff808251168652602082015115156020870152604082015116604086015263ffffffff6060820151166060860152015116910152565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595773ffffffffffffffffffffffffffffffffffffffff6004356110f581610422565b16600052600060205260206dffffffffffffffffffffffffffff60406000205416604051908152f35b600091031261035957565b50346103595760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261035957602060405160018152f35b50346103595760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261035957600467ffffffffffffffff8135818111610359576111b590369084016106b9565b9050602435916111c483610422565b604435908111610359576111db90369085016106b9565b92909115908161132d575b506112c6576014821015611236575b610f21836040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160409060208152600060208201520190565b6112466112529261124c92612b88565b90612b96565b60601c90565b3b1561125f5738806111f5565b610f21906040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160609060208152601b60208201527f41413330207061796d6173746572206e6f74206465706c6f796564000000000060408201520190565b610f21836040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160609060208152601960208201527f41413230206163636f756e74206e6f74206465706c6f7965640000000000000060408201520190565b90503b15386111e6565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043567ffffffffffffffff81116103595761138960249136906004016106b9565b906113bf6040519283927f570e1a3600000000000000000000000000000000000000000000000000000000845260048401612d2c565b0360208273ffffffffffffffffffffffffffffffffffffffff92816000857f0000000000000000000000007fc98430eaedbb6070b35b39d798725049088348165af1918215611471575b600092611441575b50604051917f6ca7b806000000000000000000000000000000000000000000000000000000008352166004820152fd5b61146391925060203d811161146a575b61145b81836105ab565b810190612d17565b9038611411565b503d611451565b611479612183565b611409565b90816101609103126103595790565b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc820112610359576004359067ffffffffffffffff8211610359576108b79160040161147e565b50346103595760206114ef6114ea3661148d565b612a0c565b604051908152f35b5060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595761002160043561153181610422565b61562b565b5034610359576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126116b1573381528060205260408120600181019063ffffffff825416908115611653576115f06115b5611618936115a76115a2855460ff9060701c1690565b61598f565b65ffffffffffff42166159f4565b84547fffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffff16602082901b69ffffffffffff000000001617909455565b7fffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffff8154169055565b60405165ffffffffffff91909116815233907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a90602090a280f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f6e6f74207374616b6564000000000000000000000000000000000000000000006044820152fd5b80fd5b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576004356116f081610422565b610ad273ffffffffffffffffffffffffffffffffffffffff6117323373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b926117ea611755610a2c86546dffffffffffffffffffffffffffff9060781c1690565b94611761861515615a0e565b6117c26001820161179a65ffffffffffff611786835465ffffffffffff9060201c1690565b16611792811515615a73565b421015615ad8565b80547fffffffffffffffffffffffffffffffffffffffffffff00000000000000000000169055565b7fffffff0000000000000000000000000000ffffffffffffffffffffffffffffff8154169055565b6040805173ffffffffffffffffffffffffffffffffffffffff831681526020810186905233917fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda391a2600080809581948294165af1611847611ea7565b50615b3d565b50346103595760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595767ffffffffffffffff6004358181116103595761189e90369060040161147e565b602435916118ab83610422565b604435908111610359576118c6610f219136906004016106b9565b6118ce611caa565b6118d785612e2b565b6118ea6118e48287613240565b906153ba565b946118fa826000924384526121e2565b96438252819360609573ffffffffffffffffffffffffffffffffffffffff8316611981575b50505050608001519361194e6040611940602084015165ffffffffffff1690565b92015165ffffffffffff1690565b906040519687967f8b7ac980000000000000000000000000000000000000000000000000000000008852600488016127e1565b8395508394965061199b60409492939451809481936127d3565b03925af19060806119aa611ea7565b92919038808061191f565b5034610359576119c43661148d565b6119cc611caa565b6119d582612e2b565b6119df8183613240565b825160a00151919391611a0c9073ffffffffffffffffffffffffffffffffffffffff166154dc565b6154dc565b90611a30611a07855173ffffffffffffffffffffffffffffffffffffffff90511690565b94611a39612b50565b50611a68611a4c60409586810190611fc8565b90600060148310611bc55750611246611a079261124c92612b88565b91611a72916153ba565b805173ffffffffffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff821660018114916080880151978781015191886020820151611ac79065ffffffffffff1690565b91015165ffffffffffff16916060015192611ae06105f9565b9a8b5260208b0152841515898b015265ffffffffffff1660608a015265ffffffffffff16608089015260a088015215159081611bbc575b50611b515750610f2192519485947fe0cff05f00000000000000000000000000000000000000000000000000000000865260048601612cbd565b9190610f2193611b60846154dc565b611b87611b6b610619565b73ffffffffffffffffffffffffffffffffffffffff9096168652565b6020850152519586957ffaecb4e400000000000000000000000000000000000000000000000000000000875260048701612c2b565b90501538611b17565b9150506154dc565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595773ffffffffffffffffffffffffffffffffffffffff600435611c1e81610422565b16600052600060205260a0604060002065ffffffffffff60018254920154604051926dffffffffffffffffffffffffffff90818116855260ff8160701c161515602086015260781c16604084015263ffffffff8116606084015260201c166080820152f35b60209067ffffffffffffffff8111611c9d575b60051b0190565b611ca5610505565b611c96565b60405190611cb782610535565b604051608083610100830167ffffffffffffffff811184821017611d20575b60405260009283815283602082015283604082015283606082015283838201528360a08201528360c08201528360e082015281528260208201528260408201528260608201520152565b611d28610505565b611cd6565b90611d3782611c83565b611d4460405191826105ab565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611d728294611c83565b019060005b828110611d8357505050565b602090611d8e611caa565b82828501015201611d77565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020918151811015611ddf575b60051b010190565b611de7611d9a565b611dd7565b9190811015611e2d575b60051b810135907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea181360301821215610359570190565b611e35611d9a565b611df6565b6002805414611e495760028055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fd5b3d15611ed2573d90611eb882610639565b91611ec660405193846105ab565b82523d6000602084013e565b606090565b73ffffffffffffffffffffffffffffffffffffffff168015611f6a57600080809381935af1611f04611ea7565b5015611f0c57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f41413931206661696c65642073656e6420746f2062656e6566696369617279006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4141393020696e76616c69642062656e656669636961727900000000000000006044820152fd5b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610359570180359067ffffffffffffffff82116103595760200191813603831361035957565b90816020910312610359575190565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b60005b83811061207a5750506000910152565b818101518382015260200161206a565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6020936120c681518092818752878088019101612067565b0116010190565b906120e76080916108b796946101c0808652850191612028565b9360e0815173ffffffffffffffffffffffffffffffffffffffff80825116602087015260208201516040870152604082015160608701526060820151858701528482015160a087015260a08201511660c086015260c081015182860152015161010084015260208101516101208401526040810151610140840152606081015161016084015201516101808201526101a081840391015261208a565b506040513d6000823e3d90fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b919082039182116121cd57565b61044d612190565b919082018092116121cd57565b905a918160206121fb6060830151936060810190611fc8565b906122348560405195869485947f1d732756000000000000000000000000000000000000000000000000000000008652600486016120cd565b03816000305af16000918161230f575b50612308575060206000803e7fdeaddead000000000000000000000000000000000000000000000000000000006000511461229b5761229561228a6108b7945a906121c0565b6080840151906121d5565b91614afc565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152600f60408201527f41413935206f7574206f6620676173000000000000000000000000000000000060608201520190565b9250505090565b61233191925060203d8111612338575b61232981836105ab565b810190612019565b9038612244565b503d61231f565b909291925a9380602061235b6060830151946060810190611fc8565b906123948660405195869485947f1d732756000000000000000000000000000000000000000000000000000000008652600486016120cd565b03816000305af160009181612471575b5061246a575060206000803e7fdeaddead00000000000000000000000000000000000000000000000000000000600051146123fc576123f66123eb6108b795965a906121c0565b6080830151906121d5565b92614ddf565b610f21836040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152600f60408201527f41413935206f7574206f6620676173000000000000000000000000000000000060608201520190565b9450505050565b61248a91925060203d81116123385761232981836105ab565b90386123a4565b6001907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146124bf570190565b6124c7612190565b0190565b919081101561250c575b60051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa181360301821215610359570190565b612514611d9a565b6124d5565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610359570180359067ffffffffffffffff821161035957602001918160051b3603831361035957565b356108b781610422565b1561257e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4141393620696e76616c69642061676772656761746f720000000000000000006044820152fd5b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561035957016020813591019167ffffffffffffffff821161035957813603831361035957565b6108b7916126578161263d8461045c565b73ffffffffffffffffffffffffffffffffffffffff169052565b602082013560208201526126f26126a361268861267760408601866125dc565b610160806040880152860191612028565b61269560608601866125dc565b908583036060870152612028565b6080840135608084015260a084013560a084015260c084013560c084015260e084013560e084015261010080850135908401526101206126e5818601866125dc565b9185840390860152612028565b9161270361014091828101906125dc565b929091818503910152612028565b949391929083604087016040885252606086019360608160051b8801019482600090815b848310612754575050505050508460206108b795968503910152612028565b9091929394977fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08b820301855288357ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea1843603018112156127cf57600191846127bd920161262c565b98602090810196950193019190612735565b8280fd5b908092918237016000815290565b9290936108b796959260c0958552602085015265ffffffffffff8092166040850152166060830152151560808201528160a0820152019061208a565b1561282457565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4141393220696e7465726e616c2063616c6c206f6e6c790000000000000000006044820152fd5b9060406108b79260008152816020820152019061208a565b6040906108b793928152816020820152019061208a565b909291925a936128c230331461281d565b8151946040860151955a6113886060830151890101116129e2576108b7966000958051612909575b50505090612903915a9003608084015101943691610682565b91615047565b612938916129349161292f855173ffffffffffffffffffffffffffffffffffffffff1690565b615c12565b1590565b612944575b80806128ea565b61290392919450612953615c24565b908151612967575b5050600193909161293d565b7f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a20173ffffffffffffffffffffffffffffffffffffffff6020870151926129d860206129c6835173ffffffffffffffffffffffffffffffffffffffff1690565b9201519560405193849316968361289a565b0390a3388061295b565b7fdeaddead0000000000000000000000000000000000000000000000000000000060005260206000fd5b612a22612a1c6040830183611fc8565b90615c07565b90612a33612a1c6060830183611fc8565b90612ae9612a48612a1c610120840184611fc8565b60405194859360208501956101008201359260e08301359260c08101359260a08201359260808301359273ffffffffffffffffffffffffffffffffffffffff60208201359135168c9693909a9998959261012098959273ffffffffffffffffffffffffffffffffffffffff6101408a019d168952602089015260408801526060870152608086015260a085015260c084015260e08301526101008201520152565b0391612b1b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938481018352826105ab565b51902060408051602081019283523091810191909152466060820152608092830181529091612b4a90826105ab565b51902090565b604051906040820182811067ffffffffffffffff821117612b7b575b60405260006020838281520152565b612b83610505565b612b6c565b906014116103595790601490565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009035818116939260148110612bcb57505050565b60140360031b82901b16169150565b9060c060a06108b793805184526020810151602085015260408101511515604085015265ffffffffffff80606083015116606086015260808201511660808501520151918160a0820152019061208a565b9294612c8c61044d95612c7a610100959998612c68612c54602097610140808c528b0190612bda565b9b878a019060208091805184520151910152565b80516060890152602001516080880152565b805160a08701526020015160c0860152565b73ffffffffffffffffffffffffffffffffffffffff81511660e0850152015191019060208091805184520151910152565b612d0661044d94612cf4612cdf60a0959998969960e0865260e0860190612bda565b98602085019060208091805184520151910152565b80516060840152602001516080830152565b019060208091805184520151910152565b9081602091031261035957516108b781610422565b9160206108b7938181520191612028565b90612d6c73ffffffffffffffffffffffffffffffffffffffff916108b797959694606085526060850191612028565b941660208201526040818503910152612028565b60009060033d11612d8d57565b905060046000803e60005160e01c90565b600060443d106108b7576040517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc91823d016004833e815167ffffffffffffffff918282113d602484011117612e1a57818401948551938411612e22573d85010160208487010111612e1a57506108b7929101602001906105ab565b949350505050565b50949350505050565b612e386040820182611fc8565b612e50612e448461256d565b93610120810190611fc8565b9290303b1561035957600093612e949160405196879586957f957122ab00000000000000000000000000000000000000000000000000000000875260048701612d3d565b0381305afa9081612f1d575b5061044d576001612eaf612d80565b6308c379a014612ec8575b612ec057565b61044d612183565b612ed0612d9e565b80612edc575b50612eba565b80516000925015612ed657610f21906040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301612882565b80610f48612f2a9261057b565b38612ea0565b9190612f3b9061317f565b73ffffffffffffffffffffffffffffffffffffffff929183166130da5761306c57612f659061317f565b9116612ffe57612f725750565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a490fd5b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601460408201527f41413334207369676e6174757265206572726f7200000000000000000000000060608201520190565b610f21836040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601760408201527f414132322065787069726564206f72206e6f742064756500000000000000000060608201520190565b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601460408201527f41413234207369676e6174757265206572726f7200000000000000000000000060608201520190565b9291906131549061317f565b909273ffffffffffffffffffffffffffffffffffffffff808095169116036130da5761306c57612f65905b80156131d25761318e9061535f565b73ffffffffffffffffffffffffffffffffffffffff65ffffffffffff8060408401511642119081156131c2575b5091511691565b90506020830151164210386131bb565b50600090600090565b156131e257565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f41413934206761732076616c756573206f766572666c6f7700000000000000006044820152fd5b916000915a9381519061325382826136b3565b61325c81612a0c565b602084015261329a6effffffffffffffffffffffffffffff60808401516060850151176040850151176101008401359060e0850135171711156131db565b6132a382613775565b6132ae818584613836565b97906132df6129346132d4875173ffffffffffffffffffffffffffffffffffffffff1690565b60208801519061546c565b6133db576132ec43600052565b73ffffffffffffffffffffffffffffffffffffffff61332460a0606097015173ffffffffffffffffffffffffffffffffffffffff1690565b166133c1575b505a810360a0840135106133545760809360c092604087015260608601525a900391013501910152565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601e60408201527f41413430206f76657220766572696669636174696f6e4761734c696d6974000060608201520190565b909350816133d2929750858461455c565b9590923861332a565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601a60408201527f4141323520696e76616c6964206163636f756e74206e6f6e636500000000000060608201520190565b9290916000925a825161345b81846136b3565b61346483612a0c565b60208501526134a26effffffffffffffffffffffffffffff60808301516060840151176040840151176101008601359060e0870135171711156131db565b6134ab81613775565b6134b78186868b613ba2565b98906134e86129346134dd865173ffffffffffffffffffffffffffffffffffffffff1690565b60208701519061546c565b6135e0576134f543600052565b73ffffffffffffffffffffffffffffffffffffffff61352d60a0606096015173ffffffffffffffffffffffffffffffffffffffff1690565b166135c5575b505a840360a08601351061355f5750604085015260608401526080919060c0905a900391013501910152565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601e60448201527f41413430206f76657220766572696669636174696f6e4761734c696d697400006064820152608490fd5b909250816135d79298508686856147ef565b96909138613533565b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601a60408201527f4141323520696e76616c6964206163636f756e74206e6f6e636500000000000060608201520190565b1561365557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4141393320696e76616c6964207061796d6173746572416e64446174610000006044820152fd5b613725906136dd6136c38261256d565b73ffffffffffffffffffffffffffffffffffffffff168452565b602081013560208401526080810135604084015260a0810135606084015260c0810135608084015260e081013560c084015261010081013560e0840152610120810190611fc8565b90811561376a5761374f61124c6112468460a09461374a601461044d9998101561364e565b612b88565b73ffffffffffffffffffffffffffffffffffffffff16910152565b505060a06000910152565b60a081015173ffffffffffffffffffffffffffffffffffffffff16156137b75760c060035b60ff60408401519116606084015102016080830151019101510290565b60c0600161379a565b6137d86040929594939560608352606083019061262c565b9460208201520152565b9061044d602f60405180947f414132332072657665727465643a20000000000000000000000000000000000060208301526138268151809260208686019101612067565b810103600f8101855201836105ab565b916000926000925a936139046020835193613865855173ffffffffffffffffffffffffffffffffffffffff1690565b9561387d6138766040830183611fc8565b9084613e0d565b60a086015173ffffffffffffffffffffffffffffffffffffffff16906138a243600052565b85809373ffffffffffffffffffffffffffffffffffffffff809416159889613b3a575b60600151908601516040517f3a871cdd0000000000000000000000000000000000000000000000000000000081529788968795869390600485016137c0565b03938a1690f1829181613b1a575b50613b115750600190613923612d80565b6308c379a014613abd575b50613a50575b613941575b50505a900391565b61396b9073ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b613986610a2c82546dffffffffffffffffffffffffffff1690565b8083116139e3576139dc926dffffffffffffffffffffffffffff9103166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b3880613939565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601760408201527f41413231206469646e2774207061792070726566756e6400000000000000000060608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601660408201527f4141323320726576657274656420286f72204f4f47290000000000000000000060608201520190565b613ac5612d9e565b9081613ad1575061392e565b610f2191613adf91506137e2565b6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301612882565b95506139349050565b613b3391925060203d81116123385761232981836105ab565b9038613912565b9450613b80610a2c613b6c8c73ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b546dffffffffffffffffffffffffffff1690565b8b811115613b975750856060835b969150506138c5565b606087918d03613b8e565b90926000936000935a94613beb6020835193613bd2855173ffffffffffffffffffffffffffffffffffffffff1690565b9561387d613be36040830183611fc8565b90848c61412b565b03938a1690f1829181613ded575b50613de45750600190613c0a612d80565b6308c379a014613d8e575b50613d20575b613c29575b5050505a900391565b613c539073ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b91613c6f610a2c84546dffffffffffffffffffffffffffff1690565b90818311613cba575082547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000169190036dffffffffffffffffffffffffffff16179055388080613c20565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152608490fd5b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601660408201527f4141323320726576657274656420286f72204f4f47290000000000000000000060608201520190565b613d96612d9e565b9081613da25750613c15565b8691613dae91506137e2565b90610f216040519283927f220266b60000000000000000000000000000000000000000000000000000000084526004840161289a565b9650613c1b9050565b613e0691925060203d81116123385761232981836105ab565b9038613bf9565b909180613e1957505050565b81515173ffffffffffffffffffffffffffffffffffffffff1692833b6140be57606083510151604051907f570e1a3600000000000000000000000000000000000000000000000000000000825260208280613e78878760048401612d2c565b0381600073ffffffffffffffffffffffffffffffffffffffff95867f0000000000000000000000007fc98430eaedbb6070b35b39d7987250490883481690f19182156140b1575b600092614091575b508082169586156140245716809503613fb7573b15613f4a5761124c6112467fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d93613f1193612b88565b602083810151935160a001516040805173ffffffffffffffffffffffffffffffffffffffff9485168152939091169183019190915290a3565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f4141313520696e6974436f6465206d757374206372656174652073656e64657260608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f4141313420696e6974436f6465206d7573742072657475726e2073656e64657260608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601b60408201527f4141313320696e6974436f6465206661696c6564206f72204f4f47000000000060608201520190565b6140aa91925060203d811161146a5761145b81836105ab565b9038613ec7565b6140b9612183565b613ebf565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601f60408201527f414131302073656e64657220616c726561647920636f6e73747275637465640060608201520190565b9290918161413a575b50505050565b82515173ffffffffffffffffffffffffffffffffffffffff1693843b6143e257606084510151604051907f570e1a3600000000000000000000000000000000000000000000000000000000825260208280614199888860048401612d2c565b0381600073ffffffffffffffffffffffffffffffffffffffff95867f0000000000000000000000007fc98430eaedbb6070b35b39d7987250490883481690f19182156143d5575b6000926143b5575b5080821696871561434757168096036142d9573b15614273575061124c6112467fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d9361423393612b88565b602083810151935160a001516040805173ffffffffffffffffffffffffffffffffffffffff9485168152939091169183019190915290a338808080614134565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602060448201527f4141313520696e6974436f6465206d757374206372656174652073656e6465726064820152608490fd5b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152602060408201527f4141313420696e6974436f6465206d7573742072657475726e2073656e64657260608201520190565b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601b60408201527f4141313320696e6974436f6465206661696c6564206f72204f4f47000000000060608201520190565b6143ce91925060203d811161146a5761145b81836105ab565b90386141e8565b6143dd612183565b6141e0565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601f60448201527f414131302073656e64657220616c726561647920636f6e7374727563746564006064820152608490fd5b1561444f57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4141343120746f6f206c6974746c6520766572696669636174696f6e476173006044820152fd5b919060408382031261035957825167ffffffffffffffff81116103595783019080601f83011215610359578151916144e483610639565b916144f260405193846105ab565b838352602084830101116103595760209261451291848085019101612067565b92015190565b9061044d602f60405180947f414133332072657665727465643a20000000000000000000000000000000000060208301526138268151809260208686019101612067565b93919260609460009460009380519261459b60a08a86015195614580888811614448565b015173ffffffffffffffffffffffffffffffffffffffff1690565b916145c68373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b946145e2610a2c87546dffffffffffffffffffffffffffff1690565b968588106147825773ffffffffffffffffffffffffffffffffffffffff60208a98946146588a966dffffffffffffffffffffffffffff8b6146919e03166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b015194604051998a98899788937ff465c77e000000000000000000000000000000000000000000000000000000008552600485016137c0565b0395169103f190818391849361475c575b506147555750506001906146b4612d80565b6308c379a014614733575b506146c657565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601660408201527f4141333320726576657274656420286f72204f4f47290000000000000000000060608201520190565b61473b612d9e565b908161474757506146bf565b610f2191613adf9150614518565b9450925050565b90925061477b91503d8085833e61477381836105ab565b8101906144ad565b91386146a2565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601e60408201527f41413331207061796d6173746572206465706f73697420746f6f206c6f77000060608201520190565b91949293909360609560009560009382519061481660a08b84015193614580848611614448565b936148418573ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b61485c610a2c82546dffffffffffffffffffffffffffff1690565b8781106149b7579273ffffffffffffffffffffffffffffffffffffffff60208a989693946146588a966dffffffffffffffffffffffffffff8d6148d69e9c9a03166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b0395169103f1908183918493614999575b506149915750506001906148f9612d80565b6308c379a014614972575b5061490c5750565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601660448201527f4141333320726576657274656420286f72204f4f4729000000000000000000006064820152608490fd5b61497a612d9e565b90816149865750614904565b613dae925050614518565b955093505050565b9092506149b091503d8085833e61477381836105ab565b91386148e7565b610f218a6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601e60408201527f41413331207061796d6173746572206465706f73697420746f6f206c6f77000060608201520190565b60031115614a2f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b929190614a7c6040916002865260606020870152606086019061208a565b930152565b939291906003811015614a2f57604091614a7c91865260606020870152606086019061208a565b9061044d603660405180947f4141353020706f73744f702072657665727465643a20000000000000000000006020830152614aec8151809260208686019101612067565b81010360168101855201836105ab565b929190925a93600091805191614b1183615318565b9260a0810195614b35875173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff93908481169081614ca457505050614b76825173ffffffffffffffffffffffffffffffffffffffff1690565b985b5a90030193840297604084019089825110614c37577f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f94614bc26020928c614c329551039061553a565b015194896020614c04614be9865173ffffffffffffffffffffffffffffffffffffffff1690565b9a5173ffffffffffffffffffffffffffffffffffffffff1690565b9401519785604051968796169a16988590949392606092608083019683521515602083015260408201520152565b0390a4565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f414135312070726566756e642062656c6f772061637475616c476173436f737460608201520190565b9a918051614cb4575b5050614b78565b6060850151600099509091803b15614ddb579189918983614d07956040518097819682957fa9a234090000000000000000000000000000000000000000000000000000000084528c029060048401614a5e565b0393f19081614dc8575b50614dc3576001614d20612d80565b6308c379a014614da4575b614d37575b3880614cad565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b614dac612d9e565b80614db75750614d2b565b613adf610f2191614aa8565b614d30565b80610f48614dd59261057b565b38614d11565b8980fd5b9392915a90600092805190614df382615318565b9360a0830196614e17885173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff95908681169081614f0d57505050614e58845173ffffffffffffffffffffffffffffffffffffffff1690565b915b5a9003019485029860408301908a825110614ea757507f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f949392614bc2614c32938c60209451039061553a565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602060448201527f414135312070726566756e642062656c6f772061637475616c476173436f73746064820152608490fd5b93918051614f1d575b5050614e5a565b606087015160009a509091803b1561504357918a918a83614f70956040518097819682957fa9a234090000000000000000000000000000000000000000000000000000000084528c029060048401614a5e565b0393f19081615030575b5061502b576001614f89612d80565b6308c379a01461500e575b614fa0575b3880614f16565b610f218b6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b615016612d9e565b806150215750614f94565b613dae8d91614aa8565b614f99565b80610f4861503d9261057b565b38614f7a565b8a80fd5b909392915a9480519161505983615318565b9260a081019561507d875173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff938185169182615165575050506150bd825173ffffffffffffffffffffffffffffffffffffffff1690565b985b5a90030193840297604084019089825110614c37577f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f946151096020928c614c329551039061553a565b61511288614a25565b015194896020615139614be9865173ffffffffffffffffffffffffffffffffffffffff1690565b940151604080519182529815602082015297880152606087015290821695909116939081906080820190565b9a918151615175575b50506150bf565b8784026151818a614a25565b60028a1461520c576060860151823b15610359576151d493600080948d604051978896879586937fa9a2340900000000000000000000000000000000000000000000000000000000855260048501614a81565b0393f180156151ff575b6151ec575b505b388061516e565b80610f486151f99261057b565b386151e3565b615207612183565b6151de565b6060860151823b156103595761525793600080948d604051978896879586937fa9a2340900000000000000000000000000000000000000000000000000000000855260048501614a81565b0393f19081615305575b50615300576001615270612d80565b6308c379a0146152ed575b156151e5576040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b6152f5612d9e565b80614db7575061527b565b6151e5565b80610f486153129261057b565b38615261565b60e060c082015191015180821461533c57480180821015615337575090565b905090565b5090565b6040519061534d8261058f565b60006040838281528260208201520152565b615367615340565b5065ffffffffffff808260a01c1680156153b3575b604051926153898461058f565b73ffffffffffffffffffffffffffffffffffffffff8116845260d01c602084015216604082015290565b508061537c565b6153cf6153d5916153c9615340565b5061535f565b9161535f565b9073ffffffffffffffffffffffffffffffffffffffff9182825116928315615461575b65ffffffffffff928391826040816020850151169301511693836040816020840151169201511690808410615459575b50808511615451575b506040519561543f8761058f565b16855216602084015216604082015290565b935038615431565b925038615428565b8151811693506153f8565b73ffffffffffffffffffffffffffffffffffffffff16600052600160205267ffffffffffffffff6154c88260401c60406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b918254926154d584612491565b9055161490565b9073ffffffffffffffffffffffffffffffffffffffff6154fa612b50565b9216600052600060205263ffffffff600160406000206dffffffffffffffffffffffffffff815460781c1685520154166020830152565b61044d3361562b565b73ffffffffffffffffffffffffffffffffffffffff16600052600060205260406000206dffffffffffffffffffffffffffff8082541692830180931161561e575b8083116155c05761044d92166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f6465706f736974206f766572666c6f77000000000000000000000000000000006044820152fd5b615626612190565b61557b565b73ffffffffffffffffffffffffffffffffffffffff9061564b348261553a565b168060005260006020527f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c460206dffffffffffffffffffffffffffff60406000205416604051908152a2565b1561569e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6d757374207370656369667920756e7374616b652064656c61790000000000006044820152fd5b1561570357565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f63616e6e6f7420646563726561736520756e7374616b652074696d65000000006044820152fd5b1561576857565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6e6f207374616b652073706563696669656400000000000000000000000000006044820152fd5b156157cd57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f7374616b65206f766572666c6f770000000000000000000000000000000000006044820152fd5b9065ffffffffffff6080600161044d9461588b6dffffffffffffffffffffffffffff86511682906dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b602085015115156eff000000000000000000000000000082549160701b16807fffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffff83161783557fffffff000000000000000000000000000000ffffffffffffffffffffffffffff7cffffffffffffffffffffffffffff000000000000000000000000000000604089015160781b16921617178155019263ffffffff6060820151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784550151167fffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffff69ffffffffffff0000000083549260201b169116179055565b1561599657565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f616c726561647920756e7374616b696e670000000000000000000000000000006044820152fd5b91909165ffffffffffff808094169116019182116121cd57565b15615a1557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4e6f207374616b6520746f2077697468647261770000000000000000000000006044820152fd5b15615a7a57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f6d7573742063616c6c20756e6c6f636b5374616b6528292066697273740000006044820152fd5b15615adf57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f5374616b65207769746864726177616c206973206e6f742064756500000000006044820152fd5b15615b4457565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661696c656420746f207769746864726177207374616b6500000000000000006044820152fd5b15615ba957565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6661696c656420746f20776974686472617700000000000000000000000000006044820152fd5b816040519182372090565b9060009283809360208451940192f190565b3d610800808211615c4b575b50604051906020818301016040528082526000602083013e90565b905038615c3056fea2646970667358221220a706d8b02d7086d80e9330811f5af84b2614abdc5e9a1f2260126070a31d7cee64736f6c63430008110033", + "balance": "0x0", + "nonce": "0x1" + }, + "69f4d1788e39c87893c980c06edf4b7f686e2938": { + "code": "0x6080604052600436106101dc5760003560e01c8063affed0e011610102578063e19a9dd911610095578063f08a032311610064578063f08a032314611647578063f698da2514611698578063f8dc5dd9146116c3578063ffa1ad741461173e57610231565b8063e19a9dd91461139b578063e318b52b146113ec578063e75235b81461147d578063e86637db146114a857610231565b8063cc2f8452116100d1578063cc2f8452146110e8578063d4d9bdcd146111b5578063d8d11f78146111f0578063e009cfde1461132a57610231565b8063affed0e014610d94578063b4faba0914610dbf578063b63e800d14610ea7578063c4ca3a9c1461101757610231565b80635624b25b1161017a5780636a761202116101495780636a761202146109945780637d83297414610b50578063934f3a1114610bbf578063a0e67e2b14610d2857610231565b80635624b25b146107fb5780635ae6bd37146108b9578063610b592514610908578063694e80c31461095957610231565b80632f54bf6e116101b65780632f54bf6e146104d35780633408e4701461053a578063468721a7146105655780635229073f1461067a57610231565b80630d582f131461029e57806312fb68e0146102f95780632d9ad53d1461046c57610231565b36610231573373ffffffffffffffffffffffffffffffffffffffff167f3d0ce9bfc3ed7d6862dbb28b2dea94561fe714a1b4d019aa8af39730d1ad7c3d346040518082815260200191505060405180910390a2005b34801561023d57600080fd5b5060007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b905080548061027257600080f35b36600080373360601b365260008060143601600080855af13d6000803e80610299573d6000fd5b3d6000f35b3480156102aa57600080fd5b506102f7600480360360408110156102c157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506117ce565b005b34801561030557600080fd5b5061046a6004803603608081101561031c57600080fd5b81019080803590602001909291908035906020019064010000000081111561034357600080fd5b82018360208201111561035557600080fd5b8035906020019184600183028401116401000000008311171561037757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001906401000000008111156103da57600080fd5b8201836020820111156103ec57600080fd5b8035906020019184600183028401116401000000008311171561040e57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190929190505050611bbe565b005b34801561047857600080fd5b506104bb6004803603602081101561048f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612440565b60405180821515815260200191505060405180910390f35b3480156104df57600080fd5b50610522600480360360208110156104f657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050612512565b60405180821515815260200191505060405180910390f35b34801561054657600080fd5b5061054f6125e4565b6040518082815260200191505060405180910390f35b34801561057157600080fd5b506106626004803603608081101561058857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156105cf57600080fd5b8201836020820111156105e157600080fd5b8035906020019184600183028401116401000000008311171561060357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506125f1565b60405180821515815260200191505060405180910390f35b34801561068657600080fd5b506107776004803603608081101561069d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156106e457600080fd5b8201836020820111156106f657600080fd5b8035906020019184600183028401116401000000008311171561071857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803560ff1690602001909291905050506127d7565b60405180831515815260200180602001828103825283818151815260200191508051906020019080838360005b838110156107bf5780820151818401526020810190506107a4565b50505050905090810190601f1680156107ec5780820380516001836020036101000a031916815260200191505b50935050505060405180910390f35b34801561080757600080fd5b5061083e6004803603604081101561081e57600080fd5b81019080803590602001909291908035906020019092919050505061280d565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561087e578082015181840152602081019050610863565b50505050905090810190601f1680156108ab5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b3480156108c557600080fd5b506108f2600480360360208110156108dc57600080fd5b8101908080359060200190929190505050612894565b6040518082815260200191505060405180910390f35b34801561091457600080fd5b506109576004803603602081101561092b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506128ac565b005b34801561096557600080fd5b506109926004803603602081101561097c57600080fd5b8101908080359060200190929190505050612c3e565b005b610b3860048036036101408110156109ab57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001906401000000008111156109f257600080fd5b820183602082011115610a0457600080fd5b80359060200191846001830284011164010000000083111715610a2657600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610ab257600080fd5b820183602082011115610ac457600080fd5b80359060200191846001830284011164010000000083111715610ae657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050612d78565b60405180821515815260200191505060405180910390f35b348015610b5c57600080fd5b50610ba960048036036040811015610b7357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506132b5565b6040518082815260200191505060405180910390f35b348015610bcb57600080fd5b50610d2660048036036060811015610be257600080fd5b810190808035906020019092919080359060200190640100000000811115610c0957600080fd5b820183602082011115610c1b57600080fd5b80359060200191846001830284011164010000000083111715610c3d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050919291929080359060200190640100000000811115610ca057600080fd5b820183602082011115610cb257600080fd5b80359060200191846001830284011164010000000083111715610cd457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506132da565b005b348015610d3457600080fd5b50610d3d613369565b6040518080602001828103825283818151815260200191508051906020019060200280838360005b83811015610d80578082015181840152602081019050610d65565b505050509050019250505060405180910390f35b348015610da057600080fd5b50610da9613512565b6040518082815260200191505060405180910390f35b348015610dcb57600080fd5b50610ea560048036036040811015610de257600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610e1f57600080fd5b820183602082011115610e3157600080fd5b80359060200191846001830284011164010000000083111715610e5357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050613518565b005b348015610eb357600080fd5b506110156004803603610100811015610ecb57600080fd5b8101908080359060200190640100000000811115610ee857600080fd5b820183602082011115610efa57600080fd5b80359060200191846020830284011164010000000083111715610f1c57600080fd5b909192939192939080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610f6757600080fd5b820183602082011115610f7957600080fd5b80359060200191846001830284011164010000000083111715610f9b57600080fd5b9091929391929390803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061353a565b005b34801561102357600080fd5b506110d26004803603608081101561103a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561108157600080fd5b82018360208201111561109357600080fd5b803590602001918460018302840111640100000000831117156110b557600080fd5b9091929391929390803560ff1690602001909291905050506136f8565b6040518082815260200191505060405180910390f35b3480156110f457600080fd5b506111416004803603604081101561110b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613820565b60405180806020018373ffffffffffffffffffffffffffffffffffffffff168152602001828103825284818151815260200191508051906020019060200280838360005b838110156111a0578082015181840152602081019050611185565b50505050905001935050505060405180910390f35b3480156111c157600080fd5b506111ee600480360360208110156111d857600080fd5b8101908080359060200190929190505050613a12565b005b3480156111fc57600080fd5b50611314600480360361014081101561121457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561125b57600080fd5b82018360208201111561126d57600080fd5b8035906020019184600183028401116401000000008311171561128f57600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050613bb1565b6040518082815260200191505060405180910390f35b34801561133657600080fd5b506113996004803603604081101561134d57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613bde565b005b3480156113a757600080fd5b506113ea600480360360208110156113be57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613f6f565b005b3480156113f857600080fd5b5061147b6004803603606081101561140f57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050613ff3565b005b34801561148957600080fd5b50611492614665565b6040518082815260200191505060405180910390f35b3480156114b457600080fd5b506115cc60048036036101408110156114cc57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561151357600080fd5b82018360208201111561152557600080fd5b8035906020019184600183028401116401000000008311171561154757600080fd5b9091929391929390803560ff169060200190929190803590602001909291908035906020019092919080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061466f565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561160c5780820151818401526020810190506115f1565b50505050905090810190601f1680156116395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561165357600080fd5b506116966004803603602081101561166a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050614817565b005b3480156116a457600080fd5b506116ad614878565b6040518082815260200191505060405180910390f35b3480156116cf57600080fd5b5061173c600480360360608110156116e657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506148f6565b005b34801561174a57600080fd5b50611753614d29565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015611793578082015181840152602081019050611778565b50505050905090810190601f1680156117c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6117d6614d62565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156118405750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b801561187857503073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6118ea576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146119eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506003600081548092919060010191905055507f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2682604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414611bba57611bb981612c3e565b5b5050565b611bd2604182614e0590919063ffffffff16565b82511015611c48576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6000808060008060005b8681101561243457611c648882614e3f565b80945081955082965050505060008460ff16141561206d578260001c9450611c96604188614e0590919063ffffffff16565b8260001c1015611d0e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8751611d2760208460001c614e6e90919063ffffffff16565b1115611d9b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006020838a01015190508851611dd182611dc360208760001c614e6e90919063ffffffff16565b614e6e90919063ffffffff16565b1115611e45576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60606020848b010190506320c13b0b60e01b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168773ffffffffffffffffffffffffffffffffffffffff166320c13b0b8d846040518363ffffffff1660e01b8152600401808060200180602001838103835285818151815260200191508051906020019080838360005b83811015611ee7578082015181840152602081019050611ecc565b50505050905090810190601f168015611f145780820380516001836020036101000a031916815260200191505b50838103825284818151815260200191508051906020019080838360005b83811015611f4d578082015181840152602081019050611f32565b50505050905090810190601f168015611f7a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b158015611f9957600080fd5b505afa158015611fad573d6000803e3d6000fd5b505050506040513d6020811015611fc357600080fd5b81019080805190602001909291905050507bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614612066576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b50506122b2565b60018460ff161415612181578260001c94508473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16148061210a57506000600860008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008c81526020019081526020016000205414155b61217c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6122b1565b601e8460ff1611156122495760018a60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a333200000000815250601c018281526020019150506040516020818303038152906040528051906020012060048603858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015612238573d6000803e3d6000fd5b5050506020604051035194506122b0565b60018a85858560405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa1580156122a3573d6000803e3d6000fd5b5050506020604051035194505b5b5b8573ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff161180156123795750600073ffffffffffffffffffffffffffffffffffffffff16600260008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b80156123b25750600173ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1614155b612424576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330323600000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8495508080600101915050611c52565b50505050505050505050565b60008173ffffffffffffffffffffffffffffffffffffffff16600173ffffffffffffffffffffffffffffffffffffffff161415801561250b5750600073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156125dd5750600073ffffffffffffffffffffffffffffffffffffffff16600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b9050919050565b6000804690508091505090565b6000600173ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16141580156126bc5750600073ffffffffffffffffffffffffffffffffffffffff16600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614155b61272e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61273b858585855a614e8d565b9050801561278b573373ffffffffffffffffffffffffffffffffffffffff167f6895c13664aa4f67288b25d7a21d7aaa34916e355fb9b6fae0a139a9085becb860405160405180910390a26127cf565b3373ffffffffffffffffffffffffffffffffffffffff167facd2c8702804128fdb0db2bb49f6d127dd0181c13fd45dbfe16de0930e2bd37560405160405180910390a25b949350505050565b600060606127e7868686866125f1565b915060405160203d0181016040523d81523d6000602083013e8091505094509492505050565b606060006020830267ffffffffffffffff8111801561282b57600080fd5b506040519080825280601f01601f19166020018201604052801561285e5781602001600182028036833780820191505090505b50905060005b8381101561288957808501548060208302602085010152508080600101915050612864565b508091505092915050565b60076020528060005260406000206000915090505481565b6128b4614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415801561291e5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b612990576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614612a91576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507fecdf3a3effea5783a3c4c2140e677577666428d44ed9d474a0b3a4c9943f844081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b612c46614d62565b600354811115612cbe576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001811015612d35576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b806004819055507f610f7ff2b304ae8903c3de74c60c6ab1f7d6226b3f52c5161905bb5ad4039c936004546040518082815260200191505060405180910390a150565b6000806000612d928e8e8e8e8e8e8e8e8e8e60055461466f565b905060056000815480929190600101919050555080805190602001209150612dbb8282866132da565b506000612dc6614ed9565b9050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614612fac578073ffffffffffffffffffffffffffffffffffffffff166375f0bb528f8f8f8f8f8f8f8f8f8f8f336040518d63ffffffff1660e01b8152600401808d73ffffffffffffffffffffffffffffffffffffffff1681526020018c8152602001806020018a6001811115612e6957fe5b81526020018981526020018881526020018781526020018673ffffffffffffffffffffffffffffffffffffffff1681526020018573ffffffffffffffffffffffffffffffffffffffff168152602001806020018473ffffffffffffffffffffffffffffffffffffffff16815260200183810383528d8d82818152602001925080828437600081840152601f19601f820116905080830192505050838103825285818151815260200191508051906020019080838360005b83811015612f3b578082015181840152602081019050612f20565b50505050905090810190601f168015612f685780820380516001836020036101000a031916815260200191505b509e505050505050505050505050505050600060405180830381600087803b158015612f9357600080fd5b505af1158015612fa7573d6000803e3d6000fd5b505050505b6101f4612fd36109c48b01603f60408d0281612fc457fe5b04614f0a90919063ffffffff16565b015a1015613049576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60005a90506130b28f8f8f8f8080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050508e60008d146130a7578e6130ad565b6109c45a035b614e8d565b93506130c75a82614f2490919063ffffffff16565b905083806130d6575060008a14155b806130e2575060008814155b613154576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60008089111561316e5761316b828b8b8b8b614f44565b90505b84156131b8577f442e715f626346e8c54381002da614f62bee8d27386535b2521ec8540898556e8482604051808381526020018281526020019250505060405180910390a16131f8565b7f23428b18acfb3ea64b08dc0c1d296ea9c09702c09083ca5272e64d115b687d238482604051808381526020018281526020019250505060405180910390a15b5050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16146132a4578073ffffffffffffffffffffffffffffffffffffffff16639327136883856040518363ffffffff1660e01b815260040180838152602001821515815260200192505050600060405180830381600087803b15801561328b57600080fd5b505af115801561329f573d6000803e3d6000fd5b505050505b50509b9a5050505050505050505050565b6008602052816000526040600020602052806000526040600020600091509150505481565b6000600454905060008111613357576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b61336384848484611bbe565b50505050565b6060600060035467ffffffffffffffff8111801561338657600080fd5b506040519080825280602002602001820160405280156133b55781602001602082028036833780820191505090505b50905060008060026000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614613509578083838151811061346057fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600260008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050818060010192505061341f565b82935050505090565b60055481565b600080825160208401855af4806000523d6020523d600060403e60403d016000fd5b6135858a8a80806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050508961514a565b600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16146135c3576135c28461564a565b5b6136118787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050615679565b600082111561362b5761362982600060018685614f44565b505b3373ffffffffffffffffffffffffffffffffffffffff167f141df868a6331af528e38c83b7aa03edc19be66e37ae67f9285bf4f8e3c6a1a88b8b8b8b8960405180806020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281038252878782818152602001925060200280828437600081840152601f19601f820116905080830192505050965050505050505060405180910390a250505050505050505050565b6000805a905061374f878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f82011690508083019250505050505050865a614e8d565b61375857600080fd5b60005a8203905080604051602001808281526020019150506040516020818303038152906040526040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156137e55780820151818401526020810190506137ca565b50505050905090810190601f1680156138125780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b606060008267ffffffffffffffff8111801561383b57600080fd5b5060405190808252806020026020018201604052801561386a5781602001602082028036833780820191505090505b509150600080600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415801561393d5750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561394857508482105b15613a03578084838151811061395a57fe5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081806001019250506138d3565b80925081845250509250929050565b600073ffffffffffffffffffffffffffffffffffffffff16600260003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415613b14576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001600860003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000838152602001908152602001600020819055503373ffffffffffffffffffffffffffffffffffffffff16817ff2a0eb156472d1440255b0d7c1e19cc07115d1051fe605b0dce69acfec884d9c60405160405180910390a350565b6000613bc68c8c8c8c8c8c8c8c8c8c8c61466f565b8051906020012090509b9a5050505050505050505050565b613be6614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614158015613c505750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b613cc2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614613dc2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600160008273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507faab4fa2b463f581b2b32cb3b7e3b704b9ce37cc209b5fb4d77e593ace405427681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613f77614d62565b60007f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b90508181557f1151116914515bc0891ff9047a6cb32cf902546f83066499bcf8ba33d2353fa282604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a15050565b613ffb614d62565b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156140655750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561409d57503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b61410f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614210576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415801561427a5750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b6142ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146143ec576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a17f9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea2681604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a1505050565b6000600454905090565b606060007fbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d860001b8d8d8d8d60405180838380828437808301925050509250505060405180910390208c8c8c8c8c8c8c604051602001808c81526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018a815260200189815260200188600181111561470057fe5b81526020018781526020018681526020018581526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018373ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019b505050505050505050505050604051602081830303815290604052805190602001209050601960f81b600160f81b61478c614878565b8360405160200180857effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152600101847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526001018381526020018281526020019450505050506040516020818303038152906040529150509b9a5050505050505050505050565b61481f614d62565b6148288161564a565b7f5ac6c46c93c8d0e53714ba3b53db3e7c046da994313d7ed0d192028bc7c228b081604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a150565b60007f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b6148a66125e4565b30604051602001808481526020018381526020018273ffffffffffffffffffffffffffffffffffffffff168152602001935050505060405160208183030381529060405280519060200120905090565b6148fe614d62565b806001600354031015614979576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141580156149e35750600173ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614155b614a55576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614614b55576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303500000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600360008154809291906001900391905055507ff8d49fc529812e9a7c5c50e69c20f0dccc0db8fa95c98bc58cc9a4f1c1299eaf82604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18060045414614d2457614d2381612c3e565b5b505050565b6040518060400160405280600581526020017f312e332e3000000000000000000000000000000000000000000000000000000081525081565b3073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614614e03576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330333100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b565b600080831415614e185760009050614e39565b6000828402905082848281614e2957fe5b0414614e3457600080fd5b809150505b92915050565b60008060008360410260208101860151925060408101860151915060ff60418201870151169350509250925092565b600080828401905083811015614e8357600080fd5b8091505092915050565b6000600180811115614e9b57fe5b836001811115614ea757fe5b1415614ec0576000808551602087018986f49050614ed0565b600080855160208701888a87f190505b95945050505050565b6000807f4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c860001b9050805491505090565b600081831015614f1a5781614f1c565b825b905092915050565b600082821115614f3357600080fd5b600082840390508091505092915050565b600080600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614614f815782614f83565b325b9050600073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16141561509b57614fed3a8610614fca573a614fcc565b855b614fdf888a614e6e90919063ffffffff16565b614e0590919063ffffffff16565b91508073ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050615096576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b615140565b6150c0856150b2888a614e6e90919063ffffffff16565b614e0590919063ffffffff16565b91506150cd8482846158b4565b61513f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330313200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5095945050505050565b6000600454146151c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b8151811115615239576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303100000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60018110156152b0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303200000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b60006001905060005b83518110156155b65760008482815181106152d057fe5b60200260200101519050600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16141580156153445750600173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b801561537c57503073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614155b80156153b457508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614155b615426576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303300000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff16600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614615527576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475332303400000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b80600260008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508092505080806001019150506152b9565b506001600260008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550825160038190555081600481905550505050565b60007f6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d560001b90508181555050565b600073ffffffffffffffffffffffffffffffffffffffff1660016000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461577b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475331303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b6001806000600173ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16146158b05761583d8260008360015a614e8d565b6158af576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260058152602001807f475330303000000000000000000000000000000000000000000000000000000081525060200191505060405180910390fd5b5b5050565b60008063a9059cbb8484604051602401808373ffffffffffffffffffffffffffffffffffffffff168152602001828152602001925050506040516020818303038152906040529060e01b6020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050602060008251602084016000896127105a03f13d6000811461595b5760208114615963576000935061596e565b81935061596e565b600051158215171593505b505050939250505056fea26469706673582212203874bcf92e1722cc7bfa0cef1a0985cf0dc3485ba0663db3747ccdf1605df53464736f6c63430007060033", + "balance": "0x0", + "nonce": "0x1" + }, + "7fc98430eaedbb6070b35b39d798725049088348": { + "code": "0x6080604052600436101561001257600080fd5b6000803560e01c63570e1a361461002857600080fd5b346100c95760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126100c95760043567ffffffffffffffff918282116100c957366023830112156100c95781600401359283116100c95736602484840101116100c9576100c561009e84602485016100fc565b60405173ffffffffffffffffffffffffffffffffffffffff90911681529081906020820190565b0390f35b80fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b90806014116101bb5767ffffffffffffffff917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec82018381116101cd575b604051937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0603f81600b8701160116850190858210908211176101c0575b604052808452602084019036848401116101bb576020946000600c819682946014880187378301015251923560601c5af19060005191156101b557565b60009150565b600080fd5b6101c86100cc565b610178565b6101d56100cc565b61013a56fea26469706673582212201927e80b76ab9b71c952137dd676621a9fdf520c25928815636594036eb1c40364736f6c63430008110033", + "balance": "0x0", + "nonce": "0x1" + }, + "914d7fec6aac8cd542e72bca78b30650d45643d7": { + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3", + "balance": "0x0", + "nonce": "0x1" + }, + "998739bfdaadde7c933b942a68053933098f9eda": { + "code": "0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b7f000000000000000000000000998739bfdaadde7c933b942a68053933098f9eda73ffffffffffffffffffffffffffffffffffffffff163073ffffffffffffffffffffffffffffffffffffffff161415610183576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260308152602001806102106030913960400191505060405180910390fd5b805160205b8181101561020a578083015160f81c6001820184015160601c6015830185015160358401860151605585018701600085600081146101cd57600181146101dd576101e8565b6000808585888a5af191506101e8565b6000808585895af491505b5060008114156101f757600080fd5b8260550187019650505050505050610188565b50505056fe4d756c746953656e642073686f756c64206f6e6c792062652063616c6c6564207669612064656c656761746563616c6ca26469706673582212205c784303626eec02b71940b551976170b500a8a36cc5adcbeb2c19751a76d05464736f6c63430007060033", + "balance": "0x0", + "nonce": "0x1" + }, + "a1dabef33b3b82c7814b6d82a79e50f4ac44102b": { + "code": "0x60806040526004361061001e5760003560e01c80638d80ff0a14610023575b600080fd5b6100dc6004803603602081101561003957600080fd5b810190808035906020019064010000000081111561005657600080fd5b82018360208201111561006857600080fd5b8035906020019184600183028401116401000000008311171561008a57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506100de565b005b805160205b8181101561015f578083015160f81c6001820184015160601c60158301850151603584018601516055850187016000856000811461012857600181146101385761013d565b6000808585888a5af1915061013d565b600080fd5b50600081141561014c57600080fd5b82605501870196505050505050506100e3565b50505056fea264697066735822122035246402746c96964495cae5b36461fd44dfb89f8e6cf6f6b8d60c0aa89f414864736f6c63430007060033", + "balance": "0x0", + "nonce": "0x1" + }, + "ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed": { + "code": "0x60806040526004361061018a5760003560e01c806381503da1116100d6578063d323826a1161007f578063e96deee411610059578063e96deee414610395578063f5745aba146103a8578063f9664498146103bb57600080fd5b8063d323826a1461034f578063ddda0acb1461036f578063e437252a1461038257600080fd5b80639c36a286116100b05780639c36a28614610316578063a7db93f214610329578063c3fe107b1461033c57600080fd5b806381503da1146102d0578063890c283b146102e357806398e810771461030357600080fd5b80632f990e3f116101385780636cec2536116101125780636cec25361461027d57806374637a7a1461029d5780637f565360146102bd57600080fd5b80632f990e3f1461023757806331a7c8c81461024a57806342d654fc1461025d57600080fd5b806327fe18221161016957806327fe1822146101f15780632852527a1461020457806328ddd0461461021757600080fd5b8062d84acb1461018f57806326307668146101cb57806326a32fc7146101de575b600080fd5b6101a261019d366004612915565b6103ce565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6101a26101d9366004612994565b6103e6565b6101a26101ec3660046129db565b610452565b6101a26101ff3660046129db565b6104de565b6101a2610212366004612a39565b610539565b34801561022357600080fd5b506101a2610232366004612a90565b6106fe565b6101a2610245366004612aa9565b61072a565b6101a2610258366004612aa9565b6107bb565b34801561026957600080fd5b506101a2610278366004612b1e565b6107c9565b34801561028957600080fd5b506101a2610298366004612a90565b610823565b3480156102a957600080fd5b506101a26102b8366004612b4a565b61084f565b6101a26102cb3660046129db565b611162565b6101a26102de366004612b74565b6111e8565b3480156102ef57600080fd5b506101a26102fe366004612bac565b611276565b6101a2610311366004612bce565b6112a3565b6101a2610324366004612994565b611505565b6101a2610337366004612c49565b6116f1565b6101a261034a366004612aa9565b611964565b34801561035b57600080fd5b506101a261036a366004612cd9565b6119ed565b6101a261037d366004612c49565b611a17565b6101a2610390366004612bce565b611e0c565b6101a26103a3366004612915565b611e95565b6101a26103b6366004612bce565b611ea4565b6101a26103c9366004612b74565b611f2d565b60006103dd8585858533611a17565b95945050505050565b6000806103f2846120db565b90508083516020850134f59150610408826123d3565b604051819073ffffffffffffffffffffffffffffffffffffffff8416907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a35092915050565b60006104d86104d260408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b836103e6565b92915050565b600081516020830134f090506104f3816123d3565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a2919050565b600080610545856120db565b905060008460601b90506040517f3d602d80600a3d3981f3363d3d373d3d3d363d7300000000000000000000000081528160148201527f5af43d82803e903d91602b57fd5bf300000000000000000000000000000000006028820152826037826000f593505073ffffffffffffffffffffffffffffffffffffffff8316610635576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed1660048201526024015b60405180910390fd5b604051829073ffffffffffffffffffffffffffffffffffffffff8516907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a36000808473ffffffffffffffffffffffffffffffffffffffff1634876040516106a19190612d29565b60006040518083038185875af1925050503d80600081146106de576040519150601f19603f3d011682016040523d82523d6000602084013e6106e3565b606091505b50915091506106f382828961247d565b505050509392505050565b60006104d87f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed8361084f565b60006107b36107aa60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b85858533611a17565b949350505050565b60006107b3848484336112a3565b60006040518260005260ff600b53836020527f21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f6040526055600b20601452806040525061d694600052600160345350506017601e20919050565b60006104d8827f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed6107c9565b600060607f9400000000000000000000000000000000000000000000000000000000000000610887600167ffffffffffffffff612d45565b67ffffffffffffffff16841115610902576040517f3c55ab3b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b836000036109c7576040517fd60000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f800000000000000000000000000000000000000000000000000000000000000060368201526037015b6040516020818303038152906040529150611152565b607f8411610a60576040517fd60000000000000000000000000000000000000000000000000000000000000060208201527fff0000000000000000000000000000000000000000000000000000000000000080831660218301527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b16602283015260f886901b1660368201526037016109b1565b60ff8411610b1f576040517fd70000000000000000000000000000000000000000000000000000000000000060208201527fff0000000000000000000000000000000000000000000000000000000000000080831660218301527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606088901b1660228301527f8100000000000000000000000000000000000000000000000000000000000000603683015260f886901b1660378201526038016109b1565b61ffff8411610bff576040517fd80000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f820000000000000000000000000000000000000000000000000000000000000060368201527fffff00000000000000000000000000000000000000000000000000000000000060f086901b1660378201526039016109b1565b62ffffff8411610ce0576040517fd90000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f830000000000000000000000000000000000000000000000000000000000000060368201527fffffff000000000000000000000000000000000000000000000000000000000060e886901b166037820152603a016109b1565b63ffffffff8411610dc2576040517fda0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f840000000000000000000000000000000000000000000000000000000000000060368201527fffffffff0000000000000000000000000000000000000000000000000000000060e086901b166037820152603b016109b1565b64ffffffffff8411610ea5576040517fdb0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f850000000000000000000000000000000000000000000000000000000000000060368201527fffffffffff00000000000000000000000000000000000000000000000000000060d886901b166037820152603c016109b1565b65ffffffffffff8411610f89576040517fdc0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f860000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffff000000000000000000000000000000000000000000000000000060d086901b166037820152603d016109b1565b66ffffffffffffff841161106e576040517fdd0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f870000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffffff0000000000000000000000000000000000000000000000000060c886901b166037820152603e016109b1565b6040517fde0000000000000000000000000000000000000000000000000000000000000060208201527fff00000000000000000000000000000000000000000000000000000000000000821660218201527fffffffffffffffffffffffffffffffffffffffff000000000000000000000000606087901b1660228201527f880000000000000000000000000000000000000000000000000000000000000060368201527fffffffffffffffff00000000000000000000000000000000000000000000000060c086901b166037820152603f0160405160208183030381529060405291505b5080516020909101209392505050565b60006104d86111e260408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b83611505565b600061126f61126860408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b8484610539565b9392505050565b600061126f83837f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed6119ed565b60008451602086018451f090506112b9816123d3565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a26000808273ffffffffffffffffffffffffffffffffffffffff168560200151876040516113279190612d29565b60006040518083038185875af1925050503d8060008114611364576040519150601f19603f3d011682016040523d82523d6000602084013e611369565b606091505b5091509150816113c9577f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed1631156114fb578373ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed73ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d8060008114611495576040519150601f19603f3d011682016040523d82523d6000602084013e61149a565b606091505b509092509050816114fb577f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b5050949350505050565b600080611511846120db565b905060006040518060400160405280601081526020017f67363d3d37363d34f03d5260086018f30000000000000000000000000000000081525090506000828251602084016000f5905073ffffffffffffffffffffffffffffffffffffffff81166115e0576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b604051839073ffffffffffffffffffffffffffffffffffffffff8316907f2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c906790600090a361162c83610823565b935060008173ffffffffffffffffffffffffffffffffffffffff1634876040516116569190612d29565b60006040518083038185875af1925050503d8060008114611693576040519150601f19603f3d011682016040523d82523d6000602084013e611698565b606091505b505090506116a681866124ff565b60405173ffffffffffffffffffffffffffffffffffffffff8616907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a25050505092915050565b6000806116fd876120db565b9050808651602088018651f59150611714826123d3565b604051819073ffffffffffffffffffffffffffffffffffffffff8416907fb8fda7e00c6b06a2b54e58521bc5894fee35f1090e5a3bb6390bfe2b98b497f790600090a36000808373ffffffffffffffffffffffffffffffffffffffff168660200151886040516117849190612d29565b60006040518083038185875af1925050503d80600081146117c1576040519150601f19603f3d011682016040523d82523d6000602084013e6117c6565b606091505b509150915081611826577f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed163115611958578473ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed73ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d80600081146118f2576040519150601f19603f3d011682016040523d82523d6000602084013e6118f7565b606091505b50909250905081611958577f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b50505095945050505050565b60006107b36119e460408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b858585336116f1565b6000604051836040820152846020820152828152600b8101905060ff815360559020949350505050565b600080611a23876120db565b905060006040518060400160405280601081526020017f67363d3d37363d34f03d5260086018f30000000000000000000000000000000081525090506000828251602084016000f5905073ffffffffffffffffffffffffffffffffffffffff8116611af2576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b604051839073ffffffffffffffffffffffffffffffffffffffff8316907f2feea65dd4e9f9cbd86b74b7734210c59a1b2981b5b137bd0ee3e208200c906790600090a3611b3e83610823565b935060008173ffffffffffffffffffffffffffffffffffffffff1687600001518a604051611b6c9190612d29565b60006040518083038185875af1925050503d8060008114611ba9576040519150601f19603f3d011682016040523d82523d6000602084013e611bae565b606091505b50509050611bbc81866124ff565b60405173ffffffffffffffffffffffffffffffffffffffff8616907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a260608573ffffffffffffffffffffffffffffffffffffffff1688602001518a604051611c299190612d29565b60006040518083038185875af1925050503d8060008114611c66576040519150601f19603f3d011682016040523d82523d6000602084013e611c6b565b606091505b50909250905081611ccc577f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed816040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b73ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed163115611dfe578673ffffffffffffffffffffffffffffffffffffffff167f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed73ffffffffffffffffffffffffffffffffffffffff163160405160006040518083038185875af1925050503d8060008114611d98576040519150601f19603f3d011682016040523d82523d6000602084013e611d9d565b606091505b50909250905081611dfe577f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed816040517fc2b3f44500000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b505050505095945050505050565b60006103dd611e8c60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b868686866116f1565b60006103dd85858585336116f1565b60006103dd611f2460408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b86868686611a17565b6000808360601b90506040517f3d602d80600a3d3981f3363d3d373d3d3d363d7300000000000000000000000081528160148201527f5af43d82803e903d91602b57fd5bf3000000000000000000000000000000000060288201526037816000f092505073ffffffffffffffffffffffffffffffffffffffff8216612016576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b60405173ffffffffffffffffffffffffffffffffffffffff8316907f4db17dd5e4732fb6da34a148104a592783ca119a1e7bb8829eba6cbadef0b51190600090a26000808373ffffffffffffffffffffffffffffffffffffffff1634866040516120809190612d29565b60006040518083038185875af1925050503d80600081146120bd576040519150601f19603f3d011682016040523d82523d6000602084013e6120c2565b606091505b50915091506120d282828861247d565b50505092915050565b60008060006120e9846125b3565b9092509050600082600281111561210257612102612e02565b1480156121205750600081600281111561211e5761211e612e02565b145b1561215e57604080513360208201524691810191909152606081018590526080016040516020818303038152906040528051906020012092506123cc565b600082600281111561217257612172612e02565b1480156121905750600181600281111561218e5761218e612e02565b145b156121b0576121a9338560009182526020526040902090565b92506123cc565b60008260028111156121c4576121c4612e02565b03612233576040517f13b3a2a100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b600182600281111561224757612247612e02565b1480156122655750600081600281111561226357612263612e02565b145b1561227e576121a9468560009182526020526040902090565b600182600281111561229257612292612e02565b1480156122b0575060028160028111156122ae576122ae612e02565b145b1561231f576040517f13b3a2a100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b61239a60408051437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101406020830152419282019290925260608101919091524260808201524460a08201524660c08201523360e08201526000906101000160405160208183030381529060405280519060200120905090565b84036123a657836123c9565b604080516020810186905201604051602081830303815290604052805190602001205b92505b5050919050565b73ffffffffffffffffffffffffffffffffffffffff8116158061240b575073ffffffffffffffffffffffffffffffffffffffff81163b155b1561247a576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b50565b82158061249f575073ffffffffffffffffffffffffffffffffffffffff81163b155b156124fa577f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed826040517fa57ca23900000000000000000000000000000000000000000000000000000000815260040161062c929190612d94565b505050565b811580612520575073ffffffffffffffffffffffffffffffffffffffff8116155b80612540575073ffffffffffffffffffffffffffffffffffffffff81163b155b156125af576040517fc05cee7a00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000ba5ed099633d3b313e4d5f7bdc1305d3c28ba5ed16600482015260240161062c565b5050565b600080606083901c3314801561261057508260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000145b1561262057506000905080915091565b606083901c3314801561265a57507fff00000000000000000000000000000000000000000000000000000000000000601484901a60f81b16155b1561266b5750600090506001915091565b33606084901c036126825750600090506002915091565b606083901c1580156126db57508260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000145b156126ec5750600190506000915091565b606083901c15801561272557507fff00000000000000000000000000000000000000000000000000000000000000601484901a60f81b16155b1561273557506001905080915091565b606083901c61274a5750600190506002915091565b8260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167f0100000000000000000000000000000000000000000000000000000000000000036127a55750600290506000915091565b8260141a60f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166000036127e15750600290506001915091565b506002905080915091565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f83011261282c57600080fd5b813567ffffffffffffffff80821115612847576128476127ec565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190828211818310171561288d5761288d6127ec565b816040528381528660208588010111156128a657600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000604082840312156128d857600080fd5b6040516040810181811067ffffffffffffffff821117156128fb576128fb6127ec565b604052823581526020928301359281019290925250919050565b60008060008060a0858703121561292b57600080fd5b84359350602085013567ffffffffffffffff8082111561294a57600080fd5b6129568883890161281b565b9450604087013591508082111561296c57600080fd5b506129798782880161281b565b92505061298986606087016128c6565b905092959194509250565b600080604083850312156129a757600080fd5b82359150602083013567ffffffffffffffff8111156129c557600080fd5b6129d18582860161281b565b9150509250929050565b6000602082840312156129ed57600080fd5b813567ffffffffffffffff811115612a0457600080fd5b6107b38482850161281b565b803573ffffffffffffffffffffffffffffffffffffffff81168114612a3457600080fd5b919050565b600080600060608486031215612a4e57600080fd5b83359250612a5e60208501612a10565b9150604084013567ffffffffffffffff811115612a7a57600080fd5b612a868682870161281b565b9150509250925092565b600060208284031215612aa257600080fd5b5035919050565b600080600060808486031215612abe57600080fd5b833567ffffffffffffffff80821115612ad657600080fd5b612ae28783880161281b565b94506020860135915080821115612af857600080fd5b50612b058682870161281b565b925050612b1585604086016128c6565b90509250925092565b60008060408385031215612b3157600080fd5b82359150612b4160208401612a10565b90509250929050565b60008060408385031215612b5d57600080fd5b612b6683612a10565b946020939093013593505050565b60008060408385031215612b8757600080fd5b612b9083612a10565b9150602083013567ffffffffffffffff8111156129c557600080fd5b60008060408385031215612bbf57600080fd5b50508035926020909101359150565b60008060008060a08587031215612be457600080fd5b843567ffffffffffffffff80821115612bfc57600080fd5b612c088883890161281b565b95506020870135915080821115612c1e57600080fd5b50612c2b8782880161281b565b935050612c3b86604087016128c6565b915061298960808601612a10565b600080600080600060c08688031215612c6157600080fd5b85359450602086013567ffffffffffffffff80821115612c8057600080fd5b612c8c89838a0161281b565b95506040880135915080821115612ca257600080fd5b50612caf8882890161281b565b935050612cbf87606088016128c6565b9150612ccd60a08701612a10565b90509295509295909350565b600080600060608486031215612cee57600080fd5b8335925060208401359150612b1560408501612a10565b60005b83811015612d20578181015183820152602001612d08565b50506000910152565b60008251612d3b818460208701612d05565b9190910192915050565b67ffffffffffffffff828116828216039080821115612d8d577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b5092915050565b73ffffffffffffffffffffffffffffffffffffffff831681526040602082015260008251806040840152612dcf816060850160208701612d05565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016919091016060019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c6343000817000a", + "balance": "0x0", + "nonce": "0x1" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30000": { + "code": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d501461004657806382e3702d14610098578063cafa81dc146100cb575b600080fd5b6100826040518060400160405280600c81526020017f312e312e312d626574612e33000000000000000000000000000000000000000081525081565b60405161008f919061019b565b60405180910390f35b6100bb6100a63660046101ec565b60006020819052908152604090205460ff1681565b604051901515815260200161008f565b6100de6100d9366004610234565b6100e0565b005b600160008083336040516020016100f8929190610303565b604080518083037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001815291815281516020928301208352908201929092520160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001691151591909117905550565b60005b8381101561018657818101518382015260200161016e565b83811115610195576000848401525b50505050565b60208152600082518060208401526101ba81604085016020870161016b565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169190910160400192915050565b6000602082840312156101fe57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561024657600080fd5b813567ffffffffffffffff8082111561025e57600080fd5b818401915084601f83011261027257600080fd5b81358181111561028457610284610205565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156102ca576102ca610205565b816040528281528760208487010111156102e357600080fd5b826020860160208301376000928101602001929092525095945050505050565b6000835161031581846020880161016b565b60609390931b7fffffffffffffffffffffffffffffffffffffffff00000000000000000000000016919092019081526014019291505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30001": { + "code": "0x6080604052600436106102385760003560e01c806386a0003c11610138578063c3c5a547116100b0578063d9ba32fc1161007f578063e73a914c11610064578063e73a914c146109b7578063f6e4b62b146109d7578063fac2c621146109f757600080fd5b8063d9ba32fc1461092f578063e559afd91461099757600080fd5b8063c3c5a54714610858578063c4d66de8146108bd578063c55b6bb7146108dd578063d124d1bc146108fd57600080fd5b80639e4f8ab811610107578063ad3e080a116100ec578063ad3e080a146107f8578063b6b3527214610818578063c375c2ef1461083857600080fd5b80639e4f8ab81461076c5780639f8a13d71461078e57600080fd5b806386a0003c146106a4578063871ff4051461070c5780638f4842da1461072c57806399c6066c1461074c57600080fd5b80633b66e9f6116101cb5780635cfbd78b1161019a57806364efb22b1161017f57806364efb22b1461053757806369dc9ff3146105a25780637901868e1461068457600080fd5b80635cfbd78b146105045780635e35359e1461051757600080fd5b80633b66e9f61461039b5780634162169f146104005780634782f7791461046b5780634f4e2e211461048b57600080fd5b80631c5d647c116102075780631c5d647c1461031b5780632be0a2951461033b5780632ce962cf1461035b578063368da1681461037b57600080fd5b8063108f5c6914610244578063139e0aa71461026657806314695ea41461027957806315ea16ad146102dd57600080fd5b3661023f57005b600080fd5b34801561025057600080fd5b5061026461025f366004613af3565b610a17565b005b610264610274366004613b71565b610c2e565b34801561028557600080fd5b506102c8610294366004613b9d565b60009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e002602052604090205460ff1690565b60405190151581526020015b60405180910390f35b3480156102e957600080fd5b507f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e003545b6040519081526020016102d4565b34801561032757600080fd5b50610264610336366004613bc4565b610e1f565b34801561034757600080fd5b50610264610356366004613bf4565b610f6f565b34801561036757600080fd5b50610264610376366004613bf4565b6110ef565b34801561038757600080fd5b50610264610396366004613b71565b6112f4565b3480156103a757600080fd5b5061030d6103b6366004613c22565b73ffffffffffffffffffffffffffffffffffffffff1660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090206001015490565b34801561040c57600080fd5b507f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016102d4565b34801561047757600080fd5b50610264610486366004613b71565b6114ed565b34801561049757600080fd5b506102c86104a6366004613c3f565b73ffffffffffffffffffffffffffffffffffffffff91821660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602090815260408083209390941682526005909201909152205460ff1690565b610264610512366004613c6d565b611707565b34801561052357600080fd5b50610264610532366004613c6d565b611a4d565b34801561054357600080fd5b50610446610552366004613c22565b73ffffffffffffffffffffffffffffffffffffffff90811660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00160205260409020546201000090041690565b3480156105ae57600080fd5b506106386105bd366004613c22565b73ffffffffffffffffffffffffffffffffffffffff90811660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020526040902080546001820154600283015460049093015460ff8084169661010085048216966201000090950490941694929392811692911690565b604080519615158752941515602087015273ffffffffffffffffffffffffffffffffffffffff9093169385019390935260608401529015156080830152151560a082015260c0016102d4565b34801561069057600080fd5b5061026461069f366004613cae565b611e05565b3480156106b057600080fd5b506102c86106bf366004613c22565b73ffffffffffffffffffffffffffffffffffffffff1660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090206004015460ff1690565b34801561071857600080fd5b50610264610727366004613b71565b61207e565b34801561073857600080fd5b50610264610747366004613d21565b61219b565b34801561075857600080fd5b50610264610767366004613b71565b6123a8565b34801561077857600080fd5b506107816124a1565b6040516102d49190613da9565b34801561079a57600080fd5b506102c86107a9366004613c22565b73ffffffffffffffffffffffffffffffffffffffff1660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020526040902054610100900460ff1690565b34801561080457600080fd5b50610264610813366004613d21565b6125c8565b34801561082457600080fd5b506102c8610833366004613c3f565b6127d5565b34801561084457600080fd5b50610264610853366004613c22565b612888565b34801561086457600080fd5b506102c8610873366004613c22565b73ffffffffffffffffffffffffffffffffffffffff1660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205460ff1690565b3480156108c957600080fd5b506102646108d8366004613c22565b6129cd565b3480156108e957600080fd5b506102646108f8366004613c3f565b612bd5565b34801561090957600080fd5b5061091d610918366004613b9d565b612e32565b6040516102d496959493929190613ded565b34801561093b57600080fd5b506102c861094a366004613c22565b73ffffffffffffffffffffffffffffffffffffffff1660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090206002015460ff1690565b3480156109a357600080fd5b506102646109b2366004613d21565b612f4c565b3480156109c357600080fd5b506102646109d2366004613c22565b61315e565b3480156109e357600080fd5b506102646109f2366004613bf4565b6132b3565b348015610a0357600080fd5b50610264610a12366004613c22565b613429565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff163314610a87576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000859003610ac2576040517f8dad8de600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710811115610afe576040517fe05f723400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008781527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00260205260409020600181018054610b3a90613ea4565b9050600003610b75576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60018101610b84878983613f75565b5060028101859055600381018490556004810180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8516908117909155600582018390556040513391908a907f9b9245463835a6078680163f22703695356642f8cf6b65a6665a0882356334e890610c1c908c908c908c908c908b90614090565b60405180910390a45050505050505050565b600260005403610c9f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c0060448201526064015b60405180910390fd5b6002600090815573ffffffffffffffffffffffffffffffffffffffff831681527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020526040902054829060ff16610d23576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000610d2e8361356e565b73ffffffffffffffffffffffffffffffffffffffff851660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020526040812060010180549293508392909190610d8a908490614124565b909155505060008381527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0026020908152604091829020600201548251848152918201523391859173ffffffffffffffffffffffffffffffffffffffff8816917f318bfca88e13e58196e4ede6142ef537ca3ef795b1decbb9e3de152a9952e5cc910160405180910390a4505060016000555050565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff163314610e8f576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00260205260409020600181018054610ecb90613ea4565b9050600003610f06576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168215159081178255604051908152339084907f2f4957288ef5019fe3f7a1074da21d82cdc2f8013c1ebba73450b423b493f525906020015b60405180910390a3505050565b73ffffffffffffffffffffffffffffffffffffffff80831660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205483917f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000916201000090041633148015906110065750805473ffffffffffffffffffffffffffffffffffffffff163314155b1561103d576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602090815260409182902060040180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001687151590811790915591519182523392917f82edf7f53728f732b3bcd1a19a1aaac6707e859838b247c07a8c90a199b0e32491015b60405180910390a350505050565b73ffffffffffffffffffffffffffffffffffffffff80831660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205483917f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000916201000090041633148015906111865750805473ffffffffffffffffffffffffffffffffffffffff163314155b156111bd576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020526040902054849060ff1661123d576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff851660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101008915159081029190911790915591519182523392917f173d95bbb28cfff9cf71289e1103c552da7a097c5d49798a535ed3756de8b8db91015b60405180910390a35050505050565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff163314611364576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020526040902054829060ff166113e4576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff831660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090206001015482811015611466576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b611470838261413c565b73ffffffffffffffffffffffffffffffffffffffff851660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020908152604091829020600101939093555185815233927fd9875ab458eea009b6a28a26de3f99493da0f8e26f57e0f90ae77011e29b455191016110e1565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff16331461155d576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff81166115ab576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600260005403611617576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610c96565b600260009081554790831561162c578361162e565b815b90508181111561166a576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60405173ffffffffffffffffffffffffffffffffffffffff86169082156108fc029083906000818181858888f193505050501580156116ad573d6000803e3d6000fd5b50604051818152339073ffffffffffffffffffffffffffffffffffffffff8716907f134d6e96840903022b8e4b57aa0644e9eb6ca6fe65a25205b0857fe918c2bcc69060200160405180910390a350506001600055505050565b8273ffffffffffffffffffffffffffffffffffffffff8116611755576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8273ffffffffffffffffffffffffffffffffffffffff81166117a3576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60026000540361180f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610c96565b6002600090815573ffffffffffffffffffffffffffffffffffffffff861681527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205460ff1615611892576040517f3a81d6fc00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b843b60008190036118cf576040517f6eefed2000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006118da8561356e565b73ffffffffffffffffffffffffffffffffffffffff88811660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604080822080546101017fffffffffffffffffffff0000000000000000000000000000000000000000000090911662010000968e169687021717815560018082018790556002820180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169091179055905194955093339392917f32cbeaec3f2fc4b88cd5d5b5ec778111e585d882f052b859822f696f5f7294da91a460008681527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0026020908152604091829020600201548251858152918201523391889173ffffffffffffffffffffffffffffffffffffffff8c16917f318bfca88e13e58196e4ede6142ef537ca3ef795b1decbb9e3de152a9952e5cc910160405180910390a450506001600055505050505050565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff163314611abd576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8173ffffffffffffffffffffffffffffffffffffffff8116611b0b576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600260005403611b77576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610c96565b600260005573ffffffffffffffffffffffffffffffffffffffff8416611bc9576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517f70a08231000000000000000000000000000000000000000000000000000000008152306004820152849060009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa158015611c38573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611c5c9190614153565b905060008415611c6c5784611c6e565b815b905081811115611caa576040517f43fb945300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517fa9059cbb00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff87811660048301526024820183905284169063a9059cbb906044016020604051808303816000875af1158015611d1f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611d43919061416c565b611d79576040517f045c4b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167f4e5ba90310f16273bb12f3c33f23905e573b86df58a2895a525285d083bf043f84604051611def91815260200190565b60405180910390a4505060016000555050505050565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff163314611e75576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000859003611eb0576040517f8dad8de600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710811115611eec576040517fe05f723400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0035460008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0026020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600190811782557f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0009291908101611f97898b83613f75565b506002810187905560038082018790556004820180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8816179055600582018590558301805490600061200083614189565b91905055503373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff16837fb85b834acc25b187c94ca1f210162af04e6c101f009bfa1f4375aa54604c30d08c8c8c8c8b60405161206b959493929190614090565b60405180910390a4505050505050505050565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff1633146120ee576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604081206001018054839290612145908490614124565b9091555050604051818152339073ffffffffffffffffffffffffffffffffffffffff8416907f037e75534584e8d0666405b1b62900a56de166245a53fca8675ef45f5365dcd29060200160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff80841660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205484917f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000916201000090041633148015906122325750805473ffffffffffffffffffffffffffffffffffffffff163314155b15612269576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838110156123485773ffffffffffffffffffffffffffffffffffffffff861660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00160205260408120600501818787858181106122ce576122ce6141c1565b90506020020160208101906122e39190613c22565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790558061234081614189565b91505061226c565b503373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167fad4278b4de5cef070d03d25a62128ea84e003c2f4328bdb44b971e8fa9fd0b0c86866040516112e59291906141f0565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff163314612418576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020908152604091829020600101805490859055825181815291820185905292339290917f90135813691c23bc6463dd8c2046f71d2b9a7878436bfbdd198dcbc5f776ea959101610f62565b60607f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000600060015b826003015481101561250e57600081815260028401602052604090205460ff16156124fc57816124f881614189565b9250505b8061250681614189565b9150506124c9565b5060008167ffffffffffffffff81111561252a5761252a613ef7565b604051908082528060200260200182016040528015612553578160200160208202803683370190505b509050600060015b84600301548110156125be57600081815260028601602052604090205460ff16156125ac5780838381518110612593576125936141c1565b6020908102919091010152816125a881614189565b9250505b806125b681614189565b91505061255b565b5090949350505050565b73ffffffffffffffffffffffffffffffffffffffff80841660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205484917f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0009162010000900416331480159061265f5750805473ffffffffffffffffffffffffffffffffffffffff163314155b15612696576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838110156127755773ffffffffffffffffffffffffffffffffffffffff861660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00160205260408120600301818787858181106126fb576126fb6141c1565b90506020020160208101906127109190613c22565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169115159190911790558061276d81614189565b915050612699565b503373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167f6eb2830031dee414085447e3d1d03e5bd0c2271d79b033d098cfd444dccd33b986866040516112e59291906141f0565b73ffffffffffffffffffffffffffffffffffffffff821660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604081206002015460ff161580612881575073ffffffffffffffffffffffffffffffffffffffff83811660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00160209081526040808320938616835260039093019052205460ff165b9392505050565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff1633146128f8576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604080822080547fffffffffffffffffffff00000000000000000000000000000000000000000000168155600181018390556002810180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00908116909155600490910180549091169055513392917f1de42a421eef953b12e8dffca9f7ba12e7962c982fc649e5e2c32ea0e7e3ca6491a350565b600154610100900460ff16158080156129ea57506001805460ff16105b80612a035750303b158015612a0357506001805460ff16145b612a8f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610c96565b600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016811790558015612aec57600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff841617905560017f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e003558015612bd157600180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff1681556040519081527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b73ffffffffffffffffffffffffffffffffffffffff80831660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205483917f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00091620100009004163314801590612c6c5750805473ffffffffffffffffffffffffffffffffffffffff163314155b15612ca3576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0016020526040902054849060ff16612d23576040517faba4733900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8373ffffffffffffffffffffffffffffffffffffffff8116612d71576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff86811660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00160209081526040918290208054620100008b87168181027fffffffffffffffffffff0000000000000000000000000000000000000000ffff841617909355845133815294519104909516949093859390927f69feba90503661246e4691cb2fe465cc9acb27d5ae05f8b48a57eefa7c726fb192918290030190a450505050505050565b6000606081808080807f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000600089815260029182016020526040902080549181015460038201546004830154600584015460018501805495975060ff909616959473ffffffffffffffffffffffffffffffffffffffff909216918590612eb690613ea4565b80601f0160208091040260200160405190810160405280929190818152602001828054612ee290613ea4565b8015612f2f5780601f10612f0457610100808354040283529160200191612f2f565b820191906000526020600020905b815481529060010190602001808311612f1257829003601f168201915b505050505094509650965096509650965096505091939550919395565b73ffffffffffffffffffffffffffffffffffffffff80841660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205484917f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00091620100009004163314801590612fe35750805473ffffffffffffffffffffffffffffffffffffffff163314155b1561301a576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b838110156130fe5773ffffffffffffffffffffffffffffffffffffffff861660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e00160205260408120600191600390910190878785818110613084576130846141c1565b90506020020160208101906130999190613c22565b73ffffffffffffffffffffffffffffffffffffffff168152602081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055806130f681614189565b91505061301d565b503373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff167f0921dd4ce99f391e92d1907d1dafbc0ae9f95645171e132a4b0ec09863d4e30786866040516112e59291906141f0565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff1633146131ce576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8073ffffffffffffffffffffffffffffffffffffffff811661321c576040517fd92e233d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e000805473ffffffffffffffffffffffffffffffffffffffff8481167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681179093556040519116919082907f0429168a83556e356cd18563753346b9c9567cbf0fbea148d40aeb84a76cc5b990600090a3505050565b73ffffffffffffffffffffffffffffffffffffffff80831660009081527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604090205483917f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0009162010000900416331480159061334a5750805473ffffffffffffffffffffffffffffffffffffffff163314155b15613381576040517fea8e4eb500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff841660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602090815260409182902060020180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001687151590811790915591519182523392917fc216c05736611219062f8c847b62cd125972df155448dab8f40dbe52478c667f91016110e1565b7f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e0005473ffffffffffffffffffffffffffffffffffffffff163314613499576040517f4ead1a5e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e001602052604080822080547fffffffffffffffffffff00000000000000000000000000000000000000000000168155600181018390556002810180547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00908116909155600490910180549091169055513392917f6c0b8518b86a3f2aab1a16148ee99e9cce485dfb40b2c510326a696b577a6f4391a350565b60008181527f64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e002602052604081206001810180546135aa90613ea4565b90506000036135e5576040517fbdc474c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805460ff16613620576040517fd1d5af5600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600481015473ffffffffffffffffffffffffffffffffffffffff1661364d5761364881613698565b61368e565b3415613685576040517ffbccebae00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61368e81613769565b6003015492915050565b80600201543410156136d6576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600081600201543411156137255760028201546136f3903461413c565b604051909150339082156108fc029083906000818181858888f19350505050158015613723573d6000803e3d6000fd5b505b6002820154604080519182526020820183905233917fafac85d422e5be6fde01b75ba6728f402efe8347cac64dff6bb333b206af7f4a910160405180910390a25050565b60048181015460028301546040517fdd62ed3e000000000000000000000000000000000000000000000000000000008152339381019390935230602484015273ffffffffffffffffffffffffffffffffffffffff90911691829063dd62ed3e90604401602060405180830381865afa1580156137e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061380d9190614153565b1015613845576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60028201546040517f23b872dd000000000000000000000000000000000000000000000000000000008152336004820152306024820152604481019190915273ffffffffffffffffffffffffffffffffffffffff8216906323b872dd906064016020604051808303816000875af11580156138c4573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906138e8919061416c565b61391e576040517f045c4b0200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600582015460009015613a2a5761271083600501548460020154613942919061424b565b61394c9190614288565b6004808501546040517f42966c6800000000000000000000000000000000000000000000000000000000815292935073ffffffffffffffffffffffffffffffffffffffff16916342966c68916139a89185910190815260200190565b600060405180830381600087803b1580156139c257600080fd5b505af19250505080156139d3575060015b15613a2a57600483015460405182815273ffffffffffffffffffffffffffffffffffffffff909116907ffd38818f5291bf0bb3a2a48aadc06ba8757865d1dabd804585338aab3009dcb69060200160405180910390a25b60048301546002840154604080519182526020820184905273ffffffffffffffffffffffffffffffffffffffff9092169133917fd3d68b58dfdd3a4aa0e0c5b89a7baff1e842c8aa696a7bc16b941ab72022236a9101610f62565b60008083601f840112613a9757600080fd5b50813567ffffffffffffffff811115613aaf57600080fd5b602083019150836020828501011115613ac757600080fd5b9250929050565b73ffffffffffffffffffffffffffffffffffffffff81168114613af057600080fd5b50565b600080600080600080600060c0888a031215613b0e57600080fd5b87359650602088013567ffffffffffffffff811115613b2c57600080fd5b613b388a828b01613a85565b90975095505060408801359350606088013592506080880135613b5a81613ace565b8092505060a0880135905092959891949750929550565b60008060408385031215613b8457600080fd5b8235613b8f81613ace565b946020939093013593505050565b600060208284031215613baf57600080fd5b5035919050565b8015158114613af057600080fd5b60008060408385031215613bd757600080fd5b823591506020830135613be981613bb6565b809150509250929050565b60008060408385031215613c0757600080fd5b8235613c1281613ace565b91506020830135613be981613bb6565b600060208284031215613c3457600080fd5b813561288181613ace565b60008060408385031215613c5257600080fd5b8235613c5d81613ace565b91506020830135613be981613ace565b600080600060608486031215613c8257600080fd5b8335613c8d81613ace565b92506020840135613c9d81613ace565b929592945050506040919091013590565b60008060008060008060a08789031215613cc757600080fd5b863567ffffffffffffffff811115613cde57600080fd5b613cea89828a01613a85565b90975095505060208701359350604087013592506060870135613d0c81613ace565b80925050608087013590509295509295509295565b600080600060408486031215613d3657600080fd5b8335613d4181613ace565b9250602084013567ffffffffffffffff80821115613d5e57600080fd5b818601915086601f830112613d7257600080fd5b813581811115613d8157600080fd5b8760208260051b8501011115613d9657600080fd5b6020830194508093505050509250925092565b6020808252825182820181905260009190848201906040850190845b81811015613de157835183529284019291840191600101613dc5565b50909695505050505050565b86151581526000602060c08184015287518060c085015260005b81811015613e235789810183015185820160e001528201613e07565b81811115613e3557600060e083870101525b5060e07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010192505050856040830152846060830152613e93608083018573ffffffffffffffffffffffffffffffffffffffff169052565b8260a0830152979650505050505050565b600181811c90821680613eb857607f821691505b602082108103613ef1577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b601f821115613f7057600081815260208120601f850160051c81016020861015613f4d5750805b601f850160051c820191505b81811015613f6c57828155600101613f59565b5050505b505050565b67ffffffffffffffff831115613f8d57613f8d613ef7565b613fa183613f9b8354613ea4565b83613f26565b6000601f841160018114613ff35760008515613fbd5750838201355b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b1c1916600186901b178355614089565b6000838152602090207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0861690835b828110156140425786850135825560209485019460019092019101614022565b508682101561407d577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60f88860031b161c19848701351681555b505060018560011b0183555b5050505050565b60808152846080820152848660a0830137600060a08683010152600060a07fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f88011683010190508460208301528360408301528260608301529695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115614137576141376140f5565b500190565b60008282101561414e5761414e6140f5565b500390565b60006020828403121561416557600080fd5b5051919050565b60006020828403121561417e57600080fd5b815161288181613bb6565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036141ba576141ba6140f5565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60208082528181018390526000908460408401835b8681101561424057823561421881613ace565b73ffffffffffffffffffffffffffffffffffffffff1682529183019190830190600101614205565b509695505050505050565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615614283576142836140f5565b500290565b6000826142be577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b50049056fea164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x64d1d9a8a451551a9514a2c08ad4e1552ed316d7dd2778a4b9494de741d8e003": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30002": { + "code": "0x608060405234801561001057600080fd5b506004361061007d5760003560e01c80638da5cb5b1161005b5780638da5cb5b146100fc5780639b19251a14610141578063b1540a0114610174578063bdc7b54f1461018757600080fd5b806308fd63221461008257806313af40351461009757806354fd4d50146100aa575b600080fd5b6100956100903660046106de565b61018f565b005b6100956100a536600461071a565b6102ef565b6100e66040518060400160405280600c81526020017f312e312e312d626574612e33000000000000000000000000000000000000000081525081565b6040516100f3919061073c565b60405180910390f35b60005461011c9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016100f3565b61016461014f36600461071a565b60016020526000908152604090205460ff1681565b60405190151581526020016100f3565b61016461018236600461071a565b610520565b610095610571565b60005473ffffffffffffffffffffffffffffffffffffffff163314610261576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604c60248201527f4465706c6f79657257686974656c6973743a2066756e6374696f6e2063616e2060448201527f6f6e6c792062652063616c6c656420627920746865206f776e6572206f66207460648201527f68697320636f6e74726163740000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff821660008181526001602090815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00168515159081179091558251938452908301527f8daaf060c3306c38e068a75c054bf96ecd85a3db1252712c4d93632744c42e0d910160405180910390a15050565b60005473ffffffffffffffffffffffffffffffffffffffff1633146103bc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604c60248201527f4465706c6f79657257686974656c6973743a2066756e6374696f6e2063616e2060448201527f6f6e6c792062652063616c6c656420627920746865206f776e6572206f66207460648201527f68697320636f6e74726163740000000000000000000000000000000000000000608482015260a401610258565b73ffffffffffffffffffffffffffffffffffffffff8116610485576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f4465706c6f79657257686974656c6973743a2063616e206f6e6c79206265206460448201527f697361626c65642076696120656e61626c65417262697472617279436f6e747260648201527f6163744465706c6f796d656e7400000000000000000000000000000000000000608482015260a401610258565b6000546040805173ffffffffffffffffffffffffffffffffffffffff928316815291831660208301527fb532073b38c83145e3e5135377a08bf9aab55bc0fd7c1179cd4fb995d2a5159c910160405180910390a1600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6000805473ffffffffffffffffffffffffffffffffffffffff16158061056b575073ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604090205460ff165b92915050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461063e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604c60248201527f4465706c6f79657257686974656c6973743a2066756e6374696f6e2063616e2060448201527f6f6e6c792062652063616c6c656420627920746865206f776e6572206f66207460648201527f68697320636f6e74726163740000000000000000000000000000000000000000608482015260a401610258565b60005460405173ffffffffffffffffffffffffffffffffffffffff90911681527fc0e106cf568e50698fdbde1eff56f5a5c966cc7958e37e276918e9e4ccdf8cd49060200160405180910390a1600080547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055565b803573ffffffffffffffffffffffffffffffffffffffff811681146106d957600080fd5b919050565b600080604083850312156106f157600080fd5b6106fa836106b5565b91506020830135801515811461070f57600080fd5b809150509250929050565b60006020828403121561072c57600080fd5b610735826106b5565b9392505050565b600060208083528351808285015260005b818110156107695785810183015185820160400152820161074d565b8181111561077b576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30007": { + "code": "0x60806040526004361061018b5760003560e01c80638cbeeef2116100d6578063c4d66de81161007f578063ddd5a40f11610059578063ddd5a40f1461043e578063e46e245a14610454578063ecc704281461046957600080fd5b8063c4d66de8146103de578063d764ad0b146103fe578063db505d801461041157600080fd5b8063a7119869116100b0578063a711986914610333578063b1b1b2091461038e578063b28ade25146103be57600080fd5b80638cbeeef2146102405780639fce812c14610333578063a4e7f8bd1461035e57600080fd5b80634c1d6a69116101385780635c975abb116101125780635c975abb146102c25780636e296e45146102e257806383a740741461031c57600080fd5b80634c1d6a691461024057806354fd4d50146102565780635644cfdf146102ac57600080fd5b80632f7d3922116101695780632f7d3922146101ed5780633dbb202b146102035780633f827a5a1461021857600080fd5b8063028f85f7146101905780630c568498146101c35780632828d7e8146101d8575b600080fd5b34801561019c57600080fd5b506101a5601081565b60405167ffffffffffffffff90911681526020015b60405180910390f35b3480156101cf57600080fd5b506101a5603f81565b3480156101e457600080fd5b506101a5604081565b3480156101f957600080fd5b506101a561520881565b610216610211366004611861565b6104ce565b005b34801561022457600080fd5b5061022d600181565b60405161ffff90911681526020016101ba565b34801561024c57600080fd5b506101a5619c4081565b34801561026257600080fd5b5061029f6040518060400160405280600581526020017f322e322e3000000000000000000000000000000000000000000000000000000081525081565b6040516101ba9190611933565b3480156102b857600080fd5b506101a561138881565b3480156102ce57600080fd5b5060005b60405190151581526020016101ba565b3480156102ee57600080fd5b506102f7610761565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101ba565b34801561032857600080fd5b506101a562030d4081565b34801561033f57600080fd5b5060cf5473ffffffffffffffffffffffffffffffffffffffff166102f7565b34801561036a57600080fd5b506102d2610379366004611946565b60ce6020526000908152604090205460ff1681565b34801561039a57600080fd5b506102d26103a9366004611946565b60cb6020526000908152604090205460ff1681565b3480156103ca57600080fd5b506101a56103d936600461198e565b61084d565b3480156103ea57600080fd5b506102166103f9366004611a6e565b61090e565b61021661040c366004611a8b565b610b0d565b34801561041d57600080fd5b5060cf546102f79073ffffffffffffffffffffffffffffffffffffffff1681565b34801561044a57600080fd5b506101a561010481565b34801561046057600080fd5b506101a5602881565b34801561047557600080fd5b506104c060cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016101ba565b60cf54604080516020601f86018190048102820181019092528481526106369273ffffffffffffffffffffffffffffffffffffffff169161052c9190879087908190840183828082843760009201919091525087925061084d915050565b347fd764ad0b0000000000000000000000000000000000000000000000000000000061059860cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b338a34898c8c6040516024016105b49796959493929190611b5a565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526113f2565b8373ffffffffffffffffffffffffffffffffffffffff167fcb0f7ffd78f9aee47a248fae8db181db6eee833039123e026dcbff529522e52a3385856106bb60cd547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b866040516106cd959493929190611bb9565b60405180910390a260405134815233907f8ebb2ec2465bdb2a06a66fc37a0963af8a2a6a1479d81d56fdb8cbb98096d5469060200160405180910390a2505060cd80547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff808216600101167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b60cc5460009073ffffffffffffffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff215301610830576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603560248201527f43726f7373446f6d61696e4d657373656e6765723a2078446f6d61696e4d657360448201527f7361676553656e646572206973206e6f7420736574000000000000000000000060648201526084015b60405180910390fd5b5060cc5473ffffffffffffffffffffffffffffffffffffffff1690565b600080603f610863604063ffffffff8616611c36565b61086d9190611c66565b611388619c406108808162030d40611cb4565b61088a9190611cb4565b6108949190611cb4565b61089e9190611cb4565b9050600061010467ffffffffffffffff1685516108bb9190611ce0565b90506108f96108cb601083611c36565b6108d59084611cb4565b67ffffffffffffffff166108ea602884611c36565b67ffffffffffffffff16611480565b61090590615208611cb4565b95945050505050565b6000547501000000000000000000000000000000000000000000900460ff1615808015610959575060005460017401000000000000000000000000000000000000000090910460ff16105b8061098b5750303b15801561098b575060005474010000000000000000000000000000000000000000900460ff166001145b610a17576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610827565b600080547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790558015610a9d57600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff1675010000000000000000000000000000000000000000001790555b610aa682611499565b8015610b0957600080547fffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b60f087901c60028110610bc8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604d60248201527f43726f7373446f6d61696e4d657373656e6765723a206f6e6c7920766572736960448201527f6f6e2030206f722031206d657373616765732061726520737570706f7274656460648201527f20617420746869732074696d6500000000000000000000000000000000000000608482015260a401610827565b8061ffff16600003610cbd576000610c19878986868080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152508f92506115d5915050565b600081815260cb602052604090205490915060ff1615610cbb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f43726f7373446f6d61696e4d657373656e6765723a206c65676163792077697460448201527f6864726177616c20616c72656164792072656c617965640000000000000000006064820152608401610827565b505b6000610d03898989898989898080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506115f492505050565b9050610d4c60cf54337fffffffffffffffffffffffffeeeeffffffffffffffffffffffffffffffffeeef0173ffffffffffffffffffffffffffffffffffffffff90811691161490565b15610d8457853414610d6057610d60611cf8565b600081815260ce602052604090205460ff1615610d7f57610d7f611cf8565b610ed6565b3415610e38576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152605060248201527f43726f7373446f6d61696e4d657373656e6765723a2076616c7565206d75737460448201527f206265207a65726f20756e6c657373206d6573736167652069732066726f6d2060648201527f612073797374656d206164647265737300000000000000000000000000000000608482015260a401610827565b600081815260ce602052604090205460ff16610ed6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520636160448201527f6e6e6f74206265207265706c61796564000000000000000000000000000000006064820152608401610827565b610edf87611617565b15610f92576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604360248201527f43726f7373446f6d61696e4d657373656e6765723a2063616e6e6f742073656e60448201527f64206d65737361676520746f20626c6f636b65642073797374656d206164647260648201527f6573730000000000000000000000000000000000000000000000000000000000608482015260a401610827565b600081815260cb602052604090205460ff1615611031576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f43726f7373446f6d61696e4d657373656e6765723a206d65737361676520686160448201527f7320616c7265616479206265656e2072656c61796564000000000000000000006064820152608401610827565b61105285611043611388619c40611cb4565b67ffffffffffffffff1661166c565b1580611078575060cc5473ffffffffffffffffffffffffffffffffffffffff1661dead14155b1561119157600081815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555182917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff320161118a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d657373616765000000000000000000000000000000000000006064820152608401610827565b50506113e9565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff8a16179055600061122288619c405a6111e59190611d27565b8988888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061168a92505050565b60cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead179055905080156112d857600082815260cb602052604090205460ff161561127557611275611cf8565b600082815260cb602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f4641df4a962071e12719d8c8c8e5ac7fc4d97b927346a3d7a335b1f7517e133c91a26113e5565b600082815260ce602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660011790555183917f99d0e048484baa1b1540b1367cb128acd7ab2946d1ed91ec10e3c85e4bf51b8f91a27fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff32016113e5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f43726f7373446f6d61696e4d657373656e6765723a206661696c656420746f2060448201527f72656c6179206d657373616765000000000000000000000000000000000000006064820152608401610827565b5050505b50505050505050565b6040517fc2b3e5ac0000000000000000000000000000000000000000000000000000000081527342000000000000000000000000000000000000169063c2b3e5ac90849061144890889088908790600401611d3e565b6000604051808303818588803b15801561146157600080fd5b505af1158015611475573d6000803e3d6000fd5b505050505050505050565b6000818310156114905781611492565b825b9392505050565b6000547501000000000000000000000000000000000000000000900460ff16611544576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e670000000000000000000000000000000000000000006064820152608401610827565b60cc5473ffffffffffffffffffffffffffffffffffffffff1661158e5760cc80547fffffffffffffffffffffffff00000000000000000000000000000000000000001661dead1790555b60cf80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60006115e3858585856116a2565b805190602001209050949350505050565b600061160487878787878761173b565b8051906020012090509695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff8216301480611666575073ffffffffffffffffffffffffffffffffffffffff8216734200000000000000000000000000000000000016145b92915050565b600080603f83619c4001026040850201603f5a021015949350505050565b6000806000835160208501868989f195945050505050565b6060848484846040516024016116bb9493929190611d7d565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fcbd4ece9000000000000000000000000000000000000000000000000000000001790529050949350505050565b606086868686868660405160240161175896959493929190611dc7565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fd764ad0b0000000000000000000000000000000000000000000000000000000017905290509695505050505050565b73ffffffffffffffffffffffffffffffffffffffff811681146117fc57600080fd5b50565b60008083601f84011261181157600080fd5b50813567ffffffffffffffff81111561182957600080fd5b60208301915083602082850101111561184157600080fd5b9250929050565b803563ffffffff8116811461185c57600080fd5b919050565b6000806000806060858703121561187757600080fd5b8435611882816117da565b9350602085013567ffffffffffffffff81111561189e57600080fd5b6118aa878288016117ff565b90945092506118bd905060408601611848565b905092959194509250565b6000815180845260005b818110156118ee576020818501810151868301820152016118d2565b81811115611900576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b60208152600061149260208301846118c8565b60006020828403121561195857600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080604083850312156119a157600080fd5b823567ffffffffffffffff808211156119b957600080fd5b818501915085601f8301126119cd57600080fd5b8135818111156119df576119df61195f565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908382118183101715611a2557611a2561195f565b81604052828152886020848701011115611a3e57600080fd5b826020860160208301376000602084830101528096505050505050611a6560208401611848565b90509250929050565b600060208284031215611a8057600080fd5b8135611492816117da565b600080600080600080600060c0888a031215611aa657600080fd5b873596506020880135611ab8816117da565b95506040880135611ac8816117da565b9450606088013593506080880135925060a088013567ffffffffffffffff811115611af257600080fd5b611afe8a828b016117ff565b989b979a50959850939692959293505050565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b878152600073ffffffffffffffffffffffffffffffffffffffff808916602084015280881660408401525085606083015263ffffffff8516608083015260c060a0830152611bac60c083018486611b11565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff86168152608060208201526000611be9608083018688611b11565b905083604083015263ffffffff831660608301529695505050505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600067ffffffffffffffff80831681851681830481118215151615611c5d57611c5d611c07565b02949350505050565b600067ffffffffffffffff80841680611ca8577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b92169190910492915050565b600067ffffffffffffffff808316818516808303821115611cd757611cd7611c07565b01949350505050565b60008219821115611cf357611cf3611c07565b500190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fd5b600082821015611d3957611d39611c07565b500390565b73ffffffffffffffffffffffffffffffffffffffff8416815267ffffffffffffffff8316602082015260606040820152600061090560608301846118c8565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401525060806040830152611db660808301856118c8565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a0830152611e1260c08301846118c8565b9897505050505050505056fea164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000010000000000000000000000000000000000000000", + "0x00000000000000000000000000000000000000000000000000000000000000cc": "0x000000000000000000000000000000000000000000000000000000000000dead" + }, + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3000f": { + "code": "0x608060405234801561001057600080fd5b50600436106101775760003560e01c806368d5dca6116100d8578063c59859181161008c578063f45e65d811610066578063f45e65d8146102ca578063f8206140146102d2578063fe173b971461026957600080fd5b8063c59859181461029c578063de26c4a1146102a4578063f1c7a58b146102b757600080fd5b80638e98b106116100bd5780638e98b1061461026f578063960e3a2314610277578063b54501bc1461028957600080fd5b806368d5dca61461024c5780636ef25c3a1461026957600080fd5b8063313ce5671161012f5780634ef6e224116101145780634ef6e224146101de578063519b4bd3146101fb57806354fd4d501461020357600080fd5b8063313ce567146101c457806349948e0e146101cb57600080fd5b8063275aedd211610160578063275aedd2146101a1578063291b0383146101b45780632e0f2625146101bc57600080fd5b80630c18c1621461017c57806322b90ab314610197575b600080fd5b6101846102da565b6040519081526020015b60405180910390f35b61019f6103fb565b005b6101846101af36600461168e565b610584565b61019f61070f565b610184600681565b6006610184565b6101846101d93660046116d6565b610937565b6000546101eb9060ff1681565b604051901515815260200161018e565b61018461096e565b61023f6040518060400160405280600581526020017f312e342e3000000000000000000000000000000000000000000000000000000081525081565b60405161018e91906117a5565b6102546109cf565b60405163ffffffff909116815260200161018e565b48610184565b61019f610a54565b6000546101eb90610100900460ff1681565b6000546101eb9062010000900460ff1681565b610254610c4e565b6101846102b23660046116d6565b610caf565b6101846102c536600461168e565b610da9565b610184610e85565b610184610f78565b6000805460ff1615610373576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f47617350726963654f7261636c653a206f76657268656164282920697320646560448201527f707265636174656400000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190611818565b905090565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146104c4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e2073657420697345636f746f6e6520666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b60005460ff1615610557576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a2045636f746f6e6520616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b6000805462010000900460ff1661059d57506000919050565b610709620f42406106668473420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16634d5d9a2a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610607573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061062b9190611831565b63ffffffff167fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821583830293840490921491909117011790565b6106709190611886565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166316d3bc7f6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156106cf573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106f391906118c1565b67ffffffffffffffff1681019081106000031790565b92915050565b3373deaddeaddeaddeaddeaddeaddeaddeaddead0001146107d8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973497374686d757320666c6160648201527f6700000000000000000000000000000000000000000000000000000000000000608482015260a40161036a565b600054610100900460ff1661086f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20497374686d75732063616e206f6e6c7960448201527f2062652061637469766174656420616674657220466a6f726400000000000000606482015260840161036a565b60005462010000900460ff1615610908576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a20497374686d757320616c72656164792060448201527f6163746976650000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ffff1662010000179055565b60008054610100900460ff16156109515761070982610fd9565b60005460ff16156109655761070982610ff8565b6107098261109c565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16635cf249696040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff166368d5dca66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906103f69190611831565b3373deaddeaddeaddeaddeaddeaddeaddeaddead000114610af7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f47617350726963654f7261636c653a206f6e6c7920746865206465706f73697460448201527f6f72206163636f756e742063616e20736574206973466a6f726420666c616700606482015260840161036a565b60005460ff16610b89576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603960248201527f47617350726963654f7261636c653a20466a6f72642063616e206f6e6c79206260448201527f65206163746976617465642061667465722045636f746f6e6500000000000000606482015260840161036a565b600054610100900460ff1615610c20576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f47617350726963654f7261636c653a20466a6f726420616c726561647920616360448201527f7469766500000000000000000000000000000000000000000000000000000000606482015260840161036a565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff16610100179055565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663c59859186040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a30573d6000803e3d6000fd5b60008054610100900460ff1615610cf657620f4240610ce1610cd0846111f0565b51610cdc9060446118eb565b61150d565b610cec906010611903565b6107099190611886565b6000610d018361156c565b60005490915060ff1615610d155792915050565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d74573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d989190611818565b610da290826118eb565b9392505050565b60008054610100900460ff16610e41576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f47617350726963654f7261636c653a206765744c314665655570706572426f7560448201527f6e64206f6e6c7920737570706f72747320466a6f726400000000000000000000606482015260840161036a565b6000610e4e8360446118eb565b90506000610e5d60ff83611886565b610e6790836118eb565b610e729060106118eb565b9050610e7d816115fc565b949350505050565b6000805460ff1615610f19576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f47617350726963654f7261636c653a207363616c61722829206973206465707260448201527f6563617465640000000000000000000000000000000000000000000000000000606482015260840161036a565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff1663f82061406040518163ffffffff1660e01b8152600401602060405180830381865afa1580156103d2573d6000803e3d6000fd5b6000610709610fe7836111f0565b51610ff39060446118eb565b6115fc565b6000806110048361156c565b9050600061101061096e565b611018610c4e565b611023906010611940565b63ffffffff166110339190611903565b9050600061103f610f78565b6110476109cf565b63ffffffff166110579190611903565b9050600061106582846118eb565b61106f9085611903565b905061107d6006600a611a8c565b611088906010611903565b6110929082611886565b9695505050505050565b6000806110a88361156c565b9050600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16639e8c49666040518163ffffffff1660e01b8152600401602060405180830381865afa15801561110b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061112f9190611818565b61113761096e565b73420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638b239f736040518163ffffffff1660e01b8152600401602060405180830381865afa158015611196573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111ba9190611818565b6111c490856118eb565b6111ce9190611903565b6111d89190611903565b90506111e66006600a611a8c565b610e7d9082611886565b606061137f565b818153600101919050565b600082840393505b83811015610da25782810151828201511860001a159093029260010161120a565b825b60208210611277578251611242601f836111f7565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09091019060210161122d565b8115610da257825161128c60018403836111f7565b520160010192915050565b60006001830392505b61010782106112d8576112ca8360ff166112c560fd6112c58760081c60e001896111f7565b6111f7565b9350610106820391506112a0565b60078210611305576112fe8360ff166112c5600785036112c58760081c60e001896111f7565b9050610da2565b610e7d8360ff166112c58560081c8560051b01876111f7565b61137782820361135b61134b84600081518060001a8160011a60081b178160021a60101b17915050919050565b639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b6180003860405139618000604051016020830180600d8551820103826002015b818110156114b2576000805b50508051604051600082901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b909118909152840190818303908484106114075750611442565b600184019350611fff821161143c578251600081901a600182901a60081b1760029190911a60101b17810361143c5750611442565b506113ab565b8383106114505750506114b2565b6001830392508583111561146e5761146b878788860361122b565b96505b611482600985016003850160038501611202565b915061148f878284611297565b9650506114a7846114a28684860161131e565b61131e565b91505080935061139f565b50506114c4838384885185010361122b565b925050506040519150618000820180820391508183526020830160005b838110156114f95782810151828201526020016114e1565b506000920191825250602001604052919050565b60008061151d83620cc394611903565b611547907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd763200611a98565b90506115576064620f4240611b0c565b81121561070957610da26064620f4240611b0c565b80516000908190815b818110156115ef5784818151811061158f5761158f611bc8565b01602001517fff00000000000000000000000000000000000000000000000000000000000000166000036115cf576115c86004846118eb565b92506115dd565b6115da6010846118eb565b92505b806115e781611bf7565b915050611575565b50610e7d826104406118eb565b6000806116088361150d565b90506000611614610f78565b61161c6109cf565b63ffffffff1661162c9190611903565b61163461096e565b61163c610c4e565b611647906010611940565b63ffffffff166116579190611903565b61166191906118eb565b905061166f60066002611903565b61167a90600a611a8c565b6116848284611903565b610e7d9190611886565b6000602082840312156116a057600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000602082840312156116e857600080fd5b813567ffffffffffffffff8082111561170057600080fd5b818401915084601f83011261171457600080fd5b813581811115611726576117266116a7565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f0116810190838211818310171561176c5761176c6116a7565b8160405282815287602084870101111561178557600080fd5b826020860160208301376000928101602001929092525095945050505050565b600060208083528351808285015260005b818110156117d2578581018301518582016040015282016117b6565b818111156117e4576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b60006020828403121561182a57600080fd5b5051919050565b60006020828403121561184357600080fd5b815163ffffffff81168114610da257600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000826118bc577f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b500490565b6000602082840312156118d357600080fd5b815167ffffffffffffffff81168114610da257600080fd5b600082198211156118fe576118fe611857565b500190565b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561193b5761193b611857565b500290565b600063ffffffff8083168185168183048111821515161561196357611963611857565b02949350505050565b600181815b808511156119c557817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048211156119ab576119ab611857565b808516156119b857918102915b93841c9390800290611971565b509250929050565b6000826119dc57506001610709565b816119e957506000610709565b81600181146119ff5760028114611a0957611a25565b6001915050610709565b60ff841115611a1a57611a1a611857565b50506001821b610709565b5060208310610133831016604e8410600b8410161715611a48575081810a610709565b611a52838361196c565b807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04821115611a8457611a84611857565b029392505050565b6000610da283836119cd565b6000808212827f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03841381151615611ad257611ad2611857565b827f8000000000000000000000000000000000000000000000000000000000000000038412811615611b0657611b06611857565b50500190565b60007f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600084136000841385830485118282161615611b4d57611b4d611857565b7f80000000000000000000000000000000000000000000000000000000000000006000871286820588128184161615611b8857611b88611857565b60008712925087820587128484161615611ba457611ba4611857565b87850587128184161615611bba57611bba611857565b505050929093029392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203611c2857611c28611857565b506001019056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30010": { + "code": "0x6080604052600436106101125760003560e01c80635c975abb116100a5578063927ede2d11610074578063c4d66de811610059578063c4d66de8146103ee578063c89701a21461040e578063e11013dd1461043b57600080fd5b8063927ede2d146103b0578063a3a79548146103db57600080fd5b80635c975abb1461032e5780637f46ddb214610244578063870876231461034a5780638f601f661461036a57600080fd5b806336c717c1116100e157806336c717c1146102445780633cb747bf14610295578063540abf73146102c257806354fd4d50146102e257600080fd5b80630166a07a146101eb57806309fc88431461020b5780631635f5fd1461021e57806332b7006d1461023157600080fd5b366101e65761011f61044e565b6101b0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084015b60405180910390fd5b6101e473deaddeaddeaddeaddeaddeaddeaddeaddead000033333462030d406040518060200160405280600081525061048b565b005b600080fd5b3480156101f757600080fd5b506101e461020636600461248c565b610566565b6101e461021936600461253d565b610908565b6101e461022c366004612590565b6109e4565b6101e461023f366004612603565b610e36565b34801561025057600080fd5b5060045473ffffffffffffffffffffffffffffffffffffffff165b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156102a157600080fd5b5060035461026b9073ffffffffffffffffffffffffffffffffffffffff1681565b3480156102ce57600080fd5b506101e46102dd366004612657565b610f15565b3480156102ee57600080fd5b50604080518082018252600681527f312e31332e3000000000000000000000000000000000000000000000000000006020820152905161028c9190612744565b34801561033a57600080fd5b506040516000815260200161028c565b34801561035657600080fd5b506101e4610365366004612757565b610f5a565b34801561037657600080fd5b506103a26103853660046127da565b600260209081526000928352604080842090915290825290205481565b60405190815260200161028c565b3480156103bc57600080fd5b5060035473ffffffffffffffffffffffffffffffffffffffff1661026b565b6101e46103e9366004612757565b611033565b3480156103fa57600080fd5b506101e4610409366004612813565b611077565b34801561041a57600080fd5b5060045461026b9073ffffffffffffffffffffffffffffffffffffffff1681565b6101e4610449366004612830565b611220565b600032330361045d5750600190565b333b60170361048557604051602081016040526020600082333c5160e81c62ef010014905090565b50600090565b7fffffffffffffffffffffffff215221522152215221522152215221522153000073ffffffffffffffffffffffffffffffffffffffff8716016104da576104d58585858585611269565b61055e565b60008673ffffffffffffffffffffffffffffffffffffffff1663c01e1bd66040518163ffffffff1660e01b8152600401602060405180830381865afa158015610527573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061054b9190612893565b905061055c87828888888888611433565b505b505050505050565b60035473ffffffffffffffffffffffffffffffffffffffff1633148015610639575060048054600354604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff938416949390921692636e296e459282820192602092908290030181865afa1580156105fd573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106219190612893565b73ffffffffffffffffffffffffffffffffffffffff16145b6106eb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20746865206f7468657220627269646760648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a4016101a7565b6106f4876117ec565b1561084257610703878761184e565b6107b5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f5374616e646172644272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433230206c60648201527f6f63616c20746f6b656e00000000000000000000000000000000000000000000608482015260a4016101a7565b6040517f40c10f1900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8581166004830152602482018590528816906340c10f1990604401600060405180830381600087803b15801561082557600080fd5b505af1158015610839573d6000803e3d6000fd5b505050506108c4565b73ffffffffffffffffffffffffffffffffffffffff8088166000908152600260209081526040808320938a16835292905220546108809084906128df565b73ffffffffffffffffffffffffffffffffffffffff8089166000818152600260209081526040808320948c16835293905291909120919091556108c490858561196e565b61055c878787878787878080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611a4292505050565b61091061044e565b61099c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101a7565b6109df3333348686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061126992505050565b505050565b60035473ffffffffffffffffffffffffffffffffffffffff1633148015610ab7575060048054600354604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff938416949390921692636e296e459282820192602092908290030181865afa158015610a7b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a9f9190612893565b73ffffffffffffffffffffffffffffffffffffffff16145b610b69576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604160248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20746865206f7468657220627269646760648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a4016101a7565b823414610bf8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f5374616e646172644272696467653a20616d6f756e742073656e7420646f657360448201527f206e6f74206d6174636820616d6f756e7420726571756972656400000000000060648201526084016101a7565b3073ffffffffffffffffffffffffffffffffffffffff851603610c9d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f5374616e646172644272696467653a2063616e6e6f742073656e6420746f207360448201527f656c66000000000000000000000000000000000000000000000000000000000060648201526084016101a7565b60035473ffffffffffffffffffffffffffffffffffffffff90811690851603610d48576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602860248201527f5374616e646172644272696467653a2063616e6e6f742073656e6420746f206d60448201527f657373656e67657200000000000000000000000000000000000000000000000060648201526084016101a7565b610d8a85858585858080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611ad092505050565b6000610da7855a8660405180602001604052806000815250611b71565b90508061055e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f5374616e646172644272696467653a20455448207472616e736665722066616960448201527f6c6564000000000000000000000000000000000000000000000000000000000060648201526084016101a7565b610e3e61044e565b610eca576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101a7565b610f0e853333878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061048b92505050565b5050505050565b61055c87873388888888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061143392505050565b610f6261044e565b610fee576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f5374616e646172644272696467653a2066756e6374696f6e2063616e206f6e6c60448201527f792062652063616c6c65642066726f6d20616e20454f4100000000000000000060648201526084016101a7565b61055e86863333888888888080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061143392505050565b61055e863387878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061048b92505050565b600054610100900460ff16158080156110975750600054600160ff909116105b806110b15750303b1580156110b1575060005460ff166001145b61113d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016101a7565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055801561119b57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b6111b973420000000000000000000000000000000000000783611b89565b801561121c57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6112633385348686868080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061126992505050565b50505050565b8234146112f8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f5374616e646172644272696467653a206272696467696e6720455448206d757360448201527f7420696e636c7564652073756666696369656e74204554482076616c7565000060648201526084016101a7565b61130485858584611c73565b60035460045460405173ffffffffffffffffffffffffffffffffffffffff92831692633dbb202b9287929116907f1635f5fd0000000000000000000000000000000000000000000000000000000090611367908b908b9086908a906024016128f6565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e086901b90921682526113fa9291889060040161293f565b6000604051808303818588803b15801561141357600080fd5b505af1158015611427573d6000803e3d6000fd5b50505050505050505050565b34156114c1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f5374616e646172644272696467653a2063616e6e6f742073656e642076616c7560448201527f650000000000000000000000000000000000000000000000000000000000000060648201526084016101a7565b6114ca876117ec565b15611618576114d9878761184e565b61158b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f5374616e646172644272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433230206c60648201527f6f63616c20746f6b656e00000000000000000000000000000000000000000000608482015260a4016101a7565b6040517f9dc29fac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff868116600483015260248201859052881690639dc29fac90604401600060405180830381600087803b1580156115fb57600080fd5b505af115801561160f573d6000803e3d6000fd5b505050506116ac565b61163a73ffffffffffffffffffffffffffffffffffffffff8816863086611d14565b73ffffffffffffffffffffffffffffffffffffffff8088166000908152600260209081526040808320938a1683529290522054611678908490612984565b73ffffffffffffffffffffffffffffffffffffffff8089166000908152600260209081526040808320938b16835292905220555b6116ba878787878786611d72565b60035460045460405173ffffffffffffffffffffffffffffffffffffffff92831692633dbb202b9216907f0166a07a000000000000000000000000000000000000000000000000000000009061171e908b908d908c908c908c908b9060240161299c565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009485161790525160e085901b90921682526117b19291879060040161293f565b600060405180830381600087803b1580156117cb57600080fd5b505af11580156117df573d6000803e3d6000fd5b5050505050505050505050565b6000611818827f1d1d8b6300000000000000000000000000000000000000000000000000000000611e00565b806118485750611848827fec4fc8e300000000000000000000000000000000000000000000000000000000611e00565b92915050565b600061187a837f1d1d8b6300000000000000000000000000000000000000000000000000000000611e00565b15611923578273ffffffffffffffffffffffffffffffffffffffff1663c01e1bd66040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118ca573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906118ee9190612893565b73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16149050611848565b8273ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156118ca573d6000803e3d6000fd5b60405173ffffffffffffffffffffffffffffffffffffffff83166024820152604481018290526109df9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff0000000000000000000000000000000000000000000000000000000090931692909217909152611e23565b8373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167fb0444523268717a02698be47d0803aa7468c00acbed2f8bd93a0459cde61dd89868686604051611aba939291906129f7565b60405180910390a461055e868686868686611f2f565b8373ffffffffffffffffffffffffffffffffffffffff1673deaddeaddeaddeaddeaddeaddeaddeaddead000073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fb0444523268717a02698be47d0803aa7468c00acbed2f8bd93a0459cde61dd89868686604051611b5d939291906129f7565b60405180910390a461126384848484611fb7565b6000806000835160208501868989f195945050505050565b600054610100900460ff16611c20576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e6700000000000000000000000000000000000000000060648201526084016101a7565b6003805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560048054929093169116179055565b8373ffffffffffffffffffffffffffffffffffffffff1673deaddeaddeaddeaddeaddeaddeaddeaddead000073ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167f73d170910aba9e6d50b102db522b1dbcd796216f5128b445aa2135272886497e868686604051611d00939291906129f7565b60405180910390a461126384848484612024565b60405173ffffffffffffffffffffffffffffffffffffffff808516602483015283166044820152606481018290526112639085907f23b872dd00000000000000000000000000000000000000000000000000000000906084016119c0565b8373ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167f73d170910aba9e6d50b102db522b1dbcd796216f5128b445aa2135272886497e868686604051611dea939291906129f7565b60405180910390a461055e868686868686612083565b6000611e0b836120fb565b8015611e1c5750611e1c838361215f565b9392505050565b6000611e85826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff1661222e9092919063ffffffff16565b8051909150156109df5780806020019051810190611ea39190612a35565b6109df576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016101a7565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167fd59c65b35445225835c83f50b6ede06a7be047d22e357073e250d9af537518cd868686604051611fa7939291906129f7565b60405180910390a4505050505050565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f31b2166ff604fc5672ea5df08a78081d2bc6d746cadce880747f3643d819e83d8484604051612016929190612a57565b60405180910390a350505050565b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f2849b43074093a05396b6f2a937dee8565b15a48a7b3d4bffb732a5017380af58484604051612016929190612a57565b8373ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff167f7ff126db8024424bbfd9826e8ab82ff59136289ea440b04b39a0df1b03b9cabf868686604051611fa7939291906129f7565b6000612127827f01ffc9a70000000000000000000000000000000000000000000000000000000061215f565b80156118485750612158827fffffffff0000000000000000000000000000000000000000000000000000000061215f565b1592915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d91506000519050828015612217575060208210155b80156122235750600081115b979650505050505050565b606061223d8484600085612245565b949350505050565b6060824710156122d7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016101a7565b73ffffffffffffffffffffffffffffffffffffffff85163b612355576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016101a7565b6000808673ffffffffffffffffffffffffffffffffffffffff16858760405161237e9190612a70565b60006040518083038185875af1925050503d80600081146123bb576040519150601f19603f3d011682016040523d82523d6000602084013e6123c0565b606091505b5091509150612223828286606083156123da575081611e1c565b8251156123ea5782518084602001fd5b816040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101a79190612744565b73ffffffffffffffffffffffffffffffffffffffff8116811461244057600080fd5b50565b60008083601f84011261245557600080fd5b50813567ffffffffffffffff81111561246d57600080fd5b60208301915083602082850101111561248557600080fd5b9250929050565b600080600080600080600060c0888a0312156124a757600080fd5b87356124b28161241e565b965060208801356124c28161241e565b955060408801356124d28161241e565b945060608801356124e28161241e565b93506080880135925060a088013567ffffffffffffffff81111561250557600080fd5b6125118a828b01612443565b989b979a50959850939692959293505050565b803563ffffffff8116811461253857600080fd5b919050565b60008060006040848603121561255257600080fd5b61255b84612524565b9250602084013567ffffffffffffffff81111561257757600080fd5b61258386828701612443565b9497909650939450505050565b6000806000806000608086880312156125a857600080fd5b85356125b38161241e565b945060208601356125c38161241e565b935060408601359250606086013567ffffffffffffffff8111156125e657600080fd5b6125f288828901612443565b969995985093965092949392505050565b60008060008060006080868803121561261b57600080fd5b85356126268161241e565b94506020860135935061263b60408701612524565b9250606086013567ffffffffffffffff8111156125e657600080fd5b600080600080600080600060c0888a03121561267257600080fd5b873561267d8161241e565b9650602088013561268d8161241e565b9550604088013561269d8161241e565b9450606088013593506126b260808901612524565b925060a088013567ffffffffffffffff81111561250557600080fd5b60005b838110156126e95781810151838201526020016126d1565b838111156112635750506000910152565b600081518084526127128160208601602086016126ce565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000611e1c60208301846126fa565b60008060008060008060a0878903121561277057600080fd5b863561277b8161241e565b9550602087013561278b8161241e565b9450604087013593506127a060608801612524565b9250608087013567ffffffffffffffff8111156127bc57600080fd5b6127c889828a01612443565b979a9699509497509295939492505050565b600080604083850312156127ed57600080fd5b82356127f88161241e565b915060208301356128088161241e565b809150509250929050565b60006020828403121561282557600080fd5b8135611e1c8161241e565b6000806000806060858703121561284657600080fd5b84356128518161241e565b935061285f60208601612524565b9250604085013567ffffffffffffffff81111561287b57600080fd5b61288787828801612443565b95989497509550505050565b6000602082840312156128a557600080fd5b8151611e1c8161241e565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000828210156128f1576128f16128b0565b500390565b600073ffffffffffffffffffffffffffffffffffffffff80871683528086166020840152508360408301526080606083015261293560808301846126fa565b9695505050505050565b73ffffffffffffffffffffffffffffffffffffffff8416815260606020820152600061296e60608301856126fa565b905063ffffffff83166040830152949350505050565b60008219821115612997576129976128b0565b500190565b600073ffffffffffffffffffffffffffffffffffffffff80891683528088166020840152808716604084015280861660608401525083608083015260c060a08301526129eb60c08301846126fa565b98975050505050505050565b73ffffffffffffffffffffffffffffffffffffffff84168152826020820152606060408201526000612a2c60608301846126fa565b95945050505050565b600060208284031215612a4757600080fd5b81518015158114611e1c57600080fd5b82815260406020820152600061223d60408301846126fa565b60008251612a828184602087016126ce565b919091019291505056fea164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000003": "0x0000000000000000000000004200000000000000000000000000000000000007" + }, + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30011": { + "code": "0x6080604052600436106100b55760003560e01c80638312f14911610069578063d0e12f901161004e578063d0e12f901461024e578063d3e5792b14610282578063d4ff92181461018c57600080fd5b80638312f149146101fb57806384411d651461023857600080fd5b806354fd4d501161009a57806354fd4d501461013657806366d003ac1461018c57806382356d8a146101bf57600080fd5b80630d9019e1146100c15780633ccfd60b1461011f57600080fd5b366100bc57005b600080fd5b3480156100cd57600080fd5b506100f57f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561012b57600080fd5b506101346102b6565b005b34801561014257600080fd5b5061017f6040518060400160405280600c81526020017f312e352e302d626574612e35000000000000000000000000000000000000000081525081565b604051610116919061068c565b34801561019857600080fd5b507f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922666100f5565b3480156101cb57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000015b6040516101169190610769565b34801561020757600080fd5b507f0000000000000000000000000000000000000000000000008ac7230489e800005b604051908152602001610116565b34801561024457600080fd5b5061022a60005481565b34801561025a57600080fd5b506101ee7f000000000000000000000000000000000000000000000000000000000000000181565b34801561028e57600080fd5b5061022a7f0000000000000000000000000000000000000000000000008ac7230489e8000081565b7f0000000000000000000000000000000000000000000000008ac7230489e80000471015610391576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b6000479050806000808282546103a7919061077d565b9091555050604080518281527f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226673ffffffffffffffffffffffffffffffffffffffff166020820152338183015290517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a17f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee817f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266337f000000000000000000000000000000000000000000000000000000000000000160405161049594939291906107bc565b60405180910390a160017f000000000000000000000000000000000000000000000000000000000000000160018111156104d1576104d16106ff565b036105955760006105027f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226683610664565b905080610591576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e74000000000000000000000000000000006064820152608401610388565b5050565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226616600482015262061a80602482015260606044820152600060648201527342000000000000000000000000000000000000169063c2b3e5ac9083906084016000604051808303818588803b15801561064857600080fd5b505af115801561065c573d6000803e3d6000fd5b505050505050565b6000610671835a84610678565b9392505050565b6000806000806000858888f1949350505050565b600060208083528351808285015260005b818110156106b95785810183015185820160400152820161069d565b818111156106cb576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b60028110610765577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b60208101610777828461072e565b92915050565b600082198211156107b7577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500190565b84815273ffffffffffffffffffffffffffffffffffffffff848116602083015283166040820152608081016107f4606083018461072e565b9594505050505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30012": { + "code": "0x60806040523480156200001157600080fd5b5060043610620000935760003560e01c8063c4d66de81162000062578063c4d66de81462000175578063ce5ac90f146200018e578063e78cea9214620001a5578063ee9a31a214620001c657600080fd5b8063316b3739146200009857806354fd4d5014620000fb578063896f93d114620001475780638cf0629c146200015e575b600080fd5b620000d1620000a936600462000636565b60026020526000908152604090205473ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b620001386040518060400160405280600681526020017f312e31302e31000000000000000000000000000000000000000000000000000081525081565b604051620000f29190620006c9565b620000d162000158366004620007c0565b620001e5565b620000d16200016f3660046200083d565b620001fc565b6200018c6200018636600462000636565b6200041b565b005b620000d16200019f366004620007c0565b620005ed565b600154620000d19073ffffffffffffffffffffffffffffffffffffffff1681565b60015473ffffffffffffffffffffffffffffffffffffffff16620000d1565b6000620001f4848484620005ed565b949350505050565b600073ffffffffffffffffffffffffffffffffffffffff8516620002a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4f7074696d69736d4d696e7461626c654552433230466163746f72793a206d7560448201527f73742070726f766964652072656d6f746520746f6b656e20616464726573730060648201526084015b60405180910390fd5b600085858585604051602001620002c29493929190620008d4565b604051602081830303815290604052805190602001209050600081600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16888888886040516200031290620005fe565b620003229594939291906200092e565b8190604051809103906000f590508015801562000343573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff81811660008181526002602052604080822080547fffffffffffffffffffffffff000000000000000000000000000000000000000016948d1694851790555193945090927fceeb8e7d520d7f3b65fc11a262b91066940193b05d4f93df07cfdced0eb551cf9190a360405133815273ffffffffffffffffffffffffffffffffffffffff80891691908316907f52fe89dd5930f343d25650b62fd367bae47088bcddffd2a88350a6ecdd620cdb9060200160405180910390a39695505050505050565b600054610100900460ff16158080156200043c5750600054600160ff909116105b80620004585750303b15801562000458575060005460ff166001145b620004e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a656400000000000000000000000000000000000060648201526084016200029e565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905580156200054557600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff84161790558015620005e957600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6000620001f48484846012620001fc565b6120e0806200099483390190565b803573ffffffffffffffffffffffffffffffffffffffff811681146200063157600080fd5b919050565b6000602082840312156200064957600080fd5b62000654826200060c565b9392505050565b6000815180845260005b81811015620006835760208185018101518683018201520162000665565b8181111562000696576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006200065460208301846200065b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f8301126200071f57600080fd5b813567ffffffffffffffff808211156200073d576200073d620006de565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715620007865762000786620006de565b81604052838152866020858801011115620007a057600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600060608486031215620007d657600080fd5b620007e1846200060c565b9250602084013567ffffffffffffffff80821115620007ff57600080fd5b6200080d878388016200070d565b935060408601359150808211156200082457600080fd5b5062000833868287016200070d565b9150509250925092565b600080600080608085870312156200085457600080fd5b6200085f856200060c565b9350602085013567ffffffffffffffff808211156200087d57600080fd5b6200088b888389016200070d565b94506040870135915080821115620008a257600080fd5b50620008b1878288016200070d565b925050606085013560ff81168114620008c957600080fd5b939692955090935050565b73ffffffffffffffffffffffffffffffffffffffff851681526080602082015260006200090560808301866200065b565b82810360408401526200091981866200065b565b91505060ff8316606083015295945050505050565b600073ffffffffffffffffffffffffffffffffffffffff808816835280871660208401525060a060408301526200096960a08301866200065b565b82810360608401526200097d81866200065b565b91505060ff83166080830152969550505050505056fe6101a06040523480156200001257600080fd5b50604051620020e0380380620020e0833981016040819052620000359162000215565b6040805180820190915260018152603160f81b6020820152839081908185600362000061838262000350565b50600462000070828262000350565b5050825160208085019190912083518483012060e08290526101008190524660a0818152604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f81880181905281830187905260608201869052608082019490945230818401528151808203909301835260c0019052805194019390932091935091906080523060c05261012052505050506001600160a01b0394851661014052509390921661016052505060ff16610180526200041c565b80516001600160a01b03811681146200014357600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200017057600080fd5b81516001600160401b03808211156200018d576200018d62000148565b604051601f8301601f19908116603f01168101908282118183101715620001b857620001b862000148565b81604052838152602092508683858801011115620001d557600080fd5b600091505b83821015620001f95785820183015181830184015290820190620001da565b838211156200020b5760008385830101525b9695505050505050565b600080600080600060a086880312156200022e57600080fd5b62000239866200012b565b945062000249602087016200012b565b60408701519094506001600160401b03808211156200026757600080fd5b6200027589838a016200015e565b945060608801519150808211156200028c57600080fd5b506200029b888289016200015e565b925050608086015160ff81168114620002b357600080fd5b809150509295509295909350565b600181811c90821680620002d657607f821691505b602082108103620002f757634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200034b57600081815260208120601f850160051c81016020861015620003265750805b601f850160051c820191505b81811015620003475782815560010162000332565b5050505b505050565b81516001600160401b038111156200036c576200036c62000148565b62000384816200037d8454620002c1565b84620002fd565b602080601f831160018114620003bc5760008415620003a35750858301515b600019600386901b1c1916600185901b17855562000347565b600085815260208120601f198616915b82811015620003ed57888601518255948401946001909101908401620003cc565b50858210156200040c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60805160a05160c05160e0516101005161012051610140516101605161018051611c37620004a960003960006102700152600081816103a70152818161041c0152818161064801526107aa0152600081816101d501526103cd01526000611174015260006111c30152600061119e015260006110f7015260006111210152600061114b0152611c376000f3fe608060405234801561001057600080fd5b50600436106101a35760003560e01c806370a08231116100ee578063ae1f6aaf11610097578063d6c0b2c411610071578063d6c0b2c4146103cb578063dd62ed3e14610404578063e78cea92146103a5578063ee9a31a21461041757600080fd5b8063ae1f6aaf146103a5578063c01e1bd6146103cb578063d505accf146103f157600080fd5b80639dc29fac116100c85780639dc29fac1461036c578063a457c2d71461037f578063a9059cbb1461039257600080fd5b806370a082311461031b5780637ecebe001461035157806395d89b411461036457600080fd5b8063313ce5671161015057806340c10f191161012a57806340c10f19146102b557806354fd4d50146102ca5780636afdd8501461030657600080fd5b8063313ce567146102695780633644e5151461029a57806339509351146102a257600080fd5b8063095ea7b311610181578063095ea7b31461023157806318160ddd1461024457806323b872dd1461025657600080fd5b806301ffc9a7146101a8578063033964be146101d057806306fdde031461021c575b600080fd5b6101bb6101b636600461194b565b61043e565b60405190151581526020015b60405180910390f35b6101f77f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101c7565b61022461052f565b6040516101c7919061198d565b6101bb61023f366004611a29565b6105c1565b6002545b6040519081526020016101c7565b6101bb610264366004611a53565b6105db565b60405160ff7f00000000000000000000000000000000000000000000000000000000000000001681526020016101c7565b6102486105ff565b6101bb6102b0366004611a29565b61060e565b6102c86102c3366004611a29565b610630565b005b6102246040518060400160405280600c81526020017f312e342e302d626574612e35000000000000000000000000000000000000000081525081565b6e22d473030f116ddee9f6b43ac78ba36101f7565b610248610329366004611a8f565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b61024861035f366004611a8f565b610758565b610224610783565b6102c861037a366004611a29565b610792565b6101bb61038d366004611a29565b6108a9565b6101bb6103a0366004611a29565b610956565b7f00000000000000000000000000000000000000000000000000000000000000006101f7565b7f00000000000000000000000000000000000000000000000000000000000000006101f7565b6102c86103ff366004611aaa565b610964565b610248610412366004611b1d565b610b23565b6101f77f000000000000000000000000000000000000000000000000000000000000000081565b60007f01ffc9a7000000000000000000000000000000000000000000000000000000007f1d1d8b63000000000000000000000000000000000000000000000000000000007fec4fc8e3000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000085168314806104f757507fffffffff00000000000000000000000000000000000000000000000000000000858116908316145b8061052657507fffffffff00000000000000000000000000000000000000000000000000000000858116908216145b95945050505050565b60606003805461053e90611b50565b80601f016020809104026020016040519081016040528092919081815260200182805461056a90611b50565b80156105b75780601f1061058c576101008083540402835291602001916105b7565b820191906000526020600020905b81548152906001019060200180831161059a57829003601f168201915b5050505050905090565b6000336105cf818585610bc4565b60019150505b92915050565b6000336105e9858285610d78565b6105f4858585610e2a565b506001949350505050565b60006106096110dd565b905090565b6000336105cf8185856106218383610b23565b61062b9190611bcc565b610bc4565b3373ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016146106fa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f4f7074696d69736d4d696e7461626c6545524332303a206f6e6c79206272696460448201527f67652063616e206d696e7420616e64206275726e00000000000000000000000060648201526084015b60405180910390fd5b6107048282611211565b8173ffffffffffffffffffffffffffffffffffffffff167f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d41213968858260405161074c91815260200190565b60405180910390a25050565b73ffffffffffffffffffffffffffffffffffffffff81166000908152600560205260408120546105d5565b60606004805461053e90611b50565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610857576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603460248201527f4f7074696d69736d4d696e7461626c6545524332303a206f6e6c79206272696460448201527f67652063616e206d696e7420616e64206275726e00000000000000000000000060648201526084016106f1565b6108618282611331565b8173ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca58260405161074c91815260200190565b600033816108b78286610b23565b905083811015610949576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760448201527f207a65726f00000000000000000000000000000000000000000000000000000060648201526084016106f1565b6105f48286868403610bc4565b6000336105cf818585610e2a565b834211156109ce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332305065726d69743a206578706972656420646561646c696e6500000060448201526064016106f1565b60007f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98888886109fd8c611516565b60408051602081019690965273ffffffffffffffffffffffffffffffffffffffff94851690860152929091166060840152608083015260a082015260c0810186905260e0016040516020818303038152906040528051906020012090506000610a658261154b565b90506000610a75828787876115b4565b90508973ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610b0c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f45524332305065726d69743a20696e76616c6964207369676e6174757265000060448201526064016106f1565b610b178a8a8a610bc4565b50505050505050505050565b60007fffffffffffffffffffffffffffffffffffdd2b8cfcf0ee922116094bc538745d73ffffffffffffffffffffffffffffffffffffffff831601610b8957507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6105d5565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600160209081526040808320938616835292905220545b9392505050565b73ffffffffffffffffffffffffffffffffffffffff8316610c66576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f726573730000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff8216610d09576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f737300000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b6000610d848484610b23565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610e245781811015610e17576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e636500000060448201526064016106f1565b610e248484848403610bc4565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8316610ecd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f20616460448201527f647265737300000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff8216610f70576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201527f657373000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604090205481811015611026576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e742065786365656473206260448201527f616c616e6365000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff80851660009081526020819052604080822085850390559185168152908120805484929061106a908490611bcc565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110d091815260200190565b60405180910390a3610e24565b60003073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614801561114357507f000000000000000000000000000000000000000000000000000000000000000046145b1561116d57507f000000000000000000000000000000000000000000000000000000000000000090565b50604080517f00000000000000000000000000000000000000000000000000000000000000006020808301919091527f0000000000000000000000000000000000000000000000000000000000000000828401527f000000000000000000000000000000000000000000000000000000000000000060608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b73ffffffffffffffffffffffffffffffffffffffff821661128e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f20616464726573730060448201526064016106f1565b80600260008282546112a09190611bcc565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600090815260208190526040812080548392906112da908490611bcc565b909155505060405181815273ffffffffffffffffffffffffffffffffffffffff8316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff82166113d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f45524332303a206275726e2066726f6d20746865207a65726f2061646472657360448201527f730000000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff82166000908152602081905260409020548181101561148a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45524332303a206275726e20616d6f756e7420657863656564732062616c616e60448201527f636500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b73ffffffffffffffffffffffffffffffffffffffff831660009081526020819052604081208383039055600280548492906114c6908490611be4565b909155505060405182815260009073ffffffffffffffffffffffffffffffffffffffff8516907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001610d6b565b73ffffffffffffffffffffffffffffffffffffffff811660009081526005602052604090208054600181018255905b50919050565b60006105d56115586110dd565b836040517f19010000000000000000000000000000000000000000000000000000000000006020820152602281018390526042810182905260009060620160405160208183030381529060405280519060200120905092915050565b60008060006115c5878787876115dc565b915091506115d2816116f4565b5095945050505050565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a083111561161357506000905060036116eb565b8460ff16601b1415801561162b57508460ff16601c14155b1561163c57506000905060046116eb565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa158015611690573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81166116e4576000600192509250506116eb565b9150600090505b94509492505050565b600081600481111561170857611708611bfb565b036117105750565b600181600481111561172457611724611bfb565b0361178b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f45434453413a20696e76616c6964207369676e6174757265000000000000000060448201526064016106f1565b600281600481111561179f5761179f611bfb565b03611806576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f45434453413a20696e76616c6964207369676e6174757265206c656e6774680060448201526064016106f1565b600381600481111561181a5761181a611bfb565b036118a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c60448201527f756500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b60048160048111156118bb576118bb611bfb565b03611948576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c60448201527f756500000000000000000000000000000000000000000000000000000000000060648201526084016106f1565b50565b60006020828403121561195d57600080fd5b81357fffffffff0000000000000000000000000000000000000000000000000000000081168114610bbd57600080fd5b600060208083528351808285015260005b818110156119ba5785810183015185820160400152820161199e565b818111156119cc576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611a2457600080fd5b919050565b60008060408385031215611a3c57600080fd5b611a4583611a00565b946020939093013593505050565b600080600060608486031215611a6857600080fd5b611a7184611a00565b9250611a7f60208501611a00565b9150604084013590509250925092565b600060208284031215611aa157600080fd5b610bbd82611a00565b600080600080600080600060e0888a031215611ac557600080fd5b611ace88611a00565b9650611adc60208901611a00565b95506040880135945060608801359350608088013560ff81168114611b0057600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215611b3057600080fd5b611b3983611a00565b9150611b4760208401611a00565b90509250929050565b600181811c90821680611b6457607f821691505b602082108103611545577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60008219821115611bdf57611bdf611b9d565b500190565b600082821015611bf657611bf6611b9d565b500390565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fdfea164736f6c634300080f000aa164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001" + }, + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30013": { + "code": "0x60806040526004361061002d5760003560e01c806354fd4d5014610052578063b9b3efe9146100b157610048565b3661004857600061003c6100d4565b90508060005260206000f35b600061003c6100d4565b34801561005e57600080fd5b5061009b6040518060400160405280600c81526020017f312e312e312d626574612e33000000000000000000000000000000000000000081525081565b6040516100a89190610168565b60405180910390f35b3480156100bd57600080fd5b506100c66100d4565b6040519081526020016100a8565b600073420000000000000000000000000000000000001573ffffffffffffffffffffffffffffffffffffffff16638381f58a6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610135573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061015991906101db565b67ffffffffffffffff16905090565b600060208083528351808285015260005b8181101561019557858101830151858201604001528201610179565b818111156101a7576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b6000602082840312156101ed57600080fd5b815167ffffffffffffffff8116811461020557600080fd5b939250505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30014": { + "code": "0x608060405234801561001057600080fd5b50600436106100be5760003560e01c80637f46ddb211610076578063aa5574521161005b578063aa557452146101c9578063c4d66de8146101dc578063c89701a2146101ef57600080fd5b80637f46ddb21461018d578063927ede2d146101ab57600080fd5b806354fd4d50116100a757806354fd4d50146101225780635c975abb1461016b578063761f44931461017a57600080fd5b80633687011a146100c35780633cb747bf146100d8575b600080fd5b6100d66100d136600461129e565b61020f565b005b6001546100f89073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b61015e6040518060400160405280600681526020017f312e31302e30000000000000000000000000000000000000000000000000000081525081565b604051610119919061138c565b60405160008152602001610119565b6100d661018836600461139f565b6102c0565b60025473ffffffffffffffffffffffffffffffffffffffff166100f8565b60015473ffffffffffffffffffffffffffffffffffffffff166100f8565b6100d66101d7366004611437565b6107de565b6100d66101ea3660046114ae565b61089a565b6002546100f89073ffffffffffffffffffffffffffffffffffffffff1681565b610217610a43565b6102a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602d60248201527f4552433732314272696467653a206163636f756e74206973206e6f742065787460448201527f65726e616c6c79206f776e65640000000000000000000000000000000000000060648201526084015b60405180910390fd5b6102b88686333388888888610a80565b505050505050565b60015473ffffffffffffffffffffffffffffffffffffffff16331480156103955750600254600154604080517f6e296e45000000000000000000000000000000000000000000000000000000008152905173ffffffffffffffffffffffffffffffffffffffff9384169390921691636e296e45916004808201926020929091908290030181865afa158015610359573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061037d91906114cb565b73ffffffffffffffffffffffffffffffffffffffff16145b610421576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603f60248201527f4552433732314272696467653a2066756e6374696f6e2063616e206f6e6c792060448201527f62652063616c6c65642066726f6d20746865206f746865722062726964676500606482015260840161029f565b3073ffffffffffffffffffffffffffffffffffffffff8816036104c6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602a60248201527f4c324552433732314272696467653a206c6f63616c20746f6b656e2063616e6e60448201527f6f742062652073656c6600000000000000000000000000000000000000000000606482015260840161029f565b6104f0877faecafc2300000000000000000000000000000000000000000000000000000000610fd6565b61057c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603660248201527f4c324552433732314272696467653a206c6f63616c20746f6b656e20696e746560448201527f7266616365206973206e6f7420636f6d706c69616e7400000000000000000000606482015260840161029f565b8673ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105c7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105eb91906114cb565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff16146106cb576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604b60248201527f4c324552433732314272696467653a2077726f6e672072656d6f746520746f6b60448201527f656e20666f72204f7074696d69736d204d696e7461626c65204552433732312060648201527f6c6f63616c20746f6b656e000000000000000000000000000000000000000000608482015260a40161029f565b6040517fa144819400000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85811660048301526024820185905288169063a144819490604401600060405180830381600087803b15801561073b57600080fd5b505af115801561074f573d6000803e3d6000fd5b505050508473ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff168873ffffffffffffffffffffffffffffffffffffffff167f1f39bf6707b5d608453e0ae4c067b562bcc4c85c0f562ef5d2c774d2e7f131ac878787876040516107cd9493929190611531565b60405180910390a450505050505050565b73ffffffffffffffffffffffffffffffffffffffff8516610881576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4552433732314272696467653a206e667420726563697069656e742063616e6e60448201527f6f74206265206164647265737328302900000000000000000000000000000000606482015260840161029f565b6108918787338888888888610a80565b50505050505050565b600054610100900460ff16158080156108ba5750600054600160ff909116105b806108d45750303b1580156108d4575060005460ff166001145b610960576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a6564000000000000000000000000000000000000606482015260840161029f565b600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905580156109be57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff166101001790555b6109dc73420000000000000000000000000000000000000783610ff9565b8015610a3f57600080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00ff169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050565b6000323303610a525750600190565b333b601703610a7a57604051602081016040526020600082333c5160e81c62ef010014905090565b50600090565b73ffffffffffffffffffffffffffffffffffffffff8716610b23576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603160248201527f4c324552433732314272696467653a2072656d6f746520746f6b656e2063616e60448201527f6e6f742062652061646472657373283029000000000000000000000000000000606482015260840161029f565b6040517f6352211e0000000000000000000000000000000000000000000000000000000081526004810185905273ffffffffffffffffffffffffffffffffffffffff891690636352211e90602401602060405180830381865afa158015610b8e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bb291906114cb565b73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff1614610c6c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4c324552433732314272696467653a205769746864726177616c206973206e6f60448201527f74206265696e6720696e69746961746564206279204e4654206f776e65720000606482015260840161029f565b60008873ffffffffffffffffffffffffffffffffffffffff1663d6c0b2c46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610cb9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610cdd91906114cb565b90508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1614610d9a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603760248201527f4c324552433732314272696467653a2072656d6f746520746f6b656e20646f6560448201527f73206e6f74206d6174636820676976656e2076616c7565000000000000000000606482015260840161029f565b6040517f9dc29fac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8881166004830152602482018790528a1690639dc29fac90604401600060405180830381600087803b158015610e0a57600080fd5b505af1158015610e1e573d6000803e3d6000fd5b505050506000818a8989898888604051602401610e419796959493929190611571565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f761f44930000000000000000000000000000000000000000000000000000000017905260015460025491517f3dbb202b00000000000000000000000000000000000000000000000000000000815292935073ffffffffffffffffffffffffffffffffffffffff90811692633dbb202b92610f1692169085908a906004016115ce565b600060405180830381600087803b158015610f3057600080fd5b505af1158015610f44573d6000803e3d6000fd5b505050508773ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff168b73ffffffffffffffffffffffffffffffffffffffff167fb7460e2a880f256ebef3406116ff3eee0cee51ebccdc2a40698f87ebb2e9c1a58a8a8989604051610fc29493929190611531565b60405180910390a450505050505050505050565b6000610fe1836110e3565b8015610ff25750610ff28383611148565b9392505050565b600054610100900460ff16611090576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201527f6e697469616c697a696e67000000000000000000000000000000000000000000606482015260840161029f565b6001805473ffffffffffffffffffffffffffffffffffffffff9384167fffffffffffffffffffffffff00000000000000000000000000000000000000009182161790915560028054929093169116179055565b600061110f827f01ffc9a700000000000000000000000000000000000000000000000000000000611148565b80156111425750611140827fffffffff00000000000000000000000000000000000000000000000000000000611148565b155b92915050565b604080517fffffffff000000000000000000000000000000000000000000000000000000008316602480830191909152825180830390910181526044909101909152602080820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f01ffc9a700000000000000000000000000000000000000000000000000000000178152825160009392849283928392918391908a617530fa92503d91506000519050828015611200575060208210155b801561120c5750600081115b979650505050505050565b73ffffffffffffffffffffffffffffffffffffffff8116811461123957600080fd5b50565b803563ffffffff8116811461125057600080fd5b919050565b60008083601f84011261126757600080fd5b50813567ffffffffffffffff81111561127f57600080fd5b60208301915083602082850101111561129757600080fd5b9250929050565b60008060008060008060a087890312156112b757600080fd5b86356112c281611217565b955060208701356112d281611217565b9450604087013593506112e76060880161123c565b9250608087013567ffffffffffffffff81111561130357600080fd5b61130f89828a01611255565b979a9699509497509295939492505050565b6000815180845260005b818110156113475760208185018101518683018201520161132b565b81811115611359576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000610ff26020830184611321565b600080600080600080600060c0888a0312156113ba57600080fd5b87356113c581611217565b965060208801356113d581611217565b955060408801356113e581611217565b945060608801356113f581611217565b93506080880135925060a088013567ffffffffffffffff81111561141857600080fd5b6114248a828b01611255565b989b979a50959850939692959293505050565b600080600080600080600060c0888a03121561145257600080fd5b873561145d81611217565b9650602088013561146d81611217565b9550604088013561147d81611217565b9450606088013593506114926080890161123c565b925060a088013567ffffffffffffffff81111561141857600080fd5b6000602082840312156114c057600080fd5b8135610ff281611217565b6000602082840312156114dd57600080fd5b8151610ff281611217565b8183528181602085013750600060208284010152600060207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f840116840101905092915050565b73ffffffffffffffffffffffffffffffffffffffff851681528360208201526060604082015260006115676060830184866114e8565b9695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff808a1683528089166020840152808816604084015280871660608401525084608083015260c060a08301526115c160c0830184866114e8565b9998505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff841681526060602082015260006115fd6060830185611321565b905063ffffffff8316604083015294935050505056fea164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000004200000000000000000000000000000000000007" + }, + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30015": { + "code": "0x608060405234801561001057600080fd5b50600436106101825760003560e01c806364ca23ef116100d8578063b80777ea1161008c578063e591b28211610066578063e591b282146103b0578063e81b2c6d146103d2578063f8206140146103db57600080fd5b8063b80777ea14610337578063c598591814610357578063d84447151461037757600080fd5b80638381f58a116100bd5780638381f58a146103115780638b239f73146103255780639e8c49661461032e57600080fd5b806364ca23ef146102e157806368d5dca6146102f557600080fd5b80634397dfef1161013a57806354fd4d501161011457806354fd4d501461025d578063550fcdc91461029f5780635cf24969146102d857600080fd5b80634397dfef146101fc578063440a5e20146102245780634d5d9a2a1461022c57600080fd5b806309bd5a601161016b57806309bd5a60146101a457806316d3bc7f146101c057806321326849146101ed57600080fd5b8063015d8eb914610187578063098999be1461019c575b600080fd5b61019a6101953660046105bc565b6103e4565b005b61019a610523565b6101ad60025481565b6040519081526020015b60405180910390f35b6008546101d49067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020016101b7565b604051600081526020016101b7565b6040805173eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee815260126020820152016101b7565b61019a61052d565b6008546102489068010000000000000000900463ffffffff1681565b60405163ffffffff90911681526020016101b7565b60408051808201909152600581527f312e362e3000000000000000000000000000000000000000000000000000000060208201525b6040516101b7919061062e565b60408051808201909152600381527f45544800000000000000000000000000000000000000000000000000000000006020820152610292565b6101ad60015481565b6003546101d49067ffffffffffffffff1681565b6003546102489068010000000000000000900463ffffffff1681565b6000546101d49067ffffffffffffffff1681565b6101ad60055481565b6101ad60065481565b6000546101d49068010000000000000000900467ffffffffffffffff1681565b600354610248906c01000000000000000000000000900463ffffffff1681565b60408051808201909152600581527f45746865720000000000000000000000000000000000000000000000000000006020820152610292565b60405173deaddeaddeaddeaddeaddeaddeaddeaddead000181526020016101b7565b6101ad60045481565b6101ad60075481565b3373deaddeaddeaddeaddeaddeaddeaddeaddead00011461048b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603b60248201527f4c31426c6f636b3a206f6e6c7920746865206465706f7369746f72206163636f60448201527f756e742063616e20736574204c3120626c6f636b2076616c7565730000000000606482015260840160405180910390fd5b6000805467ffffffffffffffff98891668010000000000000000027fffffffffffffffffffffffffffffffff00000000000000000000000000000000909116998916999099179890981790975560019490945560029290925560038054919094167fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000009190911617909255600491909155600555600655565b61052b610535565b565b61052b610548565b61053d610548565b60a43560a01c600855565b73deaddeaddeaddeaddeaddeaddeaddeaddead000133811461057257633cc50b456000526004601cfd5b60043560801c60035560143560801c60005560243560015560443560075560643560025560843560045550565b803567ffffffffffffffff811681146105b757600080fd5b919050565b600080600080600080600080610100898b0312156105d957600080fd5b6105e28961059f565b97506105f060208a0161059f565b9650604089013595506060890135945061060c60808a0161059f565b979a969950949793969560a0850135955060c08501359460e001359350915050565b600060208083528351808285015260005b8181101561065b5785810183015185820160400152820161063f565b8181111561066d576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01692909201604001939250505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30016": { + "code": "0x6080604052600436106100695760003560e01c806382e3702d1161004357806382e3702d1461012a578063c2b3e5ac1461016a578063ecc704281461017d57600080fd5b80633f827a5a1461009257806344df8e70146100bf57806354fd4d50146100d457600080fd5b3661008d5761008b33620186a0604051806020016040528060008152506101e2565b005b600080fd5b34801561009e57600080fd5b506100a7600181565b60405161ffff90911681526020015b60405180910390f35b3480156100cb57600080fd5b5061008b6103a6565b3480156100e057600080fd5b5061011d6040518060400160405280600c81526020017f312e312e312d626574612e33000000000000000000000000000000000000000081525081565b6040516100b691906104d1565b34801561013657600080fd5b5061015a6101453660046104eb565b60006020819052908152604090205460ff1681565b60405190151581526020016100b6565b61008b610178366004610533565b6101e2565b34801561018957600080fd5b506101d46001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b6040519081526020016100b6565b60006102786040518060c0016040528061023c6001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b815233602082015273ffffffffffffffffffffffffffffffffffffffff871660408201523460608201526080810186905260a0018490526103de565b600081815260208190526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055905073ffffffffffffffffffffffffffffffffffffffff8416336103136001547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff167e010000000000000000000000000000000000000000000000000000000000001790565b7f02a52367d10742d8032712c1bb8e0144ff1ec5ffda1ed7d70bb05a2744955054348787876040516103489493929190610637565b60405180910390a45050600180547dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8082168301167fffff0000000000000000000000000000000000000000000000000000000000009091161790555050565b476103b08161042b565b60405181907f7967de617a5ac1cc7eba2d6f37570a0135afa950d8bb77cdd35f0d0b4e85a16f90600090a250565b80516020808301516040808501516060860151608087015160a0880151935160009761040e979096959101610667565b604051602081830303815290604052805190602001209050919050565b806040516104389061045a565b6040518091039082f0905080158015610455573d6000803e3d6000fd5b505050565b6008806106bf83390190565b6000815180845260005b8181101561048c57602081850181015186830182015201610470565b8181111561049e576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104e46020830184610466565b9392505050565b6000602082840312156104fd57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60008060006060848603121561054857600080fd5b833573ffffffffffffffffffffffffffffffffffffffff8116811461056c57600080fd5b925060208401359150604084013567ffffffffffffffff8082111561059057600080fd5b818601915086601f8301126105a457600080fd5b8135818111156105b6576105b6610504565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156105fc576105fc610504565b8160405282815289602084870101111561061557600080fd5b8260208601602083013760006020848301015280955050505050509250925092565b8481528360208201526080604082015260006106566080830185610466565b905082606083015295945050505050565b868152600073ffffffffffffffffffffffffffffffffffffffff808816602084015280871660408401525084606083015283608083015260c060a08301526106b260c0830184610466565b9897505050505050505056fe608060405230fffea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30017": { + "code": "0x60806040523480156200001157600080fd5b5060043610620000875760003560e01c8063d23822421162000062578063d2382242146200014f578063d97df6521462000176578063e78cea9214620001b3578063ee9a31a214620001da57600080fd5b806354fd4d50146200008c5780635572acae14620000e15780637d1d0c5b1462000118575b600080fd5b620000c96040518060400160405280600c81526020017f312e342e312d626574612e37000000000000000000000000000000000000000081525081565b604051620000d891906200049b565b60405180910390f35b62000107620000f2366004620004e1565b60006020819052908152604090205460ff1681565b6040519015158152602001620000d8565b620001407f0000000000000000000000000000000000000000000000000000000000007a6981565b604051908152602001620000d8565b7f0000000000000000000000000000000000000000000000000000000000007a6962000140565b6200018d62000187366004620005e1565b62000202565b60405173ffffffffffffffffffffffffffffffffffffffff9091168152602001620000d8565b7f00000000000000000000000042000000000000000000000000000000000000146200018d565b6200018d7f000000000000000000000000420000000000000000000000000000000000001481565b600073ffffffffffffffffffffffffffffffffffffffff8416620002d3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526044602482018190527f4f7074696d69736d4d696e7461626c65455243373231466163746f72793a204c908201527f3120746f6b656e20616464726573732063616e6e6f742062652061646472657360648201527f7328302900000000000000000000000000000000000000000000000000000000608482015260a40160405180910390fd5b6000848484604051602001620002ec939291906200065e565b6040516020818303038152906040528051906020012090506000817f00000000000000000000000042000000000000000000000000000000000000147f0000000000000000000000000000000000000000000000000000000000007a698888886040516200035a906200041f565b6200036a959493929190620006ad565b8190604051809103906000f59050801580156200038b573d6000803e3d6000fd5b5073ffffffffffffffffffffffffffffffffffffffff8181166000818152602081815260409182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905590513381529394509189169290917fe72783bb8e0ca31286b85278da59684dd814df9762a52f0837f89edd1483b299910160405180910390a395945050505050565b6131bf806200070f83390190565b6000815180845260005b81811015620004555760208185018101518683018201520162000437565b8181111562000468576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b602081526000620004b060208301846200042d565b9392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114620004dc57600080fd5b919050565b600060208284031215620004f457600080fd5b620004b082620004b7565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600082601f8301126200054057600080fd5b813567ffffffffffffffff808211156200055e576200055e620004ff565b604051601f83017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f01168101908282118183101715620005a757620005a7620004ff565b81604052838152866020858801011115620005c157600080fd5b836020870160208301376000602085830101528094505050505092915050565b600080600060608486031215620005f757600080fd5b6200060284620004b7565b9250602084013567ffffffffffffffff808211156200062057600080fd5b6200062e878388016200052e565b935060408601359150808211156200064557600080fd5b5062000654868287016200052e565b9150509250925092565b73ffffffffffffffffffffffffffffffffffffffff841681526060602082015260006200068f60608301856200042d565b8281036040840152620006a381856200042d565b9695505050505050565b600073ffffffffffffffffffffffffffffffffffffffff808816835286602084015280861660408401525060a06060830152620006ee60a08301856200042d565b82810360808401526200070281856200042d565b9897505050505050505056fe60e06040523480156200001157600080fd5b50604051620031bf380380620031bf83398101604081905262000034916200062d565b8181600062000044838262000756565b50600162000053828262000756565b5050506001600160a01b038516620000d85760405162461bcd60e51b815260206004820152603360248201527f4f7074696d69736d4d696e7461626c654552433732313a20627269646765206360448201527f616e6e6f7420626520616464726573732830290000000000000000000000000060648201526084015b60405180910390fd5b83600003620001505760405162461bcd60e51b815260206004820152603660248201527f4f7074696d69736d4d696e7461626c654552433732313a2072656d6f7465206360448201527f6861696e2069642063616e6e6f74206265207a65726f000000000000000000006064820152608401620000cf565b6001600160a01b038316620001ce5760405162461bcd60e51b815260206004820152603960248201527f4f7074696d69736d4d696e7461626c654552433732313a2072656d6f7465207460448201527f6f6b656e2063616e6e6f742062652061646472657373283029000000000000006064820152608401620000cf565b60808490526001600160a01b0383811660a081905290861660c0526200020290601462000256602090811b62000eed17901c565b62000218856200041660201b620011301760201c565b6040516020016200022b92919062000822565b604051602081830303815290604052600a90816200024a919062000756565b50505050505062000993565b6060600062000267836002620008ac565b62000274906002620008ce565b6001600160401b038111156200028e576200028e62000553565b6040519080825280601f01601f191660200182016040528015620002b9576020820181803683370190505b509050600360fc1b81600081518110620002d757620002d7620008e9565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110620003095762000309620008e9565b60200101906001600160f81b031916908160001a90535060006200032f846002620008ac565b6200033c906001620008ce565b90505b6001811115620003be576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110620003745762000374620008e9565b1a60f81b8282815181106200038d576200038d620008e9565b60200101906001600160f81b031916908160001a90535060049490941c93620003b681620008ff565b90506200033f565b5083156200040f5760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401620000cf565b9392505050565b6060816000036200043e5750506040805180820190915260018152600360fc1b602082015290565b8160005b81156200046e5780620004558162000919565b9150620004669050600a836200094b565b915062000442565b6000816001600160401b038111156200048b576200048b62000553565b6040519080825280601f01601f191660200182016040528015620004b6576020820181803683370190505b5090505b84156200052e57620004ce60018362000962565b9150620004dd600a866200097c565b620004ea906030620008ce565b60f81b818381518110620005025762000502620008e9565b60200101906001600160f81b031916908160001a90535062000526600a866200094b565b9450620004ba565b949350505050565b80516001600160a01b03811681146200054e57600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b60005b83811015620005865781810151838201526020016200056c565b8381111562000596576000848401525b50505050565b600082601f830112620005ae57600080fd5b81516001600160401b0380821115620005cb57620005cb62000553565b604051601f8301601f19908116603f01168101908282118183101715620005f657620005f662000553565b816040528381528660208588010111156200061057600080fd5b6200062384602083016020890162000569565b9695505050505050565b600080600080600060a086880312156200064657600080fd5b620006518662000536565b945060208601519350620006686040870162000536565b60608701519093506001600160401b03808211156200068657600080fd5b6200069489838a016200059c565b93506080880151915080821115620006ab57600080fd5b50620006ba888289016200059c565b9150509295509295909350565b600181811c90821680620006dc57607f821691505b602082108103620006fd57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200075157600081815260208120601f850160051c810160208610156200072c5750805b601f850160051c820191505b818110156200074d5782815560010162000738565b5050505b505050565b81516001600160401b0381111562000772576200077262000553565b6200078a81620007838454620006c7565b8462000703565b602080601f831160018114620007c25760008415620007a95750858301515b600019600386901b1c1916600185901b1785556200074d565b600085815260208120601f198616915b82811015620007f357888601518255948401946001909101908401620007d2565b5085821015620008125787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6832ba3432b932bab69d60b91b8152600083516200084881600985016020880162000569565b600160fe1b60099184019182015283516200086b81600a84016020880162000569565b712f746f6b656e5552493f75696e743235363d60701b600a9290910191820152601c01949350505050565b634e487b7160e01b600052601160045260246000fd5b6000816000190483118215151615620008c957620008c962000896565b500290565b60008219821115620008e457620008e462000896565b500190565b634e487b7160e01b600052603260045260246000fd5b60008162000911576200091162000896565b506000190190565b6000600182016200092e576200092e62000896565b5060010190565b634e487b7160e01b600052601260045260246000fd5b6000826200095d576200095d62000935565b500490565b60008282101562000977576200097762000896565b500390565b6000826200098e576200098e62000935565b500690565b60805160a05160c0516127d9620009e6600039600081816103e20152818161047a01528181610b210152610c430152600081816101e001526103bc015260008181610329015261040801526127d96000f3fe608060405234801561001057600080fd5b50600436106101ae5760003560e01c80637d1d0c5b116100ee578063c87b56dd11610097578063e78cea9211610071578063e78cea92146103e0578063e951819614610406578063e985e9c51461042c578063ee9a31a21461047557600080fd5b8063c87b56dd1461039f578063d547cfb7146103b2578063d6c0b2c4146103ba57600080fd5b8063a1448194116100c8578063a144819414610366578063a22cb46514610379578063b88d4fde1461038c57600080fd5b80637d1d0c5b1461032457806395d89b411461034b5780639dc29fac1461035357600080fd5b806323b872dd1161015b5780634f6ccce7116101355780634f6ccce7146102af57806354fd4d50146102c25780636352211e146102fe57806370a082311461031157600080fd5b806323b872dd146102765780632f745c591461028957806342842e0e1461029c57600080fd5b8063081812fc1161018c578063081812fc1461023c578063095ea7b31461024f57806318160ddd1461026457600080fd5b806301ffc9a7146101b3578063033964be146101db57806306fdde0314610227575b600080fd5b6101c66101c1366004612226565b61049c565b60405190151581526020015b60405180910390f35b6102027f000000000000000000000000000000000000000000000000000000000000000081565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101d2565b61022f6104fa565b6040516101d291906122b9565b61020261024a3660046122cc565b61058c565b61026261025d36600461230e565b6105c0565b005b6008545b6040519081526020016101d2565b610262610284366004612338565b610751565b61026861029736600461230e565b6107f2565b6102626102aa366004612338565b6108c1565b6102686102bd3660046122cc565b6108dc565b61022f6040518060400160405280600c81526020017f312e332e312d626574612e36000000000000000000000000000000000000000081525081565b61020261030c3660046122cc565b61099a565b61026861031f366004612374565b610a2c565b6102687f000000000000000000000000000000000000000000000000000000000000000081565b61022f610afa565b61026261036136600461230e565b610b09565b61026261037436600461230e565b610c2b565b61026261038736600461238f565b610d42565b61026261039a3660046123fa565b610d51565b61022f6103ad3660046122cc565b610df9565b61022f610e5f565b7f0000000000000000000000000000000000000000000000000000000000000000610202565b7f0000000000000000000000000000000000000000000000000000000000000000610202565b7f0000000000000000000000000000000000000000000000000000000000000000610268565b6101c661043a3660046124f4565b73ffffffffffffffffffffffffffffffffffffffff918216600090815260056020908152604080832093909416825291909152205460ff1690565b6102027f000000000000000000000000000000000000000000000000000000000000000081565b60007faecafc23000000000000000000000000000000000000000000000000000000007fffffffff0000000000000000000000000000000000000000000000000000000083168114806104f357506104f38361126d565b9392505050565b60606000805461050990612527565b80601f016020809104026020016040519081016040528092919081815260200182805461053590612527565b80156105825780601f1061055757610100808354040283529160200191610582565b820191906000526020600020905b81548152906001019060200180831161056557829003601f168201915b5050505050905090565b6000610597826112c3565b5060009081526004602052604090205473ffffffffffffffffffffffffffffffffffffffff1690565b60006105cb8261099a565b90508073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361068d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602160248201527f4552433732313a20617070726f76616c20746f2063757272656e74206f776e6560448201527f720000000000000000000000000000000000000000000000000000000000000060648201526084015b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff821614806106b657506106b6813361043a565b610742576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603e60248201527f4552433732313a20617070726f76652063616c6c6572206973206e6f7420746f60448201527f6b656e206f776e6572206e6f7220617070726f76656420666f7220616c6c00006064820152608401610684565b61074c8383611351565b505050565b61075b33826113f1565b6107e7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560448201527f72206e6f7220617070726f7665640000000000000000000000000000000000006064820152608401610684565b61074c8383836114b0565b60006107fd83610a2c565b821061088b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602b60248201527f455243373231456e756d657261626c653a206f776e657220696e646578206f7560448201527f74206f6620626f756e64730000000000000000000000000000000000000000006064820152608401610684565b5073ffffffffffffffffffffffffffffffffffffffff919091166000908152600660209081526040808320938352929052205490565b61074c83838360405180602001604052806000815250610d51565b60006108e760085490565b8210610975576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602c60248201527f455243373231456e756d657261626c653a20676c6f62616c20696e646578206f60448201527f7574206f6620626f756e647300000000000000000000000000000000000000006064820152608401610684565b600882815481106109885761098861257a565b90600052602060002001549050919050565b60008181526002602052604081205473ffffffffffffffffffffffffffffffffffffffff1680610a26576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4552433732313a20696e76616c696420746f6b656e20494400000000000000006044820152606401610684565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff8216610ad1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602960248201527f4552433732313a2061646472657373207a65726f206973206e6f74206120766160448201527f6c6964206f776e657200000000000000000000000000000000000000000000006064820152608401610684565b5073ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205490565b60606001805461050990612527565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610bce576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4f7074696d69736d4d696e7461626c654552433732313a206f6e6c792062726960448201527f6467652063616e2063616c6c20746869732066756e6374696f6e0000000000006064820152608401610684565b610bd781611722565b8173ffffffffffffffffffffffffffffffffffffffff167fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca582604051610c1f91815260200190565b60405180910390a25050565b3373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614610cf0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f4f7074696d69736d4d696e7461626c654552433732313a206f6e6c792062726960448201527f6467652063616e2063616c6c20746869732066756e6374696f6e0000000000006064820152608401610684565b610cfa82826117fb565b8173ffffffffffffffffffffffffffffffffffffffff167f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688582604051610c1f91815260200190565b610d4d338383611815565b5050565b610d5b33836113f1565b610de7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f4552433732313a2063616c6c6572206973206e6f7420746f6b656e206f776e6560448201527f72206e6f7220617070726f7665640000000000000000000000000000000000006064820152608401610684565b610df384848484611942565b50505050565b6060610e04826112c3565b6000610e0e6119e5565b90506000815111610e2e57604051806020016040528060008152506104f3565b80610e3884611130565b604051602001610e499291906125a9565b6040516020818303038152906040529392505050565b600a8054610e6c90612527565b80601f0160208091040260200160405190810160405280929190818152602001828054610e9890612527565b8015610ee55780601f10610eba57610100808354040283529160200191610ee5565b820191906000526020600020905b815481529060010190602001808311610ec857829003601f168201915b505050505081565b60606000610efc836002612607565b610f07906002612644565b67ffffffffffffffff811115610f1f57610f1f6123cb565b6040519080825280601f01601f191660200182016040528015610f49576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110610f8057610f8061257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110610fe357610fe361257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600061101f846002612607565b61102a906001612644565b90505b60018111156110c7577f303132333435363738396162636465660000000000000000000000000000000085600f166010811061106b5761106b61257a565b1a60f81b8282815181106110815761108161257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535060049490941c936110c08161265c565b905061102d565b5083156104f3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610684565b60608160000361117357505060408051808201909152600181527f3000000000000000000000000000000000000000000000000000000000000000602082015290565b8160005b811561119d578061118781612691565b91506111969050600a836126f8565b9150611177565b60008167ffffffffffffffff8111156111b8576111b86123cb565b6040519080825280601f01601f1916602001820160405280156111e2576020820181803683370190505b5090505b8415611265576111f760018361270c565b9150611204600a86612723565b61120f906030612644565b60f81b8183815181106112245761122461257a565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535061125e600a866126f8565b94506111e6565b949350505050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f780e9d63000000000000000000000000000000000000000000000000000000001480610a265750610a26826119f4565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1661134e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4552433732313a20696e76616c696420746f6b656e20494400000000000000006044820152606401610684565b50565b600081815260046020526040902080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff841690811790915581906113ab8261099a565b73ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92560405160405180910390a45050565b6000806113fd8361099a565b90508073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16148061146b575073ffffffffffffffffffffffffffffffffffffffff80821660009081526005602090815260408083209388168352929052205460ff165b8061126557508373ffffffffffffffffffffffffffffffffffffffff166114918461058c565b73ffffffffffffffffffffffffffffffffffffffff1614949350505050565b8273ffffffffffffffffffffffffffffffffffffffff166114d08261099a565b73ffffffffffffffffffffffffffffffffffffffff1614611573576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602560248201527f4552433732313a207472616e736665722066726f6d20696e636f72726563742060448201527f6f776e65720000000000000000000000000000000000000000000000000000006064820152608401610684565b73ffffffffffffffffffffffffffffffffffffffff8216611615576040517f08c379a0000000000000000000000000000000000000000000000000000000008152602060048201526024808201527f4552433732313a207472616e7366657220746f20746865207a65726f2061646460448201527f72657373000000000000000000000000000000000000000000000000000000006064820152608401610684565b611620838383611ad7565b61162b600082611351565b73ffffffffffffffffffffffffffffffffffffffff8316600090815260036020526040812080546001929061166190849061270c565b909155505073ffffffffffffffffffffffffffffffffffffffff8216600090815260036020526040812080546001929061169c908490612644565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff86811691821790925591518493918716917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91a4505050565b600061172d8261099a565b905061173b81600084611ad7565b611746600083611351565b73ffffffffffffffffffffffffffffffffffffffff8116600090815260036020526040812080546001929061177c90849061270c565b909155505060008281526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001690555183919073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908390a45050565b610d4d828260405180602001604052806000815250611bdd565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036118aa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f4552433732313a20617070726f766520746f2063616c6c6572000000000000006044820152606401610684565b73ffffffffffffffffffffffffffffffffffffffff83811660008181526005602090815260408083209487168084529482529182902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001686151590811790915591519182527f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a3505050565b61194d8484846114b0565b61195984848484611c80565b610df3576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b6060600a805461050990612527565b60007fffffffff0000000000000000000000000000000000000000000000000000000082167f80ac58cd000000000000000000000000000000000000000000000000000000001480611a8757507fffffffff0000000000000000000000000000000000000000000000000000000082167f5b5e139f00000000000000000000000000000000000000000000000000000000145b80610a2657507f01ffc9a7000000000000000000000000000000000000000000000000000000007fffffffff00000000000000000000000000000000000000000000000000000000831614610a26565b73ffffffffffffffffffffffffffffffffffffffff8316611b3f57611b3a81600880546000838152600960205260408120829055600182018355919091527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30155565b611b7c565b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614611b7c57611b7c8382611e73565b73ffffffffffffffffffffffffffffffffffffffff8216611ba05761074c81611f2a565b8273ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161461074c5761074c8282611fd9565b611be7838361202a565b611bf46000848484611c80565b61074c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b600073ffffffffffffffffffffffffffffffffffffffff84163b15611e68576040517f150b7a0200000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85169063150b7a0290611cf7903390899088908890600401612737565b6020604051808303816000875af1925050508015611d50575060408051601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201909252611d4d91810190612780565b60015b611e1d573d808015611d7e576040519150601f19603f3d011682016040523d82523d6000602084013e611d83565b606091505b508051600003611e15576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603260248201527f4552433732313a207472616e7366657220746f206e6f6e20455243373231526560448201527f63656976657220696d706c656d656e74657200000000000000000000000000006064820152608401610684565b805181602001fd5b7fffffffff00000000000000000000000000000000000000000000000000000000167f150b7a0200000000000000000000000000000000000000000000000000000000149050611265565b506001949350505050565b60006001611e8084610a2c565b611e8a919061270c565b600083815260076020526040902054909150808214611eea5773ffffffffffffffffffffffffffffffffffffffff841660009081526006602090815260408083208584528252808320548484528184208190558352600790915290208190555b50600091825260076020908152604080842084905573ffffffffffffffffffffffffffffffffffffffff9094168352600681528383209183525290812055565b600854600090611f3c9060019061270c565b60008381526009602052604081205460088054939450909284908110611f6457611f6461257a565b906000526020600020015490508060088381548110611f8557611f8561257a565b6000918252602080832090910192909255828152600990915260408082208490558582528120556008805480611fbd57611fbd61279d565b6001900381819060005260206000200160009055905550505050565b6000611fe483610a2c565b73ffffffffffffffffffffffffffffffffffffffff9093166000908152600660209081526040808320868452825280832085905593825260079052919091209190915550565b73ffffffffffffffffffffffffffffffffffffffff82166120a7576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4552433732313a206d696e7420746f20746865207a65726f20616464726573736044820152606401610684565b60008181526002602052604090205473ffffffffffffffffffffffffffffffffffffffff1615612133576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f4552433732313a20746f6b656e20616c7265616479206d696e746564000000006044820152606401610684565b61213f60008383611ad7565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600360205260408120805460019290612175908490612644565b909155505060008181526002602052604080822080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff861690811790915590518392907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef908290a45050565b7fffffffff000000000000000000000000000000000000000000000000000000008116811461134e57600080fd5b60006020828403121561223857600080fd5b81356104f3816121f8565b60005b8381101561225e578181015183820152602001612246565b83811115610df35750506000910152565b60008151808452612287816020860160208601612243565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104f3602083018461226f565b6000602082840312156122de57600080fd5b5035919050565b803573ffffffffffffffffffffffffffffffffffffffff8116811461230957600080fd5b919050565b6000806040838503121561232157600080fd5b61232a836122e5565b946020939093013593505050565b60008060006060848603121561234d57600080fd5b612356846122e5565b9250612364602085016122e5565b9150604084013590509250925092565b60006020828403121561238657600080fd5b6104f3826122e5565b600080604083850312156123a257600080fd5b6123ab836122e5565b9150602083013580151581146123c057600080fd5b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000806000806080858703121561241057600080fd5b612419856122e5565b9350612427602086016122e5565b925060408501359150606085013567ffffffffffffffff8082111561244b57600080fd5b818701915087601f83011261245f57600080fd5b813581811115612471576124716123cb565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0908116603f011681019083821181831017156124b7576124b76123cb565b816040528281528a60208487010111156124d057600080fd5b82602086016020830137600060208483010152809550505050505092959194509250565b6000806040838503121561250757600080fd5b612510836122e5565b915061251e602084016122e5565b90509250929050565b600181811c9082168061253b57607f821691505b602082108103612574577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600083516125bb818460208801612243565b8351908301906125cf818360208801612243565b01949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561263f5761263f6125d8565b500290565b60008219821115612657576126576125d8565b500190565b60008161266b5761266b6125d8565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036126c2576126c26125d8565b5060010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b600082612707576127076126c9565b500490565b60008282101561271e5761271e6125d8565b500390565b600082612732576127326126c9565b500690565b600073ffffffffffffffffffffffffffffffffffffffff808716835280861660208401525083604083015260806060830152612776608083018461226f565b9695505050505050565b60006020828403121561279257600080fd5b81516104f3816121f8565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603160045260246000fdfea164736f6c634300080f000aa164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30018": { + "code": "0x60806040526004361061010e5760003560e01c8063860f7cda116100a557806399a88ec411610074578063b794726211610059578063b794726214610329578063f2fde38b14610364578063f3b7dead1461038457600080fd5b806399a88ec4146102e95780639b2ea4bd1461030957600080fd5b8063860f7cda1461026b5780638d52d4a01461028b5780638da5cb5b146102ab5780639623609d146102d657600080fd5b80633ab76e9f116100e15780633ab76e9f146101cc5780636bd9f516146101f9578063715018a6146102365780637eff275e1461024b57600080fd5b80630652b57a1461011357806307c8f7b014610135578063204e1c7a14610155578063238181ae1461019f575b600080fd5b34801561011f57600080fd5b5061013361012e3660046111f9565b6103a4565b005b34801561014157600080fd5b50610133610150366004611216565b6103f3565b34801561016157600080fd5b506101756101703660046111f9565b610445565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b3480156101ab57600080fd5b506101bf6101ba3660046111f9565b61066b565b60405161019691906112ae565b3480156101d857600080fd5b506003546101759073ffffffffffffffffffffffffffffffffffffffff1681565b34801561020557600080fd5b506102296102143660046111f9565b60016020526000908152604090205460ff1681565b60405161019691906112f0565b34801561024257600080fd5b50610133610705565b34801561025757600080fd5b50610133610266366004611331565b610719565b34801561027757600080fd5b5061013361028636600461148c565b6108cc565b34801561029757600080fd5b506101336102a63660046114dc565b610903565b3480156102b757600080fd5b5060005473ffffffffffffffffffffffffffffffffffffffff16610175565b6101336102e436600461150e565b610977565b3480156102f557600080fd5b50610133610304366004611331565b610b8e565b34801561031557600080fd5b50610133610324366004611584565b610e1e565b34801561033557600080fd5b5060035474010000000000000000000000000000000000000000900460ff166040519015158152602001610196565b34801561037057600080fd5b5061013361037f3660046111f9565b610eb4565b34801561039057600080fd5b5061017561039f3660046111f9565b610f6b565b6103ac6110e1565b600380547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6103fb6110e1565b6003805491151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff909216919091179055565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604081205460ff1681816002811115610481576104816112c1565b036104fc578273ffffffffffffffffffffffffffffffffffffffff16635c60da1b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104d1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104f591906115cb565b9392505050565b6001816002811115610510576105106112c1565b03610560578273ffffffffffffffffffffffffffffffffffffffff1663aaf10f426040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104d1573d6000803e3d6000fd5b6002816002811115610574576105746112c1565b036105fe5760035473ffffffffffffffffffffffffffffffffffffffff8481166000908152600260205260409081902090517fbf40fac1000000000000000000000000000000000000000000000000000000008152919092169163bf40fac1916105e19190600401611635565b602060405180830381865afa1580156104d1573d6000803e3d6000fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f50726f787941646d696e3a20756e6b6e6f776e2070726f78792074797065000060448201526064015b60405180910390fd5b50919050565b60026020526000908152604090208054610684906115e8565b80601f01602080910402602001604051908101604052809291908181526020018280546106b0906115e8565b80156106fd5780601f106106d2576101008083540402835291602001916106fd565b820191906000526020600020905b8154815290600101906020018083116106e057829003601f168201915b505050505081565b61070d6110e1565b6107176000611162565b565b6107216110e1565b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604081205460ff169081600281111561075d5761075d6112c1565b036107e9576040517f8f28397000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690638f283970906024015b600060405180830381600087803b1580156107cc57600080fd5b505af11580156107e0573d6000803e3d6000fd5b50505050505050565b60018160028111156107fd576107fd6112c1565b03610856576040517f13af403500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff83811660048301528416906313af4035906024016107b2565b600281600281111561086a5761086a6112c1565b036105fe576003546040517ff2fde38b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff84811660048301529091169063f2fde38b906024016107b2565b505050565b6108d46110e1565b73ffffffffffffffffffffffffffffffffffffffff821660009081526002602052604090206108c78282611724565b61090b6110e1565b73ffffffffffffffffffffffffffffffffffffffff82166000908152600160208190526040909120805483927fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009091169083600281111561096e5761096e6112c1565b02179055505050565b61097f6110e1565b73ffffffffffffffffffffffffffffffffffffffff831660009081526001602052604081205460ff16908160028111156109bb576109bb6112c1565b03610a81576040517f4f1ef28600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff851690634f1ef286903490610a16908790879060040161183e565b60006040518083038185885af1158015610a34573d6000803e3d6000fd5b50505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052610a7b9190810190611875565b50610b88565b610a8b8484610b8e565b60008473ffffffffffffffffffffffffffffffffffffffff163484604051610ab391906118ec565b60006040518083038185875af1925050503d8060008114610af0576040519150601f19603f3d011682016040523d82523d6000602084013e610af5565b606091505b5050905080610b86576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602e60248201527f50726f787941646d696e3a2063616c6c20746f2070726f78792061667465722060448201527f75706772616465206661696c6564000000000000000000000000000000000000606482015260840161065c565b505b50505050565b610b966110e1565b73ffffffffffffffffffffffffffffffffffffffff821660009081526001602052604081205460ff1690816002811115610bd257610bd26112c1565b03610c2b576040517f3659cfe600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8381166004830152841690633659cfe6906024016107b2565b6001816002811115610c3f57610c3f6112c1565b03610cbe576040517f9b0b0fda0000000000000000000000000000000000000000000000000000000081527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc600482015273ffffffffffffffffffffffffffffffffffffffff8381166024830152841690639b0b0fda906044016107b2565b6002816002811115610cd257610cd26112c1565b03610e165773ffffffffffffffffffffffffffffffffffffffff831660009081526002602052604081208054610d07906115e8565b80601f0160208091040260200160405190810160405280929190818152602001828054610d33906115e8565b8015610d805780601f10610d5557610100808354040283529160200191610d80565b820191906000526020600020905b815481529060010190602001808311610d6357829003601f168201915b50506003546040517f9b2ea4bd00000000000000000000000000000000000000000000000000000000815294955073ffffffffffffffffffffffffffffffffffffffff1693639b2ea4bd9350610dde92508591508790600401611908565b600060405180830381600087803b158015610df857600080fd5b505af1158015610e0c573d6000803e3d6000fd5b5050505050505050565b6108c7611940565b610e266110e1565b6003546040517f9b2ea4bd00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff90911690639b2ea4bd90610e7e9085908590600401611908565b600060405180830381600087803b158015610e9857600080fd5b505af1158015610eac573d6000803e3d6000fd5b505050505050565b610ebc6110e1565b73ffffffffffffffffffffffffffffffffffffffff8116610f5f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f6464726573730000000000000000000000000000000000000000000000000000606482015260840161065c565b610f6881611162565b50565b73ffffffffffffffffffffffffffffffffffffffff811660009081526001602052604081205460ff1681816002811115610fa757610fa76112c1565b03610ff7578273ffffffffffffffffffffffffffffffffffffffff1663f851a4406040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104d1573d6000803e3d6000fd5b600181600281111561100b5761100b6112c1565b0361105b578273ffffffffffffffffffffffffffffffffffffffff1663893d20e86040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104d1573d6000803e3d6000fd5b600281600281111561106f5761106f6112c1565b036105fe57600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156104d1573d6000803e3d6000fd5b60005473ffffffffffffffffffffffffffffffffffffffff163314610717576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015260640161065c565b6000805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b73ffffffffffffffffffffffffffffffffffffffff81168114610f6857600080fd5b60006020828403121561120b57600080fd5b81356104f5816111d7565b60006020828403121561122857600080fd5b813580151581146104f557600080fd5b60005b8381101561125357818101518382015260200161123b565b83811115610b885750506000910152565b6000815180845261127c816020860160208601611238565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104f56020830184611264565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b602081016003831061132b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b91905290565b6000806040838503121561134457600080fd5b823561134f816111d7565b9150602083013561135f816111d7565b809150509250929050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156113e0576113e061136a565b604052919050565b600067ffffffffffffffff8211156114025761140261136a565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600061144161143c846113e8565b611399565b905082815283838301111561145557600080fd5b828260208301376000602084830101529392505050565b600082601f83011261147d57600080fd5b6104f58383356020850161142e565b6000806040838503121561149f57600080fd5b82356114aa816111d7565b9150602083013567ffffffffffffffff8111156114c657600080fd5b6114d28582860161146c565b9150509250929050565b600080604083850312156114ef57600080fd5b82356114fa816111d7565b915060208301356003811061135f57600080fd5b60008060006060848603121561152357600080fd5b833561152e816111d7565b9250602084013561153e816111d7565b9150604084013567ffffffffffffffff81111561155a57600080fd5b8401601f8101861361156b57600080fd5b61157a8682356020840161142e565b9150509250925092565b6000806040838503121561159757600080fd5b823567ffffffffffffffff8111156115ae57600080fd5b6115ba8582860161146c565b925050602083013561135f816111d7565b6000602082840312156115dd57600080fd5b81516104f5816111d7565b600181811c908216806115fc57607f821691505b602082108103610665577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000602080835260008454611649816115e8565b8084870152604060018084166000811461166a57600181146116a2576116d0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff008516838a01528284151560051b8a010195506116d0565b896000528660002060005b858110156116c85781548b82018601529083019088016116ad565b8a0184019650505b509398975050505050505050565b601f8211156108c757600081815260208120601f850160051c810160208610156117055750805b601f850160051c820191505b81811015610eac57828155600101611711565b815167ffffffffffffffff81111561173e5761173e61136a565b6117528161174c84546115e8565b846116de565b602080601f8311600181146117a5576000841561176f5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610eac565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156117f2578886015182559484019460019091019084016117d3565b508582101561182e57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b73ffffffffffffffffffffffffffffffffffffffff8316815260406020820152600061186d6040830184611264565b949350505050565b60006020828403121561188757600080fd5b815167ffffffffffffffff81111561189e57600080fd5b8201601f810184136118af57600080fd5b80516118bd61143c826113e8565b8181528560208385010111156118d257600080fd5b6118e3826020830160208601611238565b95945050505050565b600082516118fe818460208701611238565b9190910192915050565b60408152600061191b6040830185611264565b905073ffffffffffffffffffffffffffffffffffffffff831660208301529392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052600160045260246000fdfea164736f6c634300080f000a", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266" + }, + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30019": { + "code": "0x60806040526004361061009a5760003560e01c806382356d8a1161006957806384411d651161004e57806384411d651461021d578063d0e12f9014610233578063d3e5792b1461026757600080fd5b806382356d8a146101a45780638312f149146101e057600080fd5b80630d9019e1146100a65780633ccfd60b1461010457806354fd4d501461011b57806366d003ac1461017157600080fd5b366100a157005b600080fd5b3480156100b257600080fd5b506100da7f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561011057600080fd5b5061011961029b565b005b34801561012757600080fd5b506101646040518060400160405280600c81526020017f312e352e302d626574612e36000000000000000000000000000000000000000081525081565b6040516100fb9190610671565b34801561017d57600080fd5b507f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922666100da565b3480156101b057600080fd5b507f00000000000000000000000000000000000000000000000000000000000000015b6040516100fb919061074e565b3480156101ec57600080fd5b507f0000000000000000000000000000000000000000000000008ac7230489e800005b6040519081526020016100fb565b34801561022957600080fd5b5061020f60005481565b34801561023f57600080fd5b506101d37f000000000000000000000000000000000000000000000000000000000000000181565b34801561027357600080fd5b5061020f7f0000000000000000000000000000000000000000000000008ac7230489e8000081565b7f0000000000000000000000000000000000000000000000008ac7230489e80000471015610376576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b60004790508060008082825461038c9190610762565b9091555050604080518281527f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226673ffffffffffffffffffffffffffffffffffffffff166020820152338183015290517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a17f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee817f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266337f000000000000000000000000000000000000000000000000000000000000000160405161047a94939291906107a1565b60405180910390a160017f000000000000000000000000000000000000000000000000000000000000000160018111156104b6576104b66106e4565b0361057a5760006104e77f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226683610649565b905080610576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e7400000000000000000000000000000000606482015260840161036d565b5050565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226616600482015262061a80602482015260606044820152600060648201527342000000000000000000000000000000000000169063c2b3e5ac9083906084016000604051808303818588803b15801561062d57600080fd5b505af1158015610641573d6000803e3d6000fd5b505050505050565b6000610656835a8461065d565b9392505050565b6000806000806000858888f1949350505050565b600060208083528351808285015260005b8181101561069e57858101830151858201604001528201610682565b818111156106b0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6002811061074a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b6020810161075c8284610713565b92915050565b6000821982111561079c577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500190565b84815273ffffffffffffffffffffffffffffffffffffffff848116602083015283166040820152608081016107d96060830184610713565b9594505050505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3001a": { + "code": "0x60806040526004361061009a5760003560e01c806382356d8a1161006957806384411d651161004e57806384411d651461021d578063d0e12f9014610233578063d3e5792b1461026757600080fd5b806382356d8a146101a45780638312f149146101e057600080fd5b80630d9019e1146100a65780633ccfd60b1461010457806354fd4d501461011b57806366d003ac1461017157600080fd5b366100a157005b600080fd5b3480156100b257600080fd5b506100da7f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561011057600080fd5b5061011961029b565b005b34801561012757600080fd5b506101646040518060400160405280600c81526020017f312e352e302d626574612e35000000000000000000000000000000000000000081525081565b6040516100fb9190610671565b34801561017d57600080fd5b507f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb922666100da565b3480156101b057600080fd5b507f00000000000000000000000000000000000000000000000000000000000000015b6040516100fb919061074e565b3480156101ec57600080fd5b507f0000000000000000000000000000000000000000000000008ac7230489e800005b6040519081526020016100fb565b34801561022957600080fd5b5061020f60005481565b34801561023f57600080fd5b506101d37f000000000000000000000000000000000000000000000000000000000000000181565b34801561027357600080fd5b5061020f7f0000000000000000000000000000000000000000000000008ac7230489e8000081565b7f0000000000000000000000000000000000000000000000008ac7230489e80000471015610376576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b60004790508060008082825461038c9190610762565b9091555050604080518281527f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226673ffffffffffffffffffffffffffffffffffffffff166020820152338183015290517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a17f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee817f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266337f000000000000000000000000000000000000000000000000000000000000000160405161047a94939291906107a1565b60405180910390a160017f000000000000000000000000000000000000000000000000000000000000000160018111156104b6576104b66106e4565b0361057a5760006104e77f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226683610649565b905080610576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e7400000000000000000000000000000000606482015260840161036d565b5050565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226616600482015262061a80602482015260606044820152600060648201527342000000000000000000000000000000000000169063c2b3e5ac9083906084016000604051808303818588803b15801561062d57600080fd5b505af1158015610641573d6000803e3d6000fd5b505050505050565b6000610656835a8461065d565b9392505050565b6000806000806000858888f1949350505050565b600060208083528351808285015260005b8181101561069e57858101830151858201604001528201610682565b818111156106b0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6002811061074a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b6020810161075c8284610713565b92915050565b6000821982111561079c577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500190565b84815273ffffffffffffffffffffffffffffffffffffffff848116602083015283166040820152608081016107d96060830184610713565b9594505050505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3001b": { + "code": "0x60806040526004361061009a5760003560e01c806382356d8a1161006957806384411d651161004e57806384411d651461021d578063d0e12f9014610233578063d3e5792b1461026757600080fd5b806382356d8a146101a45780638312f149146101e057600080fd5b80630d9019e1146100a65780633ccfd60b1461010457806354fd4d501461011b57806366d003ac1461017157600080fd5b366100a157005b600080fd5b3480156100b257600080fd5b506100da7f000000000000000000000000420000000000000000000000000000000000001981565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b34801561011057600080fd5b5061011961029b565b005b34801561012757600080fd5b506101646040518060400160405280600581526020017f312e302e3000000000000000000000000000000000000000000000000000000081525081565b6040516100fb9190610671565b34801561017d57600080fd5b507f00000000000000000000000042000000000000000000000000000000000000196100da565b3480156101b057600080fd5b507f00000000000000000000000000000000000000000000000000000000000000015b6040516100fb919061074e565b3480156101ec57600080fd5b507f00000000000000000000000000000000000000000000000000000000000000005b6040519081526020016100fb565b34801561022957600080fd5b5061020f60005481565b34801561023f57600080fd5b506101d37f000000000000000000000000000000000000000000000000000000000000000181565b34801561027357600080fd5b5061020f7f000000000000000000000000000000000000000000000000000000000000000081565b7f0000000000000000000000000000000000000000000000000000000000000000471015610376576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152604a60248201527f4665655661756c743a207769746864726177616c20616d6f756e74206d75737460448201527f2062652067726561746572207468616e206d696e696d756d207769746864726160648201527f77616c20616d6f756e7400000000000000000000000000000000000000000000608482015260a4015b60405180910390fd5b60004790508060008082825461038c9190610762565b9091555050604080518281527f000000000000000000000000420000000000000000000000000000000000001973ffffffffffffffffffffffffffffffffffffffff166020820152338183015290517fc8a211cc64b6ed1b50595a9fcb1932b6d1e5a6e8ef15b60e5b1f988ea9086bba9181900360600190a17f38e04cbeb8c10f8f568618aa75be0f10b6729b8b4237743b4de20cbcde2839ee817f0000000000000000000000004200000000000000000000000000000000000019337f000000000000000000000000000000000000000000000000000000000000000160405161047a94939291906107a1565b60405180910390a160017f000000000000000000000000000000000000000000000000000000000000000160018111156104b6576104b66106e4565b0361057a5760006104e77f000000000000000000000000420000000000000000000000000000000000001983610649565b905080610576576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603060248201527f4665655661756c743a206661696c656420746f2073656e642045544820746f2060448201527f4c322066656520726563697069656e7400000000000000000000000000000000606482015260840161036d565b5050565b6040517fc2b3e5ac00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000420000000000000000000000000000000000001916600482015262061a80602482015260606044820152600060648201527342000000000000000000000000000000000000169063c2b3e5ac9083906084016000604051808303818588803b15801561062d57600080fd5b505af1158015610641573d6000803e3d6000fd5b505050505050565b6000610656835a8461065d565b9392505050565b6000806000806000858888f1949350505050565b600060208083528351808285015260005b8181101561069e57858101830151858201604001528201610682565b818111156106b0576000604083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016929092016040019392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b6002811061074a577f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b9052565b6020810161075c8284610713565b92915050565b6000821982111561079c577f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b500190565b84815273ffffffffffffffffffffffffffffffffffffffff848116602083015283166040820152608081016107d96060830184610713565b9594505050505056fea164736f6c634300080f000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30020": { + "code": "0x608060405234801561001057600080fd5b50600436106100415760003560e01c806354fd4d501461004657806360d7a27814610098578063a2ea7c6e146100b9575b600080fd5b6100826040518060400160405280600c81526020017f312e332e312d626574612e32000000000000000000000000000000000000000081525081565b60405161008f9190610473565b60405180910390f35b6100ab6100a636600461048d565b6100d9565b60405190815260200161008f565b6100cc6100c736600461053f565b61029d565b60405161008f9190610558565b60008060405180608001604052806000801b81526020018573ffffffffffffffffffffffffffffffffffffffff168152602001841515815260200187878080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201829052509390945250929350915061015b9050826103c5565b600081815260208190526040902054909150156101a4576040517f23369fa600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b80825260008181526020818152604091829020845181559084015160018201805493860151151574010000000000000000000000000000000000000000027fffffffffffffffffffffff00000000000000000000000000000000000000000090941673ffffffffffffffffffffffffffffffffffffffff9092169190911792909217909155606083015183919060028201906102409082610682565b509050503373ffffffffffffffffffffffffffffffffffffffff16817fd0b86852e21f9e5fa4bc3b0cff9757ffe243d50c4b43968a42202153d651ea5e8460405161028b9190610558565b60405180910390a39695505050505050565b604080516080810182526000808252602082018190529181019190915260608082015260008281526020818152604091829020825160808101845281548152600182015473ffffffffffffffffffffffffffffffffffffffff8116938201939093527401000000000000000000000000000000000000000090920460ff1615159282019290925260028201805491929160608401919061033c906105e0565b80601f0160208091040260200160405190810160405280929190818152602001828054610368906105e0565b80156103b55780601f1061038a576101008083540402835291602001916103b5565b820191906000526020600020905b81548152906001019060200180831161039857829003601f168201915b5050505050815250509050919050565b60008160600151826020015183604001516040516020016103e89392919061079c565b604051602081830303815290604052805190602001209050919050565b60005b83811015610420578181015183820152602001610408565b50506000910152565b60008151808452610441816020860160208601610405565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006104866020830184610429565b9392505050565b600080600080606085870312156104a357600080fd5b843567ffffffffffffffff808211156104bb57600080fd5b818701915087601f8301126104cf57600080fd5b8135818111156104de57600080fd5b8860208285010111156104f057600080fd5b6020928301965094505085013573ffffffffffffffffffffffffffffffffffffffff8116811461051f57600080fd5b91506040850135801515811461053457600080fd5b939692955090935050565b60006020828403121561055157600080fd5b5035919050565b602081528151602082015273ffffffffffffffffffffffffffffffffffffffff6020830151166040820152604082015115156060820152600060608301516080808401526105a960a0840182610429565b949350505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600181811c908216806105f457607f821691505b60208210810361062d577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b601f82111561067d57600081815260208120601f850160051c8101602086101561065a5750805b601f850160051c820191505b8181101561067957828155600101610666565b5050505b505050565b815167ffffffffffffffff81111561069c5761069c6105b1565b6106b0816106aa84546105e0565b84610633565b602080601f83116001811461070357600084156106cd5750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555610679565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b8281101561075057888601518255948401946001909101908401610731565b508582101561078c57878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b600084516107ae818460208901610405565b60609490941b7fffffffffffffffffffffffffffffffffffffffff000000000000000000000000169190930190815290151560f81b60148201526015019291505056fea164736f6c6343000813000a", + "balance": "0x0" + }, + "c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30021": { + "code": "0x60806040526004361061018b5760003560e01c806395411525116100d6578063d45c44351161007f578063ed24911d11610059578063ed24911d146104fd578063f10b5cc814610512578063f17325e71461054157600080fd5b8063d45c443514610467578063e30bb5631461049e578063e71ff365146104dd57600080fd5b8063b469318d116100b0578063b469318d146103ba578063b83010d314610414578063cf190f341461044757600080fd5b80639541152514610367578063a3112a641461037a578063a6d4dbc7146103a757600080fd5b806344adc90e116101385780634d003070116101125780634d003070146102de57806354fd4d50146102fe57806379f7573a1461034757600080fd5b806344adc90e1461029857806346926267146102b85780634cb7e9e5146102cb57600080fd5b806317d7de7c1161016957806317d7de7c146102205780632d0335ab146102425780633c0427151461028557600080fd5b80630eabf6601461019057806312b11a17146101a557806313893f61146101e7575b600080fd5b6101a361019e3660046134c8565b610554565b005b3480156101b157600080fd5b507ffeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d9880765b6040519081526020015b60405180910390f35b3480156101f357600080fd5b506102076102023660046134c8565b6106eb565b60405167ffffffffffffffff90911681526020016101de565b34801561022c57600080fd5b50610235610730565b6040516101de9190613578565b34801561024e57600080fd5b506101d461025d3660046135bd565b73ffffffffffffffffffffffffffffffffffffffff1660009081526020819052604090205490565b6101d46102933660046135da565b610760565b6102ab6102a63660046134c8565b610863565b6040516101de9190613615565b6101a36102c6366004613659565b6109e4565b6101a36102d93660046134c8565b610a68565b3480156102ea57600080fd5b506102076102f9366004613671565b610b4b565b34801561030a57600080fd5b506102356040518060400160405280600c81526020017f312e342e312d626574612e33000000000000000000000000000000000000000081525081565b34801561035357600080fd5b506101a3610362366004613671565b610b58565b6102ab6103753660046134c8565b610bef565b34801561038657600080fd5b5061039a610395366004613671565b610e62565b6040516101de9190613771565b6101a36103b5366004613784565b611025565b3480156103c657600080fd5b506102076103d5366004613797565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152603460209081526040808320938352929052205467ffffffffffffffff1690565b34801561042057600080fd5b507fb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e756101d4565b34801561045357600080fd5b50610207610462366004613671565b6110ca565b34801561047357600080fd5b50610207610482366004613671565b60009081526033602052604090205467ffffffffffffffff1690565b3480156104aa57600080fd5b506104cd6104b9366004613671565b600090815260326020526040902054151590565b60405190151581526020016101de565b3480156104e957600080fd5b506102076104f83660046134c8565b6110d8565b34801561050957600080fd5b506101d4611110565b34801561051e57600080fd5b5060405173420000000000000000000000000000000000002081526020016101de565b6101d461054f3660046137c3565b61111a565b348160005b818110156106e4577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82018114600086868481811061059a5761059a6137fe565b90506020028101906105ac919061382d565b6105b590613ac3565b60208101518051919250908015806105d257508260400151518114155b15610609576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b818110156106ad576106a56040518060a001604052808660000151815260200185848151811061063e5761063e6137fe565b6020026020010151815260200186604001518481518110610661576106616137fe565b60200260200101518152602001866060015173ffffffffffffffffffffffffffffffffffffffff168152602001866080015167ffffffffffffffff168152506111d8565b60010161060c565b506106c383600001518385606001518a886113e9565b6106cd9088613bed565b9650505050506106dd8160010190565b9050610559565b5050505050565b60004282825b818110156107245761071c3387878481811061070f5761070f6137fe565b9050602002013585611a18565b6001016106f1565b50909150505b92915050565b606061075b7f4541530000000000000000000000000000000000000000000000000000000000611b17565b905090565b600061077361076e83613d22565b611ca5565b604080516001808252818301909252600091816020015b6040805160c081018252600080825260208083018290529282018190526060808301829052608083015260a082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff90920191018161078a5790505090506107f86020840184613d9d565b61080190613dd1565b81600081518110610814576108146137fe565b602090810291909101015261083d83358261083560c0870160a088016135bd565b346001611e2f565b60200151600081518110610853576108536137fe565b6020026020010151915050919050565b60608160008167ffffffffffffffff8111156108815761088161386b565b6040519080825280602002602001820160405280156108b457816020015b606081526020019060019003908161089f5790505b509050600034815b848110156109ce577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85018114368989848181106108fc576108fc6137fe565b905060200281019061090e9190613ddd565b905061091d6020820182613e11565b9050600003610958576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600061097d823561096c6020850185613e11565b61097591613e79565b338887611e2f565b805190915061098c9086613bed565b945080602001518785815181106109a5576109a56137fe565b6020026020010181905250806020015151860195505050506109c78160010190565b90506108bc565b506109d98383612541565b979650505050505050565b604080516001808252818301909252600091816020015b60408051808201909152600080825260208201528152602001906001900390816109fb579050509050610a3636839003830160208401613eed565b81600081518110610a4957610a496137fe565b6020908102919091010152610a63823582333460016113e9565b505050565b348160005b818110156106e4577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8201811436868684818110610aad57610aad6137fe565b9050602002810190610abf9190613ddd565b9050610b2c8135610ad36020840184613f09565b808060200260200160405190810160405280939291908181526020016000905b82821015610b1f57610b1060408302860136819003810190613eed565b81526020019060010190610af3565b50505050503388866113e9565b610b369086613bed565b94505050610b448160010190565b9050610a6d565b60004261072a838261262b565b33600090815260208190526040902054808211610ba1576040517f756688fe00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b336000908152602081815260409182902084905581518381529081018490527f57b09af877df9068fd60a69d7b21f5576b8b38955812d6ae4ac52942f1e38fb7910160405180910390a15050565b60608160008167ffffffffffffffff811115610c0d57610c0d61386b565b604051908082528060200260200182016040528015610c4057816020015b6060815260200190600190039081610c2b5790505b509050600034815b848110156109ce577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8501811436898984818110610c8857610c886137fe565b9050602002810190610c9a919061382d565b9050366000610cac6020840184613e11565b909250905080801580610ccd5750610cc76040850185613f71565b90508114155b15610d04576040517f947d5a8400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60005b81811015610de557610ddd6040518060a0016040528087600001358152602001868685818110610d3957610d396137fe565b9050602002810190610d4b9190613d9d565b610d5490613dd1565b8152602001610d666040890189613f71565b85818110610d7657610d766137fe565b905060600201803603810190610d8c9190613fd8565b8152602001610da16080890160608a016135bd565b73ffffffffffffffffffffffffffffffffffffffff168152602001610dcc60a0890160808a01613ff4565b67ffffffffffffffff169052611ca5565b600101610d07565b506000610e0e8535610df78587613e79565b610e076080890160608a016135bd565b8b8a611e2f565b8051909150610e1d9089613bed565b975080602001518a8881518110610e3657610e366137fe565b602002602001018190525080602001515189019850505050505050610e5b8160010190565b9050610c48565b604080516101408101825260008082526020820181905291810182905260608082018390526080820183905260a0820183905260c0820183905260e0820183905261010082019290925261012081019190915260008281526032602090815260409182902082516101408101845281548152600182015492810192909252600281015467ffffffffffffffff808216948401949094526801000000000000000081048416606084015270010000000000000000000000000000000090049092166080820152600382015460a0820152600482015473ffffffffffffffffffffffffffffffffffffffff90811660c0830152600583015490811660e083015274010000000000000000000000000000000000000000900460ff16151561010082015260068201805491929161012084019190610f9c9061400f565b80601f0160208091040260200160405190810160405280929190818152602001828054610fc89061400f565b80156110155780601f10610fea57610100808354040283529160200191611015565b820191906000526020600020905b815481529060010190602001808311610ff857829003601f168201915b5050505050815250509050919050565b61103c6110373683900383018361405c565b6111d8565b604080516001808252818301909252600091816020015b604080518082019091526000808252602082015281526020019060019003908161105357905050905061108e36839003830160208401613eed565b816000815181106110a1576110a16137fe565b6020908102919091010152610a638235826110c260e0860160c087016135bd565b3460016113e9565b60004261072a338483611a18565b60004282825b81811015610724576111088686838181106110fb576110fb6137fe565b905060200201358461262b565b6001016110de565b600061075b6126ed565b604080516001808252818301909252600091829190816020015b6040805160c081018252600080825260208083018290529282018190526060808301829052608083015260a082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816111345790505090506111a26020840184613d9d565b6111ab90613dd1565b816000815181106111be576111be6137fe565b602090810291909101015261083d83358233346001611e2f565b608081015167ffffffffffffffff161580159061120c57504267ffffffffffffffff16816080015167ffffffffffffffff16105b15611243576040517f1ab7da6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808201516040808401516060850151855184518587015173ffffffffffffffffffffffffffffffffffffffff84166000908152978890529487208054969794969495611337957fb5d556f07587ec0f08cf386545cc4362c702a001650c2058002615ee5c9d1e7595949392886112ba836140ca565b909155506080808c015160408051602081019990995273ffffffffffffffffffffffffffffffffffffffff9097169688019690965260608701949094529285019190915260a084015260c083015267ffffffffffffffff1660e0820152610100015b60405160208183030381529060405280519060200120612821565b90506113ad84606001518284602001518560400151866000015160405160200161139993929190928352602083019190915260f81b7fff0000000000000000000000000000000000000000000000000000000000000016604082015260410190565b604051602081830303815290604052612834565b6113e3576040517f8baa579f00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050565b6040517fa2ea7c6e0000000000000000000000000000000000000000000000000000000081526004810186905260009081907342000000000000000000000000000000000000209063a2ea7c6e90602401600060405180830381865afa158015611457573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016820160405261149d9190810190614102565b80519091506114d8576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b855160008167ffffffffffffffff8111156114f5576114f561386b565b60405190808252806020026020018201604052801561159457816020015b60408051610140810182526000808252602080830182905292820181905260608083018290526080830182905260a0830182905260c0830182905260e0830182905261010083019190915261012082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9092019101816115135790505b50905060008267ffffffffffffffff8111156115b2576115b261386b565b6040519080825280602002602001820160405280156115db578160200160208202803683370190505b50905060005b838110156119fa5760008a82815181106115fd576115fd6137fe565b6020908102919091018101518051600090815260329092526040909120805491925090611656576040517fc5723b5100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8c816001015414611693576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600581015473ffffffffffffffffffffffffffffffffffffffff8c81169116146116e9576040517f4ca8886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600581015474010000000000000000000000000000000000000000900460ff1661173f576040517f157bd4c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6002810154700100000000000000000000000000000000900467ffffffffffffffff1615611799576040517f905e710700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b426002820180547fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff811670010000000000000000000000000000000067ffffffffffffffff948516810291821793849055604080516101408101825287548152600188015460208201529386169286169290921791830191909152680100000000000000008304841660608301529091049091166080820152600382015460a0820152600482015473ffffffffffffffffffffffffffffffffffffffff90811660c0830152600583015490811660e083015274010000000000000000000000000000000000000000900460ff16151561010082015260068201805483916101208401916118a59061400f565b80601f01602080910402602001604051908101604052809291908181526020018280546118d19061400f565b801561191e5780601f106118f35761010080835404028352916020019161191e565b820191906000526020600020905b81548152906001019060200180831161190157829003601f168201915b505050505081525050858481518110611939576119396137fe565b6020026020010181905250816020015184848151811061195b5761195b6137fe565b6020026020010181815250508c8b73ffffffffffffffffffffffffffffffffffffffff16868581518110611991576119916137fe565b602002602001015160c0015173ffffffffffffffffffffffffffffffffffffffff167ff930a6e2523c9cc298691873087a740550b8fc85a0680830414c148ed927f61585600001516040516119e891815260200190565b60405180910390a450506001016115e1565b50611a0a84838360018b8b612a03565b9a9950505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff83166000908152603460209081526040808320858452918290529091205467ffffffffffffffff1615611a8c576040517fec9d6eeb00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008381526020829052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff861690811790915590519091859173ffffffffffffffffffffffffffffffffffffffff8816917f92a1f7a41a7c585a8b09e25b195e225b1d43248daca46b0faf9e0792777a222991a450505050565b604080516020808252818301909252606091600091906020820181803683370190505090506000805b6020811015611be2576000858260208110611b5d57611b5d6137fe565b1a60f81b90507fff000000000000000000000000000000000000000000000000000000000000008116600003611b935750611be2565b80848481518110611ba657611ba66137fe565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053505060019182019101611b40565b5060008167ffffffffffffffff811115611bfe57611bfe61386b565b6040519080825280601f01601f191660200182016040528015611c28576020820181803683370190505b50905060005b82811015611c9c57838181518110611c4857611c486137fe565b602001015160f81c60f81b828281518110611c6557611c656137fe565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a905350600101611c2e565b50949350505050565b608081015167ffffffffffffffff1615801590611cd957504267ffffffffffffffff16816080015167ffffffffffffffff16105b15611d10576040517f1ab7da6b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6020808201516040808401516060808601518651855186880151868801519488015160808901518051908b012060a08a015173ffffffffffffffffffffffffffffffffffffffff871660009081529b8c9052988b2080549a9b989a9899611337997ffeb2925a02bae3dae48d424a0437a2b6ac939aa9230ddc55a1a76f065d988076999493928c611da0836140ca565b919050558e6080015160405160200161131c9b9a999897969594939291909a8b5273ffffffffffffffffffffffffffffffffffffffff998a1660208c015260408b019890985295909716606089015267ffffffffffffffff938416608089015291151560a088015260c087015260e0860152610100850193909352610120840152166101408201526101600190565b60408051808201909152600081526060602082015284516040805180820190915260008152606060208201528167ffffffffffffffff811115611e7457611e7461386b565b604051908082528060200260200182016040528015611e9d578160200160208202803683370190505b5060208201526040517fa2ea7c6e000000000000000000000000000000000000000000000000000000008152600481018990526000907342000000000000000000000000000000000000209063a2ea7c6e90602401600060405180830381865afa158015611f0f573d6000803e3d6000fd5b505050506040513d6000823e601f3d9081017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0168201604052611f559190810190614102565b8051909150611f90576040517fbf37b20e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008367ffffffffffffffff811115611fab57611fab61386b565b60405190808252806020026020018201604052801561204a57816020015b60408051610140810182526000808252602080830182905292820181905260608083018290526080830182905260a0830182905260c0830182905260e0830182905261010083019190915261012082015282527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff909201910181611fc95790505b50905060008467ffffffffffffffff8111156120685761206861386b565b604051908082528060200260200182016040528015612091578160200160208202803683370190505b50905060005b858110156125205760008b82815181106120b3576120b36137fe565b60200260200101519050600067ffffffffffffffff16816020015167ffffffffffffffff16141580156120fe57504267ffffffffffffffff16816020015167ffffffffffffffff1611155b15612135576040517f08e8b93700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8460400151158015612148575080604001515b1561217f576040517f157bd4c300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006040518061014001604052806000801b81526020018f81526020016121a34290565b67ffffffffffffffff168152602001836020015167ffffffffffffffff168152602001600067ffffffffffffffff16815260200183606001518152602001836000015173ffffffffffffffffffffffffffffffffffffffff1681526020018d73ffffffffffffffffffffffffffffffffffffffff16815260200183604001511515815260200183608001518152509050600080600090505b6122458382612df4565b600081815260326020526040902054909250156122645760010161223b565b81835260008281526032602090815260409182902085518155908501516001820155908401516002820180546060870151608088015167ffffffffffffffff908116700100000000000000000000000000000000027fffffffffffffffff0000000000000000ffffffffffffffffffffffffffffffff92821668010000000000000000027fffffffffffffffffffffffffffffffff000000000000000000000000000000009094169190951617919091171691909117905560a0840151600382015560c084015160048201805473ffffffffffffffffffffffffffffffffffffffff9283167fffffffffffffffffffffffff000000000000000000000000000000000000000090911617905560e0850151600583018054610100880151151574010000000000000000000000000000000000000000027fffffffffffffffffffffff000000000000000000000000000000000000000000909116929093169190911791909117905561012084015184919060068201906123e49082614228565b50505060608401511561243b57606084015160009081526032602052604090205461243b576040517fc5723b5100000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8287868151811061244e5761244e6137fe565b60200260200101819052508360a00151868681518110612470576124706137fe565b6020026020010181815250508189602001518681518110612493576124936137fe565b6020026020010181815250508f8e73ffffffffffffffffffffffffffffffffffffffff16856000015173ffffffffffffffffffffffffffffffffffffffff167f8bf46bf4cfd674fa735a3d63ec1c9ad4153f033c290341f3a588b75685141b358560405161250391815260200190565b60405180910390a4505050506125198160010190565b9050612097565b5061253083838360008c8c612a03565b845250919998505050505050505050565b606060008267ffffffffffffffff81111561255e5761255e61386b565b604051908082528060200260200182016040528015612587578160200160208202803683370190505b508451909150600090815b818110156126205760008782815181106125ae576125ae6137fe565b6020026020010151905060008151905060005b8181101561260c578281815181106125db576125db6137fe565b60200260200101518787815181106125f5576125f56137fe565b6020908102919091010152600195860195016125c1565b5050506126198160010190565b9050612592565b509195945050505050565b60008281526033602052604090205467ffffffffffffffff161561267b576040517f2e26794600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60008281526033602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000001667ffffffffffffffff85169081179091559051909184917f5aafceeb1c7ad58e4a84898bdee37c02c0fc46e7d24e6b60e8209449f183459f9190a35050565b60003073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000a5906e11c3b7f5b832bcbf389295d44e7695b4a61614801561275357507f000000000000000000000000000000000000000000000000000000000000076346145b1561277d57507f8468d0181abf73384d91963514c8f501771a4e37c3f4b5daf31aa15cabc66e2290565b50604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6020808301919091527f9fed719e0073f95229e6f4f6b6f28f260c524ab08aa40b11f9c28cb710d7c72a828401527f6a08c3e203132c561752255a4d52ffae85bb9c5d33cb3291520dea1b8435638960608301524660808301523060a0808401919091528351808403909101815260c0909201909252805191012090565b600061072a61282e6126ed565b83612e53565b60008060006128438585612e95565b9092509050600081600481111561285c5761285c614342565b14801561289457508573ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16145b156128a4576001925050506129fc565b6000808773ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b88886040516024016128d9929190614371565b604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009094169390931790925290516129629190614392565b600060405180830381855afa9150503d806000811461299d576040519150601f19603f3d011682016040523d82523d6000602084013e6129a2565b606091505b50915091508180156129b5575080516020145b80156129f5575080517f1626ba7e00000000000000000000000000000000000000000000000000000000906129f390830160209081019084016143a4565b145b9450505050505b9392505050565b84516000906001819003612a5b57612a538888600081518110612a2857612a286137fe565b602002602001015188600081518110612a4357612a436137fe565b6020026020010151888888612eda565b915050612dea565b602088015173ffffffffffffffffffffffffffffffffffffffff8116612afc5760005b82811015612ae157878181518110612a9857612a986137fe565b6020026020010151600014612ad9576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b600101612a7e565b508315612af157612af1856131f9565b600092505050612dea565b6000808273ffffffffffffffffffffffffffffffffffffffff1663ce46e0466040518163ffffffff1660e01b8152600401602060405180830381865afa158015612b4a573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b6e91906143bd565b905060005b84811015612c2b5760008a8281518110612b8f57612b8f6137fe565b6020026020010151905080600003612ba75750612c23565b82612bde576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b88811115612c18576040517f1101129400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b978890039792909201915b600101612b73565b508715612d06576040517f88e5b2d900000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416906388e5b2d9908490612c88908e908e906004016143da565b60206040518083038185885af1158015612ca6573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612ccb91906143bd565b612d01576040517fbf2f3a8b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612dd5565b6040517f91db0b7e00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416906391db0b7e908490612d5c908e908e906004016143da565b60206040518083038185885af1158015612d7a573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612d9f91906143bd565b612dd5576040517fe8bee83900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8515612de457612de4876131f9565b50925050505b9695505050505050565b60208083015160c084015160e0850151604080870151606088015161010089015160a08a01516101208b01519451600099612e3599989796918c9101614493565b60405160208183030381529060405280519060200120905092915050565b6040517f190100000000000000000000000000000000000000000000000000000000000060208201526022810183905260428101829052600090606201612e35565b6000808251604103612ecb5760208301516040840151606085015160001a612ebf8782858561320c565b94509450505050612ed3565b506000905060025b9250929050565b602086015160009073ffffffffffffffffffffffffffffffffffffffff8116612f4e578515612f35576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b8215612f4457612f44846131f9565b6000915050612dea565b8515613039578073ffffffffffffffffffffffffffffffffffffffff1663ce46e0466040518163ffffffff1660e01b8152600401602060405180830381865afa158015612f9f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612fc391906143bd565b612ff9576040517f1574f9f300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b83861115613033576040517f1101129400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85840393505b8415613111576040517fe49617e100000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063e49617e1908890613093908b90600401613771565b60206040518083038185885af11580156130b1573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906130d691906143bd565b61310c576040517fccf3bb2700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6131de565b6040517fe60c350500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82169063e60c3505908890613165908b90600401613771565b60206040518083038185885af1158015613183573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906131a891906143bd565b6131de576040517fbd8ba84d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b82156131ed576131ed846131f9565b50939695505050505050565b8015613209576132093382613324565b50565b6000807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115613243575060009050600361331b565b8460ff16601b1415801561325b57508460ff16601c14155b1561326c575060009050600461331b565b6040805160008082526020820180845289905260ff881692820192909252606081018690526080810185905260019060a0016020604051602081039080840390855afa1580156132c0573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff81166133145760006001925092505061331b565b9150600090505b94509492505050565b80471015613393576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e636500000060448201526064015b60405180910390fd5b60008273ffffffffffffffffffffffffffffffffffffffff168260405160006040518083038185875af1925050503d80600081146133ed576040519150601f19603f3d011682016040523d82523d6000602084013e6133f2565b606091505b5050905080610a63576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c207260448201527f6563697069656e74206d61792068617665207265766572746564000000000000606482015260840161338a565b60008083601f84011261349557600080fd5b50813567ffffffffffffffff8111156134ad57600080fd5b6020830191508360208260051b8501011115612ed357600080fd5b600080602083850312156134db57600080fd5b823567ffffffffffffffff8111156134f257600080fd5b6134fe85828601613483565b90969095509350505050565b60005b8381101561352557818101518382015260200161350d565b50506000910152565b6000815180845261354681602086016020860161350a565b601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b6020815260006129fc602083018461352e565b73ffffffffffffffffffffffffffffffffffffffff8116811461320957600080fd5b80356135b88161358b565b919050565b6000602082840312156135cf57600080fd5b81356129fc8161358b565b6000602082840312156135ec57600080fd5b813567ffffffffffffffff81111561360357600080fd5b820160e081850312156129fc57600080fd5b6020808252825182820181905260009190848201906040850190845b8181101561364d57835183529284019291840191600101613631565b50909695505050505050565b60006060828403121561366b57600080fd5b50919050565b60006020828403121561368357600080fd5b5035919050565b6000610140825184526020830151602085015260408301516136b8604086018267ffffffffffffffff169052565b5060608301516136d4606086018267ffffffffffffffff169052565b5060808301516136f0608086018267ffffffffffffffff169052565b5060a083015160a085015260c083015161372260c086018273ffffffffffffffffffffffffffffffffffffffff169052565b5060e083015161374a60e086018273ffffffffffffffffffffffffffffffffffffffff169052565b506101008381015115159085015261012080840151818601839052612dea8387018261352e565b6020815260006129fc602083018461368a565b6000610100828403121561366b57600080fd5b600080604083850312156137aa57600080fd5b82356137b58161358b565b946020939093013593505050565b6000602082840312156137d557600080fd5b813567ffffffffffffffff8111156137ec57600080fd5b8201604081850312156129fc57600080fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6183360301811261386157600080fd5b9190910192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60405160a0810167ffffffffffffffff811182821017156138bd576138bd61386b565b60405290565b60405160c0810167ffffffffffffffff811182821017156138bd576138bd61386b565b6040516080810167ffffffffffffffff811182821017156138bd576138bd61386b565b604051601f82017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe016810167ffffffffffffffff811182821017156139505761395061386b565b604052919050565b600067ffffffffffffffff8211156139725761397261386b565b5060051b60200190565b60006040828403121561398e57600080fd5b6040516040810181811067ffffffffffffffff821117156139b1576139b161386b565b604052823581526020928301359281019290925250919050565b6000606082840312156139dd57600080fd5b6040516060810181811067ffffffffffffffff82111715613a0057613a0061386b565b604052905080823560ff81168114613a1757600080fd5b8082525060208301356020820152604083013560408201525092915050565b600082601f830112613a4757600080fd5b81356020613a5c613a5783613958565b613909565b82815260609283028501820192828201919087851115613a7b57600080fd5b8387015b85811015613a9e57613a9189826139cb565b8452928401928101613a7f565b5090979650505050505050565b803567ffffffffffffffff811681146135b857600080fd5b600060a08236031215613ad557600080fd5b613add61389a565b8235815260208084013567ffffffffffffffff80821115613afd57600080fd5b9085019036601f830112613b1057600080fd5b8135613b1e613a5782613958565b81815260069190911b83018401908481019036831115613b3d57600080fd5b938501935b82851015613b6657613b54368661397c565b82528582019150604085019450613b42565b80868801525050506040860135925080831115613b8257600080fd5b5050613b9036828601613a36565b604083015250613ba2606084016135ad565b6060820152613bb360808401613aab565b608082015292915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b8181038181111561072a5761072a613bbe565b801515811461320957600080fd5b600067ffffffffffffffff821115613c2857613c2861386b565b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe01660200190565b600060c08284031215613c6657600080fd5b613c6e6138c3565b90508135613c7b8161358b565b81526020613c8a838201613aab565b818301526040830135613c9c81613c00565b604083015260608381013590830152608083013567ffffffffffffffff811115613cc557600080fd5b8301601f81018513613cd657600080fd5b8035613ce4613a5782613c0e565b8181528684838501011115613cf857600080fd5b818484018583013760008483830101528060808601525050505060a082013560a082015292915050565b600060e08236031215613d3457600080fd5b613d3c61389a565b82358152602083013567ffffffffffffffff811115613d5a57600080fd5b613d6636828601613c54565b602083015250613d7936604085016139cb565b604082015260a0830135613d8c8161358b565b6060820152613bb360c08401613aab565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff4183360301811261386157600080fd5b600061072a3683613c54565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc183360301811261386157600080fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613e4657600080fd5b83018035915067ffffffffffffffff821115613e6157600080fd5b6020019150600581901b3603821315612ed357600080fd5b6000613e87613a5784613958565b80848252602080830192508560051b850136811115613ea557600080fd5b855b81811015613ee157803567ffffffffffffffff811115613ec75760008081fd5b613ed336828a01613c54565b865250938201938201613ea7565b50919695505050505050565b600060408284031215613eff57600080fd5b6129fc838361397c565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613f3e57600080fd5b83018035915067ffffffffffffffff821115613f5957600080fd5b6020019150600681901b3603821315612ed357600080fd5b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112613fa657600080fd5b83018035915067ffffffffffffffff821115613fc157600080fd5b6020019150606081023603821315612ed357600080fd5b600060608284031215613fea57600080fd5b6129fc83836139cb565b60006020828403121561400657600080fd5b6129fc82613aab565b600181811c9082168061402357607f821691505b60208210810361366b577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000610100828403121561406f57600080fd5b61407761389a565b82358152614088846020850161397c565b602082015261409a84606085016139cb565b604082015260c08301356140ad8161358b565b60608201526140be60e08401613aab565b60808201529392505050565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036140fb576140fb613bbe565b5060010190565b6000602080838503121561411557600080fd5b825167ffffffffffffffff8082111561412d57600080fd5b908401906080828703121561414157600080fd5b6141496138e6565b825181528383015161415a8161358b565b81850152604083015161416c81613c00565b604082015260608301518281111561418357600080fd5b80840193505086601f84011261419857600080fd5b825191506141a8613a5783613c0e565b82815287858486010111156141bc57600080fd5b6141cb8386830187870161350a565b60608201529695505050505050565b601f821115610a6357600081815260208120601f850160051c810160208610156142015750805b601f850160051c820191505b818110156142205782815560010161420d565b505050505050565b815167ffffffffffffffff8111156142425761424261386b565b61425681614250845461400f565b846141da565b602080601f8311600181146142a957600084156142735750858301515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600386901b1c1916600185901b178555614220565b6000858152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08616915b828110156142f6578886015182559484019460019091019084016142d7565b508582101561433257878501517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600388901b60f8161c191681555b5050505050600190811b01905550565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b82815260406020820152600061438a604083018461352e565b949350505050565b6000825161386181846020870161350a565b6000602082840312156143b657600080fd5b5051919050565b6000602082840312156143cf57600080fd5b81516129fc81613c00565b6000604082016040835280855180835260608501915060608160051b8601019250602080880160005b8381101561444f577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa088870301855261443d86835161368a565b95509382019390820190600101614403565b50508584038187015286518085528782019482019350915060005b828110156144865784518452938101939281019260010161446a565b5091979650505050505050565b89815260007fffffffffffffffffffffffffffffffffffffffff000000000000000000000000808b60601b166020840152808a60601b166034840152507fffffffffffffffff000000000000000000000000000000000000000000000000808960c01b166048840152808860c01b1660508401525085151560f81b6058830152846059830152835161452c81607985016020880161350a565b80830190507fffffffff000000000000000000000000000000000000000000000000000000008460e01b166079820152607d81019150509a995050505050505050505056fea164736f6c6343000813000a", + "balance": "0x0" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/crates/optimism/chainspec/res/superchain-configs.tar b/crates/optimism/chainspec/res/superchain-configs.tar index acfa1b81c91..da035a32da5 100644 Binary files a/crates/optimism/chainspec/res/superchain-configs.tar and b/crates/optimism/chainspec/res/superchain-configs.tar differ diff --git a/crates/optimism/chainspec/res/superchain_registry_commit b/crates/optimism/chainspec/res/superchain_registry_commit index 939855efb4f..70808136d14 100644 --- a/crates/optimism/chainspec/res/superchain_registry_commit +++ b/crates/optimism/chainspec/res/superchain_registry_commit @@ -1 +1 @@ -a9b57281842bf5742cf9e69114c6b81c622ca186 +d56233c1e5254fc2fd769d5b33269502a1fe9ef8 diff --git a/crates/optimism/chainspec/src/basefee.rs b/crates/optimism/chainspec/src/basefee.rs index b28c0c478d0..0ef712dc04f 100644 --- a/crates/optimism/chainspec/src/basefee.rs +++ b/crates/optimism/chainspec/src/basefee.rs @@ -1,10 +1,26 @@ //! Base fee related utilities for Optimism chains. use alloy_consensus::BlockHeader; -use op_alloy_consensus::{decode_holocene_extra_data, EIP1559ParamError}; +use op_alloy_consensus::{decode_holocene_extra_data, decode_jovian_extra_data, EIP1559ParamError}; use reth_chainspec::{BaseFeeParams, EthChainSpec}; use reth_optimism_forks::OpHardforks; +fn next_base_fee_params( + chain_spec: impl EthChainSpec + OpHardforks, + parent: &H, + timestamp: u64, + denominator: u32, + elasticity: u32, +) -> u64 { + let base_fee_params = if elasticity == 0 && denominator == 0 { + chain_spec.base_fee_params_at_timestamp(timestamp) + } else { + BaseFeeParams::new(denominator as u128, elasticity as u128) + }; + + parent.next_block_base_fee(base_fee_params).unwrap_or_default() +} + /// Extracts the Holocene 1599 parameters from the encoded extra data from the parent header. /// /// Caution: Caller must ensure that holocene is active in the parent header. @@ -19,11 +35,34 @@ where H: BlockHeader, { let (elasticity, denominator) = decode_holocene_extra_data(parent.extra_data())?; - let base_fee_params = if elasticity == 0 && denominator == 0 { - chain_spec.base_fee_params_at_timestamp(timestamp) - } else { - BaseFeeParams::new(denominator as u128, elasticity as u128) - }; - Ok(parent.next_block_base_fee(base_fee_params).unwrap_or_default()) + Ok(next_base_fee_params(chain_spec, parent, timestamp, denominator, elasticity)) +} + +/// Extracts the Jovian 1599 parameters from the encoded extra data from the parent header. +/// Additionally to [`decode_holocene_base_fee`], checks if the next block base fee is less than the +/// minimum base fee, then the minimum base fee is returned. +/// +/// Caution: Caller must ensure that jovian is active in the parent header. +/// +/// See also [Base fee computation](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/jovian/exec-engine.md#base-fee-computation) +/// and [Minimum base fee in block header](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/jovian/exec-engine.md#minimum-base-fee-in-block-header) +pub fn compute_jovian_base_fee( + chain_spec: impl EthChainSpec + OpHardforks, + parent: &H, + timestamp: u64, +) -> Result +where + H: BlockHeader, +{ + let (elasticity, denominator, min_base_fee) = decode_jovian_extra_data(parent.extra_data())?; + + let next_base_fee = + next_base_fee_params(chain_spec, parent, timestamp, denominator, elasticity); + + if next_base_fee < min_base_fee { + return Ok(min_base_fee); + } + + Ok(next_base_fee) } diff --git a/crates/optimism/chainspec/src/dev.rs b/crates/optimism/chainspec/src/dev.rs index 3778cd712a3..ac8eaad24a8 100644 --- a/crates/optimism/chainspec/src/dev.rs +++ b/crates/optimism/chainspec/src/dev.rs @@ -27,7 +27,6 @@ pub static OP_DEV: LazyLock> = LazyLock::new(|| { paris_block_and_final_difficulty: Some((0, U256::from(0))), hardforks, base_fee_params: BaseFeeParamsKind::Constant(BaseFeeParams::ethereum()), - deposit_contract: None, // TODO: do we even have? ..Default::default() }, } diff --git a/crates/optimism/chainspec/src/lib.rs b/crates/optimism/chainspec/src/lib.rs index 44361c7abf5..d64da95d775 100644 --- a/crates/optimism/chainspec/src/lib.rs +++ b/crates/optimism/chainspec/src/lib.rs @@ -243,10 +243,6 @@ impl EthChainSpec for OpChainSpec { self.inner.chain() } - fn base_fee_params_at_block(&self, block_number: u64) -> BaseFeeParams { - self.inner.base_fee_params_at_block(block_number) - } - fn base_fee_params_at_timestamp(&self, timestamp: u64) -> BaseFeeParams { self.inner.base_fee_params_at_timestamp(timestamp) } @@ -297,7 +293,9 @@ impl EthChainSpec for OpChainSpec { } fn next_block_base_fee(&self, parent: &Header, target_timestamp: u64) -> Option { - if self.is_holocene_active_at_timestamp(parent.timestamp()) { + if self.is_jovian_active_at_timestamp(parent.timestamp()) { + compute_jovian_base_fee(self, parent, target_timestamp).ok() + } else if self.is_holocene_active_at_timestamp(parent.timestamp()) { decode_holocene_base_fee(self, parent, target_timestamp).ok() } else { self.inner.next_block_base_fee(parent, target_timestamp) @@ -461,33 +459,33 @@ impl OpGenesisInfo { .unwrap_or_default(), ..Default::default() }; - if let Some(optimism_base_fee_info) = &info.optimism_chain_info.base_fee_info { - if let (Some(elasticity), Some(denominator)) = ( + if let Some(optimism_base_fee_info) = &info.optimism_chain_info.base_fee_info && + let (Some(elasticity), Some(denominator)) = ( optimism_base_fee_info.eip1559_elasticity, optimism_base_fee_info.eip1559_denominator, - ) { - let base_fee_params = if let Some(canyon_denominator) = - optimism_base_fee_info.eip1559_denominator_canyon - { - BaseFeeParamsKind::Variable( - vec![ - ( - EthereumHardfork::London.boxed(), - BaseFeeParams::new(denominator as u128, elasticity as u128), - ), - ( - OpHardfork::Canyon.boxed(), - BaseFeeParams::new(canyon_denominator as u128, elasticity as u128), - ), - ] - .into(), - ) - } else { - BaseFeeParams::new(denominator as u128, elasticity as u128).into() - }; - - info.base_fee_params = base_fee_params; - } + ) + { + let base_fee_params = if let Some(canyon_denominator) = + optimism_base_fee_info.eip1559_denominator_canyon + { + BaseFeeParamsKind::Variable( + vec![ + ( + EthereumHardfork::London.boxed(), + BaseFeeParams::new(denominator as u128, elasticity as u128), + ), + ( + OpHardfork::Canyon.boxed(), + BaseFeeParams::new(canyon_denominator as u128, elasticity as u128), + ), + ] + .into(), + ) + } else { + BaseFeeParams::new(denominator as u128, elasticity as u128).into() + }; + + info.base_fee_params = base_fee_params; } info @@ -500,13 +498,18 @@ pub fn make_op_genesis_header(genesis: &Genesis, hardforks: &ChainHardforks) -> // If Isthmus is active, overwrite the withdrawals root with the storage root of predeploy // `L2ToL1MessagePasser.sol` - if hardforks.fork(OpHardfork::Isthmus).active_at_timestamp(header.timestamp) { - if let Some(predeploy) = genesis.alloc.get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER) { - if let Some(storage) = &predeploy.storage { - header.withdrawals_root = - Some(storage_root_unhashed(storage.iter().map(|(k, v)| (*k, (*v).into())))) - } - } + if hardforks.fork(OpHardfork::Isthmus).active_at_timestamp(header.timestamp) && + let Some(predeploy) = genesis.alloc.get(&ADDRESS_L2_TO_L1_MESSAGE_PASSER) && + let Some(storage) = &predeploy.storage + { + header.withdrawals_root = + Some(storage_root_unhashed(storage.iter().filter_map(|(k, v)| { + if v.is_zero() { + None + } else { + Some((*k, (*v).into())) + } + }))); } header @@ -523,6 +526,45 @@ mod tests { use crate::*; + #[test] + fn test_storage_root_consistency() { + use alloy_primitives::{B256, U256}; + use std::str::FromStr; + + let k1 = + B256::from_str("0x0000000000000000000000000000000000000000000000000000000000000001") + .unwrap(); + let v1 = + U256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000") + .unwrap(); + let k2 = + B256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc") + .unwrap(); + let v2 = + U256::from_str("0x000000000000000000000000c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d3c0d30016") + .unwrap(); + let k3 = + B256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103") + .unwrap(); + let v3 = + U256::from_str("0x0000000000000000000000004200000000000000000000000000000000000018") + .unwrap(); + let origin_root = + B256::from_str("0x5d5ba3a8093ede3901ad7a569edfb7b9aecafa54730ba0bf069147cbcc00e345") + .unwrap(); + let expected_root = + B256::from_str("0x8ed4baae3a927be3dea54996b4d5899f8c01e7594bf50b17dc1e741388ce3d12") + .unwrap(); + + let storage_origin = vec![(k1, v1), (k2, v2), (k3, v3)]; + let storage_fix = vec![(k2, v2), (k3, v3)]; + let root_origin = storage_root_unhashed(storage_origin); + let root_fix = storage_root_unhashed(storage_fix); + assert_ne!(root_origin, root_fix); + assert_eq!(root_origin, origin_root); + assert_eq!(root_fix, expected_root); + } + #[test] fn base_mainnet_forkids() { let mut base_mainnet = OpChainSpecBuilder::base_mainnet().build(); diff --git a/crates/optimism/chainspec/src/superchain/chain_specs.rs b/crates/optimism/chainspec/src/superchain/chain_specs.rs index 2dd048771ad..1547082eca3 100644 --- a/crates/optimism/chainspec/src/superchain/chain_specs.rs +++ b/crates/optimism/chainspec/src/superchain/chain_specs.rs @@ -3,16 +3,20 @@ use crate::create_superchain_specs; create_superchain_specs!( ("arena-z", "mainnet"), - ("arena-z-testnet", "sepolia"), + ("arena-z", "sepolia"), ("automata", "mainnet"), ("base-devnet-0", "sepolia-dev-0"), ("bob", "mainnet"), ("boba", "sepolia"), + ("boba", "mainnet"), + ("camp", "sepolia"), + ("celo", "mainnet"), ("creator-chain-testnet", "sepolia"), ("cyber", "mainnet"), ("cyber", "sepolia"), ("ethernity", "mainnet"), ("ethernity", "sepolia"), + ("fraxtal", "mainnet"), ("funki", "mainnet"), ("funki", "sepolia"), ("hashkeychain", "mainnet"), @@ -28,11 +32,15 @@ create_superchain_specs!( ("mode", "sepolia"), ("oplabs-devnet-0", "sepolia-dev-0"), ("orderly", "mainnet"), + ("ozean", "sepolia"), ("pivotal", "sepolia"), ("polynomial", "mainnet"), ("race", "mainnet"), ("race", "sepolia"), + ("radius_testnet", "sepolia"), ("redstone", "mainnet"), + ("rehearsal-0-bn-0", "rehearsal-0-bn"), + ("rehearsal-0-bn-1", "rehearsal-0-bn"), ("settlus-mainnet", "mainnet"), ("settlus-sepolia", "sepolia"), ("shape", "mainnet"), diff --git a/crates/optimism/chainspec/src/superchain/configs.rs b/crates/optimism/chainspec/src/superchain/configs.rs index 428f197a049..53b30a2f5d9 100644 --- a/crates/optimism/chainspec/src/superchain/configs.rs +++ b/crates/optimism/chainspec/src/superchain/configs.rs @@ -8,8 +8,8 @@ use alloy_genesis::Genesis; use miniz_oxide::inflate::decompress_to_vec_zlib_with_limit; use tar_no_std::{CorruptDataError, TarArchiveRef}; -/// A genesis file can be up to 10MiB. This is a reasonable limit for the genesis file size. -const MAX_GENESIS_SIZE: usize = 16 * 1024 * 1024; // 16MiB +/// A genesis file can be up to 100MiB. This is a reasonable limit for the genesis file size. +const MAX_GENESIS_SIZE: usize = 100 * 1024 * 1024; // 100MiB /// The tar file contains the chain configs and genesis files for all chains. const SUPER_CHAIN_CONFIGS_TAR_BYTES: &[u8] = include_bytes!("../../res/superchain-configs.tar"); diff --git a/crates/optimism/cli/Cargo.toml b/crates/optimism/cli/Cargo.toml index 0da12c42b02..422da3b883e 100644 --- a/crates/optimism/cli/Cargo.toml +++ b/crates/optimism/cli/Cargo.toml @@ -12,8 +12,10 @@ workspace = true [dependencies] reth-static-file-types = { workspace = true, features = ["clap"] } +reth-cli.workspace = true reth-cli-commands.workspace = true reth-consensus.workspace = true +reth-rpc-server-types.workspace = true reth-primitives-traits.workspace = true reth-db = { workspace = true, features = ["mdbx", "op"] } reth-db-api.workspace = true @@ -39,7 +41,6 @@ reth-optimism-consensus.workspace = true reth-chainspec.workspace = true reth-node-events.workspace = true reth-optimism-evm.workspace = true -reth-cli.workspace = true reth-cli-runner.workspace = true reth-node-builder = { workspace = true, features = ["op"] } reth-tracing.workspace = true diff --git a/crates/optimism/cli/src/app.rs b/crates/optimism/cli/src/app.rs index e0774068b7e..1e9f7960ad1 100644 --- a/crates/optimism/cli/src/app.rs +++ b/crates/optimism/cli/src/app.rs @@ -7,25 +7,27 @@ use reth_node_metrics::recorder::install_prometheus_recorder; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_consensus::OpBeaconConsensus; use reth_optimism_node::{OpExecutorProvider, OpNode}; +use reth_rpc_server_types::RpcModuleValidator; use reth_tracing::{FileWorkerGuard, Layers}; use std::{fmt, sync::Arc}; use tracing::info; /// A wrapper around a parsed CLI that handles command execution. #[derive(Debug)] -pub struct CliApp { - cli: Cli, +pub struct CliApp { + cli: Cli, runner: Option, layers: Option, guard: Option, } -impl CliApp +impl CliApp where C: ChainSpecParser, Ext: clap::Args + fmt::Debug, + Rpc: RpcModuleValidator, { - pub(crate) fn new(cli: Cli) -> Self { + pub(crate) fn new(cli: Cli) -> Self { Self { cli, runner: None, layers: Some(Layers::new()), guard: None } } @@ -66,11 +68,19 @@ where let _ = install_prometheus_recorder(); let components = |spec: Arc| { - (OpExecutorProvider::optimism(spec.clone()), OpBeaconConsensus::new(spec)) + (OpExecutorProvider::optimism(spec.clone()), Arc::new(OpBeaconConsensus::new(spec))) }; match self.cli.command { Commands::Node(command) => { + // Validate RPC modules using the configured validator + if let Some(http_api) = &command.rpc.http_api { + Rpc::validate_selection(http_api, "http.api").map_err(|e| eyre!("{e}"))?; + } + if let Some(ws_api) = &command.rpc.ws_api { + Rpc::validate_selection(ws_api, "ws.api").map_err(|e| eyre!("{e}"))?; + } + runner.run_command_until_exit(|ctx| command.execute(ctx, launcher)) } Commands::Init(command) => { @@ -92,9 +102,6 @@ where } Commands::P2P(command) => runner.run_until_ctrl_c(command.execute::()), Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), - Commands::Recover(command) => { - runner.run_command_until_exit(|ctx| command.execute::(ctx)) - } Commands::Prune(command) => runner.run_until_ctrl_c(command.execute::()), #[cfg(feature = "dev")] Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), diff --git a/crates/optimism/cli/src/commands/import_receipts.rs b/crates/optimism/cli/src/commands/import_receipts.rs index f6a2214b643..c07af28c9f2 100644 --- a/crates/optimism/cli/src/commands/import_receipts.rs +++ b/crates/optimism/cli/src/commands/import_receipts.rs @@ -141,11 +141,10 @@ where // Ensure that receipts hasn't been initialized apart from `init_genesis`. if let Some(num_receipts) = - static_file_provider.get_highest_static_file_tx(StaticFileSegment::Receipts) + static_file_provider.get_highest_static_file_tx(StaticFileSegment::Receipts) && + num_receipts > 0 { - if num_receipts > 0 { - eyre::bail!("Expected no receipts in storage, but found {num_receipts}."); - } + eyre::bail!("Expected no receipts in storage, but found {num_receipts}."); } match static_file_provider.get_highest_static_file_block(StaticFileSegment::Receipts) { Some(receipts_block) => { @@ -315,7 +314,6 @@ mod test { let db = TestStageDB::default(); init_genesis(&db.factory).unwrap(); - // todo: where does import command init receipts ? probably somewhere in pipeline let provider_factory = create_test_provider_factory_with_node_types::(OP_MAINNET.clone()); let ImportReceiptsResult { total_decoded_receipts, total_filtered_out_dup_txns } = diff --git a/crates/optimism/cli/src/commands/mod.rs b/crates/optimism/cli/src/commands/mod.rs index 32e531a6710..5edd55b0ccb 100644 --- a/crates/optimism/cli/src/commands/mod.rs +++ b/crates/optimism/cli/src/commands/mod.rs @@ -7,7 +7,7 @@ use reth_cli::chainspec::ChainSpecParser; use reth_cli_commands::{ config_cmd, db, dump_genesis, init_cmd, node::{self, NoArgs}, - p2p, prune, re_execute, recover, stage, + p2p, prune, re_execute, stage, }; use std::{fmt, sync::Arc}; @@ -47,13 +47,10 @@ pub enum Commands>), /// P2P Debugging utilities #[command(name = "p2p")] - P2P(p2p::Command), + P2P(Box>), /// Write config to stdout #[command(name = "config")] Config(config_cmd::Command), - /// Scripts for node recovery - #[command(name = "recover")] - Recover(recover::Command), /// Prune according to the configuration without any limits #[command(name = "prune")] Prune(prune::PruneCommand), @@ -82,7 +79,6 @@ impl< Self::Stage(cmd) => cmd.chain_spec(), Self::P2P(cmd) => cmd.chain_spec(), Self::Config(_) => None, - Self::Recover(cmd) => cmd.chain_spec(), Self::Prune(cmd) => cmd.chain_spec(), Self::ImportOp(cmd) => cmd.chain_spec(), Self::ImportReceiptsOp(cmd) => cmd.chain_spec(), diff --git a/crates/optimism/cli/src/lib.rs b/crates/optimism/cli/src/lib.rs index 4d1d22aa4d0..529ad19bdb2 100644 --- a/crates/optimism/cli/src/lib.rs +++ b/crates/optimism/cli/src/lib.rs @@ -35,8 +35,9 @@ pub mod ovm_file_codec; pub use app::CliApp; pub use commands::{import::ImportOpCommand, import_receipts::ImportReceiptsOpCommand}; use reth_optimism_chainspec::OpChainSpec; +use reth_rpc_server_types::{DefaultRpcModuleValidator, RpcModuleValidator}; -use std::{ffi::OsString, fmt, sync::Arc}; +use std::{ffi::OsString, fmt, marker::PhantomData, sync::Arc}; use chainspec::OpChainSpecParser; use clap::{command, Parser}; @@ -59,8 +60,11 @@ use reth_node_metrics as _; /// This is the entrypoint to the executable. #[derive(Debug, Parser)] #[command(author, version = version_metadata().short_version.as_ref(), long_version = version_metadata().long_version.as_ref(), about = "Reth", long_about = None)] -pub struct Cli -{ +pub struct Cli< + Spec: ChainSpecParser = OpChainSpecParser, + Ext: clap::Args + fmt::Debug = RollupArgs, + Rpc: RpcModuleValidator = DefaultRpcModuleValidator, +> { /// The command to run #[command(subcommand)] pub command: Commands, @@ -68,6 +72,10 @@ pub struct Cli, } impl Cli { @@ -86,16 +94,17 @@ impl Cli { } } -impl Cli +impl Cli where C: ChainSpecParser, Ext: clap::Args + fmt::Debug, + Rpc: RpcModuleValidator, { /// Configures the CLI and returns a [`CliApp`] instance. /// /// This method is used to prepare the CLI for execution by wrapping it in a /// [`CliApp`] that can be further configured before running. - pub fn configure(self) -> CliApp { + pub fn configure(self) -> CliApp { CliApp::new(self) } diff --git a/crates/optimism/cli/src/ovm_file_codec.rs b/crates/optimism/cli/src/ovm_file_codec.rs index efd493b5855..83f3e487282 100644 --- a/crates/optimism/cli/src/ovm_file_codec.rs +++ b/crates/optimism/cli/src/ovm_file_codec.rs @@ -251,13 +251,7 @@ impl Encodable2718 for OvmTransactionSigned { } fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) { - match &self.transaction { - OpTypedTransaction::Legacy(tx) => tx.eip2718_encode(&self.signature, out), - OpTypedTransaction::Eip2930(tx) => tx.eip2718_encode(&self.signature, out), - OpTypedTransaction::Eip1559(tx) => tx.eip2718_encode(&self.signature, out), - OpTypedTransaction::Eip7702(tx) => tx.eip2718_encode(&self.signature, out), - OpTypedTransaction::Deposit(tx) => tx.encode_2718(out), - } + self.transaction.eip2718_encode(&self.signature, out) } } @@ -309,7 +303,7 @@ mod tests { // Verify deposit transaction let deposit_tx = match &deposit_decoded.transaction { - OpTypedTransaction::Legacy(ref tx) => tx, + OpTypedTransaction::Legacy(tx) => tx, _ => panic!("Expected legacy transaction for NFT deposit"), }; @@ -351,7 +345,7 @@ mod tests { assert!(system_decoded.is_legacy()); let system_tx = match &system_decoded.transaction { - OpTypedTransaction::Legacy(ref tx) => tx, + OpTypedTransaction::Legacy(tx) => tx, _ => panic!("Expected Legacy transaction"), }; diff --git a/crates/optimism/cli/src/receipt_file_codec.rs b/crates/optimism/cli/src/receipt_file_codec.rs index 8cd50037c57..e12af039eac 100644 --- a/crates/optimism/cli/src/receipt_file_codec.rs +++ b/crates/optimism/cli/src/receipt_file_codec.rs @@ -149,7 +149,7 @@ pub(crate) mod test { "00000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000400000000000100000000000000200000000002000000000000001000000000000000000004000000000000000000000000000040000400000100400000000000000100000000000000000000000000000020000000000000000000000000000000000000000000000001000000000000000000000100000000000000000000000000000000000000000000000000000000000000088000000080000000000010000000000000000000000000000800008000120000000000000000000000000000000002000" )), logs: receipt.receipt.into_logs(), - tx_hash: b256!("0x5e77a04531c7c107af1882d76cbff9486d0a9aa53701c30888509d4f5f2b003a"), contract_address: address!("0x0000000000000000000000000000000000000000"), gas_used: 202813, + tx_hash: b256!("0x5e77a04531c7c107af1882d76cbff9486d0a9aa53701c30888509d4f5f2b003a"), contract_address: Address::ZERO, gas_used: 202813, block_hash: b256!("0xbee7192e575af30420cae0c7776304ac196077ee72b048970549e4f08e875453"), block_number: receipt.number, transaction_index: 0, diff --git a/crates/optimism/consensus/Cargo.toml b/crates/optimism/consensus/Cargo.toml index e681112eea0..54df0af80d2 100644 --- a/crates/optimism/consensus/Cargo.toml +++ b/crates/optimism/consensus/Cargo.toml @@ -44,7 +44,6 @@ reth-db-common.workspace = true reth-revm.workspace = true reth-trie.workspace = true reth-optimism-node.workspace = true -reth-db-api = { workspace = true, features = ["op"] } alloy-chains.workspace = true diff --git a/crates/optimism/consensus/src/proof.rs b/crates/optimism/consensus/src/proof.rs index 86f7b2ecbeb..8c601942ece 100644 --- a/crates/optimism/consensus/src/proof.rs +++ b/crates/optimism/consensus/src/proof.rs @@ -118,7 +118,7 @@ mod tests { ]; for case in cases { - let receipts = vec![ + let receipts = [ // 0xb0d6ee650637911394396d81172bd1c637d568ed1fbddab0daddfca399c58b53 OpReceipt::Deposit(OpDepositReceipt { inner: Receipt { diff --git a/crates/optimism/consensus/src/validation/mod.rs b/crates/optimism/consensus/src/validation/mod.rs index 0846572a3d9..2dd4cea0904 100644 --- a/crates/optimism/consensus/src/validation/mod.rs +++ b/crates/optimism/consensus/src/validation/mod.rs @@ -93,21 +93,21 @@ pub fn validate_block_post_execution( // operation as hashing that is required for state root got calculated in every // transaction This was replaced with is_success flag. // See more about EIP here: https://eips.ethereum.org/EIPS/eip-658 - if chain_spec.is_byzantium_active_at_block(header.number()) { - if let Err(error) = verify_receipts_optimism( + if chain_spec.is_byzantium_active_at_block(header.number()) && + let Err(error) = verify_receipts_optimism( header.receipts_root(), header.logs_bloom(), receipts, chain_spec, header.timestamp(), - ) { - let receipts = receipts - .iter() - .map(|r| Bytes::from(r.with_bloom_ref().encoded_2718())) - .collect::>(); - tracing::debug!(%error, ?receipts, "receipts verification failed"); - return Err(error) - } + ) + { + let receipts = receipts + .iter() + .map(|r| Bytes::from(r.with_bloom_ref().encoded_2718())) + .collect::>(); + tracing::debug!(%error, ?receipts, "receipts verification failed"); + return Err(error) } // Check if gas used matches the value set in header. @@ -183,6 +183,9 @@ mod tests { use reth_optimism_forks::{OpHardfork, BASE_SEPOLIA_HARDFORKS}; use std::sync::Arc; + const JOVIAN_TIMESTAMP: u64 = 1900000000; + const BLOCK_TIME_SECONDS: u64 = 2; + fn holocene_chainspec() -> Arc { let mut hardforks = BASE_SEPOLIA_HARDFORKS.clone(); hardforks.insert(OpHardfork::Holocene.boxed(), ForkCondition::Timestamp(1800000000)); @@ -209,6 +212,15 @@ mod tests { chainspec } + fn jovian_chainspec() -> OpChainSpec { + let mut chainspec = BASE_SEPOLIA.as_ref().clone(); + chainspec + .inner + .hardforks + .insert(OpHardfork::Jovian.boxed(), ForkCondition::Timestamp(1900000000)); + chainspec + } + #[test] fn test_get_base_fee_pre_holocene() { let op_chain_spec = BASE_SEPOLIA.clone(); @@ -293,6 +305,179 @@ mod tests { assert_eq!(base_fee, 507); } + #[test] + fn test_get_base_fee_holocene_extra_data_set_and_min_base_fee_set() { + const MIN_BASE_FEE: u64 = 10; + + let mut extra_data = Vec::new(); + // eip1559 params + extra_data.append(&mut hex!("00000000fa0000000a").to_vec()); + // min base fee + extra_data.append(&mut MIN_BASE_FEE.to_be_bytes().to_vec()); + let extra_data = Bytes::from(extra_data); + + let parent = Header { + base_fee_per_gas: Some(507), + gas_used: 4847634, + gas_limit: 60000000, + extra_data, + timestamp: 1735315544, + ..Default::default() + }; + + let base_fee = reth_optimism_chainspec::OpChainSpec::next_block_base_fee( + &*BASE_SEPOLIA, + &parent, + 1735315546, + ); + assert_eq!(base_fee, None); + } + + /// The version byte for Jovian is 1. + const JOVIAN_EXTRA_DATA_VERSION_BYTE: u8 = 1; + + #[test] + fn test_get_base_fee_jovian_extra_data_and_min_base_fee_not_set() { + let op_chain_spec = jovian_chainspec(); + + let mut extra_data = Vec::new(); + extra_data.push(JOVIAN_EXTRA_DATA_VERSION_BYTE); + // eip1559 params + extra_data.append(&mut [0_u8; 8].to_vec()); + let extra_data = Bytes::from(extra_data); + + let parent = Header { + base_fee_per_gas: Some(1), + gas_used: 15763614, + gas_limit: 144000000, + timestamp: JOVIAN_TIMESTAMP, + extra_data, + ..Default::default() + }; + let base_fee = reth_optimism_chainspec::OpChainSpec::next_block_base_fee( + &op_chain_spec, + &parent, + JOVIAN_TIMESTAMP + BLOCK_TIME_SECONDS, + ); + assert_eq!(base_fee, None); + } + + /// After Jovian, the next block base fee cannot be less than the minimum base fee. + #[test] + fn test_get_base_fee_jovian_default_extra_data_and_min_base_fee() { + const CURR_BASE_FEE: u64 = 1; + const MIN_BASE_FEE: u64 = 10; + + let mut extra_data = Vec::new(); + extra_data.push(JOVIAN_EXTRA_DATA_VERSION_BYTE); + // eip1559 params + extra_data.append(&mut [0_u8; 8].to_vec()); + // min base fee + extra_data.append(&mut MIN_BASE_FEE.to_be_bytes().to_vec()); + let extra_data = Bytes::from(extra_data); + + let op_chain_spec = jovian_chainspec(); + let parent = Header { + base_fee_per_gas: Some(CURR_BASE_FEE), + gas_used: 15763614, + gas_limit: 144000000, + timestamp: JOVIAN_TIMESTAMP, + extra_data, + ..Default::default() + }; + let base_fee = reth_optimism_chainspec::OpChainSpec::next_block_base_fee( + &op_chain_spec, + &parent, + JOVIAN_TIMESTAMP + BLOCK_TIME_SECONDS, + ); + assert_eq!(base_fee, Some(MIN_BASE_FEE)); + } + + /// After Jovian, the next block base fee cannot be less than the minimum base fee. + #[test] + fn test_jovian_min_base_fee_cannot_decrease() { + const MIN_BASE_FEE: u64 = 10; + + let mut extra_data = Vec::new(); + extra_data.push(JOVIAN_EXTRA_DATA_VERSION_BYTE); + // eip1559 params + extra_data.append(&mut [0_u8; 8].to_vec()); + // min base fee + extra_data.append(&mut MIN_BASE_FEE.to_be_bytes().to_vec()); + let extra_data = Bytes::from(extra_data); + + let op_chain_spec = jovian_chainspec(); + + // If we're currently at the minimum base fee, the next block base fee cannot decrease. + let parent = Header { + base_fee_per_gas: Some(MIN_BASE_FEE), + gas_used: 10, + gas_limit: 144000000, + timestamp: JOVIAN_TIMESTAMP, + extra_data: extra_data.clone(), + ..Default::default() + }; + let base_fee = reth_optimism_chainspec::OpChainSpec::next_block_base_fee( + &op_chain_spec, + &parent, + JOVIAN_TIMESTAMP + BLOCK_TIME_SECONDS, + ); + assert_eq!(base_fee, Some(MIN_BASE_FEE)); + + // The next block can increase the base fee + let parent = Header { + base_fee_per_gas: Some(MIN_BASE_FEE), + gas_used: 144000000, + gas_limit: 144000000, + timestamp: JOVIAN_TIMESTAMP, + extra_data, + ..Default::default() + }; + let base_fee = reth_optimism_chainspec::OpChainSpec::next_block_base_fee( + &op_chain_spec, + &parent, + JOVIAN_TIMESTAMP + 2 * BLOCK_TIME_SECONDS, + ); + assert_eq!(base_fee, Some(MIN_BASE_FEE + 1)); + } + + #[test] + fn test_jovian_base_fee_can_decrease_if_above_min_base_fee() { + const MIN_BASE_FEE: u64 = 10; + + let mut extra_data = Vec::new(); + extra_data.push(JOVIAN_EXTRA_DATA_VERSION_BYTE); + // eip1559 params + extra_data.append(&mut [0_u8; 8].to_vec()); + // min base fee + extra_data.append(&mut MIN_BASE_FEE.to_be_bytes().to_vec()); + let extra_data = Bytes::from(extra_data); + + let op_chain_spec = jovian_chainspec(); + + let parent = Header { + base_fee_per_gas: Some(100 * MIN_BASE_FEE), + gas_used: 10, + gas_limit: 144000000, + timestamp: JOVIAN_TIMESTAMP, + extra_data, + ..Default::default() + }; + let base_fee = reth_optimism_chainspec::OpChainSpec::next_block_base_fee( + &op_chain_spec, + &parent, + JOVIAN_TIMESTAMP + BLOCK_TIME_SECONDS, + ) + .unwrap(); + assert_eq!( + base_fee, + op_chain_spec + .inner + .next_block_base_fee(&parent, JOVIAN_TIMESTAMP + BLOCK_TIME_SECONDS) + .unwrap() + ); + } + #[test] fn body_against_header_isthmus() { let chainspec = isthmus_chainspec(); diff --git a/crates/optimism/evm/src/config.rs b/crates/optimism/evm/src/config.rs index 2d4039020f1..47ed2853d0a 100644 --- a/crates/optimism/evm/src/config.rs +++ b/crates/optimism/evm/src/config.rs @@ -1,6 +1,8 @@ +pub use alloy_op_evm::{ + spec as revm_spec, spec_by_timestamp_after_bedrock as revm_spec_by_timestamp_after_bedrock, +}; + use alloy_consensus::BlockHeader; -use op_revm::OpSpecId; -use reth_optimism_forks::OpHardforks; use revm::primitives::{Address, Bytes, B256}; /// Context relevant for execution of a next block w.r.t OP. @@ -20,145 +22,18 @@ pub struct OpNextBlockEnvAttributes { pub extra_data: Bytes, } -/// Map the latest active hardfork at the given header to a revm [`OpSpecId`]. -pub fn revm_spec(chain_spec: impl OpHardforks, header: impl BlockHeader) -> OpSpecId { - revm_spec_by_timestamp_after_bedrock(chain_spec, header.timestamp()) -} - -/// Returns the revm [`OpSpecId`] at the given timestamp. -/// -/// # Note -/// -/// This is only intended to be used after the Bedrock, when hardforks are activated by -/// timestamp. -pub fn revm_spec_by_timestamp_after_bedrock( - chain_spec: impl OpHardforks, - timestamp: u64, -) -> OpSpecId { - if chain_spec.is_interop_active_at_timestamp(timestamp) { - OpSpecId::INTEROP - } else if chain_spec.is_isthmus_active_at_timestamp(timestamp) { - OpSpecId::ISTHMUS - } else if chain_spec.is_holocene_active_at_timestamp(timestamp) { - OpSpecId::HOLOCENE - } else if chain_spec.is_granite_active_at_timestamp(timestamp) { - OpSpecId::GRANITE - } else if chain_spec.is_fjord_active_at_timestamp(timestamp) { - OpSpecId::FJORD - } else if chain_spec.is_ecotone_active_at_timestamp(timestamp) { - OpSpecId::ECOTONE - } else if chain_spec.is_canyon_active_at_timestamp(timestamp) { - OpSpecId::CANYON - } else if chain_spec.is_regolith_active_at_timestamp(timestamp) { - OpSpecId::REGOLITH - } else { - OpSpecId::BEDROCK - } -} - #[cfg(feature = "rpc")] -impl reth_rpc_eth_api::helpers::pending_block::BuildPendingEnv +impl reth_rpc_eth_api::helpers::pending_block::BuildPendingEnv for OpNextBlockEnvAttributes { fn build_pending_env(parent: &crate::SealedHeader) -> Self { Self { timestamp: parent.timestamp().saturating_add(12), suggested_fee_recipient: parent.beneficiary(), - prev_randao: alloy_primitives::B256::random(), + prev_randao: B256::random(), gas_limit: parent.gas_limit(), parent_beacon_block_root: parent.parent_beacon_block_root(), extra_data: parent.extra_data().clone(), } } } - -#[cfg(test)] -mod tests { - use super::*; - use alloy_consensus::Header; - use reth_chainspec::ChainSpecBuilder; - use reth_optimism_chainspec::{OpChainSpec, OpChainSpecBuilder}; - - #[test] - fn test_revm_spec_by_timestamp_after_merge() { - #[inline(always)] - fn op_cs(f: impl FnOnce(OpChainSpecBuilder) -> OpChainSpecBuilder) -> OpChainSpec { - let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)).into(); - f(cs).build() - } - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.interop_activated()), 0), - OpSpecId::INTEROP - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.isthmus_activated()), 0), - OpSpecId::ISTHMUS - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.holocene_activated()), 0), - OpSpecId::HOLOCENE - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.granite_activated()), 0), - OpSpecId::GRANITE - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.fjord_activated()), 0), - OpSpecId::FJORD - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.ecotone_activated()), 0), - OpSpecId::ECOTONE - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.canyon_activated()), 0), - OpSpecId::CANYON - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.bedrock_activated()), 0), - OpSpecId::BEDROCK - ); - assert_eq!( - revm_spec_by_timestamp_after_bedrock(op_cs(|cs| cs.regolith_activated()), 0), - OpSpecId::REGOLITH - ); - } - - #[test] - fn test_to_revm_spec() { - #[inline(always)] - fn op_cs(f: impl FnOnce(OpChainSpecBuilder) -> OpChainSpecBuilder) -> OpChainSpec { - let cs = ChainSpecBuilder::mainnet().chain(reth_chainspec::Chain::from_id(10)).into(); - f(cs).build() - } - assert_eq!( - revm_spec(op_cs(|cs| cs.isthmus_activated()), Header::default()), - OpSpecId::ISTHMUS - ); - assert_eq!( - revm_spec(op_cs(|cs| cs.holocene_activated()), Header::default()), - OpSpecId::HOLOCENE - ); - assert_eq!( - revm_spec(op_cs(|cs| cs.granite_activated()), Header::default()), - OpSpecId::GRANITE - ); - assert_eq!(revm_spec(op_cs(|cs| cs.fjord_activated()), Header::default()), OpSpecId::FJORD); - assert_eq!( - revm_spec(op_cs(|cs| cs.ecotone_activated()), Header::default()), - OpSpecId::ECOTONE - ); - assert_eq!( - revm_spec(op_cs(|cs| cs.canyon_activated()), Header::default()), - OpSpecId::CANYON - ); - assert_eq!( - revm_spec(op_cs(|cs| cs.bedrock_activated()), Header::default()), - OpSpecId::BEDROCK - ); - assert_eq!( - revm_spec(op_cs(|cs| cs.regolith_activated()), Header::default()), - OpSpecId::REGOLITH - ); - } -} diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 7e5ae9e5b4c..73ed0fe3b8b 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -14,7 +14,7 @@ extern crate alloc; use alloc::sync::Arc; use alloy_consensus::{BlockHeader, Header}; use alloy_eips::Decodable2718; -use alloy_evm::{FromRecoveredTx, FromTxWithEncoded}; +use alloy_evm::{EvmFactory, FromRecoveredTx, FromTxWithEncoded}; use alloy_op_evm::block::receipt_builder::OpReceiptBuilder; use alloy_primitives::U256; use core::fmt::Debug; @@ -23,7 +23,8 @@ use op_alloy_rpc_types_engine::OpExecutionData; use op_revm::{OpSpecId, OpTransaction}; use reth_chainspec::EthChainSpec; use reth_evm::{ - ConfigureEngineEvm, ConfigureEvm, EvmEnv, EvmEnvFor, ExecutableTxIterator, ExecutionCtxFor, + precompiles::PrecompilesMap, ConfigureEngineEvm, ConfigureEvm, EvmEnv, EvmEnvFor, + ExecutableTxIterator, ExecutionCtxFor, TransactionEnv, }; use reth_optimism_chainspec::OpChainSpec; use reth_optimism_forks::OpHardforks; @@ -60,15 +61,19 @@ pub struct OpEvmConfig< ChainSpec = OpChainSpec, N: NodePrimitives = OpPrimitives, R = OpRethReceiptBuilder, + EvmFactory = OpEvmFactory, > { /// Inner [`OpBlockExecutorFactory`]. - pub executor_factory: OpBlockExecutorFactory>, + pub executor_factory: OpBlockExecutorFactory, EvmFactory>, /// Optimism block assembler. pub block_assembler: OpBlockAssembler, - _pd: core::marker::PhantomData, + #[doc(hidden)] + pub _pd: core::marker::PhantomData, } -impl Clone for OpEvmConfig { +impl Clone + for OpEvmConfig +{ fn clone(&self) -> Self { Self { executor_factory: self.executor_factory.clone(), @@ -98,14 +103,20 @@ impl OpEvmConfig _pd: core::marker::PhantomData, } } +} +impl OpEvmConfig +where + ChainSpec: OpHardforks, + N: NodePrimitives, +{ /// Returns the chain spec associated with this configuration. pub const fn chain_spec(&self) -> &Arc { self.executor_factory.spec() } } -impl ConfigureEvm for OpEvmConfig +impl ConfigureEvm for OpEvmConfig where ChainSpec: EthChainSpec
+ OpHardforks, N: NodePrimitives< @@ -117,12 +128,19 @@ where >, OpTransaction: FromRecoveredTx + FromTxWithEncoded, R: OpReceiptBuilder, + EvmF: EvmFactory< + Tx: FromRecoveredTx + + FromTxWithEncoded + + TransactionEnv, + Precompiles = PrecompilesMap, + Spec = OpSpecId, + > + Debug, Self: Send + Sync + Unpin + Clone + 'static, { type Primitives = N; type Error = EIP1559ParamError; type NextBlockEnvCtx = OpNextBlockEnvAttributes; - type BlockExecutorFactory = OpBlockExecutorFactory>; + type BlockExecutorFactory = OpBlockExecutorFactory, EvmF>; type BlockAssembler = OpBlockAssembler; fn block_executor_factory(&self) -> &Self::BlockExecutorFactory { @@ -133,10 +151,13 @@ where &self.block_assembler } - fn evm_env(&self, header: &Header) -> EvmEnv { + fn evm_env(&self, header: &Header) -> Result, Self::Error> { let spec = config::revm_spec(self.chain_spec(), header); - let cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec); + let mut cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec); + + // Enable per-tx gasless validation bypass in lightlink revm + cfg_env.allow_gasless = true; let blob_excess_gas_and_price = spec .into_eth_spec() @@ -163,7 +184,7 @@ where blob_excess_gas_and_price, }; - EvmEnv { cfg_env, block_env } + Ok(EvmEnv { cfg_env, block_env }) } fn next_evm_env( @@ -175,8 +196,10 @@ where let spec_id = revm_spec_by_timestamp_after_bedrock(self.chain_spec(), attributes.timestamp); // configure evm env based on parent block - let cfg_env = + let mut cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec_id); + // Enable per-tx gasless validation bypass in forked revm + cfg_env.allow_gasless = true; // if the parent block did not have excess blob gas (i.e. it was pre-cancun), but it is // cancun now, we need to set the excess blob gas to the default value(0) @@ -204,24 +227,27 @@ where Ok(EvmEnv { cfg_env, block_env }) } - fn context_for_block(&self, block: &'_ SealedBlock) -> OpBlockExecutionCtx { - OpBlockExecutionCtx { + fn context_for_block( + &self, + block: &'_ SealedBlock, + ) -> Result { + Ok(OpBlockExecutionCtx { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), extra_data: block.header().extra_data().clone(), - } + }) } fn context_for_next_block( &self, parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, - ) -> OpBlockExecutionCtx { - OpBlockExecutionCtx { + ) -> Result { + Ok(OpBlockExecutionCtx { parent_hash: parent.hash(), parent_beacon_block_root: attributes.parent_beacon_block_root, extra_data: attributes.extra_data, - } + }) } } @@ -245,7 +271,9 @@ where let spec = revm_spec_by_timestamp_after_bedrock(self.chain_spec(), timestamp); - let cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec); + let mut cfg_env = CfgEnv::new().with_chain_id(self.chain_spec().chain().id()).with_spec(spec); + // Enable per-tx gasless validation bypass in lightlink revm + cfg_env.allow_gasless = true; let blob_excess_gas_and_price = spec .into_eth_spec() @@ -341,7 +369,8 @@ mod tests { // Header, and total difficulty let EvmEnv { cfg_env, .. } = OpEvmConfig::optimism(Arc::new(OpChainSpec { inner: chain_spec.clone() })) - .evm_env(&header); + .evm_env(&header) + .unwrap(); // Assert that the chain ID in the `cfg_env` is correctly set to the chain ID of the // ChainSpec diff --git a/crates/optimism/flashblocks/Cargo.toml b/crates/optimism/flashblocks/Cargo.toml new file mode 100644 index 00000000000..2344911ffc2 --- /dev/null +++ b/crates/optimism/flashblocks/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "reth-optimism-flashblocks" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +# reth +reth-optimism-primitives = { workspace = true, features = ["serde"] } +reth-optimism-evm.workspace = true +reth-chain-state = { workspace = true, features = ["serde"] } +reth-primitives-traits = { workspace = true, features = ["serde"] } +reth-execution-types = { workspace = true, features = ["serde"] } +reth-evm.workspace = true +reth-revm.workspace = true +reth-optimism-payload-builder.workspace = true +reth-rpc-eth-types.workspace = true +reth-errors.workspace = true +reth-storage-api.workspace = true +reth-node-api.workspace = true +reth-tasks.workspace = true + +# alloy +alloy-eips = { workspace = true, features = ["serde"] } +alloy-serde.workspace = true +alloy-primitives = { workspace = true, features = ["serde"] } +alloy-rpc-types-engine = { workspace = true, features = ["serde"] } +alloy-consensus.workspace = true + +# io +tokio.workspace = true +tokio-tungstenite = { workspace = true, features = ["rustls-tls-native-roots"] } +serde.workspace = true +serde_json.workspace = true +url.workspace = true +futures-util.workspace = true +brotli.workspace = true + +# debug +tracing.workspace = true + +# errors +eyre.workspace = true + +ringbuffer.workspace = true + +[dev-dependencies] +test-case.workspace = true +alloy-consensus.workspace = true diff --git a/crates/optimism/flashblocks/src/consensus.rs b/crates/optimism/flashblocks/src/consensus.rs new file mode 100644 index 00000000000..90071141b9b --- /dev/null +++ b/crates/optimism/flashblocks/src/consensus.rs @@ -0,0 +1,81 @@ +use crate::FlashBlockCompleteSequenceRx; +use alloy_primitives::B256; +use reth_node_api::{ConsensusEngineHandle, EngineApiMessageVersion}; +use reth_optimism_payload_builder::OpPayloadTypes; +use ringbuffer::{AllocRingBuffer, RingBuffer}; +use tracing::warn; + +/// Consensus client that sends FCUs and new payloads using blocks from a [`FlashBlockService`] +/// +/// [`FlashBlockService`]: crate::FlashBlockService +#[derive(Debug)] +pub struct FlashBlockConsensusClient { + /// Handle to execution client. + engine_handle: ConsensusEngineHandle, + sequence_receiver: FlashBlockCompleteSequenceRx, +} + +impl FlashBlockConsensusClient { + /// Create a new `FlashBlockConsensusClient` with the given Op engine and sequence receiver. + pub const fn new( + engine_handle: ConsensusEngineHandle, + sequence_receiver: FlashBlockCompleteSequenceRx, + ) -> eyre::Result { + Ok(Self { engine_handle, sequence_receiver }) + } + + /// Get previous block hash using previous block hash buffer. If it isn't available (buffer + /// started more recently than `offset`), return default zero hash + fn get_previous_block_hash( + &self, + previous_block_hashes: &AllocRingBuffer, + offset: usize, + ) -> B256 { + *previous_block_hashes + .len() + .checked_sub(offset) + .and_then(|index| previous_block_hashes.get(index)) + .unwrap_or_default() + } + + /// Spawn the client to start sending FCUs and new payloads by periodically fetching recent + /// blocks. + pub async fn run(mut self) { + let mut previous_block_hashes = AllocRingBuffer::new(64); + + loop { + match self.sequence_receiver.recv().await { + Ok(sequence) => { + let block_hash = sequence.payload_base().parent_hash; + previous_block_hashes.push(block_hash); + + // Load previous block hashes. We're using (head - 32) and (head - 64) as the + // safe and finalized block hashes. + let safe_block_hash = self.get_previous_block_hash(&previous_block_hashes, 32); + let finalized_block_hash = + self.get_previous_block_hash(&previous_block_hashes, 64); + + let state = alloy_rpc_types_engine::ForkchoiceState { + head_block_hash: block_hash, + safe_block_hash, + finalized_block_hash, + }; + + // Send FCU + let _ = self + .engine_handle + .fork_choice_updated(state, None, EngineApiMessageVersion::V3) + .await; + } + Err(err) => { + warn!( + target: "consensus::flashblock-client", + %err, + "error while fetching flashblock completed sequence" + ); + break; + } + } + } + } +} diff --git a/crates/optimism/flashblocks/src/lib.rs b/crates/optimism/flashblocks/src/lib.rs new file mode 100644 index 00000000000..1d13adad894 --- /dev/null +++ b/crates/optimism/flashblocks/src/lib.rs @@ -0,0 +1,28 @@ +//! A downstream integration of Flashblocks. + +pub use payload::{ + ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashBlock, Metadata, +}; +use reth_rpc_eth_types::PendingBlock; +pub use service::FlashBlockService; +pub use ws::{WsConnect, WsFlashBlockStream}; + +mod consensus; +pub use consensus::FlashBlockConsensusClient; +mod payload; +mod sequence; +pub use sequence::FlashBlockCompleteSequence; +mod service; +mod worker; +mod ws; + +/// Receiver of the most recent [`PendingBlock`] built out of [`FlashBlock`]s. +/// +/// [`FlashBlock`]: crate::FlashBlock +pub type PendingBlockRx = tokio::sync::watch::Receiver>>; + +/// Receiver of the sequences of [`FlashBlock`]s built. +/// +/// [`FlashBlock`]: crate::FlashBlock +pub type FlashBlockCompleteSequenceRx = + tokio::sync::broadcast::Receiver; diff --git a/crates/optimism/flashblocks/src/payload.rs b/crates/optimism/flashblocks/src/payload.rs new file mode 100644 index 00000000000..dee2458178f --- /dev/null +++ b/crates/optimism/flashblocks/src/payload.rs @@ -0,0 +1,121 @@ +use alloy_eips::eip4895::Withdrawal; +use alloy_primitives::{Address, Bloom, Bytes, B256, U256}; +use alloy_rpc_types_engine::PayloadId; +use reth_optimism_evm::OpNextBlockEnvAttributes; +use reth_optimism_primitives::OpReceipt; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// Represents a Flashblock, a real-time block-like structure emitted by the Base L2 chain. +/// +/// A Flashblock provides a snapshot of a block’s effects before finalization, +/// allowing faster insight into state transitions, balance changes, and logs. +/// It includes a diff of the block’s execution and associated metadata. +/// +/// See: [Base Flashblocks Documentation](https://docs.base.org/chain/flashblocks) +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct FlashBlock { + /// The unique payload ID as assigned by the execution engine for this block. + pub payload_id: PayloadId, + /// A sequential index that identifies the order of this Flashblock. + pub index: u64, + /// A subset of block header fields. + pub base: Option, + /// The execution diff representing state transitions and transactions. + pub diff: ExecutionPayloadFlashblockDeltaV1, + /// Additional metadata about the block such as receipts and balances. + pub metadata: Metadata, +} + +impl FlashBlock { + /// Returns the block number of this flashblock. + pub const fn block_number(&self) -> u64 { + self.metadata.block_number + } + + /// Returns the first parent hash of this flashblock. + pub fn parent_hash(&self) -> Option { + Some(self.base.as_ref()?.parent_hash) + } +} + +/// Provides metadata about the block that may be useful for indexing or analysis. +#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct Metadata { + /// The number of the block in the L2 chain. + pub block_number: u64, + /// A map of addresses to their updated balances after the block execution. + /// This represents balance changes due to transactions, rewards, or system transfers. + pub new_account_balances: BTreeMap, + /// Execution receipts for all transactions in the block. + /// Contains logs, gas usage, and other EVM-level metadata. + pub receipts: BTreeMap, +} + +/// Represents the base configuration of an execution payload that remains constant +/// throughout block construction. This includes fundamental block properties like +/// parent hash, block number, and other header fields that are determined at +/// block creation and cannot be modified. +#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Serialize)] +pub struct ExecutionPayloadBaseV1 { + /// Ecotone parent beacon block root + pub parent_beacon_block_root: B256, + /// The parent hash of the block. + pub parent_hash: B256, + /// The fee recipient of the block. + pub fee_recipient: Address, + /// The previous randao of the block. + pub prev_randao: B256, + /// The block number. + #[serde(with = "alloy_serde::quantity")] + pub block_number: u64, + /// The gas limit of the block. + #[serde(with = "alloy_serde::quantity")] + pub gas_limit: u64, + /// The timestamp of the block. + #[serde(with = "alloy_serde::quantity")] + pub timestamp: u64, + /// The extra data of the block. + pub extra_data: Bytes, + /// The base fee per gas of the block. + pub base_fee_per_gas: U256, +} + +/// Represents the modified portions of an execution payload within a flashblock. +/// This structure contains only the fields that can be updated during block construction, +/// such as state root, receipts, logs, and new transactions. Other immutable block fields +/// like parent hash and block number are excluded since they remain constant throughout +/// the block's construction. +#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Serialize)] +pub struct ExecutionPayloadFlashblockDeltaV1 { + /// The state root of the block. + pub state_root: B256, + /// The receipts root of the block. + pub receipts_root: B256, + /// The logs bloom of the block. + pub logs_bloom: Bloom, + /// The gas used of the block. + #[serde(with = "alloy_serde::quantity")] + pub gas_used: u64, + /// The block hash of the block. + pub block_hash: B256, + /// The transactions of the block. + pub transactions: Vec, + /// Array of [`Withdrawal`] enabled with V2 + pub withdrawals: Vec, + /// The withdrawals root of the block. + pub withdrawals_root: B256, +} + +impl From for OpNextBlockEnvAttributes { + fn from(value: ExecutionPayloadBaseV1) -> Self { + Self { + timestamp: value.timestamp, + suggested_fee_recipient: value.fee_recipient, + prev_randao: value.prev_randao, + gas_limit: value.gas_limit, + parent_beacon_block_root: Some(value.parent_beacon_block_root), + extra_data: value.extra_data, + } + } +} diff --git a/crates/optimism/flashblocks/src/sequence.rs b/crates/optimism/flashblocks/src/sequence.rs new file mode 100644 index 00000000000..0976909442f --- /dev/null +++ b/crates/optimism/flashblocks/src/sequence.rs @@ -0,0 +1,340 @@ +use crate::{ExecutionPayloadBaseV1, FlashBlock, FlashBlockCompleteSequenceRx}; +use alloy_eips::eip2718::WithEncoded; +use core::mem; +use eyre::{bail, OptionExt}; +use reth_primitives_traits::{Recovered, SignedTransaction}; +use std::{collections::BTreeMap, ops::Deref}; +use tokio::sync::broadcast; +use tracing::{debug, trace, warn}; + +/// The size of the broadcast channel for completed flashblock sequences. +const FLASHBLOCK_SEQUENCE_CHANNEL_SIZE: usize = 128; + +/// An ordered B-tree keeping the track of a sequence of [`FlashBlock`]s by their indices. +#[derive(Debug)] +pub(crate) struct FlashBlockPendingSequence { + /// tracks the individual flashblocks in order + /// + /// With a blocktime of 2s and flashblock tick-rate of 200ms plus one extra flashblock per new + /// pending block, we expect 11 flashblocks per slot. + inner: BTreeMap>, + /// Broadcasts flashblocks to subscribers. + block_broadcaster: broadcast::Sender, +} + +impl FlashBlockPendingSequence +where + T: SignedTransaction, +{ + pub(crate) fn new() -> Self { + // Note: if the channel is full, send will not block but rather overwrite the oldest + // messages. Order is preserved. + let (tx, _) = broadcast::channel(FLASHBLOCK_SEQUENCE_CHANNEL_SIZE); + Self { inner: BTreeMap::new(), block_broadcaster: tx } + } + + /// Gets a subscriber to the flashblock sequences produced. + pub(crate) fn subscribe_block_sequence(&self) -> FlashBlockCompleteSequenceRx { + self.block_broadcaster.subscribe() + } + + // Clears the state and broadcasts the blocks produced to subscribers. + fn clear_and_broadcast_blocks(&mut self) { + let flashblocks = mem::take(&mut self.inner); + + // If there are any subscribers, send the flashblocks to them. + if self.block_broadcaster.receiver_count() > 0 { + let flashblocks = match FlashBlockCompleteSequence::new( + flashblocks.into_iter().map(|block| block.1.into()).collect(), + ) { + Ok(flashblocks) => flashblocks, + Err(err) => { + debug!(target: "flashblocks", error = ?err, "Failed to create full flashblock complete sequence"); + return; + } + }; + + // Note: this should only ever fail if there are no receivers. This can happen if + // there is a race condition between the clause right above and this + // one. We can simply warn the user and continue. + if let Err(err) = self.block_broadcaster.send(flashblocks) { + warn!(target: "flashblocks", error = ?err, "Failed to send flashblocks to subscribers"); + } + } + } + + /// Inserts a new block into the sequence. + /// + /// A [`FlashBlock`] with index 0 resets the set. + pub(crate) fn insert(&mut self, flashblock: FlashBlock) -> eyre::Result<()> { + if flashblock.index == 0 { + trace!(number=%flashblock.block_number(), "Tracking new flashblock sequence"); + + // Flash block at index zero resets the whole state. + self.clear_and_broadcast_blocks(); + + self.inner.insert(flashblock.index, PreparedFlashBlock::new(flashblock)?); + return Ok(()) + } + + // only insert if we previously received the same block, assume we received index 0 + if self.block_number() == Some(flashblock.metadata.block_number) { + trace!(number=%flashblock.block_number(), index = %flashblock.index, block_count = self.inner.len() ,"Received followup flashblock"); + self.inner.insert(flashblock.index, PreparedFlashBlock::new(flashblock)?); + } else { + trace!(number=%flashblock.block_number(), index = %flashblock.index, current=?self.block_number() ,"Ignoring untracked flashblock following"); + } + + Ok(()) + } + + /// Iterator over sequence of executable transactions. + /// + /// A flashblocks is not ready if there's missing previous flashblocks, i.e. there's a gap in + /// the sequence + /// + /// Note: flashblocks start at `index 0`. + pub(crate) fn ready_transactions( + &self, + ) -> impl Iterator>> + '_ { + self.inner + .values() + .enumerate() + .take_while(|(idx, block)| { + // flashblock index 0 is the first flashblock + block.block().index == *idx as u64 + }) + .flat_map(|(_, block)| block.txs.clone()) + } + + /// Returns the first block number + pub(crate) fn block_number(&self) -> Option { + Some(self.inner.values().next()?.block().metadata.block_number) + } + + /// Returns the payload base of the first tracked flashblock. + pub(crate) fn payload_base(&self) -> Option { + self.inner.values().next()?.block().base.clone() + } + + /// Returns the number of tracked flashblocks. + pub(crate) fn count(&self) -> usize { + self.inner.len() + } +} + +/// A complete sequence of flashblocks, often corresponding to a full block. +/// Ensure invariants of a complete flashblocks sequence. +#[derive(Debug, Clone)] +pub struct FlashBlockCompleteSequence(Vec); + +impl FlashBlockCompleteSequence { + /// Create a complete sequence from a vector of flashblocks. + /// Ensure that: + /// * vector is not empty + /// * first flashblock have the base payload + /// * sequence of flashblocks is sound (successive index from 0, same payload id, ...) + pub fn new(blocks: Vec) -> eyre::Result { + let first_block = blocks.first().ok_or_eyre("No flashblocks in sequence")?; + + // Ensure that first flashblock have base + first_block.base.as_ref().ok_or_eyre("Flashblock at index 0 has no base")?; + + // Ensure that index are successive from 0, have same block number and payload id + if !blocks.iter().enumerate().all(|(idx, block)| { + idx == block.index as usize && + block.payload_id == first_block.payload_id && + block.metadata.block_number == first_block.metadata.block_number + }) { + bail!("Flashblock inconsistencies detected in sequence"); + } + + Ok(Self(blocks)) + } + + /// Returns the block number + pub fn block_number(&self) -> u64 { + self.0.first().unwrap().metadata.block_number + } + + /// Returns the payload base of the first flashblock. + pub fn payload_base(&self) -> &ExecutionPayloadBaseV1 { + self.0.first().unwrap().base.as_ref().unwrap() + } + + /// Returns the number of flashblocks in the sequence. + pub const fn count(&self) -> usize { + self.0.len() + } + + /// Returns the last flashblock in the sequence. + pub fn last(&self) -> &FlashBlock { + self.0.last().unwrap() + } +} + +impl Deref for FlashBlockCompleteSequence { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom> for FlashBlockCompleteSequence { + type Error = eyre::Error; + fn try_from(sequence: FlashBlockPendingSequence) -> Result { + Self::new( + sequence.inner.into_values().map(|block| block.block().clone()).collect::>(), + ) + } +} + +#[derive(Debug)] +struct PreparedFlashBlock { + /// The prepared transactions, ready for execution + txs: Vec>>, + /// The tracked flashblock + block: FlashBlock, +} + +impl PreparedFlashBlock { + const fn block(&self) -> &FlashBlock { + &self.block + } +} + +impl From> for FlashBlock { + fn from(val: PreparedFlashBlock) -> Self { + val.block + } +} + +impl PreparedFlashBlock +where + T: SignedTransaction, +{ + /// Creates a flashblock that is ready for execution by preparing all transactions + /// + /// Returns an error if decoding or signer recovery fails. + fn new(block: FlashBlock) -> eyre::Result { + let mut txs = Vec::with_capacity(block.diff.transactions.len()); + for encoded in block.diff.transactions.iter().cloned() { + let tx = T::decode_2718_exact(encoded.as_ref())?; + let signer = tx.try_recover()?; + let tx = WithEncoded::new(encoded, tx.with_signer(signer)); + txs.push(tx); + } + + Ok(Self { txs, block }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ExecutionPayloadFlashblockDeltaV1; + use alloy_consensus::{ + transaction::SignerRecoverable, EthereumTxEnvelope, EthereumTypedTransaction, TxEip1559, + }; + use alloy_eips::Encodable2718; + use alloy_primitives::{hex, Signature, TxKind, U256}; + + #[test] + fn test_sequence_stops_before_gap() { + let mut sequence = FlashBlockPendingSequence::new(); + let tx = EthereumTxEnvelope::new_unhashed( + EthereumTypedTransaction::::Eip1559(TxEip1559 { + chain_id: 4, + nonce: 26u64, + max_priority_fee_per_gas: 1500000000, + max_fee_per_gas: 1500000013, + gas_limit: 21_000u64, + to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()), + value: U256::from(3000000000000000000u64), + input: Default::default(), + access_list: Default::default(), + }), + Signature::new( + U256::from_be_bytes(hex!( + "59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd" + )), + U256::from_be_bytes(hex!( + "016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469" + )), + true, + ), + ); + let tx = Recovered::new_unchecked(tx.clone(), tx.recover_signer_unchecked().unwrap()); + + sequence + .insert(FlashBlock { + payload_id: Default::default(), + index: 0, + base: None, + diff: ExecutionPayloadFlashblockDeltaV1 { + transactions: vec![tx.encoded_2718().into()], + ..Default::default() + }, + metadata: Default::default(), + }) + .unwrap(); + + sequence + .insert(FlashBlock { + payload_id: Default::default(), + index: 2, + base: None, + diff: Default::default(), + metadata: Default::default(), + }) + .unwrap(); + + let actual_txs: Vec<_> = sequence.ready_transactions().collect(); + let expected_txs = vec![WithEncoded::new(tx.encoded_2718().into(), tx)]; + + assert_eq!(actual_txs, expected_txs); + } + + #[test] + fn test_sequence_sends_flashblocks_to_subscribers() { + let mut sequence = FlashBlockPendingSequence::>::new(); + let mut subscriber = sequence.subscribe_block_sequence(); + + for idx in 0..10 { + sequence + .insert(FlashBlock { + payload_id: Default::default(), + index: idx, + base: Some(ExecutionPayloadBaseV1::default()), + diff: Default::default(), + metadata: Default::default(), + }) + .unwrap(); + } + + assert_eq!(sequence.count(), 10); + + // Then we don't receive anything until we insert a new flashblock + let no_flashblock = subscriber.try_recv(); + assert!(no_flashblock.is_err()); + + // Let's insert a new flashblock with index 0 + sequence + .insert(FlashBlock { + payload_id: Default::default(), + index: 0, + base: Some(ExecutionPayloadBaseV1::default()), + diff: Default::default(), + metadata: Default::default(), + }) + .unwrap(); + + let flashblocks = subscriber.try_recv().unwrap(); + assert_eq!(flashblocks.count(), 10); + + for (idx, block) in flashblocks.0.iter().enumerate() { + assert_eq!(block.index, idx as u64); + } + } +} diff --git a/crates/optimism/flashblocks/src/service.rs b/crates/optimism/flashblocks/src/service.rs new file mode 100644 index 00000000000..bb411f078c8 --- /dev/null +++ b/crates/optimism/flashblocks/src/service.rs @@ -0,0 +1,261 @@ +use crate::{ + sequence::FlashBlockPendingSequence, + worker::{BuildArgs, FlashBlockBuilder}, + ExecutionPayloadBaseV1, FlashBlock, FlashBlockCompleteSequenceRx, +}; +use alloy_eips::eip2718::WithEncoded; +use alloy_primitives::B256; +use futures_util::{FutureExt, Stream, StreamExt}; +use reth_chain_state::{CanonStateNotification, CanonStateNotifications, CanonStateSubscriptions}; +use reth_evm::ConfigureEvm; +use reth_primitives_traits::{ + AlloyBlockHeader, BlockTy, HeaderTy, NodePrimitives, ReceiptTy, Recovered, +}; +use reth_revm::cached::CachedReads; +use reth_rpc_eth_types::PendingBlock; +use reth_storage_api::{BlockReaderIdExt, StateProviderFactory}; +use reth_tasks::TaskExecutor; +use std::{ + pin::Pin, + task::{ready, Context, Poll}, + time::Instant, +}; +use tokio::{pin, sync::oneshot}; +use tracing::{debug, trace, warn}; + +/// The `FlashBlockService` maintains an in-memory [`PendingBlock`] built out of a sequence of +/// [`FlashBlock`]s. +#[derive(Debug)] +pub struct FlashBlockService< + N: NodePrimitives, + S, + EvmConfig: ConfigureEvm, + Provider, +> { + rx: S, + current: Option>, + blocks: FlashBlockPendingSequence, + rebuild: bool, + builder: FlashBlockBuilder, + canon_receiver: CanonStateNotifications, + spawner: TaskExecutor, + job: Option>, + /// Cached state reads for the current block. + /// Current `PendingBlock` is built out of a sequence of `FlashBlocks`, and executed again when + /// fb received on top of the same block. Avoid redundant I/O across multiple executions + /// within the same block. + cached_state: Option<(B256, CachedReads)>, +} + +impl FlashBlockService +where + N: NodePrimitives, + S: Stream> + Unpin + 'static, + EvmConfig: ConfigureEvm + Unpin> + + Clone + + 'static, + Provider: StateProviderFactory + + CanonStateSubscriptions + + BlockReaderIdExt< + Header = HeaderTy, + Block = BlockTy, + Transaction = N::SignedTx, + Receipt = ReceiptTy, + > + Unpin + + Clone + + 'static, +{ + /// Constructs a new `FlashBlockService` that receives [`FlashBlock`]s from `rx` stream. + pub fn new(rx: S, evm_config: EvmConfig, provider: Provider, spawner: TaskExecutor) -> Self { + Self { + rx, + current: None, + blocks: FlashBlockPendingSequence::new(), + canon_receiver: provider.subscribe_to_canonical_state(), + builder: FlashBlockBuilder::new(evm_config, provider), + rebuild: false, + spawner, + job: None, + cached_state: None, + } + } + + /// Returns a subscriber to the flashblock sequence. + pub fn subscribe_block_sequence(&self) -> FlashBlockCompleteSequenceRx { + self.blocks.subscribe_block_sequence() + } + + /// Drives the services and sends new blocks to the receiver + /// + /// Note: this should be spawned + pub async fn run(mut self, tx: tokio::sync::watch::Sender>>) { + while let Some(block) = self.next().await { + if let Ok(block) = block.inspect_err(|e| tracing::error!("{e}")) { + let _ = tx.send(block).inspect_err(|e| tracing::error!("{e}")); + } + } + + warn!("Flashblock service has stopped"); + } + + /// Returns the [`BuildArgs`] made purely out of [`FlashBlock`]s that were received earlier. + /// + /// Returns `None` if the flashblock have no `base` or the base is not a child block of latest. + fn build_args( + &mut self, + ) -> Option< + BuildArgs< + impl IntoIterator>> + + use, + >, + > { + let Some(base) = self.blocks.payload_base() else { + trace!( + flashblock_number = ?self.blocks.block_number(), + count = %self.blocks.count(), + "Missing flashblock payload base" + ); + + return None + }; + + // attempt an initial consecutive check + if let Some(latest) = self.builder.provider().latest_header().ok().flatten() && + latest.hash() != base.parent_hash + { + trace!(flashblock_parent=?base.parent_hash, flashblock_number=base.block_number, local_latest=?latest.num_hash(), "Skipping non consecutive build attempt"); + return None; + } + + Some(BuildArgs { + base, + transactions: self.blocks.ready_transactions().collect::>(), + cached_state: self.cached_state.take(), + }) + } + + /// Takes out `current` [`PendingBlock`] if `state` is not preceding it. + fn on_new_tip(&mut self, state: CanonStateNotification) -> Option> { + let latest = state.tip_checked()?.hash(); + self.current.take_if(|current| current.parent_hash() != latest) + } +} + +impl Stream for FlashBlockService +where + N: NodePrimitives, + S: Stream> + Unpin + 'static, + EvmConfig: ConfigureEvm + Unpin> + + Clone + + 'static, + Provider: StateProviderFactory + + CanonStateSubscriptions + + BlockReaderIdExt< + Header = HeaderTy, + Block = BlockTy, + Transaction = N::SignedTx, + Receipt = ReceiptTy, + > + Unpin + + Clone + + 'static, +{ + type Item = eyre::Result>>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + loop { + // drive pending build job to completion + let result = match this.job.as_mut() { + Some((now, rx)) => { + let result = ready!(rx.poll_unpin(cx)); + result.ok().map(|res| (*now, res)) + } + None => None, + }; + // reset job + this.job.take(); + + if let Some((now, result)) = result { + match result { + Ok(Some((new_pending, cached_reads))) => { + // built a new pending block + this.current = Some(new_pending.clone()); + // cache reads + this.cached_state = Some((new_pending.parent_hash(), cached_reads)); + this.rebuild = false; + + trace!( + parent_hash = %new_pending.block().parent_hash(), + block_number = new_pending.block().number(), + flash_blocks = this.blocks.count(), + elapsed = ?now.elapsed(), + "Built new block with flashblocks" + ); + + return Poll::Ready(Some(Ok(Some(new_pending)))); + } + Ok(None) => { + // nothing to do because tracked flashblock doesn't attach to latest + } + Err(err) => { + // we can ignore this error + debug!(%err, "failed to execute flashblock"); + } + } + } + + // consume new flashblocks while they're ready + while let Poll::Ready(Some(result)) = this.rx.poll_next_unpin(cx) { + match result { + Ok(flashblock) => match this.blocks.insert(flashblock) { + Ok(_) => this.rebuild = true, + Err(err) => debug!(%err, "Failed to prepare flashblock"), + }, + Err(err) => return Poll::Ready(Some(Err(err))), + } + } + + // update on new head block + if let Poll::Ready(Ok(state)) = { + let fut = this.canon_receiver.recv(); + pin!(fut); + fut.poll_unpin(cx) + } && let Some(current) = this.on_new_tip(state) + { + trace!( + parent_hash = %current.block().parent_hash(), + block_number = current.block().number(), + "Clearing current flashblock on new canonical block" + ); + + return Poll::Ready(Some(Ok(None))) + } + + if !this.rebuild && this.current.is_some() { + return Poll::Pending + } + + // try to build a block on top of latest + if let Some(args) = this.build_args() { + let now = Instant::now(); + + let (tx, rx) = oneshot::channel(); + let builder = this.builder.clone(); + + this.spawner.spawn_blocking(async move { + let _ = tx.send(builder.execute(args)); + }); + this.job.replace((now, rx)); + + // continue and poll the spawned job + continue + } + + return Poll::Pending + } + } +} + +type BuildJob = + (Instant, oneshot::Receiver, CachedReads)>>>); diff --git a/crates/optimism/flashblocks/src/worker.rs b/crates/optimism/flashblocks/src/worker.rs new file mode 100644 index 00000000000..c2bf04495ea --- /dev/null +++ b/crates/optimism/flashblocks/src/worker.rs @@ -0,0 +1,131 @@ +use crate::ExecutionPayloadBaseV1; +use alloy_eips::{eip2718::WithEncoded, BlockNumberOrTag}; +use alloy_primitives::B256; +use reth_chain_state::{CanonStateSubscriptions, ExecutedBlock}; +use reth_errors::RethError; +use reth_evm::{ + execute::{BlockBuilder, BlockBuilderOutcome}, + ConfigureEvm, +}; +use reth_execution_types::ExecutionOutcome; +use reth_primitives_traits::{ + AlloyBlockHeader, BlockTy, HeaderTy, NodePrimitives, ReceiptTy, Recovered, +}; +use reth_revm::{cached::CachedReads, database::StateProviderDatabase, db::State}; +use reth_rpc_eth_types::{EthApiError, PendingBlock}; +use reth_storage_api::{noop::NoopProvider, BlockReaderIdExt, StateProviderFactory}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tracing::trace; + +/// The `FlashBlockBuilder` builds [`PendingBlock`] out of a sequence of transactions. +#[derive(Debug)] +pub(crate) struct FlashBlockBuilder { + evm_config: EvmConfig, + provider: Provider, +} + +impl FlashBlockBuilder { + pub(crate) const fn new(evm_config: EvmConfig, provider: Provider) -> Self { + Self { evm_config, provider } + } + + pub(crate) const fn provider(&self) -> &Provider { + &self.provider + } +} + +pub(crate) struct BuildArgs { + pub base: ExecutionPayloadBaseV1, + pub transactions: I, + pub cached_state: Option<(B256, CachedReads)>, +} + +impl FlashBlockBuilder +where + N: NodePrimitives, + EvmConfig: ConfigureEvm + Unpin>, + Provider: StateProviderFactory + + CanonStateSubscriptions + + BlockReaderIdExt< + Header = HeaderTy, + Block = BlockTy, + Transaction = N::SignedTx, + Receipt = ReceiptTy, + > + Unpin, +{ + /// Returns the [`PendingBlock`] made purely out of transactions and [`ExecutionPayloadBaseV1`] + /// in `args`. + /// + /// Returns `None` if the flashblock doesn't attach to the latest header. + pub(crate) fn execute>>>( + &self, + mut args: BuildArgs, + ) -> eyre::Result, CachedReads)>> { + trace!("Attempting new pending block from flashblocks"); + + let latest = self + .provider + .latest_header()? + .ok_or(EthApiError::HeaderNotFound(BlockNumberOrTag::Latest.into()))?; + let latest_hash = latest.hash(); + + if args.base.parent_hash != latest_hash { + trace!(flashblock_parent = ?args.base.parent_hash, local_latest=?latest.num_hash(),"Skipping non consecutive flashblock"); + // doesn't attach to the latest block + return Ok(None) + } + + let state_provider = self.provider.history_by_block_hash(latest.hash())?; + + let mut request_cache = args + .cached_state + .take() + .filter(|(hash, _)| hash == &latest_hash) + .map(|(_, state)| state) + .unwrap_or_default(); + let cached_db = request_cache.as_db_mut(StateProviderDatabase::new(&state_provider)); + let mut state = State::builder().with_database(cached_db).with_bundle_update().build(); + + let mut builder = self + .evm_config + .builder_for_next_block(&mut state, &latest, args.base.into()) + .map_err(RethError::other)?; + + builder.apply_pre_execution_changes()?; + + for tx in args.transactions { + let _gas_used = builder.execute_transaction(tx)?; + } + + let BlockBuilderOutcome { execution_result, block, hashed_state, .. } = + builder.finish(NoopProvider::default())?; + + let execution_outcome = ExecutionOutcome::new( + state.take_bundle(), + vec![execution_result.receipts], + block.number(), + vec![execution_result.requests], + ); + + Ok(Some(( + PendingBlock::with_executed_block( + Instant::now() + Duration::from_secs(1), + ExecutedBlock { + recovered_block: block.into(), + execution_output: Arc::new(execution_outcome), + hashed_state: Arc::new(hashed_state), + }, + ), + request_cache, + ))) + } +} + +impl Clone for FlashBlockBuilder { + fn clone(&self) -> Self { + Self { evm_config: self.evm_config.clone(), provider: self.provider.clone() } + } +} diff --git a/crates/optimism/flashblocks/src/ws/decoding.rs b/crates/optimism/flashblocks/src/ws/decoding.rs new file mode 100644 index 00000000000..267f79cf19a --- /dev/null +++ b/crates/optimism/flashblocks/src/ws/decoding.rs @@ -0,0 +1,66 @@ +use crate::{ExecutionPayloadBaseV1, ExecutionPayloadFlashblockDeltaV1, FlashBlock, Metadata}; +use alloy_primitives::bytes::Bytes; +use alloy_rpc_types_engine::PayloadId; +use serde::{Deserialize, Serialize}; +use std::{fmt::Debug, io}; + +/// Internal helper for decoding +#[derive(Clone, Debug, PartialEq, Default, Deserialize, Serialize)] +struct FlashblocksPayloadV1 { + /// The payload id of the flashblock + pub payload_id: PayloadId, + /// The index of the flashblock in the block + pub index: u64, + /// The base execution payload configuration + #[serde(skip_serializing_if = "Option::is_none")] + pub base: Option, + /// The delta/diff containing modified portions of the execution payload + pub diff: ExecutionPayloadFlashblockDeltaV1, + /// Additional metadata associated with the flashblock + pub metadata: serde_json::Value, +} + +impl FlashBlock { + /// Decodes `bytes` into [`FlashBlock`]. + /// + /// This function is specific to the Base Optimism websocket encoding. + /// + /// It is assumed that the `bytes` are encoded in JSON and optionally compressed using brotli. + /// Whether the `bytes` is compressed or not is determined by looking at the first + /// non ascii-whitespace character. + pub(crate) fn decode(bytes: Bytes) -> eyre::Result { + let bytes = try_parse_message(bytes)?; + + let payload: FlashblocksPayloadV1 = serde_json::from_slice(&bytes) + .map_err(|e| eyre::eyre!("failed to parse message: {e}"))?; + + let metadata: Metadata = serde_json::from_value(payload.metadata) + .map_err(|e| eyre::eyre!("failed to parse message metadata: {e}"))?; + + Ok(Self { + payload_id: payload.payload_id, + index: payload.index, + base: payload.base, + diff: payload.diff, + metadata, + }) + } +} + +/// Maps `bytes` into a potentially different [`Bytes`]. +/// +/// If the bytes start with a "{" character, prepended by any number of ASCII-whitespaces, +/// then it assumes that it is JSON-encoded and returns it as-is. +/// +/// Otherwise, the `bytes` are passed through a brotli decompressor and returned. +fn try_parse_message(bytes: Bytes) -> eyre::Result { + if bytes.trim_ascii_start().starts_with(b"{") { + return Ok(bytes); + } + + let mut decompressor = brotli::Decompressor::new(bytes.as_ref(), 4096); + let mut decompressed = Vec::new(); + io::copy(&mut decompressor, &mut decompressed)?; + + Ok(decompressed.into()) +} diff --git a/crates/optimism/flashblocks/src/ws/mod.rs b/crates/optimism/flashblocks/src/ws/mod.rs new file mode 100644 index 00000000000..2b820899312 --- /dev/null +++ b/crates/optimism/flashblocks/src/ws/mod.rs @@ -0,0 +1,4 @@ +pub use stream::{WsConnect, WsFlashBlockStream}; + +mod decoding; +mod stream; diff --git a/crates/optimism/flashblocks/src/ws/stream.rs b/crates/optimism/flashblocks/src/ws/stream.rs new file mode 100644 index 00000000000..55b8be9939b --- /dev/null +++ b/crates/optimism/flashblocks/src/ws/stream.rs @@ -0,0 +1,551 @@ +use crate::FlashBlock; +use futures_util::{ + stream::{SplitSink, SplitStream}, + FutureExt, Sink, Stream, StreamExt, +}; +use std::{ + fmt::{Debug, Formatter}, + future::Future, + pin::Pin, + task::{ready, Context, Poll}, +}; +use tokio::net::TcpStream; +use tokio_tungstenite::{ + connect_async, + tungstenite::{protocol::CloseFrame, Bytes, Error, Message}, + MaybeTlsStream, WebSocketStream, +}; +use tracing::debug; +use url::Url; + +/// An asynchronous stream of [`FlashBlock`] from a websocket connection. +/// +/// The stream attempts to connect to a websocket URL and then decode each received item. +/// +/// If the connection fails, the error is returned and connection retried. The number of retries is +/// unbounded. +pub struct WsFlashBlockStream { + ws_url: Url, + state: State, + connector: Connector, + connect: ConnectFuture, + stream: Option, + sink: Option, +} + +impl WsFlashBlockStream { + /// Creates a new websocket stream over `ws_url`. + pub fn new(ws_url: Url) -> Self { + Self { + ws_url, + state: State::default(), + connector: WsConnector, + connect: Box::pin(async move { Err(Error::ConnectionClosed)? }), + stream: None, + sink: None, + } + } +} + +impl WsFlashBlockStream { + /// Creates a new websocket stream over `ws_url`. + pub fn with_connector(ws_url: Url, connector: C) -> Self { + Self { + ws_url, + state: State::default(), + connector, + connect: Box::pin(async move { Err(Error::ConnectionClosed)? }), + stream: None, + sink: None, + } + } +} + +impl Stream for WsFlashBlockStream +where + Str: Stream> + Unpin, + S: Sink + Send + Sync + Unpin, + C: WsConnect + Clone + Send + Sync + 'static + Unpin, +{ + type Item = eyre::Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + 'start: loop { + if this.state == State::Initial { + this.connect(); + } + + if this.state == State::Connect { + match ready!(this.connect.poll_unpin(cx)) { + Ok((sink, stream)) => this.stream(sink, stream), + Err(err) => { + this.state = State::Initial; + + return Poll::Ready(Some(Err(err))); + } + } + } + + while let State::Stream(msg) = &mut this.state { + if msg.is_some() { + let mut sink = Pin::new(this.sink.as_mut().unwrap()); + let _ = ready!(sink.as_mut().poll_ready(cx)); + if let Some(pong) = msg.take() { + let _ = sink.as_mut().start_send(pong); + } + let _ = ready!(sink.as_mut().poll_flush(cx)); + } + + let Some(msg) = ready!(this + .stream + .as_mut() + .expect("Stream state should be unreachable without stream") + .poll_next_unpin(cx)) + else { + this.state = State::Initial; + + continue 'start; + }; + + match msg { + Ok(Message::Binary(bytes)) => { + return Poll::Ready(Some(FlashBlock::decode(bytes))) + } + Ok(Message::Text(bytes)) => { + return Poll::Ready(Some(FlashBlock::decode(bytes.into()))) + } + Ok(Message::Ping(bytes)) => this.ping(bytes), + Ok(Message::Close(frame)) => this.close(frame), + Ok(msg) => debug!("Received unexpected message: {:?}", msg), + Err(err) => return Poll::Ready(Some(Err(err.into()))), + } + } + } + } +} + +impl WsFlashBlockStream +where + C: WsConnect + Clone + Send + Sync + 'static, +{ + fn connect(&mut self) { + let ws_url = self.ws_url.clone(); + let mut connector = self.connector.clone(); + + Pin::new(&mut self.connect).set(Box::pin(async move { connector.connect(ws_url).await })); + + self.state = State::Connect; + } + + fn stream(&mut self, sink: S, stream: Stream) { + self.sink.replace(sink); + self.stream.replace(stream); + + self.state = State::Stream(None); + } + + fn ping(&mut self, pong: Bytes) { + if let State::Stream(current) = &mut self.state { + current.replace(Message::Pong(pong)); + } + } + + fn close(&mut self, frame: Option) { + if let State::Stream(current) = &mut self.state { + current.replace(Message::Close(frame)); + } + } +} + +impl Debug for WsFlashBlockStream { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("FlashBlockStream") + .field("ws_url", &self.ws_url) + .field("state", &self.state) + .field("connector", &self.connector) + .field("connect", &"Pin>>") + .field("stream", &self.stream) + .finish() + } +} + +#[derive(Default, Debug, Eq, PartialEq)] +enum State { + #[default] + Initial, + Connect, + Stream(Option), +} + +type Ws = WebSocketStream>; +type WsStream = SplitStream; +type WsSink = SplitSink; +type ConnectFuture = + Pin> + Send + Sync + 'static>>; + +/// The `WsConnect` trait allows for connecting to a websocket. +/// +/// Implementors of the `WsConnect` trait are called 'connectors'. +/// +/// Connectors are defined by one method, [`connect()`]. A call to [`connect()`] attempts to +/// establish a secure websocket connection and return an asynchronous stream of [`Message`]s +/// wrapped in a [`Result`]. +/// +/// [`connect()`]: Self::connect +pub trait WsConnect { + /// An associated `Stream` of [`Message`]s wrapped in a [`Result`] that this connection returns. + type Stream; + + /// An associated `Sink` of [`Message`]s that this connection sends. + type Sink; + + /// Asynchronously connects to a websocket hosted on `ws_url`. + /// + /// See the [`WsConnect`] documentation for details. + fn connect( + &mut self, + ws_url: Url, + ) -> impl Future> + Send + Sync; +} + +/// Establishes a secure websocket subscription. +/// +/// See the [`WsConnect`] documentation for details. +#[derive(Debug, Clone)] +pub struct WsConnector; + +impl WsConnect for WsConnector { + type Stream = WsStream; + type Sink = WsSink; + + async fn connect(&mut self, ws_url: Url) -> eyre::Result<(WsSink, WsStream)> { + let (stream, _response) = connect_async(ws_url.as_str()).await?; + + Ok(stream.split()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ExecutionPayloadBaseV1; + use alloy_primitives::bytes::Bytes; + use brotli::enc::BrotliEncoderParams; + use std::{future, iter}; + use tokio_tungstenite::tungstenite::{ + protocol::frame::{coding::CloseCode, Frame}, + Error, + }; + + /// A `FakeConnector` creates [`FakeStream`]. + /// + /// It simulates the websocket stream instead of connecting to a real websocket. + #[derive(Clone)] + struct FakeConnector(FakeStream); + + /// A `FakeConnectorWithSink` creates [`FakeStream`] and [`FakeSink`]. + /// + /// It simulates the websocket stream instead of connecting to a real websocket. It also accepts + /// messages into an in-memory buffer. + #[derive(Clone)] + struct FakeConnectorWithSink(FakeStream); + + /// Simulates a websocket stream while using a preprogrammed set of messages instead. + #[derive(Default)] + struct FakeStream(Vec>); + + impl FakeStream { + fn new(mut messages: Vec>) -> Self { + messages.reverse(); + + Self(messages) + } + } + + impl Clone for FakeStream { + fn clone(&self) -> Self { + Self( + self.0 + .iter() + .map(|v| match v { + Ok(msg) => Ok(msg.clone()), + Err(err) => Err(match err { + Error::AttackAttempt => Error::AttackAttempt, + err => unimplemented!("Cannot clone this error: {err}"), + }), + }) + .collect(), + ) + } + } + + impl Stream for FakeStream { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let this = self.get_mut(); + + Poll::Ready(this.0.pop()) + } + } + + #[derive(Clone)] + struct NoopSink; + + impl Sink for NoopSink { + type Error = (); + + fn poll_ready( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + unimplemented!() + } + + fn start_send(self: Pin<&mut Self>, _item: T) -> Result<(), Self::Error> { + unimplemented!() + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + unimplemented!() + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + unimplemented!() + } + } + + /// Receives [`Message`]s and stores them. A call to `start_send` first buffers the message + /// to simulate flushing behavior. + #[derive(Clone, Default)] + struct FakeSink(Option, Vec); + + impl Sink for FakeSink { + type Error = (); + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) + } + + fn start_send(self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { + self.get_mut().0.replace(item); + Ok(()) + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + let this = self.get_mut(); + if let Some(item) = this.0.take() { + this.1.push(item); + } + Poll::Ready(Ok(())) + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + } + + impl WsConnect for FakeConnector { + type Stream = FakeStream; + type Sink = NoopSink; + + fn connect( + &mut self, + _ws_url: Url, + ) -> impl Future> + Send + Sync { + future::ready(Ok((NoopSink, self.0.clone()))) + } + } + + impl>> From for FakeConnector { + fn from(value: T) -> Self { + Self(FakeStream::new(value.into_iter().collect())) + } + } + + impl WsConnect for FakeConnectorWithSink { + type Stream = FakeStream; + type Sink = FakeSink; + + fn connect( + &mut self, + _ws_url: Url, + ) -> impl Future> + Send + Sync { + future::ready(Ok((FakeSink::default(), self.0.clone()))) + } + } + + impl>> From for FakeConnectorWithSink { + fn from(value: T) -> Self { + Self(FakeStream::new(value.into_iter().collect())) + } + } + + /// Repeatedly fails to connect with the given error message. + #[derive(Clone)] + struct FailingConnector(String); + + impl WsConnect for FailingConnector { + type Stream = FakeStream; + type Sink = NoopSink; + + fn connect( + &mut self, + _ws_url: Url, + ) -> impl Future> + Send + Sync { + future::ready(Err(eyre::eyre!("{}", &self.0))) + } + } + + fn to_json_message, F: Fn(B) -> Message>( + wrapper_f: F, + ) -> impl Fn(&FlashBlock) -> Result + use { + move |block| to_json_message_using(block, &wrapper_f) + } + + fn to_json_binary_message(block: &FlashBlock) -> Result { + to_json_message_using(block, Message::Binary) + } + + fn to_json_message_using, F: Fn(B) -> Message>( + block: &FlashBlock, + wrapper_f: F, + ) -> Result { + Ok(wrapper_f(B::try_from(Bytes::from(serde_json::to_vec(block).unwrap())).unwrap())) + } + + fn to_brotli_message(block: &FlashBlock) -> Result { + let json = serde_json::to_vec(block).unwrap(); + let mut compressed = Vec::new(); + brotli::BrotliCompress( + &mut json.as_slice(), + &mut compressed, + &BrotliEncoderParams::default(), + )?; + + Ok(Message::Binary(Bytes::from(compressed))) + } + + fn flashblock() -> FlashBlock { + FlashBlock { + payload_id: Default::default(), + index: 0, + base: Some(ExecutionPayloadBaseV1 { + parent_beacon_block_root: Default::default(), + parent_hash: Default::default(), + fee_recipient: Default::default(), + prev_randao: Default::default(), + block_number: 0, + gas_limit: 0, + timestamp: 0, + extra_data: Default::default(), + base_fee_per_gas: Default::default(), + }), + diff: Default::default(), + metadata: Default::default(), + } + } + + #[test_case::test_case(to_json_message(Message::Binary); "json binary")] + #[test_case::test_case(to_json_message(Message::Text); "json UTF-8")] + #[test_case::test_case(to_brotli_message; "brotli")] + #[tokio::test] + async fn test_stream_decodes_messages_successfully( + to_message: impl Fn(&FlashBlock) -> Result, + ) { + let flashblocks = [flashblock()]; + let connector = FakeConnector::from(flashblocks.iter().map(to_message)); + let ws_url = "http://localhost".parse().unwrap(); + let stream = WsFlashBlockStream::with_connector(ws_url, connector); + + let actual_messages: Vec<_> = stream.take(1).map(Result::unwrap).collect().await; + let expected_messages = flashblocks.to_vec(); + + assert_eq!(actual_messages, expected_messages); + } + + #[test_case::test_case(Message::Pong(Bytes::from(b"test".as_slice())); "pong")] + #[test_case::test_case(Message::Frame(Frame::pong(b"test".as_slice())); "frame")] + #[tokio::test] + async fn test_stream_ignores_unexpected_message(message: Message) { + let flashblock = flashblock(); + let connector = FakeConnector::from([Ok(message), to_json_binary_message(&flashblock)]); + let ws_url = "http://localhost".parse().unwrap(); + let mut stream = WsFlashBlockStream::with_connector(ws_url, connector); + + let expected_message = flashblock; + let actual_message = + stream.next().await.expect("Binary message should not be ignored").unwrap(); + + assert_eq!(actual_message, expected_message) + } + + #[tokio::test] + async fn test_stream_passes_errors_through() { + let connector = FakeConnector::from([Err(Error::AttackAttempt)]); + let ws_url = "http://localhost".parse().unwrap(); + let stream = WsFlashBlockStream::with_connector(ws_url, connector); + + let actual_messages: Vec<_> = + stream.take(1).map(Result::unwrap_err).map(|e| format!("{e}")).collect().await; + let expected_messages = vec!["Attack attempt detected".to_owned()]; + + assert_eq!(actual_messages, expected_messages); + } + + #[tokio::test] + async fn test_connect_error_causes_retries() { + let tries = 3; + let error_msg = "test".to_owned(); + let connector = FailingConnector(error_msg.clone()); + let ws_url = "http://localhost".parse().unwrap(); + let stream = WsFlashBlockStream::with_connector(ws_url, connector); + + let actual_errors: Vec<_> = + stream.take(tries).map(Result::unwrap_err).map(|e| format!("{e}")).collect().await; + let expected_errors: Vec<_> = iter::repeat_n(error_msg, tries).collect(); + + assert_eq!(actual_errors, expected_errors); + } + + #[test_case::test_case( + Message::Close(Some(CloseFrame { code: CloseCode::Normal, reason: "test".into() })), + Message::Close(Some(CloseFrame { code: CloseCode::Normal, reason: "test".into() })); + "close" + )] + #[test_case::test_case( + Message::Ping(Bytes::from_static(&[1u8, 2, 3])), + Message::Pong(Bytes::from_static(&[1u8, 2, 3])); + "ping" + )] + #[tokio::test] + async fn test_stream_responds_to_messages(msg: Message, expected_response: Message) { + let flashblock = flashblock(); + let messages = [Ok(msg), to_json_binary_message(&flashblock)]; + let connector = FakeConnectorWithSink::from(messages); + let ws_url = "http://localhost".parse().unwrap(); + let mut stream = WsFlashBlockStream::with_connector(ws_url, connector); + + let _ = stream.next().await; + + let expected_response = vec![expected_response]; + let FakeSink(actual_buffer, actual_response) = stream.sink.unwrap(); + + assert!(actual_buffer.is_none(), "buffer not flushed: {actual_buffer:#?}"); + assert_eq!(actual_response, expected_response); + } +} diff --git a/crates/optimism/flashblocks/tests/it/main.rs b/crates/optimism/flashblocks/tests/it/main.rs new file mode 100644 index 00000000000..bfe1f9695a9 --- /dev/null +++ b/crates/optimism/flashblocks/tests/it/main.rs @@ -0,0 +1,5 @@ +//! Integration tests. +//! +//! All the individual modules are rooted here to produce a single binary. + +mod stream; diff --git a/crates/optimism/flashblocks/tests/it/stream.rs b/crates/optimism/flashblocks/tests/it/stream.rs new file mode 100644 index 00000000000..99e78fee23a --- /dev/null +++ b/crates/optimism/flashblocks/tests/it/stream.rs @@ -0,0 +1,15 @@ +use futures_util::stream::StreamExt; +use reth_optimism_flashblocks::WsFlashBlockStream; + +#[tokio::test] +async fn test_streaming_flashblocks_from_remote_source_is_successful() { + let items = 3; + let ws_url = "wss://sepolia.flashblocks.base.org/ws".parse().unwrap(); + let stream = WsFlashBlockStream::new(ws_url); + + let blocks: Vec<_> = stream.take(items).collect().await; + + for block in blocks { + assert!(block.is_ok()); + } +} diff --git a/crates/optimism/node/Cargo.toml b/crates/optimism/node/Cargo.toml index 0d5f1112a69..162700ac0ae 100644 --- a/crates/optimism/node/Cargo.toml +++ b/crates/optimism/node/Cargo.toml @@ -63,6 +63,7 @@ tokio.workspace = true clap.workspace = true serde.workspace = true eyre.workspace = true +url.workspace = true # test-utils dependencies reth-e2e-test-utils = { workspace = true, optional = true } @@ -76,16 +77,13 @@ reth-node-builder = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } reth-tasks.workspace = true reth-payload-util.workspace = true -reth-payload-validator.workspace = true reth-revm = { workspace = true, features = ["std"] } reth-rpc.workspace = true reth-rpc-eth-types.workspace = true -reth-network-api.workspace = true alloy-network.workspace = true futures.workspace = true op-alloy-network.workspace = true -tempfile.workspace = true [features] default = ["reth-codec"] diff --git a/crates/optimism/node/src/args.rs b/crates/optimism/node/src/args.rs index 9e93f8e63f9..4e9bb2ce7c3 100644 --- a/crates/optimism/node/src/args.rs +++ b/crates/optimism/node/src/args.rs @@ -4,6 +4,7 @@ use op_alloy_consensus::interop::SafetyLevel; use reth_optimism_txpool::supervisor::DEFAULT_SUPERVISOR_URL; +use url::Url; /// Parameters for rollup configuration #[derive(Debug, Clone, PartialEq, Eq, clap::Args)] @@ -66,6 +67,13 @@ pub struct RollupArgs { /// Minimum suggested priority fee (tip) in wei, default `1_000_000` #[arg(long, default_value_t = 1_000_000)] pub min_suggested_priority_fee: u64, + + /// A URL pointing to a secure websocket subscription that streams out flashblocks. + /// + /// If given, the flashblocks are received to build pending block. All request with "pending" + /// block tag will use the pending state based on flashblocks. + #[arg(long)] + pub flashblocks_url: Option, } impl Default for RollupArgs { @@ -81,6 +89,7 @@ impl Default for RollupArgs { sequencer_headers: Vec::new(), historical_rpc: None, min_suggested_priority_fee: 1_000_000, + flashblocks_url: None, } } } diff --git a/crates/optimism/node/src/engine.rs b/crates/optimism/node/src/engine.rs index 39bad862594..af018d6f272 100644 --- a/crates/optimism/node/src/engine.rs +++ b/crates/optimism/node/src/engine.rs @@ -226,6 +226,7 @@ where "MissingEip1559ParamsInPayloadAttributes".to_string().into(), ) })?; + if elasticity != 0 && denominator == 0 { return Err(EngineObjectValidationError::InvalidParams( "Eip1559ParamsDenominatorZero".to_string().into(), @@ -233,6 +234,19 @@ where } } + if self.chain_spec().is_jovian_active_at_timestamp(attributes.payload_attributes.timestamp) + { + if attributes.min_base_fee.is_none() { + return Err(EngineObjectValidationError::InvalidParams( + "MissingMinBaseFeeInPayloadAttributes".to_string().into(), + )); + } + } else if attributes.min_base_fee.is_some() { + return Err(EngineObjectValidationError::InvalidParams( + "MinBaseFeeNotAllowedBeforeJovian".to_string().into(), + )); + } + Ok(()) } } @@ -289,32 +303,46 @@ mod test { use crate::engine; use alloy_primitives::{b64, Address, B256, B64}; use alloy_rpc_types_engine::PayloadAttributes; - use reth_chainspec::ChainSpec; + use reth_chainspec::{ChainSpec, ForkCondition, Hardfork}; use reth_optimism_chainspec::{OpChainSpec, BASE_SEPOLIA}; + use reth_optimism_forks::OpHardfork; use reth_provider::noop::NoopProvider; use reth_trie_common::KeccakKeyHasher; + const JOVIAN_TIMESTAMP: u64 = 1744909000; + fn get_chainspec() -> Arc { + let mut base_sepolia_spec = BASE_SEPOLIA.inner.clone(); + + // TODO: Remove this once we know the Jovian timestamp + base_sepolia_spec + .hardforks + .insert(OpHardfork::Jovian.boxed(), ForkCondition::Timestamp(JOVIAN_TIMESTAMP)); + Arc::new(OpChainSpec { inner: ChainSpec { - chain: BASE_SEPOLIA.inner.chain, - genesis: BASE_SEPOLIA.inner.genesis.clone(), - genesis_header: BASE_SEPOLIA.inner.genesis_header.clone(), - paris_block_and_final_difficulty: BASE_SEPOLIA - .inner + chain: base_sepolia_spec.chain, + genesis: base_sepolia_spec.genesis, + genesis_header: base_sepolia_spec.genesis_header, + paris_block_and_final_difficulty: base_sepolia_spec .paris_block_and_final_difficulty, - hardforks: BASE_SEPOLIA.inner.hardforks.clone(), - base_fee_params: BASE_SEPOLIA.inner.base_fee_params.clone(), + hardforks: base_sepolia_spec.hardforks, + base_fee_params: base_sepolia_spec.base_fee_params, prune_delete_limit: 10000, ..Default::default() }, }) } - const fn get_attributes(eip_1559_params: Option, timestamp: u64) -> OpPayloadAttributes { + const fn get_attributes( + eip_1559_params: Option, + min_base_fee: Option, + timestamp: u64, + ) -> OpPayloadAttributes { OpPayloadAttributes { gas_limit: Some(1000), eip_1559_params, + min_base_fee, transactions: None, no_tx_pool: None, payload_attributes: PayloadAttributes { @@ -331,7 +359,7 @@ mod test { fn test_well_formed_attributes_pre_holocene() { let validator = OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); - let attributes = get_attributes(None, 1732633199); + let attributes = get_attributes(None, None, 1732633199); let result = as EngineApiValidator< OpEngineTypes, @@ -345,7 +373,7 @@ mod test { fn test_well_formed_attributes_holocene_no_eip1559_params() { let validator = OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); - let attributes = get_attributes(None, 1732633200); + let attributes = get_attributes(None, None, 1732633200); let result = as EngineApiValidator< OpEngineTypes, @@ -359,7 +387,7 @@ mod test { fn test_well_formed_attributes_holocene_eip1559_params_zero_denominator() { let validator = OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); - let attributes = get_attributes(Some(b64!("0000000000000008")), 1732633200); + let attributes = get_attributes(Some(b64!("0000000000000008")), None, 1732633200); let result = as EngineApiValidator< OpEngineTypes, @@ -373,7 +401,7 @@ mod test { fn test_well_formed_attributes_holocene_valid() { let validator = OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); - let attributes = get_attributes(Some(b64!("0000000800000008")), 1732633200); + let attributes = get_attributes(Some(b64!("0000000800000008")), None, 1732633200); let result = as EngineApiValidator< OpEngineTypes, @@ -387,7 +415,21 @@ mod test { fn test_well_formed_attributes_holocene_valid_all_zero() { let validator = OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); - let attributes = get_attributes(Some(b64!("0000000000000000")), 1732633200); + let attributes = get_attributes(Some(b64!("0000000000000000")), None, 1732633200); + + let result = as EngineApiValidator< + OpEngineTypes, + >>::ensure_well_formed_attributes( + &validator, EngineApiMessageVersion::V3, &attributes, + ); + assert!(result.is_ok()); + } + + #[test] + fn test_well_formed_attributes_jovian_valid() { + let validator = + OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); + let attributes = get_attributes(Some(b64!("0000000000000000")), Some(1), JOVIAN_TIMESTAMP); let result = as EngineApiValidator< OpEngineTypes, @@ -396,4 +438,49 @@ mod test { ); assert!(result.is_ok()); } + + /// After Jovian (and holocene), eip1559 params must be Some + #[test] + fn test_malformed_attributes_jovian_with_eip_1559_params_none() { + let validator = + OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); + let attributes = get_attributes(None, Some(1), JOVIAN_TIMESTAMP); + + let result = as EngineApiValidator< + OpEngineTypes, + >>::ensure_well_formed_attributes( + &validator, EngineApiMessageVersion::V3, &attributes, + ); + assert!(matches!(result, Err(EngineObjectValidationError::InvalidParams(_)))); + } + + /// Before Jovian, min base fee must be None + #[test] + fn test_malformed_attributes_pre_jovian_with_min_base_fee() { + let validator = + OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); + let attributes = get_attributes(Some(b64!("0000000000000000")), Some(1), 1732633200); + + let result = as EngineApiValidator< + OpEngineTypes, + >>::ensure_well_formed_attributes( + &validator, EngineApiMessageVersion::V3, &attributes, + ); + assert!(matches!(result, Err(EngineObjectValidationError::InvalidParams(_)))); + } + + /// After Jovian, min base fee must be Some + #[test] + fn test_malformed_attributes_post_jovian_with_min_base_fee_none() { + let validator = + OpEngineValidator::new::(get_chainspec(), NoopProvider::default()); + let attributes = get_attributes(Some(b64!("0000000000000000")), None, JOVIAN_TIMESTAMP); + + let result = as EngineApiValidator< + OpEngineTypes, + >>::ensure_well_formed_attributes( + &validator, EngineApiMessageVersion::V3, &attributes, + ); + assert!(matches!(result, Err(EngineObjectValidationError::InvalidParams(_)))); + } } diff --git a/crates/optimism/node/src/node.rs b/crates/optimism/node/src/node.rs index 5f69ef2eba2..ebad4e66999 100644 --- a/crates/optimism/node/src/node.rs +++ b/crates/optimism/node/src/node.rs @@ -66,6 +66,7 @@ use reth_transaction_pool::{ use reth_trie_common::KeccakKeyHasher; use serde::de::DeserializeOwned; use std::{marker::PhantomData, sync::Arc}; +use url::Url; /// Marker trait for Optimism node types with standard engine, chain spec, and primitives. pub trait OpNodeTypes: @@ -175,6 +176,7 @@ impl OpNode { .with_enable_tx_conditional(self.args.enable_tx_conditional) .with_min_suggested_priority_fee(self.args.min_suggested_priority_fee) .with_historical_rpc(self.args.historical_rpc.clone()) + .with_flashblocks(self.args.flashblocks_url.clone()) } /// Instantiates the [`ProviderFactoryBuilder`] for an opstack node. @@ -620,14 +622,15 @@ where } } -impl EngineValidatorAddOn - for OpAddOns, PVB, EB, EVB> +impl EngineValidatorAddOn + for OpAddOns where N: FullNodeComponents, - OpEthApiBuilder: EthApiBuilder, + EthB: EthApiBuilder, PVB: Send, EB: EngineApiBuilder, EVB: EngineValidatorBuilder, + RpcMiddleware: Send, { type ValidatorBuilder = EVB; @@ -659,6 +662,8 @@ pub struct OpAddOnsBuilder { rpc_middleware: RpcMiddleware, /// Optional tokio runtime to use for the RPC server. tokio_runtime: Option, + /// A URL pointing to a secure websocket service that streams out flashblocks. + flashblocks_url: Option, } impl Default for OpAddOnsBuilder { @@ -673,6 +678,7 @@ impl Default for OpAddOnsBuilder { _nt: PhantomData, rpc_middleware: Identity::new(), tokio_runtime: None, + flashblocks_url: None, } } } @@ -733,6 +739,7 @@ impl OpAddOnsBuilder { min_suggested_priority_fee, tokio_runtime, _nt, + flashblocks_url, .. } = self; OpAddOnsBuilder { @@ -745,8 +752,15 @@ impl OpAddOnsBuilder { _nt, rpc_middleware, tokio_runtime, + flashblocks_url, } } + + /// With a URL pointing to a flashblocks secure websocket subscription. + pub fn with_flashblocks(mut self, flashblocks_url: Option) -> Self { + self.flashblocks_url = flashblocks_url; + self + } } impl OpAddOnsBuilder { @@ -770,6 +784,7 @@ impl OpAddOnsBuilder { historical_rpc, rpc_middleware, tokio_runtime, + flashblocks_url, .. } = self; @@ -778,7 +793,8 @@ impl OpAddOnsBuilder { OpEthApiBuilder::default() .with_sequencer(sequencer_url.clone()) .with_sequencer_headers(sequencer_headers.clone()) - .with_min_suggested_priority_fee(min_suggested_priority_fee), + .with_min_suggested_priority_fee(min_suggested_priority_fee) + .with_flashblocks(flashblocks_url), PVB::default(), EB::default(), EVB::default(), @@ -941,9 +957,7 @@ where debug!(target: "reth::cli", "Spawned txpool maintenance task"); // The Op txpool maintenance task is only spawned when interop is active - if ctx.chain_spec().is_interop_active_at_timestamp(ctx.head().timestamp) && - self.supervisor_http == DEFAULT_SUPERVISOR_URL - { + if ctx.chain_spec().is_interop_active_at_timestamp(ctx.head().timestamp) { // spawn the Op txpool maintenance task let chain_events = ctx.provider().canonical_state_stream(); ctx.task_executor().spawn_critical( @@ -1185,7 +1199,12 @@ pub struct OpEngineValidatorBuilder; impl PayloadValidatorBuilder for OpEngineValidatorBuilder where - Node: FullNodeComponents, + Node: FullNodeComponents< + Types: NodeTypes< + ChainSpec: OpHardforks, + Payload: PayloadTypes, + >, + >, { type Validator = OpEngineValidator< Node::Provider, diff --git a/crates/optimism/node/src/utils.rs b/crates/optimism/node/src/utils.rs index 9e2f7b5b3b0..42104c9df73 100644 --- a/crates/optimism/node/src/utils.rs +++ b/crates/optimism/node/src/utils.rs @@ -69,5 +69,6 @@ pub fn optimism_payload_attributes(timestamp: u64) -> OpPayloadBuilderAttribu no_tx_pool: false, gas_limit: Some(30_000_000), eip_1559_params: None, + min_base_fee: None, } } diff --git a/crates/optimism/node/tests/e2e-testsuite/testsuite.rs b/crates/optimism/node/tests/e2e-testsuite/testsuite.rs index b67c6e97705..75dff49c141 100644 --- a/crates/optimism/node/tests/e2e-testsuite/testsuite.rs +++ b/crates/optimism/node/tests/e2e-testsuite/testsuite.rs @@ -44,6 +44,7 @@ async fn test_testsuite_op_assert_mine_block() -> Result<()> { transactions: None, no_tx_pool: None, eip_1559_params: None, + min_base_fee: None, gas_limit: Some(30_000_000), }, )); diff --git a/crates/optimism/node/tests/it/builder.rs b/crates/optimism/node/tests/it/builder.rs index eba2aed422d..e0437a5f655 100644 --- a/crates/optimism/node/tests/it/builder.rs +++ b/crates/optimism/node/tests/it/builder.rs @@ -1,11 +1,32 @@ //! Node builder setup tests. +use alloy_primitives::{address, Bytes}; +use core::marker::PhantomData; +use op_revm::{ + precompiles::OpPrecompiles, OpContext, OpHaltReason, OpSpecId, OpTransaction, + OpTransactionError, +}; use reth_db::test_utils::create_test_rw_db; +use reth_evm::{precompiles::PrecompilesMap, Database, Evm, EvmEnv, EvmFactory}; use reth_node_api::{FullNodeComponents, NodeTypesWithDBAdapter}; -use reth_node_builder::{Node, NodeBuilder, NodeConfig}; -use reth_optimism_chainspec::BASE_MAINNET; -use reth_optimism_node::{args::RollupArgs, OpNode}; +use reth_node_builder::{ + components::ExecutorBuilder, BuilderContext, FullNodeTypes, Node, NodeBuilder, NodeConfig, + NodeTypes, +}; +use reth_optimism_chainspec::{OpChainSpec, BASE_MAINNET, OP_SEPOLIA}; +use reth_optimism_evm::{OpBlockExecutorFactory, OpEvm, OpEvmFactory, OpRethReceiptBuilder}; +use reth_optimism_node::{args::RollupArgs, OpEvmConfig, OpExecutorBuilder, OpNode}; +use reth_optimism_primitives::OpPrimitives; use reth_provider::providers::BlockchainProvider; +use revm::{ + context::{Cfg, ContextTr, TxEnv}, + context_interface::result::EVMError, + inspector::NoOpInspector, + interpreter::interpreter::EthInterpreter, + precompile::{Precompile, PrecompileId, PrecompileOutput, PrecompileResult, Precompiles}, + Inspector, +}; +use std::sync::OnceLock; #[test] fn test_basic_setup() { @@ -36,3 +57,112 @@ fn test_basic_setup() { }) .check_launch(); } + +#[test] +fn test_setup_custom_precompiles() { + /// Unichain custom precompiles. + struct UniPrecompiles; + + impl UniPrecompiles { + /// Returns map of precompiles for Unichain. + fn precompiles(spec_id: OpSpecId) -> PrecompilesMap { + static INSTANCE: OnceLock = OnceLock::new(); + + PrecompilesMap::from_static(INSTANCE.get_or_init(|| { + let mut precompiles = OpPrecompiles::new_with_spec(spec_id).precompiles().clone(); + // Custom precompile. + let precompile = Precompile::new( + PrecompileId::custom("custom"), + address!("0x0000000000000000000000000000000000756e69"), + |_, _| PrecompileResult::Ok(PrecompileOutput::new(0, Bytes::new())), + ); + precompiles.extend([precompile]); + precompiles + })) + } + } + + /// Builds Unichain EVM configuration. + #[derive(Clone, Debug)] + struct UniEvmFactory; + + impl EvmFactory for UniEvmFactory { + type Evm>> = OpEvm; + type Context = OpContext; + type Tx = OpTransaction; + type Error = + EVMError; + type HaltReason = OpHaltReason; + type Spec = OpSpecId; + type Precompiles = PrecompilesMap; + + fn create_evm( + &self, + db: DB, + input: EvmEnv, + ) -> Self::Evm { + let mut op_evm = OpEvmFactory::default().create_evm(db, input); + *op_evm.components_mut().2 = UniPrecompiles::precompiles(op_evm.ctx().cfg().spec()); + + op_evm + } + + fn create_evm_with_inspector< + DB: Database, + I: Inspector, EthInterpreter>, + >( + &self, + db: DB, + input: EvmEnv, + inspector: I, + ) -> Self::Evm { + let mut op_evm = + OpEvmFactory::default().create_evm_with_inspector(db, input, inspector); + *op_evm.components_mut().2 = UniPrecompiles::precompiles(op_evm.ctx().cfg().spec()); + + op_evm + } + } + + /// Unichain executor builder. + struct UniExecutorBuilder; + + impl ExecutorBuilder for UniExecutorBuilder + where + Node: FullNodeTypes>, + { + type EVM = OpEvmConfig< + OpChainSpec, + ::Primitives, + OpRethReceiptBuilder, + UniEvmFactory, + >; + + async fn build_evm(self, ctx: &BuilderContext) -> eyre::Result { + let OpEvmConfig { executor_factory, block_assembler, _pd: _ } = + OpExecutorBuilder::default().build_evm(ctx).await?; + let uni_executor_factory = OpBlockExecutorFactory::new( + *executor_factory.receipt_builder(), + ctx.chain_spec(), + UniEvmFactory, + ); + let uni_evm_config = OpEvmConfig { + executor_factory: uni_executor_factory, + block_assembler, + _pd: PhantomData, + }; + Ok(uni_evm_config) + } + } + + NodeBuilder::new(NodeConfig::new(OP_SEPOLIA.clone())) + .with_database(create_test_rw_db()) + .with_types::() + .with_components( + OpNode::default() + .components() + // Custom EVM configuration + .executor(UniExecutorBuilder), + ) + .check_launch(); +} diff --git a/crates/optimism/payload/Cargo.toml b/crates/optimism/payload/Cargo.toml index 8d1875fe753..44206c99f71 100644 --- a/crates/optimism/payload/Cargo.toml +++ b/crates/optimism/payload/Cargo.toml @@ -27,6 +27,8 @@ reth-payload-primitives = { workspace = true, features = ["op"] } reth-basic-payload-builder.workspace = true reth-chain-state.workspace = true reth-payload-validator.workspace = true +reth-gas-station.workspace = true + # op-reth reth-optimism-evm.workspace = true @@ -50,4 +52,4 @@ derive_more.workspace = true tracing.workspace = true thiserror.workspace = true sha2.workspace = true -serde.workspace = true +serde.workspace = true \ No newline at end of file diff --git a/crates/optimism/payload/src/builder.rs b/crates/optimism/payload/src/builder.rs index d511b17392f..14a35a26deb 100644 --- a/crates/optimism/payload/src/builder.rs +++ b/crates/optimism/payload/src/builder.rs @@ -20,6 +20,7 @@ use reth_evm::{ ConfigureEvm, Database, Evm, }; use reth_execution_types::ExecutionOutcome; +use reth_gas_station::{validate_gasless_tx, GasStationConfig}; use reth_optimism_forks::OpHardforks; use reth_optimism_primitives::{transaction::OpTransaction, ADDRESS_L2_TO_L1_MESSAGE_PASSER}; use reth_optimism_txpool::{ @@ -41,7 +42,8 @@ use reth_storage_api::{errors::ProviderError, StateProvider, StateProviderFactor use reth_transaction_pool::{BestTransactionsAttributes, PoolTransaction, TransactionPool}; use revm::context::{Block, BlockEnv}; use std::{marker::PhantomData, sync::Arc}; -use tracing::{debug, trace, warn}; +use tracing::{info, debug, trace, warn}; +use reth_optimism_primitives::is_gasless; /// Optimism's payload builder #[derive(Debug)] @@ -354,12 +356,16 @@ impl OpBuilder<'_, Txs> { // 3. if mem pool transactions are requested we execute them if !ctx.attributes().no_tx_pool() { let best_txs = best(ctx.best_transaction_attributes(builder.evm_mut().block())); - if ctx.execute_best_transactions(&mut info, &mut builder, best_txs)?.is_some() { - return Ok(BuildOutcomeKind::Cancelled) + if ctx + .execute_best_transactions(&mut info, &mut builder, &state_provider, best_txs)? + .is_some() + { + return Ok(BuildOutcomeKind::Cancelled); } // check if the new payload is even more valuable - if !ctx.is_better_payload(info.total_fees) { + // if fees are equal but we included any mempool transactions (e.g. gasless), still proceed + if !ctx.is_better_payload(info.total_fees) && !info.included_any_mempool_tx { // can skip building the block return Ok(BuildOutcomeKind::Aborted { fees: info.total_fees }) } @@ -490,12 +496,19 @@ pub struct ExecutionInfo { pub cumulative_da_bytes_used: u64, /// Tracks fees from executed mempool transactions pub total_fees: U256, + /// True if at least one mempool transaction was executed in this payload attempt + pub included_any_mempool_tx: bool, } impl ExecutionInfo { /// Create a new instance with allocated slots. pub const fn new() -> Self { - Self { cumulative_gas_used: 0, cumulative_da_bytes_used: 0, total_fees: U256::ZERO } + Self { + cumulative_gas_used: 0, + cumulative_da_bytes_used: 0, + total_fees: U256::ZERO, + included_any_mempool_tx: false, + } } /// Returns true if the transaction would exceed the block limits: @@ -655,6 +668,7 @@ where &self, info: &mut ExecutionInfo, builder: &mut impl BlockBuilder, + state_provider: &impl StateProvider, mut best_txs: impl PayloadTransactions< Transaction: PoolTransaction> + OpPooledTx, >, @@ -690,17 +704,44 @@ where // We skip invalid cross chain txs, they would be removed on the next block update in // the maintenance job - if let Some(interop) = interop { - if !is_valid_interop(interop, self.config.attributes.timestamp()) { - best_txs.mark_invalid(tx.signer(), tx.nonce()); - continue - } + if let Some(interop) = interop && + !is_valid_interop(interop, self.config.attributes.timestamp()) + { + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue } // check if the job was cancelled, if so we can exit early if self.cancel.is_cancelled() { return Ok(Some(())) } + // validate the gasless transaction + if is_gasless(&*tx) { + // do we allow create transactions??? + let to = match tx.kind() { + alloy_primitives::TxKind::Call(to) => to, + alloy_primitives::TxKind::Create => { + info!("gasless transaction is a create transaction, skipping"); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + }; + let from = tx.signer(); + let gas_limit = tx.gas_limit(); + if let Err(_e) = validate_gasless_tx( + &GasStationConfig::default(), + state_provider, + to, + from, + gas_limit, + None, + ) { + info!("gasless transaction validation failed: {:?}", _e); + best_txs.mark_invalid(tx.signer(), tx.nonce()); + continue; + } + } + let gas_used = match builder.execute_transaction(tx.clone()) { Ok(gas_used) => gas_used, Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx { @@ -729,11 +770,16 @@ where info.cumulative_gas_used += gas_used; info.cumulative_da_bytes_used += tx_da_size; - // update add to total fees - let miner_fee = tx - .effective_tip_per_gas(base_fee) - .expect("fee is always valid; execution succeeded"); - info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + if !is_gasless(&*tx) { + // update add to total fees; gasless transactions pay no miner fee + let miner_fee = tx + .effective_tip_per_gas(base_fee) + .expect("fee is always valid; execution succeeded"); + info.total_fees += U256::from(miner_fee) * U256::from(gas_used); + } + + // mark that we executed at least one mempool transaction + info.included_any_mempool_tx = true; } Ok(None) diff --git a/crates/optimism/payload/src/payload.rs b/crates/optimism/payload/src/payload.rs index c84e9c70ec7..de1705faa8f 100644 --- a/crates/optimism/payload/src/payload.rs +++ b/crates/optimism/payload/src/payload.rs @@ -12,7 +12,7 @@ use alloy_rpc_types_engine::{ BlobsBundleV1, ExecutionPayloadEnvelopeV2, ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, PayloadId, }; -use op_alloy_consensus::{encode_holocene_extra_data, EIP1559ParamError}; +use op_alloy_consensus::{encode_holocene_extra_data, encode_jovian_extra_data, EIP1559ParamError}; use op_alloy_rpc_types_engine::{ OpExecutionPayloadEnvelopeV3, OpExecutionPayloadEnvelopeV4, OpExecutionPayloadV4, }; @@ -44,6 +44,8 @@ pub struct OpPayloadBuilderAttributes { pub gas_limit: Option, /// EIP-1559 parameters for the generated payload pub eip_1559_params: Option, + /// Min base fee for the generated payload (only available post-Jovian) + pub min_base_fee: Option, } impl Default for OpPayloadBuilderAttributes { @@ -54,12 +56,14 @@ impl Default for OpPayloadBuilderAttributes { gas_limit: Default::default(), eip_1559_params: Default::default(), transactions: Default::default(), + min_base_fee: Default::default(), } } } impl OpPayloadBuilderAttributes { - /// Extracts the `eip1559` parameters for the payload. + /// Extracts the extra data parameters post-Holocene hardfork. + /// In Holocene, those parameters are the EIP-1559 base fee parameters. pub fn get_holocene_extra_data( &self, default_base_fee_params: BaseFeeParams, @@ -68,6 +72,18 @@ impl OpPayloadBuilderAttributes { .map(|params| encode_holocene_extra_data(params, default_base_fee_params)) .ok_or(EIP1559ParamError::NoEIP1559Params)? } + + /// Extracts the extra data parameters post-Jovian hardfork. + /// Those parameters are the EIP-1559 parameters from Holocene and the minimum base fee. + pub fn get_jovian_extra_data( + &self, + default_base_fee_params: BaseFeeParams, + ) -> Result { + let min_base_fee = self.min_base_fee.ok_or(EIP1559ParamError::MinBaseFeeNotSet)?; + self.eip_1559_params + .map(|params| encode_jovian_extra_data(params, default_base_fee_params, min_base_fee)) + .ok_or(EIP1559ParamError::NoEIP1559Params)? + } } impl PayloadBuilderAttributes @@ -91,14 +107,7 @@ impl PayloadBuilderAtt .unwrap_or_default() .into_iter() .map(|data| { - let mut buf = data.as_ref(); - let tx = Decodable2718::decode_2718(&mut buf).map_err(alloy_rlp::Error::from)?; - - if !buf.is_empty() { - return Err(alloy_rlp::Error::UnexpectedLength); - } - - Ok(WithEncoded::new(data, tx)) + Decodable2718::decode_2718_exact(data.as_ref()).map(|tx| WithEncoded::new(data, tx)) }) .collect::>()?; @@ -118,6 +127,7 @@ impl PayloadBuilderAtt transactions, gas_limit: attributes.gas_limit, eip_1559_params: attributes.eip_1559_params, + min_base_fee: attributes.min_base_fee, }) } @@ -394,7 +404,13 @@ where parent: &SealedHeader, chain_spec: &ChainSpec, ) -> Result { - let extra_data = if chain_spec.is_holocene_active_at_timestamp(attributes.timestamp()) { + let extra_data = if chain_spec.is_jovian_active_at_timestamp(attributes.timestamp()) { + attributes + .get_jovian_extra_data( + chain_spec.base_fee_params_at_timestamp(attributes.timestamp()), + ) + .map_err(PayloadBuilderError::other)? + } else if chain_spec.is_holocene_active_at_timestamp(attributes.timestamp()) { attributes .get_holocene_extra_data( chain_spec.base_fee_params_at_timestamp(attributes.timestamp()), @@ -443,6 +459,7 @@ mod tests { no_tx_pool: None, gas_limit: Some(30000000), eip_1559_params: None, + min_base_fee: None, }; // Reth's `PayloadId` should match op-geth's `PayloadId`. This fails @@ -474,4 +491,50 @@ mod tests { let extra_data = attributes.get_holocene_extra_data(BaseFeeParams::new(80, 60)); assert_eq!(extra_data.unwrap(), Bytes::copy_from_slice(&[0, 0, 0, 0, 80, 0, 0, 0, 60])); } + + #[test] + fn test_get_extra_data_post_jovian() { + let attributes: OpPayloadBuilderAttributes = + OpPayloadBuilderAttributes { + eip_1559_params: Some(B64::from_str("0x0000000800000008").unwrap()), + min_base_fee: Some(10), + ..Default::default() + }; + let extra_data = attributes.get_jovian_extra_data(BaseFeeParams::new(80, 60)); + assert_eq!( + extra_data.unwrap(), + // Version byte is 1 for Jovian, then holocene payload followed by 8 bytes for the + // minimum base fee + Bytes::copy_from_slice(&[1, 0, 0, 0, 8, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 10]) + ); + } + + #[test] + fn test_get_extra_data_post_jovian_default() { + let attributes: OpPayloadBuilderAttributes = + OpPayloadBuilderAttributes { + eip_1559_params: Some(B64::ZERO), + min_base_fee: Some(10), + ..Default::default() + }; + let extra_data = attributes.get_jovian_extra_data(BaseFeeParams::new(80, 60)); + assert_eq!( + extra_data.unwrap(), + // Version byte is 1 for Jovian, then holocene payload followed by 8 bytes for the + // minimum base fee + Bytes::copy_from_slice(&[1, 0, 0, 0, 80, 0, 0, 0, 60, 0, 0, 0, 0, 0, 0, 0, 10]) + ); + } + + #[test] + fn test_get_extra_data_post_jovian_no_base_fee() { + let attributes: OpPayloadBuilderAttributes = + OpPayloadBuilderAttributes { + eip_1559_params: Some(B64::ZERO), + min_base_fee: None, + ..Default::default() + }; + let extra_data = attributes.get_jovian_extra_data(BaseFeeParams::new(80, 60)); + assert_eq!(extra_data.unwrap_err(), EIP1559ParamError::MinBaseFeeNotSet); + } } diff --git a/crates/optimism/primitives/src/lib.rs b/crates/optimism/primitives/src/lib.rs index 8a447ffc2fa..54598bfd5aa 100644 --- a/crates/optimism/primitives/src/lib.rs +++ b/crates/optimism/primitives/src/lib.rs @@ -19,6 +19,9 @@ pub use predeploys::ADDRESS_L2_TO_L1_MESSAGE_PASSER; pub mod transaction; pub use transaction::*; +pub mod utils; +pub use utils::*; + mod receipt; pub use receipt::{DepositReceipt, OpReceipt}; diff --git a/crates/optimism/primitives/src/transaction/signed.rs b/crates/optimism/primitives/src/transaction/signed.rs index 75276754687..820cc112710 100644 --- a/crates/optimism/primitives/src/transaction/signed.rs +++ b/crates/optimism/primitives/src/transaction/signed.rs @@ -4,7 +4,7 @@ use crate::transaction::OpTransaction; use alloc::vec::Vec; use alloy_consensus::{ - transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignerRecoverable}, + transaction::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, SignerRecoverable, TxHashRef}, Sealed, SignableTransaction, Signed, Transaction, TxEip1559, TxEip2930, TxEip7702, TxLegacy, Typed2718, }; @@ -142,11 +142,13 @@ impl SignerRecoverable for OpTransactionSigned { } } -impl SignedTransaction for OpTransactionSigned { +impl TxHashRef for OpTransactionSigned { fn tx_hash(&self) -> &TxHash { self.hash.get_or_init(|| self.recalculate_hash()) } +} +impl SignedTransaction for OpTransactionSigned { fn recalculate_hash(&self) -> B256 { keccak256(self.encoded_2718()) } diff --git a/crates/optimism/primitives/src/utils.rs b/crates/optimism/primitives/src/utils.rs new file mode 100644 index 00000000000..f27bdf72b89 --- /dev/null +++ b/crates/optimism/primitives/src/utils.rs @@ -0,0 +1,16 @@ +//! Optimism utility functions + +/// Returns true if the transaction is a zero-fee transaction. +/// +/// Rules: +/// - Legacy +/// - EIP-1559 +pub fn is_gasless(tx: &T) -> bool { + match tx.ty() { + 1 => tx.gas_price().unwrap_or(0) == 0, + 2 => { + tx.max_fee_per_gas() == 0 && tx.max_priority_fee_per_gas().unwrap_or_default() == 0 + } + _ => false, + } +} diff --git a/crates/optimism/reth/Cargo.toml b/crates/optimism/reth/Cargo.toml index ae673efecf1..384eca45b8c 100644 --- a/crates/optimism/reth/Cargo.toml +++ b/crates/optimism/reth/Cargo.toml @@ -108,12 +108,14 @@ node = [ "provider", "consensus", "evm", + "network", "node-api", "dep:reth-optimism-node", "dep:reth-node-builder", "dep:reth-engine-local", "rpc", "trie-db", + "pool", ] rpc = [ "tasks", diff --git a/crates/optimism/reth/src/lib.rs b/crates/optimism/reth/src/lib.rs index dd5fb5ba6c8..10cd2bd01f9 100644 --- a/crates/optimism/reth/src/lib.rs +++ b/crates/optimism/reth/src/lib.rs @@ -24,7 +24,11 @@ pub mod primitives { #[cfg(feature = "cli")] pub mod cli { #[doc(inline)] - pub use reth_cli_util::*; + pub use reth_cli_util::{ + allocator, get_secret_key, hash_or_num_value_parser, load_secret_key, + parse_duration_from_secs, parse_duration_from_secs_or_ms, parse_ether_value, + parse_socket_address, sigsegv_handler, + }; #[doc(inline)] pub use reth_optimism_cli::*; } diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index 97f598628ef..acbc491f648 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -26,10 +26,13 @@ reth-rpc-api.workspace = true reth-node-api.workspace = true reth-node-builder.workspace = true reth-chainspec.workspace = true +reth-chain-state.workspace = true reth-rpc-engine-api.workspace = true +reth-rpc-convert.workspace = true # op-reth reth-optimism-evm.workspace = true +reth-optimism-flashblocks.workspace = true reth-optimism-payload-builder.workspace = true reth-optimism-txpool.workspace = true # TODO remove node-builder import @@ -57,6 +60,8 @@ op-revm.workspace = true # async tokio.workspace = true +futures.workspace = true +tokio-stream.workspace = true reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } async-trait.workspace = true tower.workspace = true diff --git a/crates/optimism/rpc/src/eth/call.rs b/crates/optimism/rpc/src/eth/call.rs index e929ef7ca75..b7ce75c51b2 100644 --- a/crates/optimism/rpc/src/eth/call.rs +++ b/crates/optimism/rpc/src/eth/call.rs @@ -1,5 +1,5 @@ use crate::{eth::RpcNodeCore, OpEthApi, OpEthApiError}; -use reth_evm::TxEnvFor; +use reth_evm::{SpecFor, TxEnvFor}; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall}, FromEvmError, RpcConvert, @@ -9,7 +9,12 @@ impl EthCall for OpEthApi where N: RpcNodeCore, OpEthApiError: FromEvmError, - Rpc: RpcConvert>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = OpEthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, { } @@ -17,7 +22,12 @@ impl EstimateCall for OpEthApi where N: RpcNodeCore, OpEthApiError: FromEvmError, - Rpc: RpcConvert>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = OpEthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, { } @@ -25,7 +35,12 @@ impl Call for OpEthApi where N: RpcNodeCore, OpEthApiError: FromEvmError, - Rpc: RpcConvert>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = OpEthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, { #[inline] fn call_gas_limit(&self) -> u64 { diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index fead8b490d1..8282cd99b61 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -12,29 +12,39 @@ use crate::{ eth::{receipt::OpReceiptConverter, transaction::OpTxInfoMapper}, OpEthApiError, SequencerClient, }; +use alloy_consensus::BlockHeader; use alloy_primitives::U256; use eyre::WrapErr; use op_alloy_network::Optimism; pub use receipt::{OpReceiptBuilder, OpReceiptFieldsBuilder}; +use reqwest::Url; use reth_evm::ConfigureEvm; use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy}; use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx}; +use reth_optimism_flashblocks::{ + ExecutionPayloadBaseV1, FlashBlockCompleteSequenceRx, FlashBlockService, PendingBlockRx, + WsFlashBlockStream, +}; use reth_rpc::eth::{core::EthApiInner, DevSigner}; use reth_rpc_eth_api::{ helpers::{ pending_block::BuildPendingEnv, spec::SignersForApi, AddDevSigners, EthApiSpec, EthFees, - EthState, LoadFee, LoadState, SpawnBlocking, Trace, + EthState, LoadFee, LoadPendingBlock, LoadState, SpawnBlocking, Trace, }, EthApiTypes, FromEvmError, FullEthApiServer, RpcConvert, RpcConverter, RpcNodeCore, RpcNodeCoreExt, RpcTypes, SignableTxRequest, }; -use reth_rpc_eth_types::{EthStateCache, FeeHistoryCache, GasPriceOracle}; +use reth_rpc_eth_types::{ + EthStateCache, FeeHistoryCache, GasPriceOracle, PendingBlock, PendingBlockEnvOrigin, +}; use reth_storage_api::{ProviderHeader, ProviderTx}; use reth_tasks::{ pool::{BlockingTaskGuard, BlockingTaskPool}, TaskSpawner, }; -use std::{fmt, fmt::Formatter, marker::PhantomData, sync::Arc}; +use std::{fmt, fmt::Formatter, marker::PhantomData, sync::Arc, time::Instant}; +use tokio::sync::watch; +use tracing::info; /// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API. pub type EthApiNodeBackend = EthApiInner; @@ -66,9 +76,16 @@ impl OpEthApi { eth_api: EthApiNodeBackend, sequencer_client: Option, min_suggested_priority_fee: U256, + pending_block_rx: Option>, + flashblock_rx: Option, ) -> Self { - let inner = - Arc::new(OpEthApiInner { eth_api, sequencer_client, min_suggested_priority_fee }); + let inner = Arc::new(OpEthApiInner { + eth_api, + sequencer_client, + min_suggested_priority_fee, + pending_block_rx, + flashblock_rx, + }); Self { inner } } @@ -81,10 +98,51 @@ impl OpEthApi { self.inner.sequencer_client() } + /// Returns a cloned pending block receiver, if any. + pub fn pending_block_rx(&self) -> Option> { + self.inner.pending_block_rx.clone() + } + + /// Returns a flashblock receiver, if any, by resubscribing to it. + pub fn flashblock_rx(&self) -> Option { + self.inner.flashblock_rx.as_ref().map(|rx| rx.resubscribe()) + } + /// Build a [`OpEthApi`] using [`OpEthApiBuilder`]. pub const fn builder() -> OpEthApiBuilder { OpEthApiBuilder::new() } + + /// Returns a [`PendingBlock`] that is built out of flashblocks. + /// + /// If flashblocks receiver is not set, then it always returns `None`. + pub fn pending_flashblock(&self) -> eyre::Result>> + where + OpEthApiError: FromEvmError, + Rpc: RpcConvert, + { + let pending = self.pending_block_env_and_cfg()?; + let parent = match pending.origin { + PendingBlockEnvOrigin::ActualPending(..) => return Ok(None), + PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent, + }; + + let Some(rx) = self.inner.pending_block_rx.as_ref() else { return Ok(None) }; + let pending_block = rx.borrow(); + let Some(pending_block) = pending_block.as_ref() else { return Ok(None) }; + + let now = Instant::now(); + + // Is the pending block not expired and latest is its parent? + if pending.evm_env.block_env.number == U256::from(pending_block.block().number()) && + parent.hash() == pending_block.block().parent_hash() && + now <= pending_block.expires_at + { + return Ok(Some(pending_block.clone())); + } + + Ok(None) + } } impl EthApiTypes for OpEthApi @@ -210,6 +268,7 @@ impl LoadState for OpEthApi where N: RpcNodeCore, Rpc: RpcConvert, + Self: LoadPendingBlock, { } @@ -217,6 +276,7 @@ impl EthState for OpEthApi where N: RpcNodeCore, Rpc: RpcConvert, + Self: LoadPendingBlock, { #[inline] fn max_proof_window(&self) -> u64 { @@ -269,6 +329,14 @@ pub struct OpEthApiInner { /// /// See also min_suggested_priority_fee: U256, + /// Pending block receiver. + /// + /// If set, then it provides current pending block based on received Flashblocks. + pending_block_rx: Option>, + /// Flashblocks receiver. + /// + /// If set, then it provides sequences of flashblock built. + flashblock_rx: Option, } impl fmt::Debug for OpEthApiInner { @@ -308,6 +376,10 @@ pub struct OpEthApiBuilder { sequencer_headers: Vec, /// Minimum suggested priority fee (tip) min_suggested_priority_fee: u64, + /// A URL pointing to a secure websocket connection (wss) that streams out [flashblocks]. + /// + /// [flashblocks]: reth_optimism_flashblocks + flashblocks_url: Option, /// Marker for network types. _nt: PhantomData, } @@ -318,6 +390,7 @@ impl Default for OpEthApiBuilder { sequencer_url: None, sequencer_headers: Vec::new(), min_suggested_priority_fee: 1_000_000, + flashblocks_url: None, _nt: PhantomData, } } @@ -330,6 +403,7 @@ impl OpEthApiBuilder { sequencer_url: None, sequencer_headers: Vec::new(), min_suggested_priority_fee: 1_000_000, + flashblocks_url: None, _nt: PhantomData, } } @@ -346,16 +420,28 @@ impl OpEthApiBuilder { self } - /// With minimum suggested priority fee (tip) + /// With minimum suggested priority fee (tip). pub const fn with_min_suggested_priority_fee(mut self, min: u64) -> Self { self.min_suggested_priority_fee = min; self } + + /// With a subscription to flashblocks secure websocket connection. + pub fn with_flashblocks(mut self, flashblocks_url: Option) -> Self { + self.flashblocks_url = flashblocks_url; + self + } } impl EthApiBuilder for OpEthApiBuilder where - N: FullNodeComponents>>>, + N: FullNodeComponents< + Evm: ConfigureEvm< + NextBlockEnvCtx: BuildPendingEnv> + + From + + Unpin, + >, + >, NetworkT: RpcTypes, OpRpcConvert: RpcConvert, OpEthApi>: @@ -364,13 +450,17 @@ where type EthApi = OpEthApi>; async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result { - let Self { sequencer_url, sequencer_headers, min_suggested_priority_fee, .. } = self; + let Self { + sequencer_url, + sequencer_headers, + min_suggested_priority_fee, + flashblocks_url, + .. + } = self; let rpc_converter = RpcConverter::new(OpReceiptConverter::new(ctx.components.provider().clone())) .with_mapper(OpTxInfoMapper::new(ctx.components.provider().clone())); - let eth_api = ctx.eth_api_builder().with_rpc_converter(rpc_converter).build_inner(); - let sequencer_client = if let Some(url) = sequencer_url { Some( SequencerClient::new_with_headers(&url, sequencer_headers) @@ -381,6 +471,33 @@ where None }; - Ok(OpEthApi::new(eth_api, sequencer_client, U256::from(min_suggested_priority_fee))) + let rxs = if let Some(ws_url) = flashblocks_url { + info!(target: "reth:cli", %ws_url, "Launching flashblocks service"); + let (tx, pending_block_rx) = watch::channel(None); + let stream = WsFlashBlockStream::new(ws_url); + let service = FlashBlockService::new( + stream, + ctx.components.evm_config().clone(), + ctx.components.provider().clone(), + ctx.components.task_executor().clone(), + ); + let flashblock_rx = service.subscribe_block_sequence(); + ctx.components.task_executor().spawn(Box::pin(service.run(tx))); + Some((pending_block_rx, flashblock_rx)) + } else { + None + }; + + let (pending_block_rx, flashblock_rx) = rxs.unzip(); + + let eth_api = ctx.eth_api_builder().with_rpc_converter(rpc_converter).build_inner(); + + Ok(OpEthApi::new( + eth_api, + sequencer_client, + U256::from(min_suggested_priority_fee), + pending_block_rx, + flashblock_rx, + )) } } diff --git a/crates/optimism/rpc/src/eth/pending_block.rs b/crates/optimism/rpc/src/eth/pending_block.rs index e14f1c332ac..8857b89b021 100644 --- a/crates/optimism/rpc/src/eth/pending_block.rs +++ b/crates/optimism/rpc/src/eth/pending_block.rs @@ -1,18 +1,21 @@ //! Loads OP pending block for a RPC response. -use std::sync::Arc; - use crate::{OpEthApi, OpEthApiError}; +use alloy_consensus::BlockHeader; use alloy_eips::BlockNumberOrTag; -use reth_primitives_traits::RecoveredBlock; +use reth_chain_state::BlockState; use reth_rpc_eth_api::{ - helpers::{pending_block::PendingEnvBuilder, LoadPendingBlock}, + helpers::{pending_block::PendingEnvBuilder, LoadPendingBlock, SpawnBlocking}, FromEvmError, RpcConvert, RpcNodeCore, }; -use reth_rpc_eth_types::{builder::config::PendingBlockKind, EthApiError, PendingBlock}; +use reth_rpc_eth_types::{ + block::BlockAndReceipts, builder::config::PendingBlockKind, error::FromEthApiError, + EthApiError, PendingBlock, +}; use reth_storage_api::{ - BlockReader, BlockReaderIdExt, ProviderBlock, ProviderReceipt, ReceiptProvider, + BlockReader, BlockReaderIdExt, ReceiptProvider, StateProviderBox, StateProviderFactory, }; +use std::sync::Arc; impl LoadPendingBlock for OpEthApi where @@ -38,13 +41,11 @@ where /// Returns the locally built pending block async fn local_pending_block( &self, - ) -> Result< - Option<( - Arc>>, - Arc>>, - )>, - Self::Error, - > { + ) -> Result>, Self::Error> { + if let Ok(Some(pending)) = self.pending_flashblock() { + return Ok(Some(pending.into_block_and_receipts())); + } + // See: let latest = self .provider() @@ -61,6 +62,25 @@ where .receipts_by_block(block_id)? .ok_or(EthApiError::ReceiptsNotFound(block_id.into()))?; - Ok(Some((Arc::new(block), Arc::new(receipts)))) + Ok(Some(BlockAndReceipts { block: Arc::new(block), receipts: Arc::new(receipts) })) + } + + /// Returns a [`StateProviderBox`] on a mem-pool built pending block overlaying latest. + async fn local_pending_state(&self) -> Result, Self::Error> + where + Self: SpawnBlocking, + { + let Ok(Some(pending_block)) = self.pending_flashblock() else { + return Ok(None); + }; + + let latest_historical = self + .provider() + .history_by_block_hash(pending_block.block().parent_hash()) + .map_err(Self::Error::from_eth_err)?; + + let state = BlockState::from(pending_block); + + Ok(Some(Box::new(state.state_provider(latest_historical)) as StateProviderBox)) } } diff --git a/crates/optimism/rpc/src/eth/receipt.rs b/crates/optimism/rpc/src/eth/receipt.rs index edf16900f04..23fed78d89e 100644 --- a/crates/optimism/rpc/src/eth/receipt.rs +++ b/crates/optimism/rpc/src/eth/receipt.rs @@ -1,18 +1,18 @@ //! Loads and formats OP receipt RPC response. use crate::{eth::RpcNodeCore, OpEthApi, OpEthApiError}; +use alloy_consensus::{BlockHeader, Receipt, TxReceipt}; use alloy_eips::eip2718::Encodable2718; use alloy_rpc_types_eth::{Log, TransactionReceipt}; -use op_alloy_consensus::{ - OpDepositReceipt, OpDepositReceiptWithBloom, OpReceiptEnvelope, OpTransaction, -}; +use op_alloy_consensus::{OpReceiptEnvelope, OpTransaction}; use op_alloy_rpc_types::{L1BlockInfo, OpTransactionReceipt, OpTransactionReceiptFields}; use reth_chainspec::ChainSpecProvider; use reth_node_api::NodePrimitives; use reth_optimism_evm::RethL1BlockInfo; use reth_optimism_forks::OpHardforks; -use reth_optimism_primitives::OpReceipt; +use reth_optimism_primitives::{is_gasless, OpReceipt}; use reth_primitives_traits::Block; +use reth_primitives_traits::SealedBlock; use reth_rpc_eth_api::{ helpers::LoadReceipt, transaction::{ConvertReceiptInput, ReceiptConverter}, @@ -45,7 +45,8 @@ impl OpReceiptConverter { impl ReceiptConverter for OpReceiptConverter where N: NodePrimitives, - Provider: BlockReader + ChainSpecProvider + Debug + 'static, + Provider: + BlockReader + ChainSpecProvider + Debug + 'static, { type RpcReceipt = OpTransactionReceipt; type Error = OpEthApiError; @@ -63,12 +64,20 @@ where .block_by_number(block_number)? .ok_or(EthApiError::HeaderNotFound(block_number.into()))?; + self.convert_receipts_with_block(inputs, &SealedBlock::new_unhashed(block)) + } + + fn convert_receipts_with_block( + &self, + inputs: Vec>, + block: &SealedBlock, + ) -> Result, Self::Error> { let mut l1_block_info = match reth_optimism_evm::extract_l1_info(block.body()) { Ok(l1_block_info) => l1_block_info, Err(err) => { - // If it is the genesis block (i.e block number is 0), there is no L1 info, so + // If it is the genesis block (i.e. block number is 0), there is no L1 info, so // we return an empty l1_block_info. - if block_number == 0 { + if block.header().number() == 0 { return Ok(vec![]); } return Err(err.into()); @@ -270,28 +279,48 @@ impl OpReceiptBuilder { let timestamp = input.meta.timestamp; let block_number = input.meta.block_number; let tx_signed = *input.tx.inner(); - let core_receipt = - build_receipt(&input, None, |receipt_with_bloom| match input.receipt.as_ref() { - OpReceipt::Legacy(_) => OpReceiptEnvelope::Legacy(receipt_with_bloom), - OpReceipt::Eip2930(_) => OpReceiptEnvelope::Eip2930(receipt_with_bloom), - OpReceipt::Eip1559(_) => OpReceiptEnvelope::Eip1559(receipt_with_bloom), - OpReceipt::Eip7702(_) => OpReceiptEnvelope::Eip7702(receipt_with_bloom), + let core_receipt = build_receipt(input, None, |receipt, next_log_index, meta| { + let map_logs = move |receipt: alloy_consensus::Receipt| { + let Receipt { status, cumulative_gas_used, logs } = receipt; + let logs = Log::collect_for_receipt(next_log_index, meta, logs); + Receipt { status, cumulative_gas_used, logs } + }; + match receipt { + OpReceipt::Legacy(receipt) => { + OpReceiptEnvelope::Legacy(map_logs(receipt).into_with_bloom()) + } + OpReceipt::Eip2930(receipt) => { + OpReceiptEnvelope::Eip2930(map_logs(receipt).into_with_bloom()) + } + OpReceipt::Eip1559(receipt) => { + OpReceiptEnvelope::Eip1559(map_logs(receipt).into_with_bloom()) + } + OpReceipt::Eip7702(receipt) => { + OpReceiptEnvelope::Eip7702(map_logs(receipt).into_with_bloom()) + } OpReceipt::Deposit(receipt) => { - OpReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { - receipt: OpDepositReceipt { - inner: receipt_with_bloom.receipt, - deposit_nonce: receipt.deposit_nonce, - deposit_receipt_version: receipt.deposit_receipt_version, - }, - logs_bloom: receipt_with_bloom.logs_bloom, - }) + OpReceiptEnvelope::Deposit(receipt.map_inner(map_logs).into_with_bloom()) } - }); + } + }); - let op_receipt_fields = OpReceiptFieldsBuilder::new(timestamp, block_number) + let mut op_receipt_fields = OpReceiptFieldsBuilder::new(timestamp, block_number) .l1_block_info(chain_spec, tx_signed, l1_block_info)? .build(); + if is_gasless(tx_signed) { + op_receipt_fields.l1_block_info.l1_base_fee_scalar = Some(0); + op_receipt_fields.l1_block_info.l1_blob_base_fee_scalar = Some(0); + op_receipt_fields.l1_block_info.operator_fee_scalar = Some(0); + op_receipt_fields.l1_block_info.operator_fee_constant = Some(0); + op_receipt_fields.l1_block_info.l1_gas_price = Some(0); + op_receipt_fields.l1_block_info.l1_gas_used = Some(0); + op_receipt_fields.l1_block_info.l1_fee = Some(0); + op_receipt_fields.l1_block_info.l1_fee_scalar = None; + op_receipt_fields.l1_block_info.l1_blob_base_fee = Some(0); + op_receipt_fields.l1_block_info.l1_blob_base_fee_scalar = Some(0); + } + Ok(Self { core_receipt, op_receipt_fields }) } diff --git a/crates/optimism/rpc/src/eth/transaction.rs b/crates/optimism/rpc/src/eth/transaction.rs index 8334759b81f..fb98569db10 100644 --- a/crates/optimism/rpc/src/eth/transaction.rs +++ b/crates/optimism/rpc/src/eth/transaction.rs @@ -1,31 +1,49 @@ //! Loads and formats OP transaction RPC response. use crate::{OpEthApi, OpEthApiError, SequencerClient}; +use alloy_consensus::TxReceipt as _; use alloy_primitives::{Bytes, B256}; use alloy_rpc_types_eth::TransactionInfo; +use futures::StreamExt; use op_alloy_consensus::{transaction::OpTransactionInfo, OpTransaction}; +use reth_chain_state::CanonStateSubscriptions; use reth_optimism_primitives::DepositReceipt; -use reth_primitives_traits::SignedTransaction; +use reth_primitives_traits::{BlockBody, SignedTransaction, SignerRecoverable}; +use reth_rpc_convert::transaction::ConvertReceiptInput; use reth_rpc_eth_api::{ - helpers::{spec::SignersForRpc, EthTransactions, LoadTransaction}, - try_into_op_tx_info, FromEthApiError, RpcConvert, RpcNodeCore, TxInfoMapper, + helpers::{ + receipt::calculate_gas_used_and_next_log_index, spec::SignersForRpc, EthTransactions, + LoadReceipt, LoadTransaction, + }, + try_into_op_tx_info, EthApiTypes as _, FromEthApiError, FromEvmError, RpcConvert, RpcNodeCore, + RpcReceipt, TxInfoMapper, }; -use reth_rpc_eth_types::utils::recover_raw_transaction; +use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError}; use reth_storage_api::{errors::ProviderError, ReceiptProvider}; use reth_transaction_pool::{ AddedTransactionOutcome, PoolTransaction, TransactionOrigin, TransactionPool, }; -use std::fmt::{Debug, Formatter}; +use std::{ + fmt::{Debug, Formatter}, + future::Future, + time::Duration, +}; +use tokio_stream::wrappers::WatchStream; impl EthTransactions for OpEthApi where N: RpcNodeCore, + OpEthApiError: FromEvmError, Rpc: RpcConvert, { fn signers(&self) -> &SignersForRpc { self.inner.eth_api.signers() } + fn send_raw_transaction_sync_timeout(&self) -> Duration { + self.inner.eth_api.send_raw_transaction_sync_timeout() + } + /// Decodes and recovers the transaction and submits it to the pool. /// /// Returns the hash of the transaction. @@ -62,11 +80,142 @@ where Ok(hash) } + + /// Decodes and recovers the transaction and submits it to the pool. + /// + /// And awaits the receipt, checking both canonical blocks and flashblocks for faster + /// confirmation. + fn send_raw_transaction_sync( + &self, + tx: Bytes, + ) -> impl Future, Self::Error>> + Send + where + Self: LoadReceipt + 'static, + { + let this = self.clone(); + let timeout_duration = self.send_raw_transaction_sync_timeout(); + async move { + let hash = EthTransactions::send_raw_transaction(&this, tx).await?; + let mut canonical_stream = this.provider().canonical_state_stream(); + let flashblock_rx = this.pending_block_rx(); + let mut flashblock_stream = flashblock_rx.map(WatchStream::new); + + tokio::time::timeout(timeout_duration, async { + loop { + tokio::select! { + // Listen for regular canonical block updates for inclusion + canonical_notification = canonical_stream.next() => { + if let Some(notification) = canonical_notification { + let chain = notification.committed(); + for block in chain.blocks_iter() { + if block.body().contains_transaction(&hash) + && let Some(receipt) = this.transaction_receipt(hash).await? { + return Ok(receipt); + } + } + } else { + // Canonical stream ended + break; + } + } + // check if the tx was preconfirmed in a new flashblock + _flashblock_update = async { + if let Some(ref mut stream) = flashblock_stream { + stream.next().await + } else { + futures::future::pending().await + } + } => { + // Check flashblocks for faster confirmation (Optimism-specific) + if let Ok(Some(pending_block)) = this.pending_flashblock() { + let block_and_receipts = pending_block.into_block_and_receipts(); + if block_and_receipts.block.body().contains_transaction(&hash) + && let Some(receipt) = this.transaction_receipt(hash).await? { + return Ok(receipt); + } + } + } + } + } + Err(Self::Error::from_eth_err(EthApiError::TransactionConfirmationTimeout { + hash, + duration: timeout_duration, + })) + }) + .await + .unwrap_or_else(|_elapsed| { + Err(Self::Error::from_eth_err(EthApiError::TransactionConfirmationTimeout { + hash, + duration: timeout_duration, + })) + }) + } + } + + /// Returns the transaction receipt for the given hash. + /// + /// With flashblocks, we should also lookup the pending block for the transaction + /// because this is considered confirmed/mined. + fn transaction_receipt( + &self, + hash: B256, + ) -> impl Future>, Self::Error>> + Send + { + let this = self.clone(); + async move { + // first attempt to fetch the mined transaction receipt data + let tx_receipt = this.load_transaction_and_receipt(hash).await?; + + if tx_receipt.is_none() { + // if flashblocks are supported, attempt to find id from the pending block + if let Ok(Some(pending_block)) = this.pending_flashblock() { + let block_and_receipts = pending_block.into_block_and_receipts(); + if let Some((tx, receipt)) = + block_and_receipts.find_transaction_and_receipt_by_hash(hash) + { + // Build tx receipt from pending block and receipts directly inline. + // This avoids canonical cache lookup that would be done by the + // `build_transaction_receipt` which would result in a block not found + // issue. See: https://github.com/paradigmxyz/reth/issues/18529 + let meta = tx.meta(); + let all_receipts = &block_and_receipts.receipts; + + let (gas_used, next_log_index) = + calculate_gas_used_and_next_log_index(meta.index, all_receipts); + + return Ok(Some( + this.tx_resp_builder() + .convert_receipts_with_block( + vec![ConvertReceiptInput { + tx: tx + .tx() + .clone() + .try_into_recovered_unchecked() + .map_err(Self::Error::from_eth_err)? + .as_recovered_ref(), + gas_used: receipt.cumulative_gas_used() - gas_used, + receipt: receipt.clone(), + next_log_index, + meta, + }], + block_and_receipts.sealed_block(), + )? + .pop() + .unwrap(), + )) + } + } + } + let Some((tx, meta, receipt)) = tx_receipt else { return Ok(None) }; + self.build_transaction_receipt(tx, meta, receipt).await.map(Some) + } + } } impl LoadTransaction for OpEthApi where N: RpcNodeCore, + OpEthApiError: FromEvmError, Rpc: RpcConvert, { } diff --git a/crates/optimism/rpc/src/historical.rs b/crates/optimism/rpc/src/historical.rs index f5d5e71c0dd..90357afa777 100644 --- a/crates/optimism/rpc/src/historical.rs +++ b/crates/optimism/rpc/src/historical.rs @@ -3,14 +3,14 @@ use crate::sequencer::Error; use alloy_eips::BlockId; use alloy_json_rpc::{RpcRecv, RpcSend}; -use alloy_primitives::BlockNumber; +use alloy_primitives::{BlockNumber, B256}; use alloy_rpc_client::RpcClient; use jsonrpsee_core::{ middleware::{Batch, Notification, RpcServiceT}, server::MethodResponse, }; use jsonrpsee_types::{Params, Request}; -use reth_storage_api::BlockReaderIdExt; +use reth_storage_api::{BlockReaderIdExt, TransactionsProvider}; use std::{future::Future, sync::Arc}; use tracing::{debug, warn}; @@ -124,7 +124,7 @@ impl RpcServiceT for HistoricalRpcService where S: RpcServiceT + Send + Sync + Clone + 'static, - P: BlockReaderIdExt + Send + Sync + Clone + 'static, + P: BlockReaderIdExt + TransactionsProvider + Send + Sync + Clone + 'static, { type MethodResponse = S::MethodResponse; type NotificationResponse = S::NotificationResponse; @@ -135,64 +135,12 @@ where let historical = self.historical.clone(); Box::pin(async move { - let maybe_block_id = match req.method_name() { - "eth_getBlockByNumber" | - "eth_getBlockByHash" | - "debug_traceBlockByNumber" | - "debug_traceBlockByHash" => parse_block_id_from_params(&req.params(), 0), - "eth_getBalance" | - "eth_getCode" | - "eth_getTransactionCount" | - "eth_call" | - "eth_estimateGas" | - "eth_createAccessList" | - "debug_traceCall" => parse_block_id_from_params(&req.params(), 1), - "eth_getStorageAt" | "eth_getProof" => parse_block_id_from_params(&req.params(), 2), - "debug_traceTransaction" => { - // debug_traceTransaction takes a transaction hash as its first parameter, - // not a BlockId. We assume the op-reth instance is configured with minimal - // bootstrap without the bodies so we can't check if this tx is pre bedrock - None - } - _ => None, - }; - - // if we've extracted a block ID, check if it's pre-Bedrock - if let Some(block_id) = maybe_block_id { - let is_pre_bedrock = match historical.provider.block_number_for_id(block_id) { - Ok(Some(num)) => num < historical.bedrock_block, - Ok(None) if block_id.is_hash() => { - // if we couldn't find the block number for the hash then we assume it is - // pre-Bedrock - true - } - _ => { - // If we can't convert blockid to a number, assume it's post-Bedrock - debug!(target: "rpc::historical", ?block_id, "hash unknown; not forwarding"); - false - } - }; - - // if the block is pre-Bedrock, forward the request to the historical client - if is_pre_bedrock { - debug!(target: "rpc::historical", method = %req.method_name(), ?block_id, params=?req.params(), "forwarding pre-Bedrock request"); - - let params = req.params(); - let params = params.as_str().unwrap_or("[]"); - if let Ok(params) = serde_json::from_str::(params) { - if let Ok(raw) = historical - .client - .request::<_, serde_json::Value>(req.method_name(), params) - .await - { - let payload = jsonrpsee_types::ResponsePayload::success(raw).into(); - return MethodResponse::response(req.id, payload, usize::MAX); - } - } - } + // Check if request should be forwarded to historical endpoint + if let Some(response) = historical.maybe_forward_request(&req).await { + return response } - // handle the request with the inner service + // Handle the request with the inner service inner_service.call(req).await }) } @@ -219,6 +167,147 @@ struct HistoricalRpcInner

{ bedrock_block: BlockNumber, } +impl

HistoricalRpcInner

+where + P: BlockReaderIdExt + TransactionsProvider + Send + Sync + Clone, +{ + /// Checks if a request should be forwarded to the historical endpoint and returns + /// the response if it was forwarded. + async fn maybe_forward_request(&self, req: &Request<'_>) -> Option { + let should_forward = match req.method_name() { + "debug_traceTransaction" => self.should_forward_transaction(req), + method => self.should_forward_block_request(method, req), + }; + + if should_forward { + return self.forward_to_historical(req).await + } + + None + } + + /// Determines if a transaction request should be forwarded + fn should_forward_transaction(&self, req: &Request<'_>) -> bool { + parse_transaction_hash_from_params(&req.params()) + .ok() + .map(|tx_hash| { + // Check if we can find the transaction locally and get its metadata + match self.provider.transaction_by_hash_with_meta(tx_hash) { + Ok(Some((_, meta))) => { + // Transaction found - check if it's pre-bedrock based on block number + let is_pre_bedrock = meta.block_number < self.bedrock_block; + if is_pre_bedrock { + debug!( + target: "rpc::historical", + ?tx_hash, + block_num = meta.block_number, + bedrock = self.bedrock_block, + "transaction found in pre-bedrock block, forwarding to historical endpoint" + ); + } + is_pre_bedrock + } + _ => { + // Transaction not found locally, optimistically forward to historical endpoint + debug!( + target: "rpc::historical", + ?tx_hash, + "transaction not found locally, forwarding to historical endpoint" + ); + true + } + } + }) + .unwrap_or(false) + } + + /// Determines if a block-based request should be forwarded + fn should_forward_block_request(&self, method: &str, req: &Request<'_>) -> bool { + let maybe_block_id = extract_block_id_for_method(method, &req.params()); + + maybe_block_id.map(|block_id| self.is_pre_bedrock(block_id)).unwrap_or(false) + } + + /// Checks if a block ID refers to a pre-bedrock block + fn is_pre_bedrock(&self, block_id: BlockId) -> bool { + match self.provider.block_number_for_id(block_id) { + Ok(Some(num)) => { + debug!( + target: "rpc::historical", + ?block_id, + block_num=num, + bedrock=self.bedrock_block, + "found block number" + ); + num < self.bedrock_block + } + Ok(None) if block_id.is_hash() => { + debug!( + target: "rpc::historical", + ?block_id, + "block hash not found locally, assuming pre-bedrock" + ); + true + } + _ => { + debug!( + target: "rpc::historical", + ?block_id, + "could not determine block number; not forwarding" + ); + false + } + } + } + + /// Forwards a request to the historical endpoint + async fn forward_to_historical(&self, req: &Request<'_>) -> Option { + debug!( + target: "rpc::historical", + method = %req.method_name(), + params=?req.params(), + "forwarding request to historical endpoint" + ); + + let params = req.params(); + let params_str = params.as_str().unwrap_or("[]"); + + let params = serde_json::from_str::(params_str).ok()?; + + let raw = + self.client.request::<_, serde_json::Value>(req.method_name(), params).await.ok()?; + + let payload = jsonrpsee_types::ResponsePayload::success(raw).into(); + Some(MethodResponse::response(req.id.clone(), payload, usize::MAX)) + } +} + +/// Error type for parameter parsing +#[derive(Debug)] +enum ParseError { + InvalidFormat, + MissingParameter, +} + +/// Extracts the block ID from request parameters based on the method name +fn extract_block_id_for_method(method: &str, params: &Params<'_>) -> Option { + match method { + "eth_getBlockByNumber" | + "eth_getBlockByHash" | + "debug_traceBlockByNumber" | + "debug_traceBlockByHash" => parse_block_id_from_params(params, 0), + "eth_getBalance" | + "eth_getCode" | + "eth_getTransactionCount" | + "eth_call" | + "eth_estimateGas" | + "eth_createAccessList" | + "debug_traceCall" => parse_block_id_from_params(params, 1), + "eth_getStorageAt" | "eth_getProof" => parse_block_id_from_params(params, 2), + _ => None, + } +} + /// Parses a `BlockId` from the given parameters at the specified position. fn parse_block_id_from_params(params: &Params<'_>, position: usize) -> Option { let values: Vec = params.parse().ok()?; @@ -226,6 +315,13 @@ fn parse_block_id_from_params(params: &Params<'_>, position: usize) -> Option(val).ok() } +/// Parses a transaction hash from the first parameter. +fn parse_transaction_hash_from_params(params: &Params<'_>) -> Result { + let values: Vec = params.parse().map_err(|_| ParseError::InvalidFormat)?; + let val = values.into_iter().next().ok_or(ParseError::MissingParameter)?; + serde_json::from_value::(val).map_err(|_| ParseError::InvalidFormat) +} + #[cfg(test)] mod tests { use super::*; @@ -285,4 +381,34 @@ mod tests { let result = parse_block_id_from_params(¶ms, 0); assert!(result.is_none()); } + + /// Tests that transaction hashes can be parsed from params. + #[test] + fn parses_transaction_hash_from_params() { + let hash = "0xdbdfa0f88b2cf815fdc1621bd20c2bd2b0eed4f0c56c9be2602957b5a60ec702"; + let params_str = format!(r#"["{hash}"]"#); + let params = Params::new(Some(¶ms_str)); + let result = parse_transaction_hash_from_params(¶ms); + assert!(result.is_ok()); + let parsed_hash = result.unwrap(); + assert_eq!(format!("{parsed_hash:?}"), hash); + } + + /// Tests that invalid transaction hash returns error. + #[test] + fn returns_error_for_invalid_tx_hash() { + let params = Params::new(Some(r#"["not_a_hash"]"#)); + let result = parse_transaction_hash_from_params(¶ms); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), ParseError::InvalidFormat)); + } + + /// Tests that missing parameter returns appropriate error. + #[test] + fn returns_error_for_missing_parameter() { + let params = Params::new(Some(r#"[]"#)); + let result = parse_transaction_hash_from_params(¶ms); + assert!(result.is_err()); + assert!(matches!(result.unwrap_err(), ParseError::MissingParameter)); + } } diff --git a/crates/optimism/storage/Cargo.toml b/crates/optimism/storage/Cargo.toml index 564d6e38cda..aab6ee7d8e0 100644 --- a/crates/optimism/storage/Cargo.toml +++ b/crates/optimism/storage/Cargo.toml @@ -12,16 +12,10 @@ workspace = true [dependencies] # reth -reth-node-api.workspace = true -reth-chainspec.workspace = true -reth-primitives-traits.workspace = true reth-optimism-primitives = { workspace = true, features = ["serde", "reth-codec"] } reth-storage-api = { workspace = true, features = ["db-api"] } -reth-db-api.workspace = true -reth-provider.workspace = true # ethereum -alloy-primitives.workspace = true alloy-consensus.workspace = true [dev-dependencies] @@ -33,11 +27,8 @@ reth-stages-types.workspace = true default = ["std"] std = [ "reth-storage-api/std", - "alloy-primitives/std", "reth-prune-types/std", "reth-stages-types/std", "alloy-consensus/std", - "reth-chainspec/std", "reth-optimism-primitives/std", - "reth-primitives-traits/std", ] diff --git a/crates/optimism/storage/src/chain.rs b/crates/optimism/storage/src/chain.rs index 424a773d49a..e56cd12f36d 100644 --- a/crates/optimism/storage/src/chain.rs +++ b/crates/optimism/storage/src/chain.rs @@ -1,117 +1,6 @@ -use alloc::{vec, vec::Vec}; use alloy_consensus::Header; -use alloy_primitives::BlockNumber; -use core::marker::PhantomData; -use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; -use reth_db_api::transaction::{DbTx, DbTxMut}; -use reth_node_api::{FullNodePrimitives, FullSignedTx}; use reth_optimism_primitives::OpTransactionSigned; -use reth_primitives_traits::{Block, FullBlockHeader, SignedTransaction}; -use reth_provider::{ - providers::{ChainStorage, NodeTypesForProvider}, - DatabaseProvider, -}; -use reth_storage_api::{ - errors::ProviderResult, BlockBodyReader, BlockBodyWriter, ChainStorageReader, - ChainStorageWriter, DBProvider, ReadBodyInput, StorageLocation, -}; +use reth_storage_api::EmptyBodyStorage; /// Optimism storage implementation. -#[derive(Debug, Clone, Copy)] -pub struct OpStorage(PhantomData<(T, H)>); - -impl Default for OpStorage { - fn default() -> Self { - Self(Default::default()) - } -} - -impl ChainStorage for OpStorage -where - T: FullSignedTx, - H: FullBlockHeader, - N: FullNodePrimitives< - Block = alloy_consensus::Block, - BlockHeader = H, - BlockBody = alloy_consensus::BlockBody, - SignedTx = T, - >, -{ - fn reader(&self) -> impl ChainStorageReader, N> - where - TX: DbTx + 'static, - Types: NodeTypesForProvider, - { - self - } - - fn writer(&self) -> impl ChainStorageWriter, N> - where - TX: DbTxMut + DbTx + 'static, - Types: NodeTypesForProvider, - { - self - } -} - -impl BlockBodyWriter> for OpStorage -where - Provider: DBProvider, - T: SignedTransaction, - H: FullBlockHeader, -{ - fn write_block_bodies( - &self, - _provider: &Provider, - _bodies: Vec<(u64, Option>)>, - _write_to: StorageLocation, - ) -> ProviderResult<()> { - // noop - Ok(()) - } - - fn remove_block_bodies_above( - &self, - _provider: &Provider, - _block: BlockNumber, - _remove_from: StorageLocation, - ) -> ProviderResult<()> { - // noop - Ok(()) - } -} - -impl BlockBodyReader for OpStorage -where - Provider: ChainSpecProvider + DBProvider, - T: SignedTransaction, - H: FullBlockHeader, -{ - type Block = alloy_consensus::Block; - - fn read_block_bodies( - &self, - provider: &Provider, - inputs: Vec>, - ) -> ProviderResult::Body>> { - let chain_spec = provider.chain_spec(); - - let mut bodies = Vec::with_capacity(inputs.len()); - - for (header, transactions) in inputs { - let mut withdrawals = None; - if chain_spec.is_shanghai_active_at_timestamp(header.timestamp()) { - // after shanghai the body should have an empty withdrawals list - withdrawals.replace(vec![].into()); - } - - bodies.push(alloy_consensus::BlockBody:: { - transactions, - ommers: vec![], - withdrawals, - }); - } - - Ok(bodies) - } -} +pub type OpStorage = EmptyBodyStorage; diff --git a/crates/optimism/storage/src/lib.rs b/crates/optimism/storage/src/lib.rs index adefb646f6e..60f4bffd979 100644 --- a/crates/optimism/storage/src/lib.rs +++ b/crates/optimism/storage/src/lib.rs @@ -9,67 +9,26 @@ #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(test), warn(unused_crate_dependencies))] -extern crate alloc; - mod chain; pub use chain::OpStorage; #[cfg(test)] mod tests { use reth_codecs::{test_utils::UnusedBits, validate_bitflag_backwards_compat}; - use reth_db_api::models::{ - CompactClientVersion, CompactU256, CompactU64, StoredBlockBodyIndices, - StoredBlockWithdrawals, - }; - use reth_primitives_traits::Account; + use reth_prune_types::{PruneCheckpoint, PruneMode, PruneSegment}; - use reth_stages_types::{ - AccountHashingCheckpoint, CheckpointBlockRange, EntitiesCheckpoint, ExecutionCheckpoint, - HeadersCheckpoint, IndexHistoryCheckpoint, StageCheckpoint, StageUnitCheckpoint, - StorageHashingCheckpoint, - }; #[test] fn test_ensure_backwards_compatibility() { - assert_eq!(Account::bitflag_encoded_bytes(), 2); - assert_eq!(AccountHashingCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(CheckpointBlockRange::bitflag_encoded_bytes(), 1); - assert_eq!(CompactClientVersion::bitflag_encoded_bytes(), 0); - assert_eq!(CompactU256::bitflag_encoded_bytes(), 1); - assert_eq!(CompactU64::bitflag_encoded_bytes(), 1); - assert_eq!(EntitiesCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(ExecutionCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(HeadersCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(IndexHistoryCheckpoint::bitflag_encoded_bytes(), 0); - assert_eq!(PruneCheckpoint::bitflag_encoded_bytes(), 1); assert_eq!(PruneMode::bitflag_encoded_bytes(), 1); assert_eq!(PruneSegment::bitflag_encoded_bytes(), 1); - assert_eq!(StageCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StageUnitCheckpoint::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockBodyIndices::bitflag_encoded_bytes(), 1); - assert_eq!(StoredBlockWithdrawals::bitflag_encoded_bytes(), 0); - assert_eq!(StorageHashingCheckpoint::bitflag_encoded_bytes(), 1); // In case of failure, refer to the documentation of the // [`validate_bitflag_backwards_compat`] macro for detailed instructions on handling // it. - validate_bitflag_backwards_compat!(Account, UnusedBits::NotZero); - validate_bitflag_backwards_compat!(AccountHashingCheckpoint, UnusedBits::NotZero); - validate_bitflag_backwards_compat!(CheckpointBlockRange, UnusedBits::Zero); - validate_bitflag_backwards_compat!(CompactClientVersion, UnusedBits::Zero); - validate_bitflag_backwards_compat!(CompactU256, UnusedBits::NotZero); - validate_bitflag_backwards_compat!(CompactU64, UnusedBits::NotZero); - validate_bitflag_backwards_compat!(EntitiesCheckpoint, UnusedBits::Zero); - validate_bitflag_backwards_compat!(ExecutionCheckpoint, UnusedBits::Zero); - validate_bitflag_backwards_compat!(HeadersCheckpoint, UnusedBits::Zero); - validate_bitflag_backwards_compat!(IndexHistoryCheckpoint, UnusedBits::Zero); + validate_bitflag_backwards_compat!(PruneCheckpoint, UnusedBits::NotZero); validate_bitflag_backwards_compat!(PruneMode, UnusedBits::Zero); validate_bitflag_backwards_compat!(PruneSegment, UnusedBits::Zero); - validate_bitflag_backwards_compat!(StageCheckpoint, UnusedBits::NotZero); - validate_bitflag_backwards_compat!(StageUnitCheckpoint, UnusedBits::Zero); - validate_bitflag_backwards_compat!(StoredBlockBodyIndices, UnusedBits::Zero); - validate_bitflag_backwards_compat!(StoredBlockWithdrawals, UnusedBits::Zero); - validate_bitflag_backwards_compat!(StorageHashingCheckpoint, UnusedBits::NotZero); } } diff --git a/crates/optimism/txpool/src/maintain.rs b/crates/optimism/txpool/src/maintain.rs index ce5b044b998..c071bf708e4 100644 --- a/crates/optimism/txpool/src/maintain.rs +++ b/crates/optimism/txpool/src/maintain.rs @@ -14,11 +14,12 @@ use crate::{ }; use alloy_consensus::{conditional::BlockConditionalAttributes, BlockHeader}; use futures_util::{future::BoxFuture, FutureExt, Stream, StreamExt}; -use metrics::Gauge; +use metrics::{Gauge, Histogram}; use reth_chain_state::CanonStateNotification; use reth_metrics::{metrics::Counter, Metrics}; use reth_primitives_traits::NodePrimitives; use reth_transaction_pool::{error::PoolTransactionError, PoolTransaction, TransactionPool}; +use std::time::Instant; use tracing::warn; /// Transaction pool maintenance metrics @@ -50,7 +51,8 @@ struct MaintainPoolInteropMetrics { /// Counter for interop transactions that became stale and need revalidation stale_interop_transactions: Counter, // TODO: we also should add metric for (hash, counter) to check number of validation per tx - // TODO: we should add some timing metric in here to check supervisor congestion + /// Histogram for measuring supervisor revalidation duration (congestion metric) + supervisor_revalidation_duration_seconds: Histogram, } impl MaintainPoolInteropMetrics { @@ -67,6 +69,12 @@ impl MaintainPoolInteropMetrics { fn inc_stale_tx_interop(&self, count: usize) { self.stale_interop_transactions.increment(count as u64); } + + /// Record supervisor revalidation duration + #[inline] + fn record_supervisor_duration(&self, duration: std::time::Duration) { + self.supervisor_revalidation_duration_seconds.record(duration.as_secs_f64()); + } } /// Returns a spawnable future for maintaining the state of the conditional txs in the transaction /// pool. @@ -179,6 +187,7 @@ pub async fn maintain_transaction_pool_interop( if !to_revalidate.is_empty() { metrics.inc_stale_tx_interop(to_revalidate.len()); + let revalidation_start = Instant::now(); let revalidation_stream = supervisor_client.revalidate_interop_txs_stream( to_revalidate, timestamp, @@ -211,6 +220,8 @@ pub async fn maintain_transaction_pool_interop( } } } + + metrics.record_supervisor_duration(revalidation_start.elapsed()); } if !to_remove.is_empty() { diff --git a/crates/optimism/txpool/src/supervisor/client.rs b/crates/optimism/txpool/src/supervisor/client.rs index 4cc67685b59..b362fae2e10 100644 --- a/crates/optimism/txpool/src/supervisor/client.rs +++ b/crates/optimism/txpool/src/supervisor/client.rs @@ -28,7 +28,7 @@ use std::{ use tracing::trace; /// Supervisor hosted by op-labs -// TODO: This should be changes to actual supervisor url +// TODO: This should be changed to actual supervisor url pub const DEFAULT_SUPERVISOR_URL: &str = "http://localhost:1337/"; /// The default request timeout to use diff --git a/crates/optimism/txpool/src/supervisor/metrics.rs b/crates/optimism/txpool/src/supervisor/metrics.rs index cbe08e7a442..23eec843025 100644 --- a/crates/optimism/txpool/src/supervisor/metrics.rs +++ b/crates/optimism/txpool/src/supervisor/metrics.rs @@ -65,7 +65,7 @@ impl SupervisorMetrics { SuperchainDAError::FutureData => self.future_data_count.increment(1), SuperchainDAError::MissedData => self.missed_data_count.increment(1), SuperchainDAError::DataCorruption => self.data_corruption_count.increment(1), - SuperchainDAError::UninitializedChainDatabase => {} + _ => {} } } } diff --git a/crates/optimism/txpool/src/validator.rs b/crates/optimism/txpool/src/validator.rs index 6c986e9498f..e0885ac25b8 100644 --- a/crates/optimism/txpool/src/validator.rs +++ b/crates/optimism/txpool/src/validator.rs @@ -1,5 +1,6 @@ use crate::{supervisor::SupervisorClient, InvalidCrossTx, OpPooledTx}; use alloy_consensus::{BlockHeader, Transaction}; +use alloy_primitives::U256; use op_revm::L1BlockInfo; use parking_lot::RwLock; use reth_chainspec::ChainSpecProvider; @@ -13,6 +14,7 @@ use reth_transaction_pool::{ error::InvalidPoolTransactionError, EthPoolTransaction, EthTransactionValidator, TransactionOrigin, TransactionValidationOutcome, TransactionValidator, }; +use reth_optimism_primitives::is_gasless; use std::sync::{ atomic::{AtomicBool, AtomicU64, Ordering}, Arc, @@ -43,7 +45,7 @@ impl OpL1BlockInfo { #[derive(Debug, Clone)] pub struct OpTransactionValidator { /// The type that performs the actual validation. - inner: EthTransactionValidator, + inner: Arc>, /// Additional block info required for validation. block_info: Arc, /// If true, ensure that the transaction's sender has enough balance to cover the L1 gas fee @@ -118,7 +120,7 @@ where block_info: OpL1BlockInfo, ) -> Self { Self { - inner, + inner: Arc::new(inner), block_info: Arc::new(block_info), require_l1_data_gas_fee: true, supervisor_client: None, @@ -234,19 +236,36 @@ where authorities, } = outcome { + // Bypass L1 data gas cost requirement for gasless transactions + let tx = valid_tx.transaction(); + if is_gasless(tx) { + return TransactionValidationOutcome::Valid { + balance, + state_nonce, + transaction: valid_tx, + propagate, + bytecode_hash, + authorities, + }; + } + let mut l1_block_info = self.block_info.l1_block_info.read().clone(); let encoded = valid_tx.transaction().encoded_2718(); - let cost_addition = match l1_block_info.l1_tx_data_fee( - self.chain_spec(), - self.block_timestamp(), - &encoded, - false, - ) { - Ok(cost) => cost, - Err(err) => { - return TransactionValidationOutcome::Error(*valid_tx.hash(), Box::new(err)) + let cost_addition = if is_gasless(valid_tx.transaction()) { + U256::ZERO + } else { + match l1_block_info.l1_tx_data_fee( + self.chain_spec(), + self.block_timestamp(), + &encoded, + false, + ) { + Ok(cost) => cost, + Err(err) => { + return TransactionValidationOutcome::Error(*valid_tx.hash(), Box::new(err)) + } } }; let cost = valid_tx.transaction().cost().saturating_add(cost_addition); diff --git a/crates/payload/basic/src/lib.rs b/crates/payload/basic/src/lib.rs index 470e34bee33..b60640abe4e 100644 --- a/crates/payload/basic/src/lib.rs +++ b/crates/payload/basic/src/lib.rs @@ -455,6 +455,10 @@ where Ok(self.config.attributes.clone()) } + fn payload_timestamp(&self) -> Result { + Ok(self.config.attributes.timestamp()) + } + fn resolve_kind( &mut self, kind: PayloadKind, @@ -583,15 +587,15 @@ where let this = self.get_mut(); // check if there is a better payload before returning the best payload - if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() { - if let Poll::Ready(res) = fut.poll(cx) { - this.maybe_better = None; - if let Ok(Some(payload)) = res.map(|out| out.into_payload()) - .inspect_err(|err| warn!(target: "payload_builder", %err, "failed to resolve pending payload")) - { - debug!(target: "payload_builder", "resolving better payload"); - return Poll::Ready(Ok(payload)) - } + if let Some(fut) = Pin::new(&mut this.maybe_better).as_pin_mut() && + let Poll::Ready(res) = fut.poll(cx) + { + this.maybe_better = None; + if let Ok(Some(payload)) = res.map(|out| out.into_payload()).inspect_err( + |err| warn!(target: "payload_builder", %err, "failed to resolve pending payload"), + ) { + debug!(target: "payload_builder", "resolving better payload"); + return Poll::Ready(Ok(payload)) } } @@ -600,20 +604,20 @@ where return Poll::Ready(Ok(best)) } - if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() { - if let Poll::Ready(res) = fut.poll(cx) { - this.empty_payload = None; - return match res { - Ok(res) => { - if let Err(err) = &res { - warn!(target: "payload_builder", %err, "failed to resolve empty payload"); - } else { - debug!(target: "payload_builder", "resolving empty payload"); - } - Poll::Ready(res) + if let Some(fut) = Pin::new(&mut this.empty_payload).as_pin_mut() && + let Poll::Ready(res) = fut.poll(cx) + { + this.empty_payload = None; + return match res { + Ok(res) => { + if let Err(err) = &res { + warn!(target: "payload_builder", %err, "failed to resolve empty payload"); + } else { + debug!(target: "payload_builder", "resolving empty payload"); } - Err(err) => Poll::Ready(Err(err.into())), + Poll::Ready(res) } + Err(err) => Poll::Ready(Err(err.into())), } } @@ -852,10 +856,12 @@ pub trait PayloadBuilder: Send + Sync + Clone { /// Tells the payload builder how to react to payload request if there's no payload available yet. /// /// This situation can occur if the CL requests a payload before the first payload has been built. +#[derive(Default)] pub enum MissingPayloadBehaviour { /// Await the regular scheduled payload process. AwaitInProgress, /// Race the in progress payload process with an empty payload. + #[default] RaceEmptyPayload, /// Race the in progress payload process with this job. RacePayload(Box Result + Send>), @@ -873,12 +879,6 @@ impl fmt::Debug for MissingPayloadBehaviour { } } -impl Default for MissingPayloadBehaviour { - fn default() -> Self { - Self::RaceEmptyPayload - } -} - /// Checks if the new payload is better than the current best. /// /// This compares the total fees of the blocks, higher is better. diff --git a/crates/payload/builder/Cargo.toml b/crates/payload/builder/Cargo.toml index 222af0a664d..166c538f7a1 100644 --- a/crates/payload/builder/Cargo.toml +++ b/crates/payload/builder/Cargo.toml @@ -21,7 +21,7 @@ reth-ethereum-engine-primitives.workspace = true # alloy alloy-consensus.workspace = true -alloy-primitives = { workspace = true, optional = true } +alloy-primitives.workspace = true alloy-rpc-types = { workspace = true, features = ["engine"] } # async @@ -37,13 +37,10 @@ metrics.workspace = true tracing.workspace = true [dev-dependencies] -alloy-primitives.workspace = true - tokio = { workspace = true, features = ["sync", "rt"] } [features] test-utils = [ - "alloy-primitives", "reth-chain-state/test-utils", "reth-primitives-traits/test-utils", "tokio/rt", diff --git a/crates/payload/builder/src/lib.rs b/crates/payload/builder/src/lib.rs index be3518c3669..54254b53fb8 100644 --- a/crates/payload/builder/src/lib.rs +++ b/crates/payload/builder/src/lib.rs @@ -75,6 +75,10 @@ //! Ok(self.attributes.clone()) //! } //! +//! fn payload_timestamp(&self) -> Result { +//! Ok(self.attributes.timestamp) +//! } +//! //! fn resolve_kind(&mut self, _kind: PayloadKind) -> (Self::ResolvePayloadFuture, KeepPayloadJobAlive) { //! let payload = self.best_payload(); //! (futures_util::future::ready(payload), KeepPayloadJobAlive::No) diff --git a/crates/payload/builder/src/noop.rs b/crates/payload/builder/src/noop.rs index c20dac0f2d5..3628ef83c0d 100644 --- a/crates/payload/builder/src/noop.rs +++ b/crates/payload/builder/src/noop.rs @@ -50,7 +50,7 @@ where tx.send(Ok(id)).ok() } PayloadServiceCommand::BestPayload(_, tx) => tx.send(None).ok(), - PayloadServiceCommand::PayloadAttributes(_, tx) => tx.send(None).ok(), + PayloadServiceCommand::PayloadTimestamp(_, tx) => tx.send(None).ok(), PayloadServiceCommand::Resolve(_, _, tx) => tx.send(None).ok(), PayloadServiceCommand::Subscribe(_) => None, }; diff --git a/crates/payload/builder/src/service.rs b/crates/payload/builder/src/service.rs index 48daeeca0a5..f9530d003f5 100644 --- a/crates/payload/builder/src/service.rs +++ b/crates/payload/builder/src/service.rs @@ -8,6 +8,7 @@ use crate::{ PayloadJob, }; use alloy_consensus::BlockHeader; +use alloy_primitives::BlockTimestamp; use alloy_rpc_types::engine::PayloadId; use futures_util::{future::FutureExt, Stream, StreamExt}; use reth_chain_state::CanonStateNotification; @@ -24,11 +25,12 @@ use std::{ use tokio::sync::{ broadcast, mpsc, oneshot::{self, Receiver}, + watch, }; use tokio_stream::wrappers::UnboundedReceiverStream; use tracing::{debug, info, trace, warn}; -type PayloadFuture

= Pin> + Send + Sync>>; +type PayloadFuture

= Pin> + Send>>; /// A communication channel to the [`PayloadBuilderService`] that can retrieve payloads. /// @@ -73,14 +75,14 @@ where self.inner.best_payload(id).await } - /// Returns the payload attributes associated with the given identifier. + /// Returns the payload timestamp associated with the given identifier. /// - /// Note: this returns the attributes of the payload and does not resolve the job. - pub async fn payload_attributes( + /// Note: this returns the timestamp of the payload and does not resolve the job. + pub async fn payload_timestamp( &self, id: PayloadId, - ) -> Option> { - self.inner.payload_attributes(id).await + ) -> Option> { + self.inner.payload_timestamp(id).await } } @@ -166,15 +168,15 @@ impl PayloadBuilderHandle { Ok(PayloadEvents { receiver: rx.await? }) } - /// Returns the payload attributes associated with the given identifier. + /// Returns the payload timestamp associated with the given identifier. /// - /// Note: this returns the attributes of the payload and does not resolve the job. - pub async fn payload_attributes( + /// Note: this returns the timestamp of the payload and does not resolve the job. + pub async fn payload_timestamp( &self, id: PayloadId, - ) -> Option> { + ) -> Option> { let (tx, rx) = oneshot::channel(); - self.to_service.send(PayloadServiceCommand::PayloadAttributes(id, tx)).ok()?; + self.to_service.send(PayloadServiceCommand::PayloadTimestamp(id, tx)).ok()?; rx.await.ok()? } } @@ -218,6 +220,11 @@ where chain_events: St, /// Payload events handler, used to broadcast and subscribe to payload events. payload_events: broadcast::Sender>, + /// We retain latest resolved payload just to make sure that we can handle repeating + /// requests for it gracefully. + cached_payload_rx: watch::Receiver>, + /// Sender half of the cached payload channel. + cached_payload_tx: watch::Sender>, } const PAYLOAD_EVENTS_BUFFER_SIZE: usize = 20; @@ -241,6 +248,8 @@ where let (service_tx, command_rx) = mpsc::unbounded_channel(); let (payload_events, _) = broadcast::channel(PAYLOAD_EVENTS_BUFFER_SIZE); + let (cached_payload_tx, cached_payload_rx) = watch::channel(None); + let service = Self { generator, payload_jobs: Vec::new(), @@ -249,6 +258,8 @@ where metrics: Default::default(), chain_events, payload_events, + cached_payload_rx, + cached_payload_tx, }; let handle = service.handle(); @@ -294,8 +305,15 @@ where ) -> Option> { debug!(target: "payload_builder", %id, "resolving payload job"); + if let Some((cached, _, payload)) = &*self.cached_payload_rx.borrow() && + *cached == id + { + return Some(Box::pin(core::future::ready(Ok(payload.clone())))); + } + let job = self.payload_jobs.iter().position(|(_, job_id)| *job_id == id)?; let (fut, keep_alive) = self.payload_jobs[job].0.resolve_kind(kind); + let payload_timestamp = self.payload_jobs[job].0.payload_timestamp(); if keep_alive == KeepPayloadJobAlive::No { let (_, id) = self.payload_jobs.swap_remove(job); @@ -306,6 +324,7 @@ where // the future in a new future that will update the metrics. let resolved_metrics = self.metrics.clone(); let payload_events = self.payload_events.clone(); + let cached_payload_tx = self.cached_payload_tx.clone(); let fut = async move { let res = fut.await; @@ -314,6 +333,10 @@ where payload_events.send(Events::BuiltPayload(payload.clone().into())).ok(); } + if let Ok(timestamp) = payload_timestamp { + let _ = cached_payload_tx.send(Some((id, timestamp, payload.clone().into()))); + } + resolved_metrics .set_resolved_revenue(payload.block().number(), f64::from(payload.fees())); } @@ -331,22 +354,25 @@ where Gen::Job: PayloadJob, ::BuiltPayload: Into, { - /// Returns the payload attributes for the given payload. - fn payload_attributes( - &self, - id: PayloadId, - ) -> Option::PayloadAttributes, PayloadBuilderError>> { - let attributes = self + /// Returns the payload timestamp for the given payload. + fn payload_timestamp(&self, id: PayloadId) -> Option> { + if let Some((cached_id, timestamp, _)) = *self.cached_payload_rx.borrow() && + cached_id == id + { + return Some(Ok(timestamp)); + } + + let timestamp = self .payload_jobs .iter() .find(|(_, job_id)| *job_id == id) - .map(|(j, _)| j.payload_attributes()); + .map(|(j, _)| j.payload_timestamp()); - if attributes.is_none() { - trace!(target: "payload_builder", %id, "no matching payload job found to get attributes for"); + if timestamp.is_none() { + trace!(target: "payload_builder", %id, "no matching payload job found to get timestamp for"); } - attributes + timestamp } } @@ -431,9 +457,9 @@ where PayloadServiceCommand::BestPayload(id, tx) => { let _ = tx.send(this.best_payload(id)); } - PayloadServiceCommand::PayloadAttributes(id, tx) => { - let attributes = this.payload_attributes(id); - let _ = tx.send(attributes); + PayloadServiceCommand::PayloadTimestamp(id, tx) => { + let timestamp = this.payload_timestamp(id); + let _ = tx.send(timestamp); } PayloadServiceCommand::Resolve(id, strategy, tx) => { let _ = tx.send(this.resolve(id, strategy)); @@ -461,11 +487,8 @@ pub enum PayloadServiceCommand { ), /// Get the best payload so far BestPayload(PayloadId, oneshot::Sender>>), - /// Get the payload attributes for the given payload - PayloadAttributes( - PayloadId, - oneshot::Sender>>, - ), + /// Get the payload timestamp for the given payload + PayloadTimestamp(PayloadId, oneshot::Sender>>), /// Resolve the payload and return the payload Resolve( PayloadId, @@ -488,7 +511,7 @@ where Self::BestPayload(f0, f1) => { f.debug_tuple("BestPayload").field(&f0).field(&f1).finish() } - Self::PayloadAttributes(f0, f1) => { + Self::PayloadTimestamp(f0, f1) => { f.debug_tuple("PayloadAttributes").field(&f0).field(&f1).finish() } Self::Resolve(f0, f1, _f2) => f.debug_tuple("Resolve").field(&f0).field(&f1).finish(), diff --git a/crates/payload/builder/src/test_utils.rs b/crates/payload/builder/src/test_utils.rs index 5058d48f246..bf4e85122ea 100644 --- a/crates/payload/builder/src/test_utils.rs +++ b/crates/payload/builder/src/test_utils.rs @@ -98,6 +98,10 @@ impl PayloadJob for TestPayloadJob { Ok(self.attr.clone()) } + fn payload_timestamp(&self) -> Result { + Ok(self.attr.timestamp) + } + fn resolve_kind( &mut self, _kind: PayloadKind, diff --git a/crates/payload/builder/src/traits.rs b/crates/payload/builder/src/traits.rs index 807bfa186ec..1e4158addde 100644 --- a/crates/payload/builder/src/traits.rs +++ b/crates/payload/builder/src/traits.rs @@ -17,13 +17,12 @@ use std::future::Future; /// empty. /// /// Note: A `PayloadJob` need to be cancel safe because it might be dropped after the CL has requested the payload via `engine_getPayloadV1` (see also [engine API docs](https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#engine_getpayloadv1)) -pub trait PayloadJob: Future> + Send + Sync { +pub trait PayloadJob: Future> { /// Represents the payload attributes type that is used to spawn this payload job. type PayloadAttributes: PayloadBuilderAttributes + std::fmt::Debug; /// Represents the future that resolves the block that's returned to the CL. type ResolvePayloadFuture: Future> + Send - + Sync + 'static; /// Represents the built payload type that is returned to the CL. type BuiltPayload: BuiltPayload + Clone + std::fmt::Debug; @@ -36,6 +35,14 @@ pub trait PayloadJob: Future> + Send + /// Returns the payload attributes for the payload being built. fn payload_attributes(&self) -> Result; + /// Returns the payload timestamp for the payload being built. + /// The default implementation allocates full attributes only to + /// extract the timestamp. Provide your own implementation if you + /// need performance here. + fn payload_timestamp(&self) -> Result { + Ok(self.payload_attributes()?.timestamp()) + } + /// Called when the payload is requested by the CL. /// /// This is invoked on [`engine_getPayloadV2`](https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_getpayloadv2) and [`engine_getPayloadV1`](https://github.com/ethereum/execution-apis/blob/main/src/engine/paris.md#engine_getpayloadv1). @@ -85,7 +92,7 @@ pub enum KeepPayloadJobAlive { } /// A type that knows how to create new jobs for creating payloads. -pub trait PayloadJobGenerator: Send + Sync { +pub trait PayloadJobGenerator { /// The type that manages the lifecycle of a payload. /// /// This type is a future that yields better payloads. diff --git a/crates/payload/primitives/Cargo.toml b/crates/payload/primitives/Cargo.toml index bb305961fe1..0c7e80ea9bc 100644 --- a/crates/payload/primitives/Cargo.toml +++ b/crates/payload/primitives/Cargo.toml @@ -26,6 +26,7 @@ op-alloy-rpc-types-engine = { workspace = true, optional = true } # misc auto_impl.workspace = true +either.workspace = true serde.workspace = true thiserror.workspace = true tokio = { workspace = true, default-features = false, features = ["sync"] } @@ -44,6 +45,7 @@ std = [ "serde/std", "thiserror/std", "reth-primitives-traits/std", + "either/std", ] op = [ "dep:op-alloy-rpc-types-engine", diff --git a/crates/payload/primitives/src/traits.rs b/crates/payload/primitives/src/traits.rs index 868929c2b1b..39bd14cc63b 100644 --- a/crates/payload/primitives/src/traits.rs +++ b/crates/payload/primitives/src/traits.rs @@ -1,6 +1,7 @@ //! Core traits for working with execution payloads. -use alloc::vec::Vec; +use crate::PayloadBuilderError; +use alloc::{boxed::Box, vec::Vec}; use alloy_eips::{ eip4895::{Withdrawal, Withdrawals}, eip7685::Requests, @@ -11,8 +12,6 @@ use core::fmt; use reth_chain_state::ExecutedBlockWithTrieUpdates; use reth_primitives_traits::{NodePrimitives, SealedBlock, SealedHeader}; -use crate::PayloadBuilderError; - /// Represents a successfully built execution payload (block). /// /// Provides access to the underlying block data, execution results, and associated metadata @@ -147,6 +146,38 @@ pub trait PayloadAttributesBuilder: Send + Sync + 'static { fn build(&self, timestamp: u64) -> Attributes; } +impl PayloadAttributesBuilder for F +where + F: Fn(u64) -> Attributes + Send + Sync + 'static, +{ + fn build(&self, timestamp: u64) -> Attributes { + self(timestamp) + } +} + +impl PayloadAttributesBuilder for either::Either +where + L: PayloadAttributesBuilder, + R: PayloadAttributesBuilder, +{ + fn build(&self, timestamp: u64) -> Attributes { + match self { + Self::Left(l) => l.build(timestamp), + Self::Right(r) => r.build(timestamp), + } + } +} + +impl PayloadAttributesBuilder + for Box> +where + Attributes: 'static, +{ + fn build(&self, timestamp: u64) -> Attributes { + self.as_ref().build(timestamp) + } +} + /// Trait to build the EVM environment for the next block from the given payload attributes. /// /// Accepts payload attributes from CL, parent header and additional payload builder context. diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index a5bdd9a0ae7..8d09ecb14f9 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -70,7 +70,6 @@ rand_08.workspace = true serde.workspace = true serde_json.workspace = true test-fuzz.workspace = true -modular-bitfield.workspace = true [features] default = ["std"] diff --git a/crates/primitives-traits/src/block/body.rs b/crates/primitives-traits/src/block/body.rs index 688431940e7..4dc9a67e887 100644 --- a/crates/primitives-traits/src/block/body.rs +++ b/crates/primitives-traits/src/block/body.rs @@ -5,7 +5,10 @@ use crate::{ MaybeSerdeBincodeCompat, SignedTransaction, }; use alloc::{fmt, vec::Vec}; -use alloy_consensus::{Transaction, Typed2718}; +use alloy_consensus::{ + transaction::{Recovered, TxHashRef}, + Transaction, Typed2718, +}; use alloy_eips::{eip2718::Encodable2718, eip4895::Withdrawals}; use alloy_primitives::{Address, Bytes, B256}; @@ -109,7 +112,7 @@ pub trait BlockBody: /// Calculate the withdrawals root for the block body. /// - /// Returns `RecoveryError` if there are no withdrawals in the block. + /// Returns `Some(root)` if withdrawals are present, otherwise `None`. fn calculate_withdrawals_root(&self) -> Option { self.withdrawals().map(|withdrawals| { alloy_consensus::proofs::calculate_withdrawals_root(withdrawals.as_slice()) @@ -121,7 +124,7 @@ pub trait BlockBody: /// Calculate the ommers root for the block body. /// - /// Returns `RecoveryError` if there are no ommers in the block. + /// Returns `Some(root)` if ommers are present, otherwise `None`. fn calculate_ommers_root(&self) -> Option { self.ommers().map(alloy_consensus::proofs::calculate_ommers_root) } @@ -157,20 +160,14 @@ pub trait BlockBody: } /// Recover signer addresses for all transactions in the block body. - fn recover_signers(&self) -> Result, RecoveryError> - where - Self::Transaction: SignedTransaction, - { + fn recover_signers(&self) -> Result, RecoveryError> { crate::transaction::recover::recover_signers(self.transactions()) } /// Recover signer addresses for all transactions in the block body. /// /// Returns an error if some transaction's signature is invalid. - fn try_recover_signers(&self) -> Result, RecoveryError> - where - Self::Transaction: SignedTransaction, - { + fn try_recover_signers(&self) -> Result, RecoveryError> { self.recover_signers() } @@ -178,10 +175,7 @@ pub trait BlockBody: /// signature has a low `s` value_. /// /// Returns `RecoveryError`, if some transaction's signature is invalid. - fn recover_signers_unchecked(&self) -> Result, RecoveryError> - where - Self::Transaction: SignedTransaction, - { + fn recover_signers_unchecked(&self) -> Result, RecoveryError> { crate::transaction::recover::recover_signers_unchecked(self.transactions()) } @@ -189,12 +183,21 @@ pub trait BlockBody: /// signature has a low `s` value_. /// /// Returns an error if some transaction's signature is invalid. - fn try_recover_signers_unchecked(&self) -> Result, RecoveryError> - where - Self::Transaction: SignedTransaction, - { + fn try_recover_signers_unchecked(&self) -> Result, RecoveryError> { self.recover_signers_unchecked() } + + /// Recovers signers for all transactions in the block body and returns a vector of + /// [`Recovered`]. + fn recover_transactions(&self) -> Result>, RecoveryError> { + self.recover_signers().map(|signers| { + self.transactions() + .iter() + .zip(signers) + .map(|(tx, signer)| tx.clone().with_signer(signer)) + .collect() + }) + } } impl BlockBody for alloy_consensus::BlockBody diff --git a/crates/primitives-traits/src/block/mod.rs b/crates/primitives-traits/src/block/mod.rs index 35ecb171440..2aeade9bc17 100644 --- a/crates/primitives-traits/src/block/mod.rs +++ b/crates/primitives-traits/src/block/mod.rs @@ -190,10 +190,7 @@ pub trait Block: /// transactions. /// /// Returns the block as error if a signature is invalid. - fn try_into_recovered(self) -> Result, BlockRecoveryError> - where - ::Transaction: SignedTransaction, - { + fn try_into_recovered(self) -> Result, BlockRecoveryError> { let Ok(signers) = self.body().recover_signers() else { return Err(BlockRecoveryError::new(self)) }; diff --git a/crates/primitives-traits/src/block/recovered.rs b/crates/primitives-traits/src/block/recovered.rs index fd2acea1d00..d139345bf50 100644 --- a/crates/primitives-traits/src/block/recovered.rs +++ b/crates/primitives-traits/src/block/recovered.rs @@ -103,7 +103,7 @@ impl RecoveredBlock { Self { block, senders } } - /// A safer variant of [`Self::new_unhashed`] that checks if the number of senders is equal to + /// A safer variant of [`Self::new`] that checks if the number of senders is equal to /// the number of transactions in the block and recovers the senders from the transactions, if /// not using [`SignedTransaction::recover_signer`](crate::transaction::signed::SignedTransaction) /// to recover the senders. @@ -216,7 +216,7 @@ impl RecoveredBlock { Ok(Self::new(block, senders, hash)) } - /// A safer variant of [`Self::new_unhashed`] that checks if the number of senders is equal to + /// A safer variant of [`Self::new_sealed`] that checks if the number of senders is equal to /// the number of transactions in the block and recovers the senders from the transactions, if /// not using [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction) /// to recover the senders. @@ -230,7 +230,7 @@ impl RecoveredBlock { Self::try_new(block, senders, hash) } - /// A safer variant of [`Self::new`] that checks if the number of senders is equal to + /// A safer variant of [`Self::new_sealed`] that checks if the number of senders is equal to /// the number of transactions in the block and recovers the senders from the transactions, if /// not using [`SignedTransaction::recover_signer_unchecked`](crate::transaction::signed::SignedTransaction) /// to recover the senders. @@ -616,6 +616,12 @@ impl<'a, B: Block> IndexedTx<'a, B> { self.tx } + /// Returns the recovered transaction with the sender. + pub fn recovered_tx(&self) -> Recovered<&::Transaction> { + let sender = self.block.senders[self.index]; + Recovered::new_unchecked(self.tx, sender) + } + /// Returns the transaction hash. pub fn tx_hash(&self) -> TxHash { self.tx.trie_hash() @@ -653,7 +659,8 @@ mod rpc_compat { use crate::{block::error::BlockRecoveryError, SealedHeader}; use alloc::vec::Vec; use alloy_consensus::{ - transaction::Recovered, Block as CBlock, BlockBody, BlockHeader, Sealable, + transaction::{Recovered, TxHashRef}, + Block as CBlock, BlockBody, BlockHeader, Sealable, }; use alloy_rpc_types_eth::{Block, BlockTransactions, BlockTransactionsKind, TransactionInfo}; diff --git a/crates/primitives-traits/src/block/sealed.rs b/crates/primitives-traits/src/block/sealed.rs index 9e160728192..5c43178146b 100644 --- a/crates/primitives-traits/src/block/sealed.rs +++ b/crates/primitives-traits/src/block/sealed.rs @@ -308,7 +308,8 @@ impl Deref for SealedBlock { impl Encodable for SealedBlock { fn encode(&self, out: &mut dyn BufMut) { - self.body.encode(out); + // TODO: https://github.com/paradigmxyz/reth/issues/18002 + self.clone().into_block().encode(out); } } @@ -469,3 +470,84 @@ pub(super) mod serde_bincode_compat { } } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_rlp::{Decodable, Encodable}; + + #[test] + fn test_sealed_block_rlp_roundtrip() { + // Create a sample block using alloy_consensus::Block + let header = alloy_consensus::Header { + parent_hash: B256::ZERO, + ommers_hash: B256::ZERO, + beneficiary: Address::ZERO, + state_root: B256::ZERO, + transactions_root: B256::ZERO, + receipts_root: B256::ZERO, + logs_bloom: Default::default(), + difficulty: Default::default(), + number: 42, + gas_limit: 30_000_000, + gas_used: 21_000, + timestamp: 1_000_000, + extra_data: Default::default(), + mix_hash: B256::ZERO, + nonce: Default::default(), + base_fee_per_gas: Some(1_000_000_000), + withdrawals_root: None, + blob_gas_used: None, + excess_blob_gas: None, + parent_beacon_block_root: None, + requests_hash: None, + }; + + // Create a simple transaction + let tx = alloy_consensus::TxLegacy { + chain_id: Some(1), + nonce: 0, + gas_price: 21_000_000_000, + gas_limit: 21_000, + to: alloy_primitives::TxKind::Call(Address::ZERO), + value: alloy_primitives::U256::from(100), + input: alloy_primitives::Bytes::default(), + }; + + let tx_signed = + alloy_consensus::TxEnvelope::Legacy(alloy_consensus::Signed::new_unchecked( + tx, + alloy_primitives::Signature::test_signature(), + B256::ZERO, + )); + + // Create block body with the transaction + let body = alloy_consensus::BlockBody { + transactions: vec![tx_signed], + ommers: vec![], + withdrawals: Some(Default::default()), + }; + + // Create the block + let block = alloy_consensus::Block::new(header, body); + + // Create a sealed block + let sealed_block = SealedBlock::seal_slow(block); + + // Encode the sealed block + let mut encoded = Vec::new(); + sealed_block.encode(&mut encoded); + + // Decode the sealed block + let decoded = SealedBlock::< + alloy_consensus::Block, + >::decode(&mut encoded.as_slice()) + .expect("Failed to decode sealed block"); + + // Verify the roundtrip + assert_eq!(sealed_block.hash(), decoded.hash()); + assert_eq!(sealed_block.header().number, decoded.header().number); + assert_eq!(sealed_block.header().state_root, decoded.header().state_root); + assert_eq!(sealed_block.body().transactions.len(), decoded.body().transactions.len()); + } +} diff --git a/crates/primitives-traits/src/constants/mod.rs b/crates/primitives-traits/src/constants/mod.rs index 7df2c017b30..a9aa18fac31 100644 --- a/crates/primitives-traits/src/constants/mod.rs +++ b/crates/primitives-traits/src/constants/mod.rs @@ -18,7 +18,7 @@ pub const MAXIMUM_GAS_LIMIT_BLOCK: u64 = 2u64.pow(63) - 1; pub const GAS_LIMIT_BOUND_DIVISOR: u64 = 1024; /// Maximum transaction gas limit as defined by [EIP-7825](https://eips.ethereum.org/EIPS/eip-7825) activated in `Osaka` hardfork. -pub const MAX_TX_GAS_LIMIT_OSAKA: u64 = 30_000_000; +pub const MAX_TX_GAS_LIMIT_OSAKA: u64 = 2u64.pow(24); /// The number of blocks to unwind during a reorg that already became a part of canonical chain. /// diff --git a/crates/primitives-traits/src/extended.rs b/crates/primitives-traits/src/extended.rs index b2731aa5a96..45530fc8c58 100644 --- a/crates/primitives-traits/src/extended.rs +++ b/crates/primitives-traits/src/extended.rs @@ -3,7 +3,10 @@ use crate::{ transaction::signed::{RecoveryError, SignedTransaction}, }; use alloc::vec::Vec; -use alloy_consensus::{transaction::SignerRecoverable, EthereumTxEnvelope, Transaction}; +use alloy_consensus::{ + transaction::{SignerRecoverable, TxHashRef}, + EthereumTxEnvelope, Transaction, +}; use alloy_eips::{ eip2718::{Eip2718Error, Eip2718Result, IsTyped2718}, eip2930::AccessList, @@ -155,19 +158,23 @@ where } } -impl SignedTransaction for Extended +impl TxHashRef for Extended where - B: SignedTransaction + IsTyped2718, - T: SignedTransaction, + B: TxHashRef, + T: TxHashRef, { fn tx_hash(&self) -> &TxHash { - match self { - Self::BuiltIn(tx) => tx.tx_hash(), - Self::Other(tx) => tx.tx_hash(), - } + delegate!(self => tx.tx_hash()) } } +impl SignedTransaction for Extended +where + B: SignedTransaction + IsTyped2718 + TxHashRef, + T: SignedTransaction + TxHashRef, +{ +} + impl Typed2718 for Extended where B: Typed2718, diff --git a/crates/primitives-traits/src/node.rs b/crates/primitives-traits/src/node.rs index 42f7c74b1d3..1f5bfed139e 100644 --- a/crates/primitives-traits/src/node.rs +++ b/crates/primitives-traits/src/node.rs @@ -30,39 +30,23 @@ pub trait NodePrimitives: pub trait FullNodePrimitives where Self: NodePrimitives< - Block: FullBlock

, - BlockHeader: FullBlockHeader, - BlockBody: FullBlockBody, - SignedTx: FullSignedTx, - Receipt: FullReceipt, - > + Send - + Sync - + Unpin - + Clone - + Default - + fmt::Debug - + PartialEq - + Eq - + 'static, + Block: FullBlock
, + BlockHeader: FullBlockHeader, + BlockBody: FullBlockBody, + SignedTx: FullSignedTx, + Receipt: FullReceipt, + >, { } impl FullNodePrimitives for T where T: NodePrimitives< - Block: FullBlock
, - BlockHeader: FullBlockHeader, - BlockBody: FullBlockBody, - SignedTx: FullSignedTx, - Receipt: FullReceipt, - > + Send - + Sync - + Unpin - + Clone - + Default - + fmt::Debug - + PartialEq - + Eq - + 'static + Block: FullBlock
, + BlockHeader: FullBlockHeader, + BlockBody: FullBlockBody, + SignedTx: FullSignedTx, + Receipt: FullReceipt, + > { } diff --git a/crates/primitives-traits/src/transaction/error.rs b/crates/primitives-traits/src/transaction/error.rs index b87405e4abd..f358e222443 100644 --- a/crates/primitives-traits/src/transaction/error.rs +++ b/crates/primitives-traits/src/transaction/error.rs @@ -2,6 +2,7 @@ use crate::GotExpectedBoxed; use alloy_primitives::U256; +use crate::transaction::gasless_error::GaslessValidationError; /// Represents error variants that can happen when trying to validate a transaction. #[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)] @@ -64,6 +65,11 @@ pub enum InvalidTransactionError { /// Thrown post Osaka if gas limit is too high. #[error("gas limit too high")] GasLimitTooHigh, + + // Gasless errors + /// Thrown if the transaction is a gasless transaction and the validation fails. + #[error("gasless transaction validation failed")] + GaslessValidationError(GaslessValidationError), } /// Represents error variants that can happen when trying to convert a transaction to pooled diff --git a/crates/primitives-traits/src/transaction/gasless_error.rs b/crates/primitives-traits/src/transaction/gasless_error.rs new file mode 100644 index 00000000000..5b8adfa199f --- /dev/null +++ b/crates/primitives-traits/src/transaction/gasless_error.rs @@ -0,0 +1,38 @@ +use revm_primitives::U256; + +/// Errors that can occur during gasless transaction validation. +/// +/// These errors represent various failure modes when validating whether a transaction +/// is eligible for gasless execution through the gas station mechanism. +#[derive(thiserror::Error, Clone, Debug, PartialEq, Eq)] +pub enum GaslessValidationError { + /// The gas station feature is disabled in the current configuration. + #[error("gas station feature disabled")] + Disabled, + /// The transaction is a contract creation transaction, which is not supported for gasless execution. + #[error("destination is create transaction")] + Create, + /// No gas station contract address has been configured. + #[error("gas station contract not configured")] + NoAddress, + /// The destination contract is not registered for gasless transactions. + #[error("not registered for gasless")] + NotRegistered, + /// The destination contract is registered but currently inactive for gasless transactions. + #[error("contract inactive for gasless")] + Inactive, + /// Insufficient credits available for the gasless transaction. + #[error("insufficient credits: have {available}, need {needed}")] + InsufficientCredits { + /// The amount of credits currently available. + available: U256, + /// The amount of credits needed for this transaction. + needed: U256, + }, + /// The transaction sender is not on the required whitelist. + #[error("whitelist required")] + NotWhitelisted, + /// A single-use gasless transaction has already been consumed. + #[error("single-use already used")] + SingleUseConsumed, +} diff --git a/crates/primitives-traits/src/transaction/mod.rs b/crates/primitives-traits/src/transaction/mod.rs index f11c3346aec..5ef7e64f8bd 100644 --- a/crates/primitives-traits/src/transaction/mod.rs +++ b/crates/primitives-traits/src/transaction/mod.rs @@ -17,8 +17,12 @@ pub mod signed; pub mod error; pub mod recover; +/// Gasless transaction validation errors. +pub mod gasless_error; -pub use alloy_consensus::transaction::{SignerRecoverable, TransactionInfo, TransactionMeta}; +pub use alloy_consensus::transaction::{ + SignerRecoverable, TransactionInfo, TransactionMeta, TxHashRef, +}; use crate::{InMemorySize, MaybeCompact, MaybeSerde}; use core::{fmt, hash::Hash}; diff --git a/crates/primitives-traits/src/transaction/recover.rs b/crates/primitives-traits/src/transaction/recover.rs index 704f11f58c6..59e6e8a6943 100644 --- a/crates/primitives-traits/src/transaction/recover.rs +++ b/crates/primitives-traits/src/transaction/recover.rs @@ -15,11 +15,11 @@ mod rayon { /// Recovers a list of signers from a transaction list iterator. /// - /// Returns `None`, if some transaction's signature is invalid + /// Returns `Err(RecoveryError)`, if some transaction's signature is invalid pub fn recover_signers<'a, I, T>(txes: I) -> Result, RecoveryError> where T: SignedTransaction, - I: IntoParallelIterator + IntoIterator + Send, + I: IntoParallelIterator, { txes.into_par_iter().map(|tx| tx.recover_signer()).collect() } @@ -27,11 +27,11 @@ mod rayon { /// Recovers a list of signers from a transaction list iterator _without ensuring that the /// signature has a low `s` value_. /// - /// Returns `None`, if some transaction's signature is invalid. + /// Returns `Err(RecoveryError)`, if some transaction's signature is invalid. pub fn recover_signers_unchecked<'a, I, T>(txes: I) -> Result, RecoveryError> where T: SignedTransaction, - I: IntoParallelIterator + IntoIterator + Send, + I: IntoParallelIterator, { txes.into_par_iter().map(|tx| tx.recover_signer_unchecked()).collect() } diff --git a/crates/primitives-traits/src/transaction/signature.rs b/crates/primitives-traits/src/transaction/signature.rs index 2e994f1e5f4..481096b7936 100644 --- a/crates/primitives-traits/src/transaction/signature.rs +++ b/crates/primitives-traits/src/transaction/signature.rs @@ -6,7 +6,7 @@ pub use alloy_primitives::Signature; #[cfg(test)] mod tests { use crate::crypto::secp256k1::recover_signer; - use alloy_primitives::{address, Signature, B256, U256}; + use alloy_primitives::{address, b256, Signature, U256}; use std::str::FromStr; #[test] @@ -22,9 +22,7 @@ mod tests { .unwrap(), false, ); - let hash = - B256::from_str("daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53") - .unwrap(); + let hash = b256!("0xdaf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"); let signer = recover_signer(&signature, hash).unwrap(); let expected = address!("0x9d8a62f656a8d1615c1294fd71e9cfb3e4855a4f"); assert_eq!(expected, signer); diff --git a/crates/primitives-traits/src/transaction/signed.rs b/crates/primitives-traits/src/transaction/signed.rs index 104555db0f7..08a6758d8d4 100644 --- a/crates/primitives-traits/src/transaction/signed.rs +++ b/crates/primitives-traits/src/transaction/signed.rs @@ -3,11 +3,11 @@ use crate::{InMemorySize, MaybeCompact, MaybeSerde, MaybeSerdeBincodeCompat}; use alloc::fmt; use alloy_consensus::{ - transaction::{Recovered, RlpEcdsaEncodableTx, SignerRecoverable}, + transaction::{Recovered, RlpEcdsaEncodableTx, SignerRecoverable, TxHashRef}, EthereumTxEnvelope, SignableTransaction, }; use alloy_eips::eip2718::{Decodable2718, Encodable2718}; -use alloy_primitives::{keccak256, Address, Signature, TxHash, B256}; +use alloy_primitives::{keccak256, Address, Signature, B256}; use alloy_rlp::{Decodable, Encodable}; use core::hash::Hash; @@ -45,10 +45,8 @@ pub trait SignedTransaction: + MaybeSerde + InMemorySize + SignerRecoverable + + TxHashRef { - /// Returns reference to transaction hash. - fn tx_hash(&self) -> &TxHash; - /// Returns whether this transaction type can be __broadcasted__ as full transaction over the /// network. /// @@ -136,15 +134,6 @@ where T: RlpEcdsaEncodableTx + SignableTransaction + Unpin, Self: Clone + PartialEq + Eq + Decodable + Decodable2718 + MaybeSerde + InMemorySize, { - fn tx_hash(&self) -> &TxHash { - match self { - Self::Legacy(tx) => tx.hash(), - Self::Eip2930(tx) => tx.hash(), - Self::Eip1559(tx) => tx.hash(), - Self::Eip7702(tx) => tx.hash(), - Self::Eip4844(tx) => tx.hash(), - } - } } #[cfg(feature = "op")] @@ -152,26 +141,7 @@ mod op { use super::*; use op_alloy_consensus::{OpPooledTransaction, OpTxEnvelope}; - impl SignedTransaction for OpPooledTransaction { - fn tx_hash(&self) -> &TxHash { - match self { - Self::Legacy(tx) => tx.hash(), - Self::Eip2930(tx) => tx.hash(), - Self::Eip1559(tx) => tx.hash(), - Self::Eip7702(tx) => tx.hash(), - } - } - } + impl SignedTransaction for OpPooledTransaction {} - impl SignedTransaction for OpTxEnvelope { - fn tx_hash(&self) -> &TxHash { - match self { - Self::Legacy(tx) => tx.hash(), - Self::Eip2930(tx) => tx.hash(), - Self::Eip1559(tx) => tx.hash(), - Self::Eip7702(tx) => tx.hash(), - Self::Deposit(tx) => tx.hash_ref(), - } - } - } + impl SignedTransaction for OpTxEnvelope {} } diff --git a/crates/prune/prune/src/builder.rs b/crates/prune/prune/src/builder.rs index 509ef6a5be8..1987c500da7 100644 --- a/crates/prune/prune/src/builder.rs +++ b/crates/prune/prune/src/builder.rs @@ -7,7 +7,8 @@ use reth_exex_types::FinishedExExHeight; use reth_primitives_traits::NodePrimitives; use reth_provider::{ providers::StaticFileProvider, BlockReader, DBProvider, DatabaseProviderFactory, - NodePrimitivesProvider, PruneCheckpointWriter, StaticFileProviderFactory, + NodePrimitivesProvider, PruneCheckpointReader, PruneCheckpointWriter, + StaticFileProviderFactory, }; use reth_prune_types::PruneModes; use std::time::Duration; @@ -80,6 +81,7 @@ impl PrunerBuilder { where PF: DatabaseProviderFactory< ProviderRW: PruneCheckpointWriter + + PruneCheckpointReader + BlockReader + StaticFileProviderFactory< Primitives: NodePrimitives, @@ -111,7 +113,8 @@ impl PrunerBuilder { Primitives: NodePrimitives, > + DBProvider + BlockReader - + PruneCheckpointWriter, + + PruneCheckpointWriter + + PruneCheckpointReader, { let segments = SegmentSet::::from_components(static_file_provider, self.segments); diff --git a/crates/prune/prune/src/segments/set.rs b/crates/prune/prune/src/segments/set.rs index 7d5db03714b..08e41bcdf75 100644 --- a/crates/prune/prune/src/segments/set.rs +++ b/crates/prune/prune/src/segments/set.rs @@ -6,8 +6,8 @@ use alloy_eips::eip2718::Encodable2718; use reth_db_api::{table::Value, transaction::DbTxMut}; use reth_primitives_traits::NodePrimitives; use reth_provider::{ - providers::StaticFileProvider, BlockReader, DBProvider, PruneCheckpointWriter, - StaticFileProviderFactory, + providers::StaticFileProvider, BlockReader, DBProvider, PruneCheckpointReader, + PruneCheckpointWriter, StaticFileProviderFactory, }; use reth_prune_types::PruneModes; @@ -51,6 +51,7 @@ where Primitives: NodePrimitives, > + DBProvider + PruneCheckpointWriter + + PruneCheckpointReader + BlockReader, { /// Creates a [`SegmentSet`] from an existing components, such as [`StaticFileProvider`] and diff --git a/crates/prune/prune/src/segments/user/transaction_lookup.rs b/crates/prune/prune/src/segments/user/transaction_lookup.rs index 92a69dfd127..dcf7c195d9e 100644 --- a/crates/prune/prune/src/segments/user/transaction_lookup.rs +++ b/crates/prune/prune/src/segments/user/transaction_lookup.rs @@ -6,9 +6,9 @@ use crate::{ use alloy_eips::eip2718::Encodable2718; use rayon::prelude::*; use reth_db_api::{tables, transaction::DbTxMut}; -use reth_provider::{BlockReader, DBProvider}; +use reth_provider::{BlockReader, DBProvider, PruneCheckpointReader}; use reth_prune_types::{PruneMode, PrunePurpose, PruneSegment, SegmentOutputCheckpoint}; -use tracing::{instrument, trace}; +use tracing::{debug, instrument, trace}; #[derive(Debug)] pub struct TransactionLookup { @@ -23,7 +23,8 @@ impl TransactionLookup { impl Segment for TransactionLookup where - Provider: DBProvider + BlockReader, + Provider: + DBProvider + BlockReader + PruneCheckpointReader, { fn segment(&self) -> PruneSegment { PruneSegment::TransactionLookup @@ -38,7 +39,28 @@ where } #[instrument(level = "trace", target = "pruner", skip(self, provider), ret)] - fn prune(&self, provider: &Provider, input: PruneInput) -> Result { + fn prune( + &self, + provider: &Provider, + mut input: PruneInput, + ) -> Result { + // It is not possible to prune TransactionLookup data for which we don't have transaction + // data. If the TransactionLookup checkpoint is lagging behind (which can happen e.g. when + // pre-merge history is dropped and then later tx lookup pruning is enabled) then we can + // only prune from the tx checkpoint and onwards. + if let Some(txs_checkpoint) = provider.get_prune_checkpoint(PruneSegment::Transactions)? && + input + .previous_checkpoint + .is_none_or(|checkpoint| checkpoint.block_number < txs_checkpoint.block_number) + { + input.previous_checkpoint = Some(txs_checkpoint); + debug!( + target: "pruner", + transactions_checkpoint = ?input.previous_checkpoint, + "No TransactionLookup checkpoint found, using Transactions checkpoint as fallback" + ); + } + let (start, end) = match input.get_next_tx_num_range(provider)? { Some(range) => range, None => { diff --git a/crates/prune/types/Cargo.toml b/crates/prune/types/Cargo.toml index 42a6d7f2082..5215eea7257 100644 --- a/crates/prune/types/Cargo.toml +++ b/crates/prune/types/Cargo.toml @@ -27,7 +27,6 @@ reth-codecs.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } serde.workspace = true -modular-bitfield.workspace = true arbitrary = { workspace = true, features = ["derive"] } assert_matches.workspace = true proptest.workspace = true @@ -46,6 +45,7 @@ std = [ "thiserror/std", ] test-utils = [ + "std", "dep:arbitrary", "reth-codecs?/test-utils", ] diff --git a/crates/prune/types/src/lib.rs b/crates/prune/types/src/lib.rs index c1d268a0fb7..639d3bde920 100644 --- a/crates/prune/types/src/lib.rs +++ b/crates/prune/types/src/lib.rs @@ -96,12 +96,11 @@ impl ReceiptsLogPruneConfig { let mut lowest = None; for mode in self.values() { - if mode.is_distance() { - if let Some((block, _)) = + if mode.is_distance() && + let Some((block, _)) = mode.prune_target_block(tip, PruneSegment::ContractLogs, PrunePurpose::User)? - { - lowest = Some(lowest.unwrap_or(u64::MAX).min(block)); - } + { + lowest = Some(lowest.unwrap_or(u64::MAX).min(block)); } } diff --git a/crates/prune/types/src/mode.rs b/crates/prune/types/src/mode.rs index 42d34b30cc7..4c09ccfa639 100644 --- a/crates/prune/types/src/mode.rs +++ b/crates/prune/types/src/mode.rs @@ -18,6 +18,7 @@ pub enum PruneMode { } #[cfg(any(test, feature = "test-utils"))] +#[allow(clippy::derivable_impls)] impl Default for PruneMode { fn default() -> Self { Self::Full diff --git a/crates/prune/types/src/segment.rs b/crates/prune/types/src/segment.rs index 443acf1ed79..e131f353fe3 100644 --- a/crates/prune/types/src/segment.rs +++ b/crates/prune/types/src/segment.rs @@ -28,6 +28,14 @@ pub enum PruneSegment { Transactions, } +#[cfg(test)] +#[allow(clippy::derivable_impls)] +impl Default for PruneSegment { + fn default() -> Self { + Self::SenderRecovery + } +} + impl PruneSegment { /// Returns minimum number of blocks to keep in the database for this segment. pub const fn min_blocks(&self, purpose: PrunePurpose) -> u64 { @@ -42,6 +50,16 @@ impl PruneSegment { Self::Receipts => MINIMUM_PRUNING_DISTANCE, } } + + /// Returns true if this is [`Self::AccountHistory`]. + pub const fn is_account_history(&self) -> bool { + matches!(self, Self::AccountHistory) + } + + /// Returns true if this is [`Self::StorageHistory`]. + pub const fn is_storage_history(&self) -> bool { + matches!(self, Self::StorageHistory) + } } /// Prune purpose. @@ -72,10 +90,3 @@ pub enum PruneSegmentError { #[error("the configuration provided for {0} is invalid")] Configuration(PruneSegment), } - -#[cfg(test)] -impl Default for PruneSegment { - fn default() -> Self { - Self::SenderRecovery - } -} diff --git a/crates/prune/types/src/target.rs b/crates/prune/types/src/target.rs index a77b204e1ba..574a0e2e555 100644 --- a/crates/prune/types/src/target.rs +++ b/crates/prune/types/src/target.rs @@ -2,7 +2,7 @@ use alloy_primitives::BlockNumber; use derive_more::Display; use thiserror::Error; -use crate::{PruneMode, ReceiptsLogPruneConfig}; +use crate::{PruneCheckpoint, PruneMode, PruneSegment, ReceiptsLogPruneConfig}; /// Minimum distance from the tip necessary for the node to work correctly: /// 1. Minimum 2 epochs (32 blocks per epoch) required to handle any reorg according to the @@ -121,33 +121,52 @@ impl PruneModes { self == &Self::none() } - /// Returns true if target block is within history limit + /// Returns an error if we can't unwind to the targeted block because the target block is + /// outside the range. + /// + /// This is only relevant for certain tables that are required by other stages + /// + /// See also pub fn ensure_unwind_target_unpruned( &self, latest_block: u64, target_block: u64, + checkpoints: &[(PruneSegment, PruneCheckpoint)], ) -> Result<(), UnwindTargetPrunedError> { let distance = latest_block.saturating_sub(target_block); - [ - (self.account_history, HistoryType::AccountHistory), - (self.storage_history, HistoryType::StorageHistory), - ] - .iter() - .find_map(|(prune_mode, history_type)| { + for (prune_mode, history_type, checkpoint) in &[ + ( + self.account_history, + HistoryType::AccountHistory, + checkpoints.iter().find(|(segment, _)| segment.is_account_history()), + ), + ( + self.storage_history, + HistoryType::StorageHistory, + checkpoints.iter().find(|(segment, _)| segment.is_storage_history()), + ), + ] { if let Some(PruneMode::Distance(limit)) = prune_mode { - (distance > *limit).then_some(Err( - UnwindTargetPrunedError::TargetBeyondHistoryLimit { - latest_block, - target_block, - history_type: history_type.clone(), - limit: *limit, - }, - )) - } else { - None + // check if distance exceeds the configured limit + if distance > *limit { + // but only if have haven't pruned the target yet, if we dont have a checkpoint + // yet, it's fully unpruned yet + let pruned_height = checkpoint + .and_then(|checkpoint| checkpoint.1.block_number) + .unwrap_or(latest_block); + if pruned_height >= target_block { + // we've pruned the target block already and can't unwind past it + return Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit { + latest_block, + target_block, + history_type: history_type.clone(), + limit: *limit, + }) + } + } } - }) - .unwrap_or(Ok(())) + } + Ok(()) } } @@ -217,4 +236,165 @@ mod tests { Err(err) if err.to_string() == "invalid value: string \"full\", expected prune mode that leaves at least 10 blocks in the database" ); } + + #[test] + fn test_unwind_target_unpruned() { + // Test case 1: No pruning configured - should always succeed + let prune_modes = PruneModes::none(); + assert!(prune_modes.ensure_unwind_target_unpruned(1000, 500, &[]).is_ok()); + assert!(prune_modes.ensure_unwind_target_unpruned(1000, 0, &[]).is_ok()); + + // Test case 2: Distance pruning within limit - should succeed + let prune_modes = PruneModes { + account_history: Some(PruneMode::Distance(100)), + storage_history: Some(PruneMode::Distance(100)), + ..Default::default() + }; + // Distance is 50, limit is 100 - OK + assert!(prune_modes.ensure_unwind_target_unpruned(1000, 950, &[]).is_ok()); + + // Test case 3: Distance exceeds limit with no checkpoint + // NOTE: Current implementation assumes pruned_height = latest_block when no checkpoint + // exists This means it will fail because it assumes we've pruned up to block 1000 > + // target 800 + let prune_modes = + PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() }; + // Distance is 200 > 100, no checkpoint - current impl treats as pruned up to latest_block + let result = prune_modes.ensure_unwind_target_unpruned(1000, 800, &[]); + assert_matches!( + result, + Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit { + latest_block: 1000, + target_block: 800, + history_type: HistoryType::AccountHistory, + limit: 100 + }) + ); + + // Test case 4: Distance exceeds limit and target is pruned - should fail + let prune_modes = + PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() }; + let checkpoints = vec![( + PruneSegment::AccountHistory, + PruneCheckpoint { + block_number: Some(850), + tx_number: None, + prune_mode: PruneMode::Distance(100), + }, + )]; + // Distance is 200 > 100, and checkpoint shows we've pruned up to block 850 > target 800 + let result = prune_modes.ensure_unwind_target_unpruned(1000, 800, &checkpoints); + assert_matches!( + result, + Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit { + latest_block: 1000, + target_block: 800, + history_type: HistoryType::AccountHistory, + limit: 100 + }) + ); + + // Test case 5: Storage history exceeds limit and is pruned - should fail + let prune_modes = + PruneModes { storage_history: Some(PruneMode::Distance(50)), ..Default::default() }; + let checkpoints = vec![( + PruneSegment::StorageHistory, + PruneCheckpoint { + block_number: Some(960), + tx_number: None, + prune_mode: PruneMode::Distance(50), + }, + )]; + // Distance is 100 > 50, and checkpoint shows we've pruned up to block 960 > target 900 + let result = prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints); + assert_matches!( + result, + Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit { + latest_block: 1000, + target_block: 900, + history_type: HistoryType::StorageHistory, + limit: 50 + }) + ); + + // Test case 6: Distance exceeds limit but target block not pruned yet - should succeed + let prune_modes = + PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() }; + let checkpoints = vec![( + PruneSegment::AccountHistory, + PruneCheckpoint { + block_number: Some(700), + tx_number: None, + prune_mode: PruneMode::Distance(100), + }, + )]; + // Distance is 200 > 100, but checkpoint shows we've only pruned up to block 700 < target + // 800 + assert!(prune_modes.ensure_unwind_target_unpruned(1000, 800, &checkpoints).is_ok()); + + // Test case 7: Both account and storage history configured, only one fails + let prune_modes = PruneModes { + account_history: Some(PruneMode::Distance(200)), + storage_history: Some(PruneMode::Distance(50)), + ..Default::default() + }; + let checkpoints = vec![ + ( + PruneSegment::AccountHistory, + PruneCheckpoint { + block_number: Some(700), + tx_number: None, + prune_mode: PruneMode::Distance(200), + }, + ), + ( + PruneSegment::StorageHistory, + PruneCheckpoint { + block_number: Some(960), + tx_number: None, + prune_mode: PruneMode::Distance(50), + }, + ), + ]; + // For target 900: account history OK (distance 100 < 200), storage history fails (distance + // 100 > 50, pruned at 960) + let result = prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints); + assert_matches!( + result, + Err(UnwindTargetPrunedError::TargetBeyondHistoryLimit { + latest_block: 1000, + target_block: 900, + history_type: HistoryType::StorageHistory, + limit: 50 + }) + ); + + // Test case 8: Edge case - exact boundary + let prune_modes = + PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() }; + let checkpoints = vec![( + PruneSegment::AccountHistory, + PruneCheckpoint { + block_number: Some(900), + tx_number: None, + prune_mode: PruneMode::Distance(100), + }, + )]; + // Distance is exactly 100, checkpoint at exactly the target block + assert!(prune_modes.ensure_unwind_target_unpruned(1000, 900, &checkpoints).is_ok()); + + // Test case 9: Full pruning mode - should succeed (no distance check) + let prune_modes = PruneModes { + account_history: Some(PruneMode::Full), + storage_history: Some(PruneMode::Full), + ..Default::default() + }; + assert!(prune_modes.ensure_unwind_target_unpruned(1000, 0, &[]).is_ok()); + + // Test case 10: Edge case - saturating subtraction (target > latest) + let prune_modes = + PruneModes { account_history: Some(PruneMode::Distance(100)), ..Default::default() }; + // Target block (1500) > latest block (1000) - distance should be 0 + assert!(prune_modes.ensure_unwind_target_unpruned(1000, 1500, &[]).is_ok()); + } } diff --git a/crates/ress/provider/src/lib.rs b/crates/ress/provider/src/lib.rs index 41318ebaaf1..c157b5adf41 100644 --- a/crates/ress/provider/src/lib.rs +++ b/crates/ress/provider/src/lib.rs @@ -120,19 +120,15 @@ where let mut executed = self.pending_state.executed_block(&ancestor_hash); // If it's not present, attempt to lookup invalid block. - if executed.is_none() { - if let Some(invalid) = + if executed.is_none() && + let Some(invalid) = self.pending_state.invalid_recovered_block(&ancestor_hash) - { - trace!(target: "reth::ress_provider", %block_hash, %ancestor_hash, "Using invalid ancestor block for witness construction"); - executed = Some(ExecutedBlockWithTrieUpdates { - block: ExecutedBlock { - recovered_block: invalid, - ..Default::default() - }, - trie: ExecutedTrieUpdates::empty(), - }); - } + { + trace!(target: "reth::ress_provider", %block_hash, %ancestor_hash, "Using invalid ancestor block for witness construction"); + executed = Some(ExecutedBlockWithTrieUpdates { + block: ExecutedBlock { recovered_block: invalid, ..Default::default() }, + trie: ExecutedTrieUpdates::empty(), + }); } let Some(executed) = executed else { diff --git a/crates/rpc/ipc/src/server/mod.rs b/crates/rpc/ipc/src/server/mod.rs index ece2eef7803..b6114938d2b 100644 --- a/crates/rpc/ipc/src/server/mod.rs +++ b/crates/rpc/ipc/src/server/mod.rs @@ -144,11 +144,11 @@ where { // set permissions only on unix use std::os::unix::fs::PermissionsExt; - if let Some(perms_str) = &self.cfg.ipc_socket_permissions { - if let Ok(mode) = u32::from_str_radix(&perms_str.replace("0o", ""), 8) { - let perms = std::fs::Permissions::from_mode(mode); - let _ = std::fs::set_permissions(&self.endpoint, perms); - } + if let Some(perms_str) = &self.cfg.ipc_socket_permissions && + let Ok(mode) = u32::from_str_radix(&perms_str.replace("0o", ""), 8) + { + let perms = std::fs::Permissions::from_mode(mode); + let _ = std::fs::set_permissions(&self.endpoint, perms); } } listener diff --git a/crates/rpc/rpc-api/src/admin.rs b/crates/rpc/rpc-api/src/admin.rs index e6484937783..2c6de0bcd1b 100644 --- a/crates/rpc/rpc-api/src/admin.rs +++ b/crates/rpc/rpc-api/src/admin.rs @@ -45,4 +45,9 @@ pub trait AdminApi { /// Returns the ENR of the node. #[method(name = "nodeInfo")] async fn node_info(&self) -> RpcResult; + + /// Clears all transactions from the transaction pool. + /// Returns the number of transactions that were removed from the pool. + #[method(name = "clearTxpool")] + async fn clear_txpool(&self) -> RpcResult; } diff --git a/crates/rpc/rpc-builder/Cargo.toml b/crates/rpc/rpc-builder/Cargo.toml index 12da375f143..e7178405b3b 100644 --- a/crates/rpc/rpc-builder/Cargo.toml +++ b/crates/rpc/rpc-builder/Cargo.toml @@ -43,6 +43,7 @@ reth-metrics = { workspace = true, features = ["common"] } metrics.workspace = true # misc +dyn-clone.workspace = true serde = { workspace = true, features = ["derive"] } thiserror.workspace = true tracing.workspace = true @@ -62,9 +63,7 @@ reth-rpc-api = { workspace = true, features = ["client"] } reth-rpc-engine-api.workspace = true reth-tracing.workspace = true reth-transaction-pool = { workspace = true, features = ["test-utils"] } -reth-rpc-convert.workspace = true reth-engine-primitives.workspace = true -reth-engine-tree.workspace = true reth-node-ethereum.workspace = true alloy-primitives.workspace = true diff --git a/crates/rpc/rpc-builder/src/config.rs b/crates/rpc/rpc-builder/src/config.rs index e64a08aa313..a8349a17524 100644 --- a/crates/rpc/rpc-builder/src/config.rs +++ b/crates/rpc/rpc-builder/src/config.rs @@ -104,6 +104,7 @@ impl RethRpcServerConfig for RpcServerArgs { .gpo_config(self.gas_price_oracle_config()) .proof_permits(self.rpc_proof_permits) .pending_block_kind(self.rpc_pending_block) + .raw_tx_forwarder(self.rpc_forwarder.clone()) } fn flashbots_config(&self) -> ValidationApiConfig { diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 76e889eec63..5377fb87598 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use crate::{auth::AuthRpcModule, error::WsHttpSamePortError, metrics::RpcRequestMetrics}; -use alloy_network::Ethereum; +use alloy_network::{Ethereum, IntoWallet}; use alloy_provider::{fillers::RecommendedFillers, Provider, ProviderBuilder}; use core::marker::PhantomData; use error::{ConflictingModules, RpcError, ServerKind}; @@ -310,7 +310,7 @@ where + CanonStateSubscriptions + AccountReader + ChangeSetReader, - Pool: TransactionPool + 'static, + Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, EvmConfig: ConfigureEvm + 'static, Consensus: FullConsensus + Clone + 'static, @@ -453,7 +453,7 @@ pub struct RpcModuleConfigBuilder { impl RpcModuleConfigBuilder { /// Configures a custom eth namespace config - pub const fn eth(mut self, eth: EthConfig) -> Self { + pub fn eth(mut self, eth: EthConfig) -> Self { self.eth = Some(eth); self } @@ -547,7 +547,7 @@ where { let blocking_pool_guard = BlockingTaskGuard::new(config.eth.max_tracing_requests); - let eth = EthHandlers::bootstrap(config.eth, executor.clone(), eth_api); + let eth = EthHandlers::bootstrap(config.eth.clone(), executor.clone(), eth_api); Self { provider, @@ -619,11 +619,12 @@ where EvmConfig: ConfigureEvm, { /// Instantiates `AdminApi` - pub fn admin_api(&self) -> AdminApi + pub fn admin_api(&self) -> AdminApi where Network: Peers, + Pool: TransactionPool + Clone + 'static, { - AdminApi::new(self.network.clone(), self.provider.chain_spec()) + AdminApi::new(self.network.clone(), self.provider.chain_spec(), self.pool.clone()) } /// Instantiates `Web3Api` @@ -635,6 +636,7 @@ where pub fn register_admin(&mut self) -> &mut Self where Network: Peers, + Pool: TransactionPool + Clone + 'static, { let adminapi = self.admin_api(); self.modules.insert(RethRpcModule::Admin, adminapi.into_rpc().into()); @@ -786,7 +788,11 @@ where /// /// If called outside of the tokio runtime. See also [`Self::eth_api`] pub fn trace_api(&self) -> TraceApi { - TraceApi::new(self.eth_api().clone(), self.blocking_pool_guard.clone(), self.eth_config) + TraceApi::new( + self.eth_api().clone(), + self.blocking_pool_guard.clone(), + self.eth_config.clone(), + ) } /// Instantiates [`EthBundle`] Api @@ -838,7 +844,7 @@ where + CanonStateSubscriptions + AccountReader + ChangeSetReader, - Pool: TransactionPool + 'static, + Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, EthApi: FullEthApiServer, EvmConfig: ConfigureEvm + 'static, @@ -915,16 +921,17 @@ where let namespaces: Vec<_> = namespaces.collect(); namespaces .iter() - .copied() .map(|namespace| { self.modules - .entry(namespace) - .or_insert_with(|| match namespace { - RethRpcModule::Admin => { - AdminApi::new(self.network.clone(), self.provider.chain_spec()) - .into_rpc() - .into() - } + .entry(namespace.clone()) + .or_insert_with(|| match namespace.clone() { + RethRpcModule::Admin => AdminApi::new( + self.network.clone(), + self.provider.chain_spec(), + self.pool.clone(), + ) + .into_rpc() + .into(), RethRpcModule::Debug => { DebugApi::new(eth_api.clone(), self.blocking_pool_guard.clone()) .into_rpc() @@ -953,14 +960,14 @@ where RethRpcModule::Trace => TraceApi::new( eth_api.clone(), self.blocking_pool_guard.clone(), - self.eth_config, + self.eth_config.clone(), ) .into_rpc() .into(), RethRpcModule::Web3 => Web3Api::new(self.network.clone()).into_rpc().into(), RethRpcModule::Txpool => TxPoolApi::new( self.eth.api.pool().clone(), - self.eth.api.tx_resp_builder().clone(), + dyn_clone::clone(self.eth.api.tx_resp_builder()), ) .into_rpc() .into(), @@ -981,7 +988,9 @@ where // only relevant for Ethereum and configured in `EthereumAddOns` // implementation // TODO: can we get rid of this here? - RethRpcModule::Flashbots => Default::default(), + // Custom modules are not handled here - they should be registered via + // extend_rpc_modules + RethRpcModule::Flashbots | RethRpcModule::Other(_) => Default::default(), RethRpcModule::Miner => MinerApi::default().into_rpc().into(), RethRpcModule::Mev => { EthSimBundle::new(eth_api.clone(), self.blocking_pool_guard.clone()) @@ -1570,9 +1579,9 @@ impl TransportRpcModuleConfig { let ws_modules = self.ws.as_ref().map(RpcModuleSelection::to_selection).unwrap_or_default(); - let http_not_ws = http_modules.difference(&ws_modules).copied().collect(); - let ws_not_http = ws_modules.difference(&http_modules).copied().collect(); - let overlap = http_modules.intersection(&ws_modules).copied().collect(); + let http_not_ws = http_modules.difference(&ws_modules).cloned().collect(); + let ws_not_http = ws_modules.difference(&http_modules).cloned().collect(); + let overlap = http_modules.intersection(&ws_modules).cloned().collect(); Err(WsHttpSamePortError::ConflictingModules(Box::new(ConflictingModules { overlap, @@ -1708,7 +1717,7 @@ impl TransportRpcModules { /// Returns all unique endpoints installed for the given module. /// /// Note: In case of duplicate method names this only record the first occurrence. - pub fn methods_by_module(&self, module: RethRpcModule) -> Methods { + pub fn methods_by_module(&self, module: RethRpcModule) -> Methods { self.methods_by(|name| name.starts_with(module.as_str())) } @@ -2089,6 +2098,21 @@ impl RpcServerHandle { self.new_http_provider_for() } + /// Returns a new [`alloy_network::Ethereum`] http provider with its recommended fillers and + /// installed wallet. + pub fn eth_http_provider_with_wallet( + &self, + wallet: W, + ) -> Option + Clone + Unpin + 'static> + where + W: IntoWallet, + { + let rpc_url = self.http_url()?; + let provider = + ProviderBuilder::new().wallet(wallet).connect_http(rpc_url.parse().expect("valid url")); + Some(provider) + } + /// Returns an http provider from the rpc server handle for the /// specified [`alloy_network::Network`]. /// @@ -2111,6 +2135,24 @@ impl RpcServerHandle { self.new_ws_provider_for().await } + /// Returns a new [`alloy_network::Ethereum`] ws provider with its recommended fillers and + /// installed wallet. + pub async fn eth_ws_provider_with_wallet( + &self, + wallet: W, + ) -> Option + Clone + Unpin + 'static> + where + W: IntoWallet, + { + let rpc_url = self.ws_url()?; + let provider = ProviderBuilder::new() + .wallet(wallet) + .connect(&rpc_url) + .await + .expect("failed to create ws client"); + Some(provider) + } + /// Returns an ws provider from the rpc server handle for the /// specified [`alloy_network::Network`]. /// diff --git a/crates/rpc/rpc-convert/Cargo.toml b/crates/rpc/rpc-convert/Cargo.toml index abaf8d8d04b..af43e9c54a2 100644 --- a/crates/rpc/rpc-convert/Cargo.toml +++ b/crates/rpc/rpc-convert/Cargo.toml @@ -42,6 +42,12 @@ jsonrpsee-types.workspace = true # error thiserror.workspace = true +auto_impl.workspace = true +dyn-clone.workspace = true + +[dev-dependencies] +serde_json.workspace = true + [features] default = [] op = [ diff --git a/crates/rpc/rpc-convert/src/rpc.rs b/crates/rpc/rpc-convert/src/rpc.rs index bd5555a3013..cf67bc11add 100644 --- a/crates/rpc/rpc-convert/src/rpc.rs +++ b/crates/rpc/rpc-convert/src/rpc.rs @@ -1,8 +1,6 @@ use std::{fmt::Debug, future::Future}; -use alloy_consensus::{ - EthereumTxEnvelope, EthereumTypedTransaction, SignableTransaction, TxEip4844, -}; +use alloy_consensus::{EthereumTxEnvelope, SignableTransaction, TxEip4844}; use alloy_json_rpc::RpcObject; use alloy_network::{ primitives::HeaderResponse, Network, ReceiptResponse, TransactionResponse, TxSigner, @@ -78,24 +76,7 @@ impl SignableTxRequest> for TransactionRequest { let mut tx = self.build_typed_tx().map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; let signature = signer.sign_transaction(&mut tx).await?; - let signed = match tx { - EthereumTypedTransaction::Legacy(tx) => { - EthereumTxEnvelope::Legacy(tx.into_signed(signature)) - } - EthereumTypedTransaction::Eip2930(tx) => { - EthereumTxEnvelope::Eip2930(tx.into_signed(signature)) - } - EthereumTypedTransaction::Eip1559(tx) => { - EthereumTxEnvelope::Eip1559(tx.into_signed(signature)) - } - EthereumTypedTransaction::Eip4844(tx) => { - EthereumTxEnvelope::Eip4844(TxEip4844::from(tx).into_signed(signature)) - } - EthereumTypedTransaction::Eip7702(tx) => { - EthereumTxEnvelope::Eip7702(tx.into_signed(signature)) - } - }; - Ok(signed) + Ok(tx.into_signed(signature).into()) } } @@ -110,23 +91,12 @@ impl SignableTxRequest let mut tx = self.build_typed_tx().map_err(|_| SignTxRequestError::InvalidTransactionRequest)?; let signature = signer.sign_transaction(&mut tx).await?; - let signed = match tx { - op_alloy_consensus::OpTypedTransaction::Legacy(tx) => { - op_alloy_consensus::OpTxEnvelope::Legacy(tx.into_signed(signature)) - } - op_alloy_consensus::OpTypedTransaction::Eip2930(tx) => { - op_alloy_consensus::OpTxEnvelope::Eip2930(tx.into_signed(signature)) - } - op_alloy_consensus::OpTypedTransaction::Eip1559(tx) => { - op_alloy_consensus::OpTxEnvelope::Eip1559(tx.into_signed(signature)) - } - op_alloy_consensus::OpTypedTransaction::Eip7702(tx) => { - op_alloy_consensus::OpTxEnvelope::Eip7702(tx.into_signed(signature)) - } - op_alloy_consensus::OpTypedTransaction::Deposit(_) => { - return Err(SignTxRequestError::InvalidTransactionRequest); - } - }; - Ok(signed) + + // sanity check + if tx.is_deposit() { + return Err(SignTxRequestError::InvalidTransactionRequest); + } + + Ok(tx.into_signed(signature).into()) } } diff --git a/crates/rpc/rpc-convert/src/transaction.rs b/crates/rpc/rpc-convert/src/transaction.rs index affba2aa0a4..b8fb25c66c4 100644 --- a/crates/rpc/rpc-convert/src/transaction.rs +++ b/crates/rpc/rpc-convert/src/transaction.rs @@ -14,22 +14,24 @@ use alloy_rpc_types_eth::{ Transaction, TransactionInfo, }; use core::error; +use dyn_clone::DynClone; use reth_evm::{ revm::context_interface::{either::Either, Block}, - ConfigureEvm, TxEnvFor, + ConfigureEvm, SpecFor, TxEnvFor, }; use reth_primitives_traits::{ - HeaderTy, NodePrimitives, SealedHeader, SealedHeaderFor, TransactionMeta, TxTy, + BlockTy, HeaderTy, NodePrimitives, SealedBlock, SealedHeader, SealedHeaderFor, TransactionMeta, + TxTy, }; use revm_context::{BlockEnv, CfgEnv, TxEnv}; -use std::{borrow::Cow, convert::Infallible, error::Error, fmt::Debug, marker::PhantomData}; +use std::{convert::Infallible, error::Error, fmt::Debug, marker::PhantomData}; use thiserror::Error; /// Input for [`RpcConvert::convert_receipts`]. #[derive(Debug, Clone)] pub struct ConvertReceiptInput<'a, N: NodePrimitives> { /// Primitive receipt. - pub receipt: Cow<'a, N::Receipt>, + pub receipt: N::Receipt, /// Transaction the receipt corresponds to. pub tx: Recovered<&'a N::SignedTx>, /// Gas used by the transaction. @@ -54,12 +56,29 @@ pub trait ReceiptConverter: Debug + 'static { &self, receipts: Vec>, ) -> Result, Self::Error>; + + /// Converts a set of primitive receipts to RPC representations. It is guaranteed that all + /// receipts are from `block`. + fn convert_receipts_with_block( + &self, + receipts: Vec>, + _block: &SealedBlock, + ) -> Result, Self::Error> { + self.convert_receipts(receipts) + } } /// A type that knows how to convert a consensus header into an RPC header. pub trait HeaderConverter: Debug + Send + Sync + Unpin + Clone + 'static { + /// An associated RPC conversion error. + type Err: error::Error; + /// Converts a consensus header into an RPC header. - fn convert_header(&self, header: SealedHeader, block_size: usize) -> Rpc; + fn convert_header( + &self, + header: SealedHeader, + block_size: usize, + ) -> Result; } /// Default implementation of [`HeaderConverter`] that uses [`FromConsensusHeader`] to convert @@ -68,8 +87,14 @@ impl HeaderConverter for () where Rpc: FromConsensusHeader, { - fn convert_header(&self, header: SealedHeader, block_size: usize) -> Rpc { - Rpc::from_consensus_header(header, block_size) + type Err = Infallible; + + fn convert_header( + &self, + header: SealedHeader, + block_size: usize, + ) -> Result { + Ok(Rpc::from_consensus_header(header, block_size)) } } @@ -93,7 +118,8 @@ impl FromConsensusHeader for alloy_rpc_types_eth::Header { /// A generic implementation [`RpcConverter`] should be preferred over a manual implementation. As /// long as its trait bound requirements are met, the implementation is created automatically and /// can be used in RPC method handlers for all the conversions. -pub trait RpcConvert: Send + Sync + Unpin + Clone + Debug + 'static { +#[auto_impl::auto_impl(&, Box, Arc)] +pub trait RpcConvert: Send + Sync + Unpin + Debug + DynClone + 'static { /// Associated lower layer consensus types to convert from and into types of [`Self::Network`]. type Primitives: NodePrimitives; @@ -107,6 +133,9 @@ pub trait RpcConvert: Send + Sync + Unpin + Clone + Debug + 'static { /// An associated RPC conversion error. type Error: error::Error + Into>; + /// The EVM specification identifier. + type Spec; + /// Wrapper for `fill()` with default `TransactionInfo` /// Create a new rpc transaction result for a _pending_ signed transaction, setting block /// environment related fields to `None`. @@ -137,10 +166,10 @@ pub trait RpcConvert: Send + Sync + Unpin + Clone + Debug + 'static { /// Creates a transaction environment for execution based on `request` with corresponding /// `cfg_env` and `block_env`. - fn tx_env( + fn tx_env( &self, request: RpcTxReq, - cfg_env: &CfgEnv, + cfg_env: &CfgEnv, block_env: &BlockEnv, ) -> Result; @@ -151,6 +180,16 @@ pub trait RpcConvert: Send + Sync + Unpin + Clone + Debug + 'static { receipts: Vec>, ) -> Result>, Self::Error>; + /// Converts a set of primitive receipts to RPC representations. It is guaranteed that all + /// receipts are from the same block. + /// + /// Also accepts the corresponding block in case the receipt requires additional metadata. + fn convert_receipts_with_block( + &self, + receipts: Vec>, + block: &SealedBlock>, + ) -> Result>, Self::Error>; + /// Converts a primitive header to an RPC header. fn convert_header( &self, @@ -159,6 +198,11 @@ pub trait RpcConvert: Send + Sync + Unpin + Clone + Debug + 'static { ) -> Result, Self::Error>; } +dyn_clone::clone_trait_object!( + + RpcConvert +); + /// Converts `self` into `T`. The opposite of [`FromConsensusTx`]. /// /// Should create an RPC transaction response object based on a consensus transaction, its signer @@ -174,10 +218,12 @@ pub trait IntoRpcTx { /// An additional context, usually [`TransactionInfo`] in a wrapper that carries some /// implementation specific extra information. type TxInfo; + /// An associated RPC conversion error. + type Err: error::Error; /// Performs the conversion consuming `self` with `signer` and `tx_info`. See [`IntoRpcTx`] /// for details. - fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> T; + fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> Result; } /// Converts `T` into `self`. It is reciprocal of [`IntoRpcTx`]. @@ -191,23 +237,30 @@ pub trait IntoRpcTx { /// Prefer using [`IntoRpcTx`] over using [`FromConsensusTx`] when specifying trait bounds on a /// generic function. This way, types that directly implement [`IntoRpcTx`] can be used as arguments /// as well. -pub trait FromConsensusTx { +pub trait FromConsensusTx: Sized { /// An additional context, usually [`TransactionInfo`] in a wrapper that carries some /// implementation specific extra information. type TxInfo; + /// An associated RPC conversion error. + type Err: error::Error; /// Performs the conversion consuming `tx` with `signer` and `tx_info`. See [`FromConsensusTx`] /// for details. - fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self; + fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Result; } impl> FromConsensusTx for Transaction { type TxInfo = TransactionInfo; + type Err = Infallible; - fn from_consensus_tx(tx: TxIn, signer: Address, tx_info: Self::TxInfo) -> Self { - Self::from_transaction(Recovered::new_unchecked(tx.into(), signer), tx_info) + fn from_consensus_tx( + tx: TxIn, + signer: Address, + tx_info: Self::TxInfo, + ) -> Result { + Ok(Self::from_transaction(Recovered::new_unchecked(tx.into(), signer), tx_info)) } } @@ -215,10 +268,12 @@ impl IntoRpcTx for ConsensusTx where ConsensusTx: alloy_consensus::Transaction, RpcTx: FromConsensusTx, + >::Err: Debug, { type TxInfo = RpcTx::TxInfo; + type Err = >::Err; - fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> RpcTx { + fn into_rpc_tx(self, signer: Address, tx_info: Self::TxInfo) -> Result { RpcTx::from_consensus_tx(self, signer, tx_info) } } @@ -254,7 +309,7 @@ impl RpcTxConverter for () where Tx: IntoRpcTx, { - type Err = Infallible; + type Err = Tx::Err; fn convert_rpc_tx( &self, @@ -262,7 +317,7 @@ where signer: Address, tx_info: Tx::TxInfo, ) -> Result { - Ok(tx.into_rpc_tx(signer, tx_info)) + tx.into_rpc_tx(signer, tx_info) } } @@ -369,6 +424,79 @@ impl TryIntoSimTx> for TransactionRequest { } } +/// Converts `TxReq` into `TxEnv`. +/// +/// Where: +/// * `TxReq` is a transaction request received from an RPC API +/// * `TxEnv` is the corresponding transaction environment for execution +/// +/// The `TxEnvConverter` has two blanket implementations: +/// * `()` assuming `TxReq` implements [`TryIntoTxEnv`] and is used as default for [`RpcConverter`]. +/// * `Fn(TxReq, &CfgEnv, &BlockEnv) -> Result` and can be applied using +/// [`RpcConverter::with_tx_env_converter`]. +/// +/// One should prefer to implement [`TryIntoTxEnv`] for `TxReq` to get the `TxEnvConverter` +/// implementation for free, thanks to the blanket implementation, unless the conversion requires +/// more context. For example, some configuration parameters or access handles to database, network, +/// etc. +pub trait TxEnvConverter: + Debug + Send + Sync + Unpin + Clone + 'static +{ + /// An associated error that can occur during conversion. + type Error; + + /// Converts a rpc transaction request into a transaction environment. + /// + /// See [`TxEnvConverter`] for more information. + fn convert_tx_env( + &self, + tx_req: TxReq, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result; +} + +impl TxEnvConverter for () +where + TxReq: TryIntoTxEnv, +{ + type Error = TxReq::Err; + + fn convert_tx_env( + &self, + tx_req: TxReq, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result { + tx_req.try_into_tx_env(cfg_env, block_env) + } +} + +/// Converts rpc transaction requests into transaction environment using a closure. +impl TxEnvConverter for F +where + F: Fn(TxReq, &CfgEnv, &BlockEnv) -> Result + + Debug + + Send + + Sync + + Unpin + + Clone + + 'static, + TxReq: Clone, + E: error::Error + Send + Sync + 'static, +{ + type Error = E; + + fn convert_tx_env( + &self, + tx_req: TxReq, + cfg_env: &CfgEnv, + block_env: &BlockEnv, + ) -> Result { + self(tx_req, cfg_env, block_env) + } +} + /// Converts `self` into `T`. /// /// Should create an executable transaction environment using [`TransactionRequest`]. @@ -499,18 +627,29 @@ pub struct TransactionConversionError(String); /// network and EVM associated primitives: /// * [`FromConsensusTx`]: from signed transaction into RPC response object. /// * [`TryIntoSimTx`]: from RPC transaction request into a simulated transaction. -/// * [`TryIntoTxEnv`]: from RPC transaction request into an executable transaction. +/// * [`TryIntoTxEnv`] or [`TxEnvConverter`]: from RPC transaction request into an executable +/// transaction. /// * [`TxInfoMapper`]: from [`TransactionInfo`] into [`FromConsensusTx::TxInfo`]. Should be /// implemented for a dedicated struct that is assigned to `Map`. If [`FromConsensusTx::TxInfo`] /// is [`TransactionInfo`] then `()` can be used as `Map` which trivially passes over the input /// object. #[derive(Debug)] -pub struct RpcConverter { +pub struct RpcConverter< + Network, + Evm, + Receipt, + Header = (), + Map = (), + SimTx = (), + RpcTx = (), + TxEnv = (), +> { network: PhantomData, evm: PhantomData, receipt_converter: Receipt, header_converter: Header, mapper: Map, + tx_env_converter: TxEnv, sim_tx_converter: SimTx, rpc_tx_converter: RpcTx, } @@ -524,17 +663,20 @@ impl RpcConverter { receipt_converter, header_converter: (), mapper: (), + tx_env_converter: (), sim_tx_converter: (), rpc_tx_converter: (), } } } -impl - RpcConverter +impl + RpcConverter { /// Converts the network type - pub fn with_network(self) -> RpcConverter { + pub fn with_network( + self, + ) -> RpcConverter { let Self { receipt_converter, header_converter, @@ -542,6 +684,7 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, .. } = self; RpcConverter { @@ -552,6 +695,35 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, + } + } + + /// Converts the transaction environment type. + pub fn with_tx_env_converter( + self, + tx_env_converter: TxEnvNew, + ) -> RpcConverter { + let Self { + receipt_converter, + header_converter, + mapper, + network, + evm, + sim_tx_converter, + rpc_tx_converter, + tx_env_converter: _, + .. + } = self; + RpcConverter { + receipt_converter, + header_converter, + mapper, + network, + evm, + sim_tx_converter, + rpc_tx_converter, + tx_env_converter, } } @@ -559,7 +731,7 @@ impl pub fn with_header_converter( self, header_converter: HeaderNew, - ) -> RpcConverter { + ) -> RpcConverter { let Self { receipt_converter, header_converter: _, @@ -568,6 +740,7 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, } = self; RpcConverter { receipt_converter, @@ -577,6 +750,7 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, } } @@ -584,7 +758,7 @@ impl pub fn with_mapper( self, mapper: MapNew, - ) -> RpcConverter { + ) -> RpcConverter { let Self { receipt_converter, header_converter, @@ -593,6 +767,7 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, } = self; RpcConverter { receipt_converter, @@ -602,6 +777,7 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, } } @@ -609,7 +785,7 @@ impl pub fn with_sim_tx_converter( self, sim_tx_converter: SimTxNew, - ) -> RpcConverter { + ) -> RpcConverter { let Self { receipt_converter, header_converter, @@ -617,6 +793,7 @@ impl network, evm, rpc_tx_converter, + tx_env_converter, .. } = self; RpcConverter { @@ -627,6 +804,7 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, } } @@ -634,7 +812,7 @@ impl pub fn with_rpc_tx_converter( self, rpc_tx_converter: RpcTxNew, - ) -> RpcConverter { + ) -> RpcConverter { let Self { receipt_converter, header_converter, @@ -642,6 +820,7 @@ impl network, evm, sim_tx_converter, + tx_env_converter, .. } = self; RpcConverter { @@ -652,18 +831,39 @@ impl evm, sim_tx_converter, rpc_tx_converter, + tx_env_converter, } } + + /// Converts `self` into a boxed converter. + #[expect(clippy::type_complexity)] + pub fn erased( + self, + ) -> Box< + dyn RpcConvert< + Primitives = ::Primitives, + Network = ::Network, + Error = ::Error, + TxEnv = ::TxEnv, + Spec = ::Spec, + >, + > + where + Self: RpcConvert, + { + Box::new(self) + } } -impl Default - for RpcConverter +impl Default + for RpcConverter where Receipt: Default, Header: Default, Map: Default, SimTx: Default, RpcTx: Default, + TxEnv: Default, { fn default() -> Self { Self { @@ -674,12 +874,21 @@ where mapper: Default::default(), sim_tx_converter: Default::default(), rpc_tx_converter: Default::default(), + tx_env_converter: Default::default(), } } } -impl Clone - for RpcConverter +impl< + Network, + Evm, + Receipt: Clone, + Header: Clone, + Map: Clone, + SimTx: Clone, + RpcTx: Clone, + TxEnv: Clone, + > Clone for RpcConverter { fn clone(&self) -> Self { Self { @@ -690,25 +899,25 @@ impl RpcConvert - for RpcConverter +impl RpcConvert + for RpcConverter where N: NodePrimitives, Network: RpcTypes + Send + Sync + Unpin + Clone + Debug, Evm: ConfigureEvm + 'static, - TxTy: Clone + Debug, - RpcTxReq: TryIntoTxEnv>, Receipt: ReceiptConverter< N, RpcReceipt = RpcReceipt, Error: From - + From< as TryIntoTxEnv>>::Err> + + From + From<>>::Err> + From + + From + Error + Unpin + Sync @@ -724,11 +933,13 @@ where SimTx: SimTxConverter, TxTy>, RpcTx: RpcTxConverter, Network::TransactionResponse, >>::Out>, + TxEnv: TxEnvConverter, TxEnvFor, SpecFor>, { type Primitives = N; type Network = Network; type TxEnv = TxEnvFor; type Error = Receipt::Error; + type Spec = SpecFor; fn fill( &self, @@ -738,7 +949,7 @@ where let (tx, signer) = tx.into_parts(); let tx_info = self.mapper.try_map(&tx, tx_info)?; - Ok(self.rpc_tx_converter.convert_rpc_tx(tx, signer, tx_info)?) + self.rpc_tx_converter.convert_rpc_tx(tx, signer, tx_info).map_err(Into::into) } fn build_simulate_v1_transaction( @@ -751,13 +962,13 @@ where .map_err(|e| TransactionConversionError(e.to_string()))?) } - fn tx_env( + fn tx_env( &self, request: RpcTxReq, - cfg_env: &CfgEnv, + cfg_env: &CfgEnv>, block_env: &BlockEnv, ) -> Result { - Ok(request.try_into_tx_env(cfg_env, block_env)?) + self.tx_env_converter.convert_tx_env(request, cfg_env, block_env).map_err(Into::into) } fn convert_receipts( @@ -767,12 +978,20 @@ where self.receipt_converter.convert_receipts(receipts) } + fn convert_receipts_with_block( + &self, + receipts: Vec>, + block: &SealedBlock>, + ) -> Result>, Self::Error> { + self.receipt_converter.convert_receipts_with_block(receipts, block) + } + fn convert_header( &self, header: SealedHeaderFor, block_size: usize, ) -> Result, Self::Error> { - Ok(self.header_converter.convert_header(header, block_size)) + Ok(self.header_converter.convert_header(header, block_size)?) } } @@ -822,9 +1041,14 @@ pub mod op { for op_alloy_rpc_types::Transaction { type TxInfo = OpTransactionInfo; - - fn from_consensus_tx(tx: T, signer: Address, tx_info: Self::TxInfo) -> Self { - Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info) + type Err = Infallible; + + fn from_consensus_tx( + tx: T, + signer: Address, + tx_info: Self::TxInfo, + ) -> Result { + Ok(Self::from_transaction(Recovered::new_unchecked(tx, signer), tx_info)) } } @@ -930,35 +1154,59 @@ mod transaction_response_tests { } #[cfg(feature = "op")] - #[test] - fn test_optimism_transaction_conversion() { - use op_alloy_consensus::OpTxEnvelope; - use op_alloy_network::Optimism; - use reth_optimism_primitives::OpTransactionSigned; - - let signed_tx = Signed::new_unchecked( - TxLegacy::default(), - Signature::new(U256::ONE, U256::ONE, false), - B256::ZERO, - ); - let envelope = OpTxEnvelope::Legacy(signed_tx); + mod op { + use super::*; + use crate::transaction::TryIntoTxEnv; + use revm_context::{BlockEnv, CfgEnv}; + + #[test] + fn test_optimism_transaction_conversion() { + use op_alloy_consensus::OpTxEnvelope; + use op_alloy_network::Optimism; + use reth_optimism_primitives::OpTransactionSigned; + + let signed_tx = Signed::new_unchecked( + TxLegacy::default(), + Signature::new(U256::ONE, U256::ONE, false), + B256::ZERO, + ); + let envelope = OpTxEnvelope::Legacy(signed_tx); + + let inner_tx = Transaction { + inner: Recovered::new_unchecked(envelope, Address::ZERO), + block_hash: None, + block_number: None, + transaction_index: None, + effective_gas_price: None, + }; + + let tx_response = op_alloy_rpc_types::Transaction { + inner: inner_tx, + deposit_nonce: None, + deposit_receipt_version: None, + }; + + let result = >::from_transaction_response(tx_response); + + assert!(result.is_ok()); + } - let inner_tx = Transaction { - inner: Recovered::new_unchecked(envelope, Address::ZERO), - block_hash: None, - block_number: None, - transaction_index: None, - effective_gas_price: None, - }; + #[test] + fn test_op_into_tx_env() { + use op_alloy_rpc_types::OpTransactionRequest; + use op_revm::{transaction::OpTxTr, OpSpecId}; + use revm_context::Transaction; - let tx_response = op_alloy_rpc_types::Transaction { - inner: inner_tx, - deposit_nonce: None, - deposit_receipt_version: None, - }; + let s = r#"{"from":"0x0000000000000000000000000000000000000000","to":"0x6d362b9c3ab68c0b7c79e8a714f1d7f3af63655f","input":"0x1626ba7ec8ee0d506e864589b799a645ddb88b08f5d39e8049f9f702b3b61fa15e55fc73000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000550000002d6db27c52e3c11c1cf24072004ac75cba49b25bf45f513902e469755e1f3bf2ca8324ad16930b0a965c012a24bb1101f876ebebac047bd3b6bf610205a27171eaaeffe4b5e5589936f4e542d637b627311b0000000000000000000000","data":"0x1626ba7ec8ee0d506e864589b799a645ddb88b08f5d39e8049f9f702b3b61fa15e55fc73000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000550000002d6db27c52e3c11c1cf24072004ac75cba49b25bf45f513902e469755e1f3bf2ca8324ad16930b0a965c012a24bb1101f876ebebac047bd3b6bf610205a27171eaaeffe4b5e5589936f4e542d637b627311b0000000000000000000000","chainId":"0x7a69"}"#; - let result = >::from_transaction_response(tx_response); + let req: OpTransactionRequest = serde_json::from_str(s).unwrap(); - assert!(result.is_ok()); + let cfg = CfgEnv::::default(); + let block_env = BlockEnv::default(); + let tx_env = req.try_into_tx_env(&cfg, &block_env).unwrap(); + assert_eq!(tx_env.gas_limit(), block_env.gas_limit); + assert_eq!(tx_env.gas_price(), 0); + assert!(tx_env.enveloped_tx().unwrap().is_empty()); + } } } diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index ed311ef645a..ae039b4ac9a 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -21,15 +21,18 @@ use reth_chainspec::EthereumHardforks; use reth_engine_primitives::{ConsensusEngineHandle, EngineApiValidator, EngineTypes}; use reth_payload_builder::PayloadStore; use reth_payload_primitives::{ - validate_payload_timestamp, EngineApiMessageVersion, ExecutionPayload, - PayloadBuilderAttributes, PayloadOrAttributes, PayloadTypes, + validate_payload_timestamp, EngineApiMessageVersion, ExecutionPayload, PayloadOrAttributes, + PayloadTypes, }; use reth_primitives_traits::{Block, BlockBody}; use reth_rpc_api::{EngineApiServer, IntoEngineApiRpcModule}; use reth_storage_api::{BlockReader, HeaderProvider, StateProviderFactory}; use reth_tasks::TaskSpawner; use reth_transaction_pool::TransactionPool; -use std::{sync::Arc, time::Instant}; +use std::{ + sync::Arc, + time::{Instant, SystemTime}, +}; use tokio::sync::oneshot; use tracing::{debug, trace, warn}; @@ -118,15 +121,12 @@ where Ok(vec![self.inner.client.clone()]) } - /// Fetches the attributes for the payload with the given id. - async fn get_payload_attributes( - &self, - payload_id: PayloadId, - ) -> EngineApiResult { + /// Fetches the timestamp of the payload with the given id. + async fn get_payload_timestamp(&self, payload_id: PayloadId) -> EngineApiResult { Ok(self .inner .payload_store - .payload_attributes(payload_id) + .payload_timestamp(payload_id) .await .ok_or(EngineApiError::UnknownPayload)??) } @@ -399,11 +399,9 @@ where where EngineT::BuiltPayload: TryInto, { - // First we fetch the payload attributes to check the timestamp - let attributes = self.get_payload_attributes(payload_id).await?; - // validate timestamp according to engine rules - validate_payload_timestamp(&self.inner.chain_spec, version, attributes.timestamp())?; + let timestamp = self.get_payload_timestamp(payload_id).await?; + validate_payload_timestamp(&self.inner.chain_spec, version, timestamp)?; // Now resolve the payload self.get_built_payload(payload_id).await?.try_into().map_err(|_| { @@ -577,11 +575,10 @@ where // > Client software MUST NOT return trailing null values if the request extends past the current latest known block. // truncate the end if it's greater than the last block - if let Ok(best_block) = inner.provider.best_block_number() { - if end > best_block { + if let Ok(best_block) = inner.provider.best_block_number() + && end > best_block { end = best_block; } - } for num in start..=end { let block_result = inner.provider.block(BlockHashOrNumber::Number(num)); @@ -758,6 +755,15 @@ where &self, versioned_hashes: Vec, ) -> EngineApiResult>> { + // Only allow this method before Osaka fork + let current_timestamp = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs(); + if self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) { + return Err(EngineApiError::EngineObjectValidationError( + reth_payload_primitives::EngineObjectValidationError::UnsupportedFork, + )); + } + if versioned_hashes.len() > MAX_BLOB_LIMIT { return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() }) } @@ -793,6 +799,15 @@ where &self, versioned_hashes: Vec, ) -> EngineApiResult>> { + // Check if Osaka fork is active + let current_timestamp = + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs(); + if !self.inner.chain_spec.is_osaka_active_at_timestamp(current_timestamp) { + return Err(EngineApiError::EngineObjectValidationError( + reth_payload_primitives::EngineObjectValidationError::UnsupportedFork, + )); + } + if versioned_hashes.len() > MAX_BLOB_LIMIT { return Err(EngineApiError::BlobRequestTooLarge { len: versioned_hashes.len() }) } diff --git a/crates/rpc/rpc-eth-api/Cargo.toml b/crates/rpc/rpc-eth-api/Cargo.toml index 44637d1931c..adc07dd0582 100644 --- a/crates/rpc/rpc-eth-api/Cargo.toml +++ b/crates/rpc/rpc-eth-api/Cargo.toml @@ -13,7 +13,7 @@ workspace = true [dependencies] # reth -revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip3607", "optional_no_base_fee"] } +revm = { workspace = true, features = ["optional_block_gas_limit", "optional_eip3607", "optional_no_base_fee", "optional_gasless"] } reth-chain-state.workspace = true revm-inspectors.workspace = true reth-primitives-traits = { workspace = true, features = ["rpc-compat"] } diff --git a/crates/rpc/rpc-eth-api/src/helpers/block.rs b/crates/rpc/rpc-eth-api/src/helpers/block.rs index badffeda7b8..17e4b000b35 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/block.rs @@ -5,19 +5,17 @@ use crate::{ node::RpcNodeCoreExt, EthApiTypes, FromEthApiError, FullEthApiTypes, RpcBlock, RpcNodeCore, RpcReceipt, }; -use alloy_consensus::TxReceipt; +use alloy_consensus::{transaction::TxHashRef, TxReceipt}; use alloy_eips::BlockId; use alloy_rlp::Encodable; use alloy_rpc_types_eth::{Block, BlockTransactions, Index}; use futures::Future; use reth_node_api::BlockBody; -use reth_primitives_traits::{ - AlloyBlockHeader, RecoveredBlock, SealedHeader, SignedTransaction, TransactionMeta, -}; +use reth_primitives_traits::{AlloyBlockHeader, RecoveredBlock, SealedHeader, TransactionMeta}; use reth_rpc_convert::{transaction::ConvertReceiptInput, RpcConvert, RpcHeader}; use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx}; use reth_transaction_pool::{PoolTransaction, TransactionPool}; -use std::{borrow::Cow, sync::Arc}; +use std::sync::Arc; /// Result type of the fetched block receipts. pub type BlockReceiptsResult = Result>>, E>; @@ -127,7 +125,7 @@ pub trait EthBlocks: let inputs = block .transactions_recovered() - .zip(receipts.iter()) + .zip(Arc::unwrap_or_clone(receipts)) .enumerate() .map(|(idx, (tx, receipt))| { let meta = TransactionMeta { @@ -140,22 +138,28 @@ pub trait EthBlocks: timestamp, }; + let cumulative_gas_used = receipt.cumulative_gas_used(); + let logs_len = receipt.logs().len(); + let input = ConvertReceiptInput { - receipt: Cow::Borrowed(receipt), tx, - gas_used: receipt.cumulative_gas_used() - gas_used, + gas_used: cumulative_gas_used - gas_used, next_log_index, meta, + receipt, }; - gas_used = receipt.cumulative_gas_used(); - next_log_index += receipt.logs().len(); + gas_used = cumulative_gas_used; + next_log_index += logs_len; input }) .collect::>(); - return self.tx_resp_builder().convert_receipts(inputs).map(Some) + return self + .tx_resp_builder() + .convert_receipts_with_block(inputs, block.sealed_block()) + .map(Some) } Ok(None) @@ -185,22 +189,20 @@ pub trait EthBlocks: } // If no pending block from provider, build the pending block locally. - if let Some((block, receipts)) = self.local_pending_block().await? { - return Ok(Some((block, receipts))); + if let Some(pending) = self.local_pending_block().await? { + return Ok(Some((pending.block, pending.receipts))); } } if let Some(block_hash) = - self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)? - { - if let Some((block, receipts)) = self + self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)? && + let Some((block, receipts)) = self .cache() .get_block_and_receipts(block_hash) .await .map_err(Self::Error::from_eth_err)? - { - return Ok(Some((block, receipts))); - } + { + return Ok(Some((block, receipts))); } Ok(None) @@ -296,7 +298,7 @@ pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt { // If no pending block from provider, try to get local pending block return match self.local_pending_block().await? { - Some((block, _)) => Ok(Some(block)), + Some(pending) => Ok(Some(pending.block)), None => Ok(None), }; } diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index b95b290de46..b96dab882a0 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -7,12 +7,9 @@ use super::{LoadBlock, LoadPendingBlock, LoadState, LoadTransaction, SpawnBlocki use crate::{ helpers::estimate::EstimateCall, FromEvmError, FullEthApiTypes, RpcBlock, RpcNodeCore, }; -use alloy_consensus::BlockHeader; +use alloy_consensus::{transaction::TxHashRef, BlockHeader}; use alloy_eips::eip2930::AccessListResult; -use alloy_evm::{ - call::caller_gas_allowance, - overrides::{apply_block_overrides, apply_state_overrides, OverrideBlockHashes}, -}; +use alloy_evm::overrides::{apply_block_overrides, apply_state_overrides, OverrideBlockHashes}; use alloy_network::TransactionBuilder; use alloy_primitives::{Bytes, B256, U256}; use alloy_rpc_types_eth::{ @@ -27,7 +24,7 @@ use reth_evm::{ TxEnvFor, }; use reth_node_api::BlockBody; -use reth_primitives_traits::{Recovered, SignedTransaction}; +use reth_primitives_traits::Recovered; use reth_revm::{ database::StateProviderDatabase, db::{CacheDB, State}, @@ -117,6 +114,7 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA // If not explicitly required, we disable nonce check evm_env.cfg_env.disable_nonce_check = true; evm_env.cfg_env.disable_base_fee = true; + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); evm_env.block_env.basefee = 0; } @@ -124,14 +122,11 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA if let Some(block_overrides) = block_overrides { // ensure we don't allow uncapped gas limit per block - if let Some(gas_limit_override) = block_overrides.gas_limit { - if gas_limit_override > evm_env.block_env.gas_limit && - gas_limit_override > this.call_gas_limit() - { - return Err( - EthApiError::other(EthSimulateError::GasLimitReached).into() - ) - } + if let Some(gas_limit_override) = block_overrides.gas_limit && + gas_limit_override > evm_env.block_env.gas_limit && + gas_limit_override > this.call_gas_limit() + { + return Err(EthApiError::other(EthSimulateError::GasLimitReached).into()) } apply_block_overrides(block_overrides, &mut db, &mut evm_env.block_env); } @@ -165,7 +160,9 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA let ctx = this .evm_config() - .context_for_next_block(&parent, this.next_env_attributes(&parent)?); + .context_for_next_block(&parent, this.next_env_attributes(&parent)?) + .map_err(RethError::other) + .map_err(Self::Error::from_eth_err)?; let (result, results) = if trace_transfers { // prepare inspector to capture transfer inside the evm so they are recorded // and included in logs @@ -361,8 +358,8 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA let block_id = block_number.unwrap_or_default(); let (evm_env, at) = self.evm_env_at(block_id).await?; - self.spawn_blocking_io(move |this| { - this.create_access_list_with(evm_env, at, request, state_override) + self.spawn_blocking_io_fut(move |this| async move { + this.create_access_list_with(evm_env, at, request, state_override).await }) .await } @@ -376,83 +373,95 @@ pub trait EthCall: EstimateCall + Call + LoadPendingBlock + LoadBlock + FullEthA at: BlockId, request: RpcTxReq<::Network>, state_override: Option, - ) -> Result + ) -> impl Future> + Send where Self: Trace, { - let state = self.state_at_block_id(at)?; - let mut db = CacheDB::new(StateProviderDatabase::new(state)); + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id(at).await?; + let mut db = CacheDB::new(StateProviderDatabase::new(state)); - if let Some(state_overrides) = state_override { - apply_state_overrides(state_overrides, &mut db).map_err(Self::Error::from_eth_err)?; - } - - let mut tx_env = self.create_txn_env(&evm_env, request.clone(), &mut db)?; + if let Some(state_overrides) = state_override { + apply_state_overrides(state_overrides, &mut db) + .map_err(Self::Error::from_eth_err)?; + } - // we want to disable this in eth_createAccessList, since this is common practice used by - // other node impls and providers - evm_env.cfg_env.disable_block_gas_limit = true; + let mut tx_env = this.create_txn_env(&evm_env, request.clone(), &mut db)?; - // The basefee should be ignored for eth_createAccessList - // See: - // - evm_env.cfg_env.disable_base_fee = true; + // we want to disable this in eth_createAccessList, since this is common practice used + // by other node impls and providers + evm_env.cfg_env.disable_block_gas_limit = true; - // Disabled because eth_createAccessList is sometimes used with non-eoa senders - evm_env.cfg_env.disable_eip3607 = true; + // The basefee should be ignored for eth_createAccessList + // See: + // + evm_env.cfg_env.disable_base_fee = true; - if request.as_ref().gas_limit().is_none() && tx_env.gas_price() > 0 { - let cap = caller_gas_allowance(&mut db, &tx_env).map_err(Self::Error::from_eth_err)?; - // no gas limit was provided in the request, so we need to cap the request's gas limit - tx_env.set_gas_limit(cap.min(evm_env.block_env.gas_limit)); - } + // Disabled because eth_createAccessList is sometimes used with non-eoa senders + evm_env.cfg_env.disable_eip3607 = true; - // can consume the list since we're not using the request anymore - let initial = request.as_ref().access_list().cloned().unwrap_or_default(); + if request.as_ref().gas_limit().is_none() && tx_env.gas_price() > 0 { + let cap = this.caller_gas_allowance(&mut db, &evm_env, &tx_env)?; + // no gas limit was provided in the request, so we need to cap the request's gas + // limit + tx_env.set_gas_limit(cap.min(evm_env.block_env.gas_limit)); + } - let mut inspector = AccessListInspector::new(initial); + // can consume the list since we're not using the request anymore + let initial = request.as_ref().access_list().cloned().unwrap_or_default(); + + let mut inspector = AccessListInspector::new(initial); + + let result = this.inspect(&mut db, evm_env.clone(), tx_env.clone(), &mut inspector)?; + let access_list = inspector.into_access_list(); + tx_env.set_access_list(access_list.clone()); + match result.result { + ExecutionResult::Halt { reason, gas_used } => { + let error = + Some(Self::Error::from_evm_halt(reason, tx_env.gas_limit()).to_string()); + return Ok(AccessListResult { + access_list, + gas_used: U256::from(gas_used), + error, + }) + } + ExecutionResult::Revert { output, gas_used } => { + let error = Some(RevertError::new(output).to_string()); + return Ok(AccessListResult { + access_list, + gas_used: U256::from(gas_used), + error, + }) + } + ExecutionResult::Success { .. } => {} + }; - let result = self.inspect(&mut db, evm_env.clone(), tx_env.clone(), &mut inspector)?; - let access_list = inspector.into_access_list(); - tx_env.set_access_list(access_list.clone()); - match result.result { - ExecutionResult::Halt { reason, gas_used } => { - let error = - Some(Self::Error::from_evm_halt(reason, tx_env.gas_limit()).to_string()); - return Ok(AccessListResult { access_list, gas_used: U256::from(gas_used), error }) - } - ExecutionResult::Revert { output, gas_used } => { - let error = Some(RevertError::new(output).to_string()); - return Ok(AccessListResult { access_list, gas_used: U256::from(gas_used), error }) - } - ExecutionResult::Success { .. } => {} - }; - - // transact again to get the exact gas used - let gas_limit = tx_env.gas_limit(); - let result = self.transact(&mut db, evm_env, tx_env)?; - let res = match result.result { - ExecutionResult::Halt { reason, gas_used } => { - let error = Some(Self::Error::from_evm_halt(reason, gas_limit).to_string()); - AccessListResult { access_list, gas_used: U256::from(gas_used), error } - } - ExecutionResult::Revert { output, gas_used } => { - let error = Some(RevertError::new(output).to_string()); - AccessListResult { access_list, gas_used: U256::from(gas_used), error } - } - ExecutionResult::Success { gas_used, .. } => { - AccessListResult { access_list, gas_used: U256::from(gas_used), error: None } - } - }; + // transact again to get the exact gas used + let gas_limit = tx_env.gas_limit(); + let result = this.transact(&mut db, evm_env, tx_env)?; + let res = match result.result { + ExecutionResult::Halt { reason, gas_used } => { + let error = Some(Self::Error::from_evm_halt(reason, gas_limit).to_string()); + AccessListResult { access_list, gas_used: U256::from(gas_used), error } + } + ExecutionResult::Revert { output, gas_used } => { + let error = Some(RevertError::new(output).to_string()); + AccessListResult { access_list, gas_used: U256::from(gas_used), error } + } + ExecutionResult::Success { gas_used, .. } => { + AccessListResult { access_list, gas_used: U256::from(gas_used), error: None } + } + }; - Ok(res) + Ok(res) + }) } } /// Executes code on state. pub trait Call: LoadState< - RpcConvert: RpcConvert>, + RpcConvert: RpcConvert, Spec = SpecFor>, Error: FromEvmError + From<::Error> + From, @@ -466,13 +475,32 @@ pub trait Call: /// Returns the maximum number of blocks accepted for `eth_simulateV1`. fn max_simulate_blocks(&self) -> u64; + /// Returns the max gas limit that the caller can afford given a transaction environment. + fn caller_gas_allowance( + &self, + mut db: impl Database>, + _evm_env: &EvmEnvFor, + tx_env: &TxEnvFor, + ) -> Result { + alloy_evm::call::caller_gas_allowance(&mut db, tx_env).map_err(Self::Error::from_eth_err) + } + /// Executes the closure with the state that corresponds to the given [`BlockId`]. - fn with_state_at_block(&self, at: BlockId, f: F) -> Result + fn with_state_at_block( + &self, + at: BlockId, + f: F, + ) -> impl Future> + Send where - F: FnOnce(StateProviderTraitObjWrapper<'_>) -> Result, + R: Send + 'static, + F: FnOnce(Self, StateProviderTraitObjWrapper<'_>) -> Result + + Send + + 'static, { - let state = self.state_at_block_id(at)?; - f(StateProviderTraitObjWrapper(&state)) + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id(at).await?; + f(this, StateProviderTraitObjWrapper(&state)) + }) } /// Executes the `TxEnv` against the given [Database] without committing state @@ -537,8 +565,8 @@ pub trait Call: F: FnOnce(StateProviderTraitObjWrapper<'_>) -> Result + Send + 'static, R: Send + 'static, { - self.spawn_tracing(move |this| { - let state = this.state_at_block_id(at)?; + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id(at).await?; f(StateProviderTraitObjWrapper(&state)) }) } @@ -579,8 +607,8 @@ pub trait Call: async move { let (evm_env, at) = self.evm_env_at(at).await?; let this = self.clone(); - self.spawn_blocking_io(move |_| { - let state = this.state_at_block_id(at)?; + self.spawn_blocking_io_fut(move |_| async move { + let state = this.state_at_block_id(at).await?; let mut db = CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state))); @@ -728,16 +756,23 @@ pub trait Call: DB: Database + DatabaseCommit + OverrideBlockHashes, EthApiError: From<::Error>, { + // track whether the request has a gas limit set + let request_has_gas_limit = request.as_ref().gas_limit().is_some(); + if let Some(requested_gas) = request.as_ref().gas_limit() { let global_gas_cap = self.call_gas_limit(); if global_gas_cap != 0 && global_gas_cap < requested_gas { warn!(target: "rpc::eth::call", ?request, ?global_gas_cap, "Capping gas limit to global gas cap"); request.as_mut().set_gas_limit(global_gas_cap); } + } else { + // cap request's gas limit to call gas limit + request.as_mut().set_gas_limit(self.call_gas_limit()); } - // apply configured gas cap - evm_env.block_env.gas_limit = self.call_gas_limit(); + // Disable block gas limit check to allow executing transactions with higher gas limit (call + // gas limit): https://github.com/paradigmxyz/reth/issues/18577 + evm_env.cfg_env.disable_block_gas_limit = true; // Disabled because eth_call is sometimes used with eoa senders // See @@ -748,6 +783,9 @@ pub trait Call: // evm_env.cfg_env.disable_base_fee = true; + // Disable EIP-7825 transaction gas limit to support larger transactions + evm_env.cfg_env.tx_gas_limit_cap = Some(u64::MAX); + // set nonce to None so that the correct nonce is chosen by the EVM request.as_mut().take_nonce(); @@ -759,7 +797,6 @@ pub trait Call: .map_err(EthApiError::from_state_overrides_err)?; } - let request_gas = request.as_ref().gas_limit(); let mut tx_env = self.create_txn_env(&evm_env, request, &mut *db)?; // lower the basefee to 0 to avoid breaking EVM invariants (basefee < gasprice): @@ -767,12 +804,12 @@ pub trait Call: evm_env.block_env.basefee = 0; } - if request_gas.is_none() { + if !request_has_gas_limit { // No gas limit was provided in the request, so we need to cap the transaction gas limit if tx_env.gas_price() > 0 { // If gas price is specified, cap transaction gas limit with caller allowance trace!(target: "rpc::eth::call", ?tx_env, "Applying gas limit cap with caller allowance"); - let cap = caller_gas_allowance(db, &tx_env).map_err(EthApiError::from_call_err)?; + let cap = self.caller_gas_allowance(db, &evm_env, &tx_env)?; // ensure we cap gas_limit to the block's tx_env.set_gas_limit(cap.min(evm_env.block_env.gas_limit)); } diff --git a/crates/rpc/rpc-eth-api/src/helpers/config.rs b/crates/rpc/rpc-eth-api/src/helpers/config.rs new file mode 100644 index 00000000000..c4014e6f204 --- /dev/null +++ b/crates/rpc/rpc-eth-api/src/helpers/config.rs @@ -0,0 +1,173 @@ +//! Loads chain configuration. + +use alloy_consensus::Header; +use alloy_eips::eip7910::{EthConfig, EthForkConfig, SystemContract}; +use alloy_evm::precompiles::Precompile; +use alloy_primitives::Address; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks, Hardforks, Head}; +use reth_errors::{ProviderError, RethError}; +use reth_evm::{precompiles::PrecompilesMap, ConfigureEvm, Evm}; +use reth_node_api::NodePrimitives; +use reth_revm::db::EmptyDB; +use reth_rpc_eth_types::EthApiError; +use reth_storage_api::BlockReaderIdExt; +use std::collections::BTreeMap; + +/// RPC endpoint support for [EIP-7910](https://eips.ethereum.org/EIPS/eip-7910) +#[cfg_attr(not(feature = "client"), rpc(server, namespace = "eth"))] +#[cfg_attr(feature = "client", rpc(server, client, namespace = "eth"))] +pub trait EthConfigApi { + /// Returns an object with data about recent and upcoming fork configurations. + #[method(name = "config")] + fn config(&self) -> RpcResult; +} + +/// Handler for the `eth_config` RPC endpoint. +/// +/// Ref: +#[derive(Debug, Clone)] +pub struct EthConfigHandler { + provider: Provider, + evm_config: Evm, +} + +impl EthConfigHandler +where + Provider: ChainSpecProvider + + BlockReaderIdExt
+ + 'static, + Evm: ConfigureEvm> + 'static, +{ + /// Creates a new [`EthConfigHandler`]. + pub const fn new(provider: Provider, evm_config: Evm) -> Self { + Self { provider, evm_config } + } + + /// Returns fork config for specific timestamp. + /// Returns [`None`] if no blob params were found for this fork. + fn build_fork_config_at( + &self, + timestamp: u64, + precompiles: BTreeMap, + ) -> Option { + let chain_spec = self.provider.chain_spec(); + + let mut system_contracts = BTreeMap::::default(); + + if chain_spec.is_cancun_active_at_timestamp(timestamp) { + system_contracts.extend(SystemContract::cancun()); + } + + if chain_spec.is_prague_active_at_timestamp(timestamp) { + system_contracts + .extend(SystemContract::prague(chain_spec.deposit_contract().map(|c| c.address))); + } + + // Fork config only exists for timestamp-based hardforks. + let fork_id = chain_spec + .fork_id(&Head { timestamp, number: u64::MAX, ..Default::default() }) + .hash + .0 + .into(); + + Some(EthForkConfig { + activation_time: timestamp, + blob_schedule: chain_spec.blob_params_at_timestamp(timestamp)?, + chain_id: chain_spec.chain().id(), + fork_id, + precompiles, + system_contracts, + }) + } + + fn config(&self) -> Result { + let chain_spec = self.provider.chain_spec(); + let latest = self + .provider + .latest_header()? + .ok_or_else(|| ProviderError::BestBlockNotFound)? + .into_header(); + + let current_precompiles = evm_to_precompiles_map( + self.evm_config.evm_for_block(EmptyDB::default(), &latest).map_err(RethError::other)?, + ); + + let mut fork_timestamps = + chain_spec.forks_iter().filter_map(|(_, cond)| cond.as_timestamp()).collect::>(); + fork_timestamps.sort_unstable(); + fork_timestamps.dedup(); + + let (current_fork_idx, current_fork_timestamp) = fork_timestamps + .iter() + .position(|ts| &latest.timestamp < ts) + .and_then(|idx| idx.checked_sub(1)) + .or_else(|| fork_timestamps.len().checked_sub(1)) + .and_then(|idx| fork_timestamps.get(idx).map(|ts| (idx, *ts))) + .ok_or_else(|| RethError::msg("no active timestamp fork found"))?; + + let current = self + .build_fork_config_at(current_fork_timestamp, current_precompiles) + .ok_or_else(|| RethError::msg("no fork config for current fork"))?; + + let mut config = EthConfig { current, next: None, last: None }; + + if let Some(next_fork_timestamp) = fork_timestamps.get(current_fork_idx + 1).copied() { + let fake_header = { + let mut header = latest.clone(); + header.timestamp = next_fork_timestamp; + header + }; + let next_precompiles = evm_to_precompiles_map( + self.evm_config + .evm_for_block(EmptyDB::default(), &fake_header) + .map_err(RethError::other)?, + ); + + config.next = self.build_fork_config_at(next_fork_timestamp, next_precompiles); + } else { + // If there is no fork scheduled, there is no "last" or "final" fork scheduled. + return Ok(config); + } + + let last_fork_timestamp = fork_timestamps.last().copied().unwrap(); + let fake_header = { + let mut header = latest; + header.timestamp = last_fork_timestamp; + header + }; + let last_precompiles = evm_to_precompiles_map( + self.evm_config + .evm_for_block(EmptyDB::default(), &fake_header) + .map_err(RethError::other)?, + ); + + config.last = self.build_fork_config_at(last_fork_timestamp, last_precompiles); + + Ok(config) + } +} + +impl EthConfigApiServer for EthConfigHandler +where + Provider: ChainSpecProvider + + BlockReaderIdExt
+ + 'static, + Evm: ConfigureEvm> + 'static, +{ + fn config(&self) -> RpcResult { + Ok(self.config().map_err(EthApiError::from)?) + } +} + +fn evm_to_precompiles_map( + evm: impl Evm, +) -> BTreeMap { + let precompiles = evm.precompiles(); + precompiles + .addresses() + .filter_map(|address| { + Some((precompiles.get(address)?.precompile_id().name().to_string(), *address)) + }) + .collect() +} diff --git a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs index 3f58d97f7df..cca674e9739 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/estimate.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/estimate.rs @@ -2,7 +2,7 @@ use super::{Call, LoadPendingBlock}; use crate::{AsEthApiError, FromEthApiError, IntoEthApiError}; -use alloy_evm::{call::caller_gas_allowance, overrides::apply_state_overrides}; +use alloy_evm::overrides::apply_state_overrides; use alloy_network::TransactionBuilder; use alloy_primitives::{TxKind, U256}; use alloy_rpc_types_eth::{state::StateOverride, BlockId}; @@ -60,19 +60,22 @@ pub trait EstimateCall: Call { let tx_request_gas_limit = request.as_ref().gas_limit(); let tx_request_gas_price = request.as_ref().gas_price(); // the gas limit of the corresponding block - let block_env_gas_limit = evm_env.block_env.gas_limit; + let max_gas_limit = evm_env + .cfg_env + .tx_gas_limit_cap + .map_or(evm_env.block_env.gas_limit, |cap| cap.min(evm_env.block_env.gas_limit)); // Determine the highest possible gas limit, considering both the request's specified limit // and the block's limit. let mut highest_gas_limit = tx_request_gas_limit .map(|mut tx_gas_limit| { - if block_env_gas_limit < tx_gas_limit { + if max_gas_limit < tx_gas_limit { // requested gas limit is higher than the allowed gas limit, capping - tx_gas_limit = block_env_gas_limit; + tx_gas_limit = max_gas_limit; } tx_gas_limit }) - .unwrap_or(block_env_gas_limit); + .unwrap_or(max_gas_limit); // Configure the evm env let mut db = CacheDB::new(StateProviderDatabase::new(state)); @@ -85,22 +88,22 @@ pub trait EstimateCall: Call { let mut tx_env = self.create_txn_env(&evm_env, request, &mut db)?; // Check if this is a basic transfer (no input data to account with no code) - let mut is_basic_transfer = false; - if tx_env.input().is_empty() { - if let TxKind::Call(to) = tx_env.kind() { - if let Ok(code) = db.db.account_code(&to) { - is_basic_transfer = code.map(|code| code.is_empty()).unwrap_or(true); - } - } - } + let is_basic_transfer = if tx_env.input().is_empty() && + let TxKind::Call(to) = tx_env.kind() && + let Ok(code) = db.db.account_code(&to) + { + code.map(|code| code.is_empty()).unwrap_or(true) + } else { + false + }; // Check funds of the sender (only useful to check if transaction gas price is more than 0). // // The caller allowance is check by doing `(account.balance - tx.value) / tx.gas_price` if tx_env.gas_price() > 0 { // cap the highest gas limit by max gas caller can afford with given gas price - highest_gas_limit = highest_gas_limit - .min(caller_gas_allowance(&mut db, &tx_env).map_err(Self::Error::from_eth_err)?); + highest_gas_limit = + highest_gas_limit.min(self.caller_gas_allowance(&mut db, &evm_env, &tx_env)?); } // If the provided gas limit is less than computed cap, use that @@ -120,10 +123,10 @@ pub trait EstimateCall: Call { min_tx_env.set_gas_limit(MIN_TRANSACTION_GAS); // Reuse the same EVM instance - if let Ok(res) = evm.transact(min_tx_env).map_err(Self::Error::from_evm_err) { - if res.result.is_success() { - return Ok(U256::from(MIN_TRANSACTION_GAS)) - } + if let Ok(res) = evm.transact(min_tx_env).map_err(Self::Error::from_evm_err) && + res.result.is_success() + { + return Ok(U256::from(MIN_TRANSACTION_GAS)) } } @@ -139,7 +142,7 @@ pub trait EstimateCall: Call { if err.is_gas_too_high() && (tx_request_gas_limit.is_some() || tx_request_gas_price.is_some()) => { - return Self::map_out_of_gas_err(&mut evm, tx_env, block_env_gas_limit); + return Self::map_out_of_gas_err(&mut evm, tx_env, max_gas_limit); } Err(err) if err.is_gas_too_low() => { // This failed because the configured gas cost of the tx was lower than what @@ -166,7 +169,7 @@ pub trait EstimateCall: Call { // if price or limit was included in the request then we can execute the request // again with the block's gas limit to check if revert is gas related or not return if tx_request_gas_limit.is_some() || tx_request_gas_price.is_some() { - Self::map_out_of_gas_err(&mut evm, tx_env, block_env_gas_limit) + Self::map_out_of_gas_err(&mut evm, tx_env, max_gas_limit) } else { // the transaction did revert Err(RpcInvalidTransactionError::Revert(RevertError::new(output)).into_eth_err()) @@ -281,8 +284,8 @@ pub trait EstimateCall: Call { async move { let (evm_env, at) = self.evm_env_at(at).await?; - self.spawn_blocking_io(move |this| { - let state = this.state_at_block_id(at)?; + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id(at).await?; EstimateCall::estimate_gas_with(&this, evm_env, request, state, state_override) }) .await @@ -295,14 +298,14 @@ pub trait EstimateCall: Call { fn map_out_of_gas_err( evm: &mut EvmFor, mut tx_env: TxEnvFor, - higher_gas_limit: u64, + max_gas_limit: u64, ) -> Result where DB: Database, EthApiError: From, { let req_gas_limit = tx_env.gas_limit(); - tx_env.set_gas_limit(higher_gas_limit); + tx_env.set_gas_limit(max_gas_limit); let retry_res = evm.transact(tx_env).map_err(Self::Error::from_evm_err)?; diff --git a/crates/rpc/rpc-eth-api/src/helpers/fee.rs b/crates/rpc/rpc-eth-api/src/helpers/fee.rs index ae558d40559..b0d736981c2 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/fee.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/fee.rs @@ -109,10 +109,10 @@ pub trait EthFees: // need to validate that they are monotonically // increasing and 0 <= p <= 100 // Note: The types used ensure that the percentiles are never < 0 - if let Some(percentiles) = &reward_percentiles { - if percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) { - return Err(EthApiError::InvalidRewardPercentiles.into()) - } + if let Some(percentiles) = &reward_percentiles && + percentiles.windows(2).any(|w| w[0] > w[1] || w[0] > 100.) + { + return Err(EthApiError::InvalidRewardPercentiles.into()) } // Fetch the headers and ensure we got all of them diff --git a/crates/rpc/rpc-eth-api/src/helpers/mod.rs b/crates/rpc/rpc-eth-api/src/helpers/mod.rs index 27d23da74b2..29223d78913 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/mod.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/mod.rs @@ -17,6 +17,7 @@ pub mod block; pub mod blocking_task; pub mod call; +pub mod config; pub mod estimate; pub mod fee; pub mod pending_block; diff --git a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs index deb6883640e..94dc214b6c8 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/pending_block.rs @@ -8,29 +8,27 @@ use alloy_eips::eip7840::BlobParams; use alloy_primitives::{B256, U256}; use alloy_rpc_types_eth::BlockNumberOrTag; use futures::Future; -use reth_chain_state::ExecutedBlock; +use reth_chain_state::{BlockState, ExecutedBlock}; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError, RethError}; use reth_evm::{ execute::{BlockBuilder, BlockBuilderOutcome, ExecutionOutcome}, ConfigureEvm, Evm, NextBlockEnvAttributes, SpecFor, }; -use reth_primitives_traits::{ - transaction::error::InvalidTransactionError, HeaderTy, RecoveredBlock, SealedHeader, -}; +use reth_primitives_traits::{transaction::error::InvalidTransactionError, HeaderTy, SealedHeader}; use reth_revm::{database::StateProviderDatabase, db::State}; use reth_rpc_convert::RpcConvert; use reth_rpc_eth_types::{ - builder::config::PendingBlockKind, EthApiError, PendingBlock, PendingBlockEnv, - PendingBlockEnvOrigin, + block::BlockAndReceipts, builder::config::PendingBlockKind, EthApiError, PendingBlock, + PendingBlockEnv, PendingBlockEnvOrigin, }; use reth_storage_api::{ - BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, ProviderReceipt, ProviderTx, - ReceiptProvider, StateProviderFactory, + noop::NoopProvider, BlockReader, BlockReaderIdExt, ProviderBlock, ProviderHeader, + ProviderReceipt, ProviderTx, ReceiptProvider, StateProviderBox, StateProviderFactory, }; use reth_transaction_pool::{ - error::InvalidPoolTransactionError, BestTransactionsAttributes, PoolTransaction, - TransactionPool, + error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes, + PoolTransaction, TransactionPool, }; use revm::context_interface::Block; use std::{ @@ -74,22 +72,25 @@ pub trait LoadPendingBlock: >, Self::Error, > { - if let Some(block) = self.provider().pending_block().map_err(Self::Error::from_eth_err)? { - if let Some(receipts) = self + if let Some(block) = self.provider().pending_block().map_err(Self::Error::from_eth_err)? && + let Some(receipts) = self .provider() .receipts_by_block(block.hash().into()) .map_err(Self::Error::from_eth_err)? - { - // Note: for the PENDING block we assume it is past the known merge block and - // thus this will not fail when looking up the total - // difficulty value for the blockenv. - let evm_env = self.evm_config().evm_env(block.header()); - - return Ok(PendingBlockEnv::new( - evm_env, - PendingBlockEnvOrigin::ActualPending(Arc::new(block), Arc::new(receipts)), - )); - } + { + // Note: for the PENDING block we assume it is past the known merge block and + // thus this will not fail when looking up the total + // difficulty value for the blockenv. + let evm_env = self + .evm_config() + .evm_env(block.header()) + .map_err(RethError::other) + .map_err(Self::Error::from_eth_err)?; + + return Ok(PendingBlockEnv::new( + evm_env, + PendingBlockEnvOrigin::ActualPending(Arc::new(block), Arc::new(receipts)), + )); } // no pending block from the CL yet, so we use the latest block and modify the env @@ -117,33 +118,43 @@ pub trait LoadPendingBlock: Ok(self.pending_env_builder().pending_env_attributes(parent)?) } - /// Returns the locally built pending block - #[expect(clippy::type_complexity)] - fn local_pending_block( + /// Returns a [`StateProviderBox`] on a mem-pool built pending block overlaying latest. + fn local_pending_state( &self, - ) -> impl Future< - Output = Result< - Option<( - Arc::Block>>, - Arc>>, - )>, - Self::Error, - >, - > + Send + ) -> impl Future, Self::Error>> + Send + where + Self: SpawnBlocking, + { + async move { + let Some(pending_block) = self.pool_pending_block().await? else { + return Ok(None); + }; + + let latest_historical = self + .provider() + .history_by_block_hash(pending_block.block().parent_hash()) + .map_err(Self::Error::from_eth_err)?; + + let state = BlockState::from(pending_block); + + Ok(Some(Box::new(state.state_provider(latest_historical)) as StateProviderBox)) + } + } + + /// Returns a mem-pool built pending block. + fn pool_pending_block( + &self, + ) -> impl Future>, Self::Error>> + Send where Self: SpawnBlocking, - Self::Pool: - TransactionPool>>, { async move { - if self.pending_block_kind() == PendingBlockKind::None { + if self.pending_block_kind().is_none() { return Ok(None); } let pending = self.pending_block_env_and_cfg()?; let parent = match pending.origin { - PendingBlockEnvOrigin::ActualPending(block, receipts) => { - return Ok(Some((block, receipts))); - } + PendingBlockEnvOrigin::ActualPending(..) => return Ok(None), PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent, }; @@ -152,18 +163,17 @@ pub trait LoadPendingBlock: let now = Instant::now(); - // check if the block is still good + // Is the pending block cached? if let Some(pending_block) = lock.as_ref() { - // this is guaranteed to be the `latest` header - if pending.evm_env.block_env.number == U256::from(pending_block.block.number()) && - parent.hash() == pending_block.block.parent_hash() && + // Is the cached block not expired and latest is its parent? + if pending.evm_env.block_env.number == U256::from(pending_block.block().number()) && + parent.hash() == pending_block.block().parent_hash() && now <= pending_block.expires_at { - return Ok(Some((pending_block.block.clone(), pending_block.receipts.clone()))); + return Ok(Some(pending_block.clone())); } } - // no pending block from the CL yet, so we need to build it ourselves via txpool let executed_block = match self .spawn_blocking_io(move |this| { // we rebuild the block @@ -178,20 +188,41 @@ pub trait LoadPendingBlock: } }; - let block = executed_block.recovered_block; - - let pending = PendingBlock::new( + let pending = PendingBlock::with_executed_block( Instant::now() + Duration::from_secs(1), - block.clone(), - Arc::new( - executed_block.execution_output.receipts.iter().flatten().cloned().collect(), - ), + executed_block, ); - let receipts = pending.receipts.clone(); - *lock = Some(pending); + *lock = Some(pending.clone()); + + Ok(Some(pending)) + } + } + + /// Returns the locally built pending block + fn local_pending_block( + &self, + ) -> impl Future>, Self::Error>> + Send + where + Self: SpawnBlocking, + Self::Pool: + TransactionPool>>, + { + async move { + if self.pending_block_kind().is_none() { + return Ok(None); + } + + let pending = self.pending_block_env_and_cfg()?; - Ok(Some((block, receipts))) + Ok(match pending.origin { + PendingBlockEnvOrigin::ActualPending(block, receipts) => { + Some(BlockAndReceipts { block, receipts }) + } + PendingBlockEnvOrigin::DerivedFromLatest(..) => { + self.pool_pending_block().await?.map(PendingBlock::into_block_and_receipts) + } + }) } } @@ -238,11 +269,14 @@ pub trait LoadPendingBlock: // Only include transactions if not configured as Empty if !self.pending_block_kind().is_empty() { - let mut best_txs = - self.pool().best_transactions_with_attributes(BestTransactionsAttributes::new( + let mut best_txs = self + .pool() + .best_transactions_with_attributes(BestTransactionsAttributes::new( block_env.basefee, block_env.blob_gasprice().map(|gasprice| gasprice as u64), - )); + )) + // freeze to get a block as fast as possible + .without_updates(); while let Some(pool_tx) = best_txs.next() { // ensure we still have capacity for this transaction @@ -278,21 +312,21 @@ pub trait LoadPendingBlock: // There's only limited amount of blob space available per block, so we need to // check if the EIP-4844 can still fit in the block - if let Some(tx_blob_gas) = tx.blob_gas_used() { - if sum_blob_gas_used + tx_blob_gas > blob_params.max_blob_gas_per_block() { - // we can't fit this _blob_ transaction into the block, so we mark it as - // invalid, which removes its dependent transactions from - // the iterator. This is similar to the gas limit condition - // for regular transactions above. - best_txs.mark_invalid( - &pool_tx, - InvalidPoolTransactionError::ExceedsGasLimit( - tx_blob_gas, - blob_params.max_blob_gas_per_block(), - ), - ); - continue - } + if let Some(tx_blob_gas) = tx.blob_gas_used() && + sum_blob_gas_used + tx_blob_gas > blob_params.max_blob_gas_per_block() + { + // we can't fit this _blob_ transaction into the block, so we mark it as + // invalid, which removes its dependent transactions from + // the iterator. This is similar to the gas limit condition + // for regular transactions above. + best_txs.mark_invalid( + &pool_tx, + InvalidPoolTransactionError::ExceedsGasLimit( + tx_blob_gas, + blob_params.max_blob_gas_per_block(), + ), + ); + continue } let gas_used = match builder.execute_transaction(tx.clone()) { @@ -336,7 +370,7 @@ pub trait LoadPendingBlock: } let BlockBuilderOutcome { execution_result, block, hashed_state, .. } = - builder.finish(&state_provider).map_err(Self::Error::from_eth_err)?; + builder.finish(NoopProvider::default()).map_err(Self::Error::from_eth_err)?; let execution_outcome = ExecutionOutcome::new( db.take_bundle(), diff --git a/crates/rpc/rpc-eth-api/src/helpers/receipt.rs b/crates/rpc/rpc-eth-api/src/helpers/receipt.rs index 7ff64be65de..58c3e8897dc 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/receipt.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/receipt.rs @@ -8,7 +8,24 @@ use reth_primitives_traits::SignerRecoverable; use reth_rpc_convert::{transaction::ConvertReceiptInput, RpcConvert}; use reth_rpc_eth_types::{error::FromEthApiError, EthApiError}; use reth_storage_api::{ProviderReceipt, ProviderTx}; -use std::borrow::Cow; + +/// Calculates the gas used and next log index for a transaction at the given index +pub fn calculate_gas_used_and_next_log_index( + tx_index: u64, + all_receipts: &[impl TxReceipt], +) -> (u64, usize) { + let mut gas_used = 0; + let mut next_log_index = 0; + + if tx_index > 0 { + for receipt in all_receipts.iter().take(tx_index as usize) { + gas_used = receipt.cumulative_gas_used(); + next_log_index += receipt.logs().len(); + } + } + + (gas_used, next_log_index) +} /// Assembles transaction receipt data w.r.t to network. /// @@ -42,15 +59,8 @@ pub trait LoadReceipt: .map_err(Self::Error::from_eth_err)? .ok_or(EthApiError::HeaderNotFound(hash.into()))?; - let mut gas_used = 0; - let mut next_log_index = 0; - - if meta.index > 0 { - for receipt in all_receipts.iter().take(meta.index as usize) { - gas_used = receipt.cumulative_gas_used(); - next_log_index += receipt.logs().len(); - } - } + let (gas_used, next_log_index) = + calculate_gas_used_and_next_log_index(meta.index, &all_receipts); Ok(self .tx_resp_builder() @@ -60,7 +70,7 @@ pub trait LoadReceipt: .map_err(Self::Error::from_eth_err)? .as_recovered_ref(), gas_used: receipt.cumulative_gas_used() - gas_used, - receipt: Cow::Owned(receipt), + receipt, next_log_index, meta, }])? diff --git a/crates/rpc/rpc-eth-api/src/helpers/spec.rs b/crates/rpc/rpc-eth-api/src/helpers/spec.rs index fd3e13620c5..ea9eb143607 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/spec.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/spec.rs @@ -3,7 +3,7 @@ use alloy_primitives::{Address, U256, U64}; use alloy_rpc_types_eth::{Stage, SyncInfo, SyncStatus}; use futures::Future; -use reth_chainspec::{ChainInfo, ChainSpecProvider, EthereumHardforks}; +use reth_chainspec::{ChainInfo, ChainSpecProvider, EthereumHardforks, Hardforks}; use reth_errors::{RethError, RethResult}; use reth_network_api::NetworkInfo; use reth_rpc_convert::{RpcTxReq, RpcTypes}; @@ -17,7 +17,7 @@ use crate::{helpers::EthSigner, RpcNodeCore}; #[auto_impl::auto_impl(&, Arc)] pub trait EthApiSpec: RpcNodeCore< - Provider: ChainSpecProvider + Provider: ChainSpecProvider + BlockNumReader + StageCheckpointReader, Network: NetworkInfo, diff --git a/crates/rpc/rpc-eth-api/src/helpers/state.rs b/crates/rpc/rpc-eth-api/src/helpers/state.rs index c9daa1790dc..1b3dbfcdee6 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/state.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/state.rs @@ -1,5 +1,6 @@ //! Loads a pending block from database. Helper trait for `eth_` block, transaction, call and trace //! RPC methods. + use super::{EthApiSpec, LoadPendingBlock, SpawnBlocking}; use crate::{EthApiTypes, FromEthApiError, RpcNodeCore, RpcNodeCoreExt}; use alloy_consensus::constants::KECCAK_EMPTY; @@ -10,7 +11,10 @@ use alloy_serde::JsonStorageKey; use futures::Future; use reth_errors::RethError; use reth_evm::{ConfigureEvm, EvmEnvFor}; -use reth_rpc_eth_types::{EthApiError, PendingBlockEnv, RpcInvalidTransactionError}; +use reth_rpc_convert::RpcConvert; +use reth_rpc_eth_types::{ + error::FromEvmError, EthApiError, PendingBlockEnv, RpcInvalidTransactionError, +}; use reth_storage_api::{ BlockIdReader, BlockNumReader, StateProvider, StateProviderBox, StateProviderFactory, }; @@ -48,9 +52,10 @@ pub trait EthState: LoadState + SpawnBlocking { address: Address, block_id: Option, ) -> impl Future> + Send { - self.spawn_blocking_io(move |this| { + self.spawn_blocking_io_fut(move |this| async move { Ok(this - .state_at_block_id_or_latest(block_id)? + .state_at_block_id_or_latest(block_id) + .await? .account_balance(&address) .map_err(Self::Error::from_eth_err)? .unwrap_or_default()) @@ -64,9 +69,10 @@ pub trait EthState: LoadState + SpawnBlocking { index: JsonStorageKey, block_id: Option, ) -> impl Future> + Send { - self.spawn_blocking_io(move |this| { + self.spawn_blocking_io_fut(move |this| async move { Ok(B256::new( - this.state_at_block_id_or_latest(block_id)? + this.state_at_block_id_or_latest(block_id) + .await? .storage(address, index.as_b256()) .map_err(Self::Error::from_eth_err)? .unwrap_or_default() @@ -109,8 +115,8 @@ pub trait EthState: LoadState + SpawnBlocking { return Err(EthApiError::ExceedsMaxProofWindow.into()) } - self.spawn_blocking_io(move |this| { - let state = this.state_at_block_id(block_id)?; + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id(block_id).await?; let storage_keys = keys.iter().map(|key| key.as_b256()).collect::>(); let proof = state .proof(Default::default(), address, &storage_keys) @@ -127,8 +133,8 @@ pub trait EthState: LoadState + SpawnBlocking { address: Address, block_id: BlockId, ) -> impl Future, Self::Error>> + Send { - self.spawn_blocking_io(move |this| { - let state = this.state_at_block_id(block_id)?; + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id(block_id).await?; let account = state.basic_account(&address).map_err(Self::Error::from_eth_err)?; let Some(account) = account else { return Ok(None) }; @@ -164,8 +170,8 @@ pub trait EthState: LoadState + SpawnBlocking { address: Address, block_id: BlockId, ) -> impl Future> + Send { - self.spawn_blocking_io(move |this| { - let state = this.state_at_block_id(block_id)?; + self.spawn_blocking_io_fut(move |this| async move { + let state = this.state_at_block_id(block_id).await?; let account = state .basic_account(&address) .map_err(Self::Error::from_eth_err)? @@ -191,7 +197,13 @@ pub trait EthState: LoadState + SpawnBlocking { /// Loads state from database. /// /// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` state RPC methods. -pub trait LoadState: EthApiTypes + RpcNodeCoreExt { +pub trait LoadState: + LoadPendingBlock + + EthApiTypes< + Error: FromEvmError + FromEthApiError, + RpcConvert: RpcConvert, + > + RpcNodeCoreExt +{ /// Returns the state at the given block number fn state_at_hash(&self, block_hash: B256) -> Result { self.provider().history_by_block_hash(block_hash).map_err(Self::Error::from_eth_err) @@ -201,8 +213,22 @@ pub trait LoadState: EthApiTypes + RpcNodeCoreExt { /// /// Note: if not [`BlockNumberOrTag::Pending`](alloy_eips::BlockNumberOrTag) then this /// will only return canonical state. See also - fn state_at_block_id(&self, at: BlockId) -> Result { - self.provider().state_by_block_id(at).map_err(Self::Error::from_eth_err) + fn state_at_block_id( + &self, + at: BlockId, + ) -> impl Future> + Send + where + Self: SpawnBlocking, + { + async move { + if at.is_pending() && + let Ok(Some(state)) = self.local_pending_state().await + { + return Ok(state) + } + + self.provider().state_by_block_id(at).map_err(Self::Error::from_eth_err) + } } /// Returns the _latest_ state @@ -216,11 +242,16 @@ pub trait LoadState: EthApiTypes + RpcNodeCoreExt { fn state_at_block_id_or_latest( &self, block_id: Option, - ) -> Result { - if let Some(block_id) = block_id { - self.state_at_block_id(block_id) - } else { - Ok(self.latest_state()?) + ) -> impl Future> + Send + where + Self: SpawnBlocking, + { + async move { + if let Some(block_id) = block_id { + self.state_at_block_id(block_id).await + } else { + Ok(self.latest_state()?) + } } } @@ -235,7 +266,7 @@ pub trait LoadState: EthApiTypes + RpcNodeCoreExt { at: BlockId, ) -> impl Future, BlockId), Self::Error>> + Send where - Self: LoadPendingBlock + SpawnBlocking, + Self: SpawnBlocking, { async move { if at.is_pending() { @@ -250,7 +281,11 @@ pub trait LoadState: EthApiTypes + RpcNodeCoreExt { let header = self.cache().get_header(block_hash).await.map_err(Self::Error::from_eth_err)?; - let evm_env = self.evm_config().evm_env(&header); + let evm_env = self + .evm_config() + .evm_env(&header) + .map_err(RethError::other) + .map_err(Self::Error::from_eth_err)?; Ok((evm_env, block_hash.into())) } @@ -303,10 +338,11 @@ pub trait LoadState: EthApiTypes + RpcNodeCoreExt { where Self: SpawnBlocking, { - self.spawn_blocking_io(move |this| { + self.spawn_blocking_io_fut(move |this| async move { // first fetch the on chain nonce of the account let on_chain_account_nonce = this - .state_at_block_id_or_latest(block_id)? + .state_at_block_id_or_latest(block_id) + .await? .account_nonce(&address) .map_err(Self::Error::from_eth_err)? .unwrap_or_default(); @@ -348,9 +384,10 @@ pub trait LoadState: EthApiTypes + RpcNodeCoreExt { where Self: SpawnBlocking, { - self.spawn_blocking_io(move |this| { + self.spawn_blocking_io_fut(move |this| async move { Ok(this - .state_at_block_id_or_latest(block_id)? + .state_at_block_id_or_latest(block_id) + .await? .account_code(&address) .map_err(Self::Error::from_eth_err)? .unwrap_or_default() diff --git a/crates/rpc/rpc-eth-api/src/helpers/trace.rs b/crates/rpc/rpc-eth-api/src/helpers/trace.rs index 329b4292f00..a3c79416cfe 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/trace.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/trace.rs @@ -2,7 +2,7 @@ use super::{Call, LoadBlock, LoadPendingBlock, LoadState, LoadTransaction}; use crate::FromEvmError; -use alloy_consensus::BlockHeader; +use alloy_consensus::{transaction::TxHashRef, BlockHeader}; use alloy_primitives::B256; use alloy_rpc_types_eth::{BlockId, TransactionInfo}; use futures::Future; @@ -12,7 +12,7 @@ use reth_evm::{ evm::EvmFactoryExt, system_calls::SystemCaller, tracing::TracingCtx, ConfigureEvm, Database, Evm, EvmEnvFor, EvmFor, HaltReasonFor, InspectorFor, TxEnvFor, }; -use reth_primitives_traits::{BlockBody, Recovered, RecoveredBlock, SignedTransaction}; +use reth_primitives_traits::{BlockBody, Recovered, RecoveredBlock}; use reth_revm::{database::StateProviderDatabase, db::CacheDB}; use reth_rpc_eth_types::{ cache::db::{StateCacheDb, StateCacheDbRefMutWrapper, StateProviderTraitObjWrapper}, @@ -56,18 +56,21 @@ pub trait Trace: LoadState> { config: TracingInspectorConfig, at: BlockId, f: F, - ) -> Result + ) -> impl Future> + Send where Self: Call, + R: Send + 'static, F: FnOnce( - TracingInspector, - ResultAndState>, - ) -> Result, + TracingInspector, + ResultAndState>, + ) -> Result + + Send + + 'static, { - self.with_state_at_block(at, |state| { + self.with_state_at_block(at, move |this, state| { let mut db = CacheDB::new(StateProviderDatabase::new(state)); let mut inspector = TracingInspector::new(config); - let res = self.inspect(&mut db, evm_env, tx_env, &mut inspector)?; + let res = this.inspect(&mut db, evm_env, tx_env, &mut inspector)?; f(inspector, res) }) } @@ -292,7 +295,7 @@ pub trait Trace: LoadState> { } // replay all transactions of the block - self.spawn_tracing(move |this| { + self.spawn_blocking_io_fut(move |this| async move { // we need to get the state of the parent block because we're replaying this block // on top of its parent block's state let state_at = block.parent_hash(); @@ -302,7 +305,7 @@ pub trait Trace: LoadState> { let base_fee = evm_env.block_env.basefee; // now get the state - let state = this.state_at_block_id(state_at.into())?; + let state = this.state_at_block_id(state_at.into()).await?; let mut db = CacheDB::new(StateProviderDatabase::new(StateProviderTraitObjWrapper(&state))); diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 168653e7c60..6d3615f865c 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -8,7 +8,7 @@ use crate::{ RpcTransaction, }; use alloy_consensus::{ - transaction::{SignerRecoverable, TransactionMeta}, + transaction::{SignerRecoverable, TransactionMeta, TxHashRef}, BlockHeader, Transaction, }; use alloy_dyn_abi::TypedData; @@ -32,7 +32,7 @@ use reth_storage_api::{ use reth_transaction_pool::{ AddedTransactionOutcome, PoolTransaction, TransactionOrigin, TransactionPool, }; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; /// Transaction related functions for the [`EthApiServer`](crate::EthApiServer) trait in /// the `eth_` namespace. @@ -62,6 +62,9 @@ pub trait EthTransactions: LoadTransaction { /// Signer access in default (L1) trait method implementations. fn signers(&self) -> &SignersForRpc; + /// Returns the timeout duration for `send_raw_transaction_sync` RPC method. + fn send_raw_transaction_sync_timeout(&self) -> Duration; + /// Decodes and recovers the transaction and submits it to the pool. /// /// Returns the hash of the transaction. @@ -81,31 +84,31 @@ pub trait EthTransactions: LoadTransaction { Self: LoadReceipt + 'static, { let this = self.clone(); + let timeout_duration = self.send_raw_transaction_sync_timeout(); async move { let hash = EthTransactions::send_raw_transaction(&this, tx).await?; let mut stream = this.provider().canonical_state_stream(); - const TIMEOUT_DURATION: tokio::time::Duration = tokio::time::Duration::from_secs(30); - tokio::time::timeout(TIMEOUT_DURATION, async { + tokio::time::timeout(timeout_duration, async { while let Some(notification) = stream.next().await { let chain = notification.committed(); for block in chain.blocks_iter() { - if block.body().contains_transaction(&hash) { - if let Some(receipt) = this.transaction_receipt(hash).await? { - return Ok(receipt); - } + if block.body().contains_transaction(&hash) && + let Some(receipt) = this.transaction_receipt(hash).await? + { + return Ok(receipt); } } } Err(Self::Error::from_eth_err(TransactionConfirmationTimeout { hash, - duration: TIMEOUT_DURATION, + duration: timeout_duration, })) }) .await .unwrap_or_else(|_elapsed| { Err(Self::Error::from_eth_err(TransactionConfirmationTimeout { hash, - duration: TIMEOUT_DURATION, + duration: timeout_duration, })) }) } @@ -291,13 +294,12 @@ pub trait EthTransactions: LoadTransaction { { async move { // Check the pool first - if include_pending { - if let Some(tx) = + if include_pending && + let Some(tx) = RpcNodeCore::pool(self).get_transaction_by_sender_and_nonce(sender, nonce) - { - let transaction = tx.transaction.clone_into_consensus(); - return Ok(Some(self.tx_resp_builder().fill_pending(transaction)?)); - } + { + let transaction = tx.transaction.clone_into_consensus(); + return Ok(Some(self.tx_resp_builder().fill_pending(transaction)?)); } // Check if the sender is a contract @@ -367,10 +369,10 @@ pub trait EthTransactions: LoadTransaction { Self: LoadBlock, { async move { - if let Some(block) = self.recovered_block(block_id).await? { - if let Some(tx) = block.body().transactions().get(index) { - return Ok(Some(tx.encoded_2718().into())) - } + if let Some(block) = self.recovered_block(block_id).await? && + let Some(tx) = block.body().transactions().get(index) + { + return Ok(Some(tx.encoded_2718().into())) } Ok(None) diff --git a/crates/rpc/rpc-eth-api/src/node.rs b/crates/rpc/rpc-eth-api/src/node.rs index 0cd113d33eb..bde95b9c572 100644 --- a/crates/rpc/rpc-eth-api/src/node.rs +++ b/crates/rpc/rpc-eth-api/src/node.rs @@ -1,7 +1,7 @@ //! Helper trait for interfacing with [`FullNodeComponents`]. use reth_chain_state::CanonStateSubscriptions; -use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks, Hardforks}; use reth_evm::ConfigureEvm; use reth_network_api::NetworkInfo; use reth_node_api::{FullNodeComponents, NodePrimitives, PrimitivesTy}; @@ -31,7 +31,9 @@ pub trait RpcNodeCore: Clone + Send + Sync + Unpin + 'static { Header = HeaderTy, Transaction = TxTy, > + ChainSpecProvider< - ChainSpec: EthChainSpec
> + EthereumHardforks, + ChainSpec: EthChainSpec
> + + Hardforks + + EthereumHardforks, > + StateProviderFactory + CanonStateSubscriptions + StageCheckpointReader @@ -62,7 +64,7 @@ pub trait RpcNodeCore: Clone + Send + Sync + Unpin + 'static { impl RpcNodeCore for T where - T: FullNodeComponents>, + T: FullNodeComponents>, { type Primitives = PrimitivesTy; type Provider = T::Provider; @@ -122,7 +124,9 @@ where Header = HeaderTy, Transaction = TxTy, > + ChainSpecProvider< - ChainSpec: EthChainSpec
> + EthereumHardforks, + ChainSpec: EthChainSpec
> + + Hardforks + + EthereumHardforks, > + StateProviderFactory + CanonStateSubscriptions + StageCheckpointReader diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index 4eb8b466ed3..22100520016 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -31,7 +31,7 @@ pub trait EthApiTypes: Send + Sync + Clone { /// Blockchain primitive types, specific to network, e.g. block and transaction. type NetworkTypes: RpcTypes; /// Conversion methods for transaction RPC type. - type RpcConvert: Send + Sync + Clone + fmt::Debug; + type RpcConvert: Send + Sync + fmt::Debug; /// Returns reference to transaction response builder. fn tx_resp_builder(&self) -> &Self::RpcConvert; diff --git a/crates/rpc/rpc-eth-types/Cargo.toml b/crates/rpc/rpc-eth-types/Cargo.toml index 2148ba7e37b..7eed1aa3db1 100644 --- a/crates/rpc/rpc-eth-types/Cargo.toml +++ b/crates/rpc/rpc-eth-types/Cargo.toml @@ -18,7 +18,7 @@ reth-errors.workspace = true reth-evm.workspace = true reth-execution-types.workspace = true reth-metrics.workspace = true -reth-ethereum-primitives.workspace = true +reth-ethereum-primitives = { workspace = true, features = ["rpc"] } reth-primitives-traits = { workspace = true, features = ["rpc-compat"] } reth-storage-api.workspace = true reth-revm.workspace = true @@ -34,6 +34,8 @@ alloy-evm = { workspace = true, features = ["overrides", "call-util"] } alloy-primitives.workspace = true alloy-consensus.workspace = true alloy-sol-types.workspace = true +alloy-transport.workspace = true +alloy-rpc-client = { workspace = true, features = ["reqwest"] } alloy-rpc-types-eth.workspace = true alloy-network.workspace = true revm.workspace = true @@ -47,6 +49,7 @@ jsonrpsee-types.workspace = true futures.workspace = true tokio.workspace = true tokio-stream.workspace = true +reqwest = { workspace = true, features = ["rustls-tls-native-roots"] } # metrics metrics.workspace = true diff --git a/crates/rpc/rpc-eth-types/src/block.rs b/crates/rpc/rpc-eth-types/src/block.rs new file mode 100644 index 00000000000..624ce53c26f --- /dev/null +++ b/crates/rpc/rpc-eth-types/src/block.rs @@ -0,0 +1,47 @@ +//! Block related types for RPC API. + +use std::sync::Arc; + +use alloy_primitives::TxHash; +use reth_primitives_traits::{ + BlockTy, IndexedTx, NodePrimitives, ReceiptTy, RecoveredBlock, SealedBlock, +}; + +/// A pair of an [`Arc`] wrapped [`RecoveredBlock`] and its corresponding receipts. +/// +/// This type is used throughout the RPC layer to efficiently pass around +/// blocks with their execution receipts, avoiding unnecessary cloning. +#[derive(Debug, Clone)] +pub struct BlockAndReceipts { + /// The recovered block. + pub block: Arc>>, + /// The receipts for the block. + pub receipts: Arc>>, +} + +impl BlockAndReceipts { + /// Creates a new [`BlockAndReceipts`] instance. + pub const fn new( + block: Arc>>, + receipts: Arc>>, + ) -> Self { + Self { block, receipts } + } + + /// Finds a transaction by hash and returns it along with its corresponding receipt. + /// + /// Returns `None` if the transaction is not found in this block. + pub fn find_transaction_and_receipt_by_hash( + &self, + tx_hash: TxHash, + ) -> Option<(IndexedTx<'_, N::Block>, &N::Receipt)> { + let indexed_tx = self.block.find_indexed(tx_hash)?; + let receipt = self.receipts.get(indexed_tx.index())?; + Some((indexed_tx, receipt)) + } + + /// Returns the underlying sealed block. + pub fn sealed_block(&self) -> &SealedBlock> { + self.block.sealed_block() + } +} diff --git a/crates/rpc/rpc-eth-types/src/builder/config.rs b/crates/rpc/rpc-eth-types/src/builder/config.rs index 6faa40701fd..47f15ae5ae7 100644 --- a/crates/rpc/rpc-eth-types/src/builder/config.rs +++ b/crates/rpc/rpc-eth-types/src/builder/config.rs @@ -3,12 +3,14 @@ use std::time::Duration; use crate::{ - EthStateCacheConfig, FeeHistoryCacheConfig, GasPriceOracleConfig, RPC_DEFAULT_GAS_CAP, + EthStateCacheConfig, FeeHistoryCacheConfig, ForwardConfig, GasPriceOracleConfig, + RPC_DEFAULT_GAS_CAP, }; +use reqwest::Url; use reth_rpc_server_types::constants::{ default_max_tracing_requests, DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_BLOCKS_PER_FILTER, DEFAULT_MAX_LOGS_PER_RESPONSE, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_MAX_TRACE_FILTER_BLOCKS, - DEFAULT_PROOF_PERMITS, + DEFAULT_PROOF_PERMITS, RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS, }; use serde::{Deserialize, Serialize}; @@ -56,7 +58,7 @@ impl PendingBlockKind { } /// Additional config values for the eth namespace. -#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct EthConfig { /// Settings for the caching layer pub cache: EthStateCacheConfig, @@ -89,6 +91,10 @@ pub struct EthConfig { pub max_batch_size: usize, /// Controls how pending blocks are built when requested via RPC methods pub pending_block_kind: PendingBlockKind, + /// The raw transaction forwarder. + pub raw_tx_forwarder: ForwardConfig, + /// Timeout duration for `send_raw_transaction_sync` RPC method. + pub send_raw_transaction_sync_timeout: Duration, } impl EthConfig { @@ -118,6 +124,8 @@ impl Default for EthConfig { proof_permits: DEFAULT_PROOF_PERMITS, max_batch_size: 1, pending_block_kind: PendingBlockKind::Full, + raw_tx_forwarder: ForwardConfig::default(), + send_raw_transaction_sync_timeout: RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS, } } } @@ -194,6 +202,20 @@ impl EthConfig { self.pending_block_kind = pending_block_kind; self } + + /// Configures the raw transaction forwarder. + pub fn raw_tx_forwarder(mut self, tx_forwarder: Option) -> Self { + if let Some(tx_forwarder) = tx_forwarder { + self.raw_tx_forwarder.tx_forwarder = Some(tx_forwarder); + } + self + } + + /// Configures the timeout duration for `send_raw_transaction_sync` RPC method. + pub const fn send_raw_transaction_sync_timeout(mut self, timeout: Duration) -> Self { + self.send_raw_transaction_sync_timeout = timeout; + self + } } /// Config for the filter diff --git a/crates/rpc/rpc-eth-types/src/cache/multi_consumer.rs b/crates/rpc/rpc-eth-types/src/cache/multi_consumer.rs index bae39c78f0f..dec5dcb09a0 100644 --- a/crates/rpc/rpc-eth-types/src/cache/multi_consumer.rs +++ b/crates/rpc/rpc-eth-types/src/cache/multi_consumer.rs @@ -100,11 +100,11 @@ where { let size = value.size(); - if self.cache.limiter().is_over_the_limit(self.cache.len() + 1) { - if let Some((_, evicted)) = self.cache.pop_oldest() { - // update tracked memory with the evicted value - self.memory_usage = self.memory_usage.saturating_sub(evicted.size()); - } + if self.cache.limiter().is_over_the_limit(self.cache.len() + 1) && + let Some((_, evicted)) = self.cache.pop_oldest() + { + // update tracked memory with the evicted value + self.memory_usage = self.memory_usage.saturating_sub(evicted.size()); } if self.cache.insert(key, value) { diff --git a/crates/rpc/rpc-eth-types/src/error/mod.rs b/crates/rpc/rpc-eth-types/src/error/mod.rs index 413e15585a8..aae656674a7 100644 --- a/crates/rpc/rpc-eth-types/src/error/mod.rs +++ b/crates/rpc/rpc-eth-types/src/error/mod.rs @@ -7,6 +7,7 @@ use alloy_evm::{call::CallError, overrides::StateOverrideError}; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rpc_types_eth::{error::EthRpcErrorCode, request::TransactionInputError, BlockError}; use alloy_sol_types::{ContractError, RevertReason}; +use alloy_transport::{RpcError, TransportErrorKind}; pub use api::{AsEthApiError, FromEthApiError, FromEvmError, IntoEthApiError}; use core::time::Duration; use reth_errors::{BlockExecutionError, BlockValidationError, RethError}; @@ -39,6 +40,19 @@ impl ToRpcError for jsonrpsee_types::ErrorObject<'static> { } } +impl ToRpcError for RpcError { + fn to_rpc_error(&self) -> jsonrpsee_types::ErrorObject<'static> { + match self { + Self::ErrorResp(payload) => jsonrpsee_types::error::ErrorObject::owned( + payload.code as i32, + payload.message.clone(), + payload.data.clone(), + ), + err => internal_rpc_err(err.to_string()), + } + } +} + /// Result alias pub type EthResult = Result; @@ -186,7 +200,13 @@ impl EthApiError { /// Returns `true` if error is [`RpcInvalidTransactionError::GasTooHigh`] pub const fn is_gas_too_high(&self) -> bool { - matches!(self, Self::InvalidTransaction(RpcInvalidTransactionError::GasTooHigh)) + matches!( + self, + Self::InvalidTransaction( + RpcInvalidTransactionError::GasTooHigh | + RpcInvalidTransactionError::GasLimitTooHigh + ) + ) } /// Returns `true` if error is [`RpcInvalidTransactionError::GasTooLow`] @@ -269,9 +289,10 @@ impl From for jsonrpsee_types::error::ErrorObject<'static> { block_id_to_str(end_id), ), ), - err @ EthApiError::TransactionConfirmationTimeout { .. } => { - rpc_error_with_code(EthRpcErrorCode::TransactionRejected.code(), err.to_string()) - } + err @ EthApiError::TransactionConfirmationTimeout { .. } => rpc_error_with_code( + EthRpcErrorCode::TransactionConfirmationTimeout.code(), + err.to_string(), + ), EthApiError::Unsupported(msg) => internal_rpc_err(msg), EthApiError::InternalJsTracerError(msg) => internal_rpc_err(msg), EthApiError::InvalidParams(msg) => invalid_params_rpc_err(msg), @@ -785,6 +806,9 @@ impl From for RpcInvalidTransactionError { InvalidTransactionError::FeeCapTooLow => Self::FeeCapTooLow, InvalidTransactionError::SignerAccountHasBytecode => Self::SenderNoEOA, InvalidTransactionError::GasLimitTooHigh => Self::GasLimitTooHigh, + InvalidTransactionError::GaslessValidationError(err) => { + Self::other(internal_rpc_err(err.to_string())) + } } } } diff --git a/crates/rpc/rpc-eth-types/src/fee_history.rs b/crates/rpc/rpc-eth-types/src/fee_history.rs index 7838cc3304d..3eaf69d2c4c 100644 --- a/crates/rpc/rpc-eth-types/src/fee_history.rs +++ b/crates/rpc/rpc-eth-types/src/fee_history.rs @@ -234,13 +234,13 @@ pub async fn fee_history_cache_new_blocks_task( let mut fetch_missing_block = Fuse::terminated(); loop { - if fetch_missing_block.is_terminated() { - if let Some(block_number) = missing_blocks.pop_front() { - trace!(target: "rpc::fee", ?block_number, "Fetching missing block for fee history cache"); - if let Ok(Some(hash)) = provider.block_hash(block_number) { - // fetch missing block - fetch_missing_block = cache.get_block_and_receipts(hash).boxed().fuse(); - } + if fetch_missing_block.is_terminated() && + let Some(block_number) = missing_blocks.pop_front() + { + trace!(target: "rpc::fee", ?block_number, "Fetching missing block for fee history cache"); + if let Ok(Some(hash)) = provider.block_hash(block_number) { + // fetch missing block + fetch_missing_block = cache.get_block_and_receipts(hash).boxed().fuse(); } } @@ -405,10 +405,11 @@ where /// Returns a `None` if no excess blob gas is set, no EIP-4844 support pub fn next_block_excess_blob_gas(&self) -> Option { self.header.excess_blob_gas().and_then(|excess_blob_gas| { - Some( - self.blob_params? - .next_block_excess_blob_gas(excess_blob_gas, self.header.blob_gas_used()?), - ) + Some(self.blob_params?.next_block_excess_blob_gas_osaka( + excess_blob_gas, + self.header.blob_gas_used()?, + self.header.base_fee_per_gas()?, + )) }) } } diff --git a/crates/rpc/rpc-eth-types/src/gas_oracle.rs b/crates/rpc/rpc-eth-types/src/gas_oracle.rs index 00df9f7360b..7bbf6433c6d 100644 --- a/crates/rpc/rpc-eth-types/src/gas_oracle.rs +++ b/crates/rpc/rpc-eth-types/src/gas_oracle.rs @@ -49,7 +49,7 @@ pub struct GasPriceOracleConfig { pub max_reward_percentile_count: u64, /// The default gas price to use if there are no blocks to use - pub default: Option, + pub default_suggested_fee: Option, /// The maximum gas price to use for the estimate pub max_price: Option, @@ -66,7 +66,7 @@ impl Default for GasPriceOracleConfig { max_header_history: MAX_HEADER_HISTORY, max_block_history: MAX_HEADER_HISTORY, max_reward_percentile_count: MAX_REWARD_PERCENTILE_COUNT, - default: None, + default_suggested_fee: None, max_price: Some(DEFAULT_MAX_GAS_PRICE), ignore_price: Some(DEFAULT_IGNORE_GAS_PRICE), } @@ -112,7 +112,12 @@ where // this is the number of blocks that we will cache the values for let cached_values = (oracle_config.blocks * 5).max(oracle_config.max_block_history as u32); let inner = Mutex::new(GasPriceOracleInner { - last_price: Default::default(), + last_price: GasPriceOracleResult { + block_hash: B256::ZERO, + price: oracle_config + .default_suggested_fee + .unwrap_or_else(|| GasPriceOracleResult::default().price), + }, lowest_effective_tip_cache: EffectiveTipLruCache(LruMap::new(ByLength::new( cached_values, ))), @@ -199,10 +204,10 @@ where }; // constrain to the max price - if let Some(max_price) = self.oracle_config.max_price { - if price > max_price { - price = max_price; - } + if let Some(max_price) = self.oracle_config.max_price && + price > max_price + { + price = max_price; } inner.last_price = GasPriceOracleResult { block_hash: header.hash(), price }; @@ -249,10 +254,10 @@ where }; // ignore transactions with a tip under the configured threshold - if let Some(ignore_under) = self.ignore_price { - if effective_tip < Some(ignore_under) { - continue - } + if let Some(ignore_under) = self.ignore_price && + effective_tip < Some(ignore_under) + { + continue } // check if the sender was the coinbase, if so, ignore @@ -333,10 +338,10 @@ where } // constrain to the max price - if let Some(max_price) = self.oracle_config.max_price { - if suggestion > max_price { - suggestion = max_price; - } + if let Some(max_price) = self.oracle_config.max_price && + suggestion > max_price + { + suggestion = max_price; } inner.last_price = GasPriceOracleResult { block_hash: header.hash(), price: suggestion }; diff --git a/crates/rpc/rpc-eth-types/src/lib.rs b/crates/rpc/rpc-eth-types/src/lib.rs index eead8c5fc2a..f943febb007 100644 --- a/crates/rpc/rpc-eth-types/src/lib.rs +++ b/crates/rpc/rpc-eth-types/src/lib.rs @@ -8,6 +8,7 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(test), warn(unused_crate_dependencies))] +pub mod block; pub mod builder; pub mod cache; pub mod error; @@ -19,6 +20,7 @@ pub mod pending_block; pub mod receipt; pub mod simulate; pub mod transaction; +pub mod tx_forward; pub mod utils; pub use builder::config::{EthConfig, EthFilterConfig}; @@ -34,3 +36,4 @@ pub use gas_oracle::{ pub use id_provider::EthSubscriptionIdProvider; pub use pending_block::{PendingBlock, PendingBlockEnv, PendingBlockEnvOrigin}; pub use transaction::TransactionSource; +pub use tx_forward::ForwardConfig; diff --git a/crates/rpc/rpc-eth-types/src/logs_utils.rs b/crates/rpc/rpc-eth-types/src/logs_utils.rs index dee33a7a175..1d93de4bb1f 100644 --- a/crates/rpc/rpc-eth-types/src/logs_utils.rs +++ b/crates/rpc/rpc-eth-types/src/logs_utils.rs @@ -145,7 +145,9 @@ where Ok(()) } -/// Computes the block range based on the filter range and current block numbers +/// Computes the block range based on the filter range and current block numbers. +/// +/// This returns `(min(best,from), min(best,to))`. pub fn get_filter_block_range( from_block: Option, to_block: Option, diff --git a/crates/rpc/rpc-eth-types/src/pending_block.rs b/crates/rpc/rpc-eth-types/src/pending_block.rs index a339b6b0730..05ad6fb4e27 100644 --- a/crates/rpc/rpc-eth-types/src/pending_block.rs +++ b/crates/rpc/rpc-eth-types/src/pending_block.rs @@ -4,13 +4,19 @@ use std::{sync::Arc, time::Instant}; +use crate::block::BlockAndReceipts; use alloy_consensus::BlockHeader; use alloy_eips::{BlockId, BlockNumberOrTag}; -use alloy_primitives::B256; +use alloy_primitives::{BlockHash, B256}; use derive_more::Constructor; +use reth_chain_state::{ + BlockState, ExecutedBlock, ExecutedBlockWithTrieUpdates, ExecutedTrieUpdates, +}; use reth_ethereum_primitives::Receipt; use reth_evm::EvmEnv; -use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock, SealedHeader}; +use reth_primitives_traits::{ + Block, BlockTy, NodePrimitives, ReceiptTy, RecoveredBlock, SealedHeader, +}; /// Configured [`EvmEnv`] for a pending block. #[derive(Debug, Clone, Constructor)] @@ -73,13 +79,67 @@ impl PendingBlockEnvOrigin { } } +/// A type alias for a pair of an [`Arc`] wrapped [`RecoveredBlock`] and a vector of +/// [`NodePrimitives::Receipt`]. +pub type PendingBlockAndReceipts = BlockAndReceipts; + /// Locally built pending block for `pending` tag. -#[derive(Debug, Constructor)] +#[derive(Debug, Clone, Constructor)] pub struct PendingBlock { /// Timestamp when the pending block is considered outdated. pub expires_at: Instant, - /// The locally built pending block. - pub block: Arc>, /// The receipts for the pending block - pub receipts: Arc>, + pub receipts: Arc>>, + /// The locally built pending block with execution output. + pub executed_block: ExecutedBlock, +} + +impl PendingBlock { + /// Creates a new instance of [`PendingBlock`] with `executed_block` as its output that should + /// not be used past `expires_at`. + pub fn with_executed_block(expires_at: Instant, executed_block: ExecutedBlock) -> Self { + Self { + expires_at, + receipts: Arc::new( + executed_block.execution_output.receipts.iter().flatten().cloned().collect(), + ), + executed_block, + } + } + + /// Returns the locally built pending [`RecoveredBlock`]. + pub const fn block(&self) -> &Arc>> { + &self.executed_block.recovered_block + } + + /// Converts this [`PendingBlock`] into a pair of [`RecoveredBlock`] and a vector of + /// [`NodePrimitives::Receipt`]s, taking self. + pub fn into_block_and_receipts(self) -> PendingBlockAndReceipts { + BlockAndReceipts { block: self.executed_block.recovered_block, receipts: self.receipts } + } + + /// Returns a pair of [`RecoveredBlock`] and a vector of [`NodePrimitives::Receipt`]s by + /// cloning from borrowed self. + pub fn to_block_and_receipts(&self) -> PendingBlockAndReceipts { + BlockAndReceipts { + block: self.executed_block.recovered_block.clone(), + receipts: self.receipts.clone(), + } + } + + /// Returns a hash of the parent block for this `executed_block`. + pub fn parent_hash(&self) -> BlockHash { + self.executed_block.recovered_block().parent_hash() + } +} + +impl From> for BlockState { + fn from(pending_block: PendingBlock) -> Self { + Self::new(ExecutedBlockWithTrieUpdates::::new( + pending_block.executed_block.recovered_block, + pending_block.executed_block.execution_output, + pending_block.executed_block.hashed_state, + ExecutedTrieUpdates::Missing, + )) + } } diff --git a/crates/rpc/rpc-eth-types/src/receipt.rs b/crates/rpc/rpc-eth-types/src/receipt.rs index 4ea4ad1daf5..48dbf1e5add 100644 --- a/crates/rpc/rpc-eth-types/src/receipt.rs +++ b/crates/rpc/rpc-eth-types/src/receipt.rs @@ -1,21 +1,21 @@ //! RPC receipt response builder, extends a layer one receipt with layer two data. use crate::EthApiError; -use alloy_consensus::{ReceiptEnvelope, Transaction, TxReceipt}; +use alloy_consensus::{ReceiptEnvelope, Transaction}; use alloy_eips::eip7840::BlobParams; use alloy_primitives::{Address, TxKind}; -use alloy_rpc_types_eth::{Log, ReceiptWithBloom, TransactionReceipt}; +use alloy_rpc_types_eth::{Log, TransactionReceipt}; use reth_chainspec::EthChainSpec; use reth_ethereum_primitives::Receipt; -use reth_primitives_traits::NodePrimitives; +use reth_primitives_traits::{NodePrimitives, TransactionMeta}; use reth_rpc_convert::transaction::{ConvertReceiptInput, ReceiptConverter}; -use std::{borrow::Cow, sync::Arc}; +use std::sync::Arc; /// Builds an [`TransactionReceipt`] obtaining the inner receipt envelope from the given closure. pub fn build_receipt( - input: &ConvertReceiptInput<'_, N>, + input: ConvertReceiptInput<'_, N>, blob_params: Option, - build_envelope: impl FnOnce(ReceiptWithBloom>) -> E, + build_rpc_receipt: impl FnOnce(N::Receipt, usize, TransactionMeta) -> E, ) -> TransactionReceipt where N: NodePrimitives, @@ -28,33 +28,20 @@ where let blob_gas_price = blob_gas_used.and_then(|_| Some(blob_params?.calc_blob_fee(meta.excess_blob_gas?))); - let status = receipt.status_or_post_state(); - let cumulative_gas_used = receipt.cumulative_gas_used(); - let logs_bloom = receipt.bloom(); - - let logs = match receipt { - Cow::Borrowed(r) => { - Log::collect_for_receipt(*next_log_index, *meta, r.logs().iter().cloned()) - } - Cow::Owned(r) => Log::collect_for_receipt(*next_log_index, *meta, r.into_logs()), - }; - - let rpc_receipt = alloy_rpc_types_eth::Receipt { status, cumulative_gas_used, logs }; - let (contract_address, to) = match tx.kind() { TxKind::Create => (Some(from.create(tx.nonce())), None), TxKind::Call(addr) => (None, Some(Address(*addr))), }; TransactionReceipt { - inner: build_envelope(ReceiptWithBloom { receipt: rpc_receipt, logs_bloom }), + inner: build_rpc_receipt(receipt, next_log_index, meta), transaction_hash: meta.tx_hash, transaction_index: Some(meta.index), block_hash: Some(meta.block_hash), block_number: Some(meta.block_number), from, to, - gas_used: *gas_used, + gas_used, contract_address, effective_gas_price: tx.effective_gas_price(meta.base_fee), // EIP-4844 fields @@ -64,30 +51,55 @@ where } /// Converter for Ethereum receipts. -#[derive(Debug)] -pub struct EthReceiptConverter { +#[derive(derive_more::Debug)] +pub struct EthReceiptConverter< + ChainSpec, + Builder = fn(Receipt, usize, TransactionMeta) -> ReceiptEnvelope, +> { chain_spec: Arc, + #[debug(skip)] + build_rpc_receipt: Builder, } -impl Clone for EthReceiptConverter { +impl Clone for EthReceiptConverter +where + Builder: Clone, +{ fn clone(&self) -> Self { - Self { chain_spec: self.chain_spec.clone() } + Self { + chain_spec: self.chain_spec.clone(), + build_rpc_receipt: self.build_rpc_receipt.clone(), + } } } impl EthReceiptConverter { /// Creates a new converter with the given chain spec. pub const fn new(chain_spec: Arc) -> Self { - Self { chain_spec } + Self { + chain_spec, + build_rpc_receipt: |receipt, next_log_index, meta| { + receipt.into_rpc(next_log_index, meta).into() + }, + } + } + + /// Sets new builder for the converter. + pub fn with_builder( + self, + build_rpc_receipt: Builder, + ) -> EthReceiptConverter { + EthReceiptConverter { chain_spec: self.chain_spec, build_rpc_receipt } } } -impl ReceiptConverter for EthReceiptConverter +impl ReceiptConverter for EthReceiptConverter where - N: NodePrimitives, + N: NodePrimitives, ChainSpec: EthChainSpec + 'static, + Builder: Fn(N::Receipt, usize, TransactionMeta) -> Rpc + 'static, { - type RpcReceipt = TransactionReceipt; + type RpcReceipt = TransactionReceipt; type Error = EthApiError; fn convert_receipts( @@ -97,11 +109,8 @@ where let mut receipts = Vec::with_capacity(inputs.len()); for input in inputs { - let tx_type = input.receipt.tx_type; let blob_params = self.chain_spec.blob_params_at_timestamp(input.meta.timestamp); - receipts.push(build_receipt(&input, blob_params, |receipt_with_bloom| { - ReceiptEnvelope::from_typed(tx_type, receipt_with_bloom) - })); + receipts.push(build_receipt(input, blob_params, &self.build_rpc_receipt)); } Ok(receipts) diff --git a/crates/rpc/rpc-eth-types/src/simulate.rs b/crates/rpc/rpc-eth-types/src/simulate.rs index 733390a1965..5492e127b77 100644 --- a/crates/rpc/rpc-eth-types/src/simulate.rs +++ b/crates/rpc/rpc-eth-types/src/simulate.rs @@ -7,7 +7,7 @@ use crate::{ }, EthApiError, RevertError, }; -use alloy_consensus::{BlockHeader, Transaction as _}; +use alloy_consensus::{transaction::TxHashRef, BlockHeader, Transaction as _}; use alloy_eips::eip2718::WithEncoded; use alloy_network::TransactionBuilder; use alloy_rpc_types_eth::{ @@ -19,9 +19,7 @@ use reth_evm::{ execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor}, Evm, }; -use reth_primitives_traits::{ - BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock, SignedTransaction, -}; +use reth_primitives_traits::{BlockBody as _, BlockTy, NodePrimitives, Recovered, RecoveredBlock}; use reth_rpc_convert::{RpcBlock, RpcConvert, RpcTxReq}; use reth_rpc_server_types::result::rpc_err; use reth_storage_api::noop::NoopProvider; diff --git a/crates/rpc/rpc-eth-types/src/tx_forward.rs b/crates/rpc/rpc-eth-types/src/tx_forward.rs new file mode 100644 index 00000000000..07499a5a9f5 --- /dev/null +++ b/crates/rpc/rpc-eth-types/src/tx_forward.rs @@ -0,0 +1,22 @@ +//! Consist of types adjacent to the fee history cache and its configs + +use alloy_rpc_client::RpcClient; +use reqwest::Url; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; + +/// Configuration for the transaction forwarder. +#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize)] +pub struct ForwardConfig { + /// The raw transaction forwarder. + /// + /// Default is `None` + pub tx_forwarder: Option, +} + +impl ForwardConfig { + /// Builds an [`RpcClient`] from the forwarder URL, if configured. + pub fn forwarder_client(&self) -> Option { + self.tx_forwarder.clone().map(RpcClient::new_http) + } +} diff --git a/crates/rpc/rpc-eth-types/src/utils.rs b/crates/rpc/rpc-eth-types/src/utils.rs index 33616679ddd..69f9833af5e 100644 --- a/crates/rpc/rpc-eth-types/src/utils.rs +++ b/crates/rpc/rpc-eth-types/src/utils.rs @@ -9,14 +9,17 @@ use std::future::Future; /// This is a helper function that returns the appropriate RPC-specific error if the input data is /// malformed. /// -/// See [`alloy_eips::eip2718::Decodable2718::decode_2718`] -pub fn recover_raw_transaction(mut data: &[u8]) -> EthResult> { +/// This function uses [`alloy_eips::eip2718::Decodable2718::decode_2718_exact`] to ensure +/// that the entire input buffer is consumed and no trailing bytes are allowed. +/// +/// See [`alloy_eips::eip2718::Decodable2718::decode_2718_exact`] +pub fn recover_raw_transaction(data: &[u8]) -> EthResult> { if data.is_empty() { return Err(EthApiError::EmptyRawTransactionData) } let transaction = - T::decode_2718(&mut data).map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?; + T::decode_2718_exact(data).map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?; SignedTransaction::try_into_recovered(transaction) .or(Err(EthApiError::InvalidTransactionSignature)) diff --git a/crates/rpc/rpc-server-types/src/constants.rs b/crates/rpc/rpc-server-types/src/constants.rs index 46dac33ba11..8861af7b54d 100644 --- a/crates/rpc/rpc-server-types/src/constants.rs +++ b/crates/rpc/rpc-server-types/src/constants.rs @@ -1,4 +1,4 @@ -use std::cmp::max; +use std::{cmp::max, time::Duration}; /// The default port for the http server pub const DEFAULT_HTTP_RPC_PORT: u16 = 8545; @@ -61,6 +61,9 @@ pub const DEFAULT_TX_FEE_CAP_WEI: u128 = 1_000_000_000_000_000_000u128; /// second block time, and a month on a 2 second block time. pub const MAX_ETH_PROOF_WINDOW: u64 = 28 * 24 * 60 * 60 / 2; +/// Default timeout for send raw transaction sync in seconds. +pub const RPC_DEFAULT_SEND_RAW_TX_SYNC_TIMEOUT_SECS: Duration = Duration::from_secs(30); + /// GPO specific constants pub mod gas_oracle { use alloy_primitives::U256; @@ -104,18 +107,6 @@ pub mod gas_oracle { /// Cache specific constants pub mod cache { - // TODO: memory based limiter is currently disabled pending - /// Default cache size for the block cache: 500MB - /// - /// With an average block size of ~100kb this should be able to cache ~5000 blocks. - pub const DEFAULT_BLOCK_CACHE_SIZE_BYTES_MB: usize = 500; - - /// Default cache size for the receipts cache: 500MB - pub const DEFAULT_RECEIPT_CACHE_SIZE_BYTES_MB: usize = 500; - - /// Default cache size for the env cache: 1MB - pub const DEFAULT_ENV_CACHE_SIZE_BYTES_MB: usize = 1; - /// Default cache size for the block cache: 5000 blocks. pub const DEFAULT_BLOCK_CACHE_MAX_LEN: u32 = 5000; diff --git a/crates/rpc/rpc-server-types/src/lib.rs b/crates/rpc/rpc-server-types/src/lib.rs index c20b578816b..2c7203241c0 100644 --- a/crates/rpc/rpc-server-types/src/lib.rs +++ b/crates/rpc/rpc-server-types/src/lib.rs @@ -13,6 +13,9 @@ pub mod constants; pub mod result; mod module; -pub use module::{RethRpcModule, RpcModuleSelection}; +pub use module::{ + DefaultRpcModuleValidator, LenientRpcModuleValidator, RethRpcModule, RpcModuleSelection, + RpcModuleValidator, +}; pub use result::ToRpcResult; diff --git a/crates/rpc/rpc-server-types/src/module.rs b/crates/rpc/rpc-server-types/src/module.rs index fdca41cc196..db9268d5d6e 100644 --- a/crates/rpc/rpc-server-types/src/module.rs +++ b/crates/rpc/rpc-server-types/src/module.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, fmt, str::FromStr}; use serde::{Deserialize, Serialize, Serializer}; -use strum::{AsRefStr, EnumIter, IntoStaticStr, ParseError, VariantArray, VariantNames}; +use strum::{ParseError, VariantNames}; /// Describes the modules that should be installed. /// @@ -98,12 +98,17 @@ impl RpcModuleSelection { } } + /// Returns true if all modules are selected + pub const fn is_all(&self) -> bool { + matches!(self, Self::All) + } + /// Returns an iterator over all configured [`RethRpcModule`] pub fn iter_selection(&self) -> Box + '_> { match self { Self::All => Box::new(RethRpcModule::modules().into_iter()), - Self::Standard => Box::new(Self::STANDARD_MODULES.iter().copied()), - Self::Selection(s) => Box::new(s.iter().copied()), + Self::Standard => Box::new(Self::STANDARD_MODULES.iter().cloned()), + Self::Selection(s) => Box::new(s.iter().cloned()), } } @@ -149,6 +154,64 @@ impl RpcModuleSelection { Self::Selection(s) => s.contains(module), } } + + /// Adds a module to the selection. + /// + /// If the selection is `All`, this is a no-op. + /// Otherwise, converts to a `Selection` and adds the module. + pub fn push(&mut self, module: RethRpcModule) { + if !self.is_all() { + let mut modules = self.to_selection(); + modules.insert(module); + *self = Self::Selection(modules); + } + } + + /// Returns a new selection with the given module added. + /// + /// If the selection is `All`, returns `All`. + /// Otherwise, converts to a `Selection` and adds the module. + pub fn append(self, module: RethRpcModule) -> Self { + if self.is_all() { + Self::All + } else { + let mut modules = self.into_selection(); + modules.insert(module); + Self::Selection(modules) + } + } + + /// Extends the selection with modules from an iterator. + /// + /// If the selection is `All`, this is a no-op. + /// Otherwise, converts to a `Selection` and adds the modules. + pub fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + if !self.is_all() { + let mut modules = self.to_selection(); + modules.extend(iter); + *self = Self::Selection(modules); + } + } + + /// Returns a new selection with modules from an iterator added. + /// + /// If the selection is `All`, returns `All`. + /// Otherwise, converts to a `Selection` and adds the modules. + pub fn extended(self, iter: I) -> Self + where + I: IntoIterator, + { + if self.is_all() { + Self::All + } else { + let mut modules = self.into_selection(); + modules.extend(iter); + Self::Selection(modules) + } + } } impl From<&HashSet> for RpcModuleSelection { @@ -165,7 +228,7 @@ impl From> for RpcModuleSelection { impl From<&[RethRpcModule]> for RpcModuleSelection { fn from(s: &[RethRpcModule]) -> Self { - Self::Selection(s.iter().copied().collect()) + Self::Selection(s.iter().cloned().collect()) } } @@ -177,7 +240,7 @@ impl From> for RpcModuleSelection { impl From<[RethRpcModule; N]> for RpcModuleSelection { fn from(s: [RethRpcModule; N]) -> Self { - Self::Selection(s.iter().copied().collect()) + Self::Selection(s.iter().cloned().collect()) } } @@ -186,7 +249,7 @@ impl<'a> FromIterator<&'a RethRpcModule> for RpcModuleSelection { where I: IntoIterator, { - iter.into_iter().copied().collect() + iter.into_iter().cloned().collect() } } @@ -230,20 +293,7 @@ impl fmt::Display for RpcModuleSelection { } /// Represents RPC modules that are supported by reth -#[derive( - Debug, - Clone, - Copy, - Eq, - PartialEq, - Hash, - AsRefStr, - IntoStaticStr, - VariantNames, - VariantArray, - EnumIter, - Deserialize, -)] +#[derive(Debug, Clone, Eq, PartialEq, Hash, VariantNames, Deserialize)] #[serde(rename_all = "snake_case")] #[strum(serialize_all = "kebab-case")] pub enum RethRpcModule { @@ -273,36 +323,90 @@ pub enum RethRpcModule { Miner, /// `mev_` module Mev, + /// Custom RPC module not part of the standard set + #[strum(default)] + #[serde(untagged)] + Other(String), } // === impl RethRpcModule === impl RethRpcModule { - /// Returns the number of variants in the enum + /// All standard variants (excludes Other) + const STANDARD_VARIANTS: &'static [Self] = &[ + Self::Admin, + Self::Debug, + Self::Eth, + Self::Net, + Self::Trace, + Self::Txpool, + Self::Web3, + Self::Rpc, + Self::Reth, + Self::Ots, + Self::Flashbots, + Self::Miner, + Self::Mev, + ]; + + /// Returns the number of standard variants (excludes Other) pub const fn variant_count() -> usize { - ::VARIANTS.len() + Self::STANDARD_VARIANTS.len() } - /// Returns all variant names of the enum + /// Returns all variant names including Other (for parsing) pub const fn all_variant_names() -> &'static [&'static str] { ::VARIANTS } - /// Returns all variants of the enum + /// Returns standard variant names (excludes "other") for CLI display + pub fn standard_variant_names() -> impl Iterator { + ::VARIANTS.iter().copied().filter(|&name| name != "other") + } + + /// Returns all standard variants (excludes Other) pub const fn all_variants() -> &'static [Self] { - ::VARIANTS + Self::STANDARD_VARIANTS } - /// Returns all variants of the enum - pub fn modules() -> impl IntoIterator { - use strum::IntoEnumIterator; - Self::iter() + /// Returns iterator over standard modules only + pub fn modules() -> impl IntoIterator + Clone { + Self::STANDARD_VARIANTS.iter().cloned() } /// Returns the string representation of the module. - #[inline] - pub fn as_str(&self) -> &'static str { - self.into() + pub fn as_str(&self) -> &str { + match self { + Self::Other(s) => s.as_str(), + _ => self.as_ref(), // Uses AsRefStr trait + } + } + + /// Returns true if this is an `Other` variant. + pub const fn is_other(&self) -> bool { + matches!(self, Self::Other(_)) + } +} + +impl AsRef for RethRpcModule { + fn as_ref(&self) -> &str { + match self { + Self::Other(s) => s.as_str(), + // For standard variants, use the derive-generated static strings + Self::Admin => "admin", + Self::Debug => "debug", + Self::Eth => "eth", + Self::Net => "net", + Self::Trace => "trace", + Self::Txpool => "txpool", + Self::Web3 => "web3", + Self::Rpc => "rpc", + Self::Reth => "reth", + Self::Ots => "ots", + Self::Flashbots => "flashbots", + Self::Miner => "miner", + Self::Mev => "mev", + } } } @@ -324,7 +428,8 @@ impl FromStr for RethRpcModule { "flashbots" => Self::Flashbots, "miner" => Self::Miner, "mev" => Self::Mev, - _ => return Err(ParseError::VariantNotFound), + // Any unknown module becomes Other + other => Self::Other(other.to_string()), }) } } @@ -347,7 +452,81 @@ impl Serialize for RethRpcModule { where S: Serializer, { - s.serialize_str(self.as_ref()) + s.serialize_str(self.as_str()) + } +} + +/// Trait for validating RPC module selections. +/// +/// This allows customizing how RPC module names are validated when parsing +/// CLI arguments or configuration. +pub trait RpcModuleValidator: Clone + Send + Sync + 'static { + /// Parse and validate an RPC module selection string. + fn parse_selection(s: &str) -> Result; + + /// Validates RPC module selection that was already parsed. + /// + /// This is used to validate modules that were parsed as `Other` variants + /// to ensure they meet the validation rules of the specific implementation. + fn validate_selection(modules: &RpcModuleSelection, arg_name: &str) -> Result<(), String> { + // Re-validate the modules using the parser's validator + // This is necessary because the clap value parser accepts any input + // and we need to validate according to the specific parser's rules + let RpcModuleSelection::Selection(module_set) = modules else { + // All or Standard variants are always valid + return Ok(()); + }; + + for module in module_set { + let RethRpcModule::Other(name) = module else { + // Standard modules are always valid + continue; + }; + + // Try to parse and validate using the configured validator + // This will check for typos and other validation rules + Self::parse_selection(name) + .map_err(|e| format!("Invalid RPC module '{name}' in {arg_name}: {e}"))?; + } + + Ok(()) + } +} + +/// Default validator that rejects unknown module names. +/// +/// This validator only accepts known RPC module names. +#[derive(Debug, Clone, Copy)] +pub struct DefaultRpcModuleValidator; + +impl RpcModuleValidator for DefaultRpcModuleValidator { + fn parse_selection(s: &str) -> Result { + // First try standard parsing + let selection = RpcModuleSelection::from_str(s) + .map_err(|e| format!("Failed to parse RPC modules: {}", e))?; + + // Validate each module in the selection + if let RpcModuleSelection::Selection(modules) = &selection { + for module in modules { + if let RethRpcModule::Other(name) = module { + return Err(format!("Unknown RPC module: '{}'", name)); + } + } + } + + Ok(selection) + } +} + +/// Lenient validator that accepts any module name without validation. +/// +/// This validator accepts any module name, including unknown ones. +#[derive(Debug, Clone, Copy)] +pub struct LenientRpcModuleValidator; + +impl RpcModuleValidator for LenientRpcModuleValidator { + fn parse_selection(s: &str) -> Result { + RpcModuleSelection::from_str(s).map_err(|e| format!("Failed to parse RPC modules: {}", e)) } } @@ -514,6 +693,52 @@ mod test { assert!(!RpcModuleSelection::are_identical(Some(&standard), Some(&non_matching_standard))); } + #[test] + fn test_rpc_module_selection_append() { + // Test append on Standard selection + let selection = RpcModuleSelection::Standard; + let new_selection = selection.append(RethRpcModule::Admin); + assert!(new_selection.contains(&RethRpcModule::Eth)); + assert!(new_selection.contains(&RethRpcModule::Net)); + assert!(new_selection.contains(&RethRpcModule::Web3)); + assert!(new_selection.contains(&RethRpcModule::Admin)); + + // Test append on empty Selection + let selection = RpcModuleSelection::Selection(HashSet::new()); + let new_selection = selection.append(RethRpcModule::Eth); + assert!(new_selection.contains(&RethRpcModule::Eth)); + assert_eq!(new_selection.len(), 1); + + // Test append on All (should return All) + let selection = RpcModuleSelection::All; + let new_selection = selection.append(RethRpcModule::Eth); + assert_eq!(new_selection, RpcModuleSelection::All); + } + + #[test] + fn test_rpc_module_selection_extend() { + // Test extend on Standard selection + let mut selection = RpcModuleSelection::Standard; + selection.extend(vec![RethRpcModule::Admin, RethRpcModule::Debug]); + assert!(selection.contains(&RethRpcModule::Eth)); + assert!(selection.contains(&RethRpcModule::Net)); + assert!(selection.contains(&RethRpcModule::Web3)); + assert!(selection.contains(&RethRpcModule::Admin)); + assert!(selection.contains(&RethRpcModule::Debug)); + + // Test extend on empty Selection + let mut selection = RpcModuleSelection::Selection(HashSet::new()); + selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]); + assert!(selection.contains(&RethRpcModule::Eth)); + assert!(selection.contains(&RethRpcModule::Admin)); + assert_eq!(selection.len(), 2); + + // Test extend on All (should be no-op) + let mut selection = RpcModuleSelection::All; + selection.extend(vec![RethRpcModule::Eth, RethRpcModule::Admin]); + assert_eq!(selection, RpcModuleSelection::All); + } + #[test] fn test_rpc_module_selection_from_str() { // Test empty string returns default selection @@ -559,10 +784,12 @@ mod test { assert!(result.is_ok()); assert_eq!(result.unwrap(), expected_selection); - // Test invalid selection should return error + // Test custom module selections now work (no longer return errors) let result = RpcModuleSelection::from_str("invalid,unknown"); - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), ParseError::VariantNotFound); + assert!(result.is_ok()); + let selection = result.unwrap(); + assert!(selection.contains(&RethRpcModule::Other("invalid".to_string()))); + assert!(selection.contains(&RethRpcModule::Other("unknown".to_string()))); // Test single valid selection: "eth" let result = RpcModuleSelection::from_str("eth"); @@ -570,9 +797,160 @@ mod test { let expected_selection = RpcModuleSelection::from([RethRpcModule::Eth]); assert_eq!(result.unwrap(), expected_selection); - // Test single invalid selection: "unknown" + // Test single custom module selection: "unknown" now becomes Other let result = RpcModuleSelection::from_str("unknown"); + assert!(result.is_ok()); + let expected_selection = + RpcModuleSelection::from([RethRpcModule::Other("unknown".to_string())]); + assert_eq!(result.unwrap(), expected_selection); + } + + #[test] + fn test_rpc_module_other_variant() { + // Test parsing custom module + let custom_module = RethRpcModule::from_str("myCustomModule").unwrap(); + assert_eq!(custom_module, RethRpcModule::Other("myCustomModule".to_string())); + + // Test as_str for Other variant + assert_eq!(custom_module.as_str(), "myCustomModule"); + + // Test as_ref for Other variant + assert_eq!(custom_module.as_ref(), "myCustomModule"); + + // Test Display impl + assert_eq!(custom_module.to_string(), "myCustomModule"); + } + + #[test] + fn test_rpc_module_selection_with_mixed_modules() { + // Test selection with both standard and custom modules + let result = RpcModuleSelection::from_str("eth,admin,myCustomModule,anotherCustom"); + assert!(result.is_ok()); + + let selection = result.unwrap(); + assert!(selection.contains(&RethRpcModule::Eth)); + assert!(selection.contains(&RethRpcModule::Admin)); + assert!(selection.contains(&RethRpcModule::Other("myCustomModule".to_string()))); + assert!(selection.contains(&RethRpcModule::Other("anotherCustom".to_string()))); + } + + #[test] + fn test_rpc_module_all_excludes_custom() { + // Test that All selection doesn't include custom modules + let all_selection = RpcModuleSelection::All; + + // All should contain standard modules + assert!(all_selection.contains(&RethRpcModule::Eth)); + assert!(all_selection.contains(&RethRpcModule::Admin)); + + // But All doesn't explicitly contain custom modules + // (though contains() returns true for all modules when selection is All) + assert_eq!(all_selection.len(), RethRpcModule::variant_count()); + } + + #[test] + fn test_rpc_module_equality_with_other() { + let other1 = RethRpcModule::Other("custom".to_string()); + let other2 = RethRpcModule::Other("custom".to_string()); + let other3 = RethRpcModule::Other("different".to_string()); + + assert_eq!(other1, other2); + assert_ne!(other1, other3); + assert_ne!(other1, RethRpcModule::Eth); + } + + #[test] + fn test_rpc_module_is_other() { + // Standard modules should return false + assert!(!RethRpcModule::Eth.is_other()); + assert!(!RethRpcModule::Admin.is_other()); + assert!(!RethRpcModule::Debug.is_other()); + + // Other variants should return true + assert!(RethRpcModule::Other("custom".to_string()).is_other()); + assert!(RethRpcModule::Other("mycustomrpc".to_string()).is_other()); + } + + #[test] + fn test_standard_variant_names_excludes_other() { + let standard_names: Vec<_> = RethRpcModule::standard_variant_names().collect(); + + // Verify "other" is not in the list + assert!(!standard_names.contains(&"other")); + + // Should have exactly as many names as STANDARD_VARIANTS + assert_eq!(standard_names.len(), RethRpcModule::STANDARD_VARIANTS.len()); + + // Verify all standard variants have their names in the list + for variant in RethRpcModule::STANDARD_VARIANTS { + assert!(standard_names.contains(&variant.as_ref())); + } + } + + #[test] + fn test_default_validator_accepts_standard_modules() { + // Should accept standard modules + let result = DefaultRpcModuleValidator::parse_selection("eth,admin,debug"); + assert!(result.is_ok()); + + let selection = result.unwrap(); + assert!(matches!(selection, RpcModuleSelection::Selection(_))); + } + + #[test] + fn test_default_validator_rejects_unknown_modules() { + // Should reject unknown module names + let result = DefaultRpcModuleValidator::parse_selection("eth,mycustom"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("Unknown RPC module: 'mycustom'")); + + let result = DefaultRpcModuleValidator::parse_selection("unknownmodule"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("Unknown RPC module: 'unknownmodule'")); + + let result = DefaultRpcModuleValidator::parse_selection("eth,admin,xyz123"); + assert!(result.is_err()); + assert!(result.unwrap_err().contains("Unknown RPC module: 'xyz123'")); + } + + #[test] + fn test_default_validator_all_selection() { + // Should accept "all" selection + let result = DefaultRpcModuleValidator::parse_selection("all"); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), RpcModuleSelection::All); + } + + #[test] + fn test_default_validator_none_selection() { + // Should accept "none" selection + let result = DefaultRpcModuleValidator::parse_selection("none"); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), RpcModuleSelection::Selection(Default::default())); + } + + #[test] + fn test_lenient_validator_accepts_unknown_modules() { + // Lenient validator should accept any module name without validation + let result = LenientRpcModuleValidator::parse_selection("eht,adimn,xyz123,customrpc"); + assert!(result.is_ok()); + + let selection = result.unwrap(); + if let RpcModuleSelection::Selection(modules) = selection { + assert!(modules.contains(&RethRpcModule::Other("eht".to_string()))); + assert!(modules.contains(&RethRpcModule::Other("adimn".to_string()))); + assert!(modules.contains(&RethRpcModule::Other("xyz123".to_string()))); + assert!(modules.contains(&RethRpcModule::Other("customrpc".to_string()))); + } else { + panic!("Expected Selection variant"); + } + } + + #[test] + fn test_default_validator_mixed_standard_and_custom() { + // Should reject mix of standard and custom modules + let result = DefaultRpcModuleValidator::parse_selection("eth,admin,mycustom,debug"); assert!(result.is_err()); - assert_eq!(result.unwrap_err(), ParseError::VariantNotFound); + assert!(result.unwrap_err().contains("Unknown RPC module: 'mycustom'")); } } diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index e0d1fcb601f..c47c383f057 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -37,6 +37,7 @@ reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true reth-network-types.workspace = true reth-consensus.workspace = true +reth-consensus-common.workspace = true reth-node-api.workspace = true reth-trie-common.workspace = true @@ -51,6 +52,7 @@ alloy-genesis.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true alloy-rlp.workspace = true +alloy-rpc-client.workspace = true alloy-rpc-types-beacon = { workspace = true, features = ["ssz"] } alloy-rpc-types.workspace = true alloy-rpc-types-eth = { workspace = true, features = ["serde"] } @@ -82,6 +84,7 @@ pin-project.workspace = true parking_lot.workspace = true # misc +dyn-clone.workspace = true tracing.workspace = true tracing-futures.workspace = true futures.workspace = true diff --git a/crates/rpc/rpc/src/admin.rs b/crates/rpc/rpc/src/admin.rs index 731021fb435..ce548230864 100644 --- a/crates/rpc/rpc/src/admin.rs +++ b/crates/rpc/rpc/src/admin.rs @@ -13,29 +13,33 @@ use reth_network_peers::{id2pk, AnyNode, NodeRecord}; use reth_network_types::PeerKind; use reth_rpc_api::AdminApiServer; use reth_rpc_server_types::ToRpcResult; +use reth_transaction_pool::TransactionPool; /// `admin` API implementation. /// /// This type provides the functionality for handling `admin` related requests. -pub struct AdminApi { +pub struct AdminApi { /// An interface to interact with the network network: N, /// The specification of the blockchain's configuration. chain_spec: Arc, + /// The transaction pool + pool: Pool, } -impl AdminApi { +impl AdminApi { /// Creates a new instance of `AdminApi`. - pub const fn new(network: N, chain_spec: Arc) -> Self { - Self { network, chain_spec } + pub const fn new(network: N, chain_spec: Arc, pool: Pool) -> Self { + Self { network, chain_spec, pool } } } #[async_trait] -impl AdminApiServer for AdminApi +impl AdminApiServer for AdminApi where N: NetworkInfo + Peers + 'static, ChainSpec: EthChainSpec + EthereumHardforks + Send + Sync + 'static, + Pool: TransactionPool + 'static, { /// Handler for `admin_addPeer` fn add_peer(&self, record: NodeRecord) -> RpcResult { @@ -189,9 +193,17 @@ where ) -> jsonrpsee::core::SubscriptionResult { Err("admin_peerEvents is not implemented yet".into()) } + + /// Handler for `admin_clearTxpool` + async fn clear_txpool(&self) -> RpcResult { + let all_hashes = self.pool.all_transaction_hashes(); + let count = all_hashes.len() as u64; + let _ = self.pool.remove_transactions(all_hashes); + Ok(count) + } } -impl std::fmt::Debug for AdminApi { +impl std::fmt::Debug for AdminApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("AdminApi").finish_non_exhaustive() } diff --git a/crates/rpc/rpc/src/aliases.rs b/crates/rpc/rpc/src/aliases.rs new file mode 100644 index 00000000000..4e317305ca4 --- /dev/null +++ b/crates/rpc/rpc/src/aliases.rs @@ -0,0 +1,14 @@ +use reth_evm::{ConfigureEvm, SpecFor, TxEnvFor}; +use reth_rpc_convert::RpcConvert; +use reth_rpc_eth_types::EthApiError; + +/// Boxed RPC converter. +pub type DynRpcConverter = Box< + dyn RpcConvert< + Primitives = ::Primitives, + Network = Network, + Error = Error, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, +>; diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 43c6422605c..e0f5b4eabce 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -1,4 +1,7 @@ -use alloy_consensus::{transaction::SignerRecoverable, BlockHeader}; +use alloy_consensus::{ + transaction::{SignerRecoverable, TxHashRef}, + BlockHeader, +}; use alloy_eips::{eip2718::Encodable2718, BlockId, BlockNumberOrTag}; use alloy_genesis::ChainConfig; use alloy_primitives::{uint, Address, Bytes, B256}; @@ -15,10 +18,9 @@ use alloy_rpc_types_trace::geth::{ use async_trait::async_trait; use jsonrpsee::core::RpcResult; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; +use reth_errors::RethError; use reth_evm::{execute::Executor, ConfigureEvm, EvmEnvFor, TxEnvFor}; -use reth_primitives_traits::{ - Block as _, BlockBody, ReceiptWithBloom, RecoveredBlock, SignedTransaction, -}; +use reth_primitives_traits::{Block as _, BlockBody, ReceiptWithBloom, RecoveredBlock}; use reth_revm::{ database::StateProviderDatabase, db::{CacheDB, State}, @@ -150,7 +152,12 @@ where .map_err(BlockError::RlpDecodeRawBlock) .map_err(Eth::Error::from_eth_err)?; - let evm_env = self.eth_api().evm_config().evm_env(block.header()); + let evm_env = self + .eth_api() + .evm_config() + .evm_env(block.header()) + .map_err(RethError::other) + .map_err(Eth::Error::from_eth_err)?; // Depending on EIP-2 we need to recover the transactions differently let senders = @@ -269,8 +276,9 @@ where opts: GethDebugTracingCallOptions, ) -> Result { let at = block_id.unwrap_or_default(); - let GethDebugTracingCallOptions { tracing_options, state_overrides, block_overrides } = - opts; + let GethDebugTracingCallOptions { + tracing_options, state_overrides, block_overrides, .. + } = opts; let overrides = EvmOverrides::new(state_overrides, block_overrides.map(Box::new)); let GethDebugTracingOptions { config, tracer, tracer_config, .. } = tracing_options; @@ -670,7 +678,6 @@ where }; let range = smallest..block_number; - // TODO: Check if headers_range errors when one of the headers in the range is missing exec_witness.headers = self .provider() .headers_range(range) diff --git a/crates/rpc/rpc/src/eth/builder.rs b/crates/rpc/rpc/src/eth/builder.rs index fada116afec..c34d268d64a 100644 --- a/crates/rpc/rpc/src/eth/builder.rs +++ b/crates/rpc/rpc/src/eth/builder.rs @@ -12,13 +12,13 @@ use reth_rpc_eth_api::{ use reth_rpc_eth_types::{ builder::config::PendingBlockKind, fee_history::fee_history_cache_new_blocks_task, receipt::EthReceiptConverter, EthStateCache, EthStateCacheConfig, FeeHistoryCache, - FeeHistoryCacheConfig, GasCap, GasPriceOracle, GasPriceOracleConfig, + FeeHistoryCacheConfig, ForwardConfig, GasCap, GasPriceOracle, GasPriceOracleConfig, }; use reth_rpc_server_types::constants::{ DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS, }; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner, TokioTaskExecutor}; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; /// A helper to build the `EthApi` handler instance. /// @@ -42,6 +42,8 @@ pub struct EthApiBuilder { next_env: NextEnv, max_batch_size: usize, pending_block_kind: PendingBlockKind, + raw_tx_forwarder: ForwardConfig, + send_raw_transaction_sync_timeout: Duration, } impl @@ -60,6 +62,14 @@ where } impl EthApiBuilder { + /// Apply a function to the builder + pub fn apply(self, f: F) -> Self + where + F: FnOnce(Self) -> Self, + { + f(self) + } + /// Converts the RPC converter type of this builder pub fn map_converter(self, f: F) -> EthApiBuilder where @@ -82,6 +92,8 @@ impl EthApiBuilder { next_env, max_batch_size, pending_block_kind, + raw_tx_forwarder, + send_raw_transaction_sync_timeout, } = self; EthApiBuilder { components, @@ -100,6 +112,8 @@ impl EthApiBuilder { next_env, max_batch_size, pending_block_kind, + raw_tx_forwarder, + send_raw_transaction_sync_timeout, } } } @@ -129,6 +143,8 @@ where next_env: Default::default(), max_batch_size: 1, pending_block_kind: PendingBlockKind::Full, + raw_tx_forwarder: ForwardConfig::default(), + send_raw_transaction_sync_timeout: Duration::from_secs(30), } } } @@ -165,6 +181,8 @@ where next_env, max_batch_size, pending_block_kind, + raw_tx_forwarder, + send_raw_transaction_sync_timeout, } = self; EthApiBuilder { components, @@ -183,6 +201,8 @@ where next_env, max_batch_size, pending_block_kind, + raw_tx_forwarder, + send_raw_transaction_sync_timeout, } } @@ -208,6 +228,8 @@ where next_env: _, max_batch_size, pending_block_kind, + raw_tx_forwarder, + send_raw_transaction_sync_timeout, } = self; EthApiBuilder { components, @@ -226,6 +248,8 @@ where next_env, max_batch_size, pending_block_kind, + raw_tx_forwarder, + send_raw_transaction_sync_timeout, } } @@ -309,6 +333,118 @@ where self } + /// Sets the raw transaction forwarder. + pub fn raw_tx_forwarder(mut self, tx_forwarder: ForwardConfig) -> Self { + self.raw_tx_forwarder = tx_forwarder; + self + } + + /// Returns the gas cap. + pub const fn get_gas_cap(&self) -> &GasCap { + &self.gas_cap + } + + /// Returns the maximum simulate blocks. + pub const fn get_max_simulate_blocks(&self) -> u64 { + self.max_simulate_blocks + } + + /// Returns the ETH proof window. + pub const fn get_eth_proof_window(&self) -> u64 { + self.eth_proof_window + } + + /// Returns a reference to the fee history cache config. + pub const fn get_fee_history_cache_config(&self) -> &FeeHistoryCacheConfig { + &self.fee_history_cache_config + } + + /// Returns the proof permits. + pub const fn get_proof_permits(&self) -> usize { + self.proof_permits + } + + /// Returns a reference to the ETH state cache config. + pub const fn get_eth_state_cache_config(&self) -> &EthStateCacheConfig { + &self.eth_state_cache_config + } + + /// Returns a reference to the gas oracle config. + pub const fn get_gas_oracle_config(&self) -> &GasPriceOracleConfig { + &self.gas_oracle_config + } + + /// Returns the max batch size. + pub const fn get_max_batch_size(&self) -> usize { + self.max_batch_size + } + + /// Returns the pending block kind. + pub const fn get_pending_block_kind(&self) -> PendingBlockKind { + self.pending_block_kind + } + + /// Returns a reference to the raw tx forwarder config. + pub const fn get_raw_tx_forwarder(&self) -> &ForwardConfig { + &self.raw_tx_forwarder + } + + /// Returns a mutable reference to the fee history cache config. + pub const fn fee_history_cache_config_mut(&mut self) -> &mut FeeHistoryCacheConfig { + &mut self.fee_history_cache_config + } + + /// Returns a mutable reference to the ETH state cache config. + pub const fn eth_state_cache_config_mut(&mut self) -> &mut EthStateCacheConfig { + &mut self.eth_state_cache_config + } + + /// Returns a mutable reference to the gas oracle config. + pub const fn gas_oracle_config_mut(&mut self) -> &mut GasPriceOracleConfig { + &mut self.gas_oracle_config + } + + /// Returns a mutable reference to the raw tx forwarder config. + pub const fn raw_tx_forwarder_mut(&mut self) -> &mut ForwardConfig { + &mut self.raw_tx_forwarder + } + + /// Modifies the fee history cache configuration using a closure. + pub fn modify_fee_history_cache_config(mut self, f: F) -> Self + where + F: FnOnce(&mut FeeHistoryCacheConfig), + { + f(&mut self.fee_history_cache_config); + self + } + + /// Modifies the ETH state cache configuration using a closure. + pub fn modify_eth_state_cache_config(mut self, f: F) -> Self + where + F: FnOnce(&mut EthStateCacheConfig), + { + f(&mut self.eth_state_cache_config); + self + } + + /// Modifies the gas oracle configuration using a closure. + pub fn modify_gas_oracle_config(mut self, f: F) -> Self + where + F: FnOnce(&mut GasPriceOracleConfig), + { + f(&mut self.gas_oracle_config); + self + } + + /// Modifies the raw tx forwarder configuration using a closure. + pub fn modify_raw_tx_forwarder(mut self, f: F) -> Self + where + F: FnOnce(&mut ForwardConfig), + { + f(&mut self.raw_tx_forwarder); + self + } + /// Builds the [`EthApiInner`] instance. /// /// If not configured, this will spawn the cache backend: [`EthStateCache::spawn`]. @@ -339,6 +475,8 @@ where next_env, max_batch_size, pending_block_kind, + raw_tx_forwarder, + send_raw_transaction_sync_timeout, } = self; let provider = components.provider().clone(); @@ -377,6 +515,8 @@ where next_env, max_batch_size, pending_block_kind, + raw_tx_forwarder.forwarder_client(), + send_raw_transaction_sync_timeout, ) } @@ -395,4 +535,10 @@ where { EthApi { inner: Arc::new(self.build_inner()) } } + + /// Sets the timeout for `send_raw_transaction_sync` RPC method. + pub const fn send_raw_transaction_sync_timeout(mut self, timeout: Duration) -> Self { + self.send_raw_transaction_sync_timeout = timeout; + self + } } diff --git a/crates/rpc/rpc/src/eth/bundle.rs b/crates/rpc/rpc/src/eth/bundle.rs index 0ff7fb1dde4..48e3219daa3 100644 --- a/crates/rpc/rpc/src/eth/bundle.rs +++ b/crates/rpc/rpc/src/eth/bundle.rs @@ -1,13 +1,13 @@ //! `Eth` bundle implementation and helpers. -use alloy_consensus::{EnvKzgSettings, Transaction as _}; +use alloy_consensus::{transaction::TxHashRef, EnvKzgSettings, Transaction as _}; use alloy_eips::eip7840::BlobParams; use alloy_primitives::{uint, Keccak256, U256}; use alloy_rpc_types_mev::{EthCallBundle, EthCallBundleResponse, EthCallBundleTransactionResult}; use jsonrpsee::core::RpcResult; use reth_chainspec::{ChainSpecProvider, EthChainSpec}; use reth_evm::{ConfigureEvm, Evm}; -use reth_primitives_traits::SignedTransaction; + use reth_revm::{database::StateProviderDatabase, db::CacheDB}; use reth_rpc_eth_api::{ helpers::{Call, EthTransactions, LoadPendingBlock}, diff --git a/crates/rpc/rpc/src/eth/core.rs b/crates/rpc/rpc/src/eth/core.rs index d1b0374da61..61082f4f929 100644 --- a/crates/rpc/rpc/src/eth/core.rs +++ b/crates/rpc/rpc/src/eth/core.rs @@ -1,13 +1,14 @@ //! Implementation of the [`jsonrpsee`] generated [`EthApiServer`](crate::EthApi) trait //! Handles RPC requests for the `eth_` namespace. -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use crate::{eth::helpers::types::EthRpcConverter, EthApiBuilder}; use alloy_consensus::BlockHeader; use alloy_eips::BlockNumberOrTag; use alloy_network::Ethereum; use alloy_primitives::{Bytes, U256}; +use alloy_rpc_client::RpcClient; use derive_more::Deref; use reth_chainspec::{ChainSpec, ChainSpecProvider}; use reth_evm_ethereum::EthEvmConfig; @@ -20,8 +21,8 @@ use reth_rpc_eth_api::{ EthApiTypes, RpcNodeCore, }; use reth_rpc_eth_types::{ - builder::config::PendingBlockKind, receipt::EthReceiptConverter, EthApiError, EthStateCache, - FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock, + builder::config::PendingBlockKind, receipt::EthReceiptConverter, tx_forward::ForwardConfig, + EthApiError, EthStateCache, FeeHistoryCache, GasCap, GasPriceOracle, PendingBlock, }; use reth_storage_api::{noop::NoopProvider, BlockReaderIdExt, ProviderHeader}; use reth_tasks::{ @@ -152,6 +153,8 @@ where rpc_converter: Rpc, max_batch_size: usize, pending_block_kind: PendingBlockKind, + raw_tx_forwarder: ForwardConfig, + send_raw_transaction_sync_timeout: Duration, ) -> Self { let inner = EthApiInner::new( components, @@ -168,6 +171,8 @@ where (), max_batch_size, pending_block_kind, + raw_tx_forwarder.forwarder_client(), + send_raw_transaction_sync_timeout, ); Self { inner: Arc::new(inner) } @@ -292,6 +297,9 @@ pub struct EthApiInner { /// Transaction broadcast channel raw_tx_sender: broadcast::Sender, + /// Raw transaction forwarder + raw_tx_forwarder: Option, + /// Converter for RPC types. tx_resp_builder: Rpc, @@ -304,6 +312,9 @@ pub struct EthApiInner { /// Configuration for pending block construction. pending_block_kind: PendingBlockKind, + + /// Timeout duration for `send_raw_transaction_sync` RPC method. + send_raw_transaction_sync_timeout: Duration, } impl EthApiInner @@ -328,6 +339,8 @@ where next_env: impl PendingEnvBuilder, max_batch_size: usize, pending_block_kind: PendingBlockKind, + raw_tx_forwarder: Option, + send_raw_transaction_sync_timeout: Duration, ) -> Self { let signers = parking_lot::RwLock::new(Default::default()); // get the block number of the latest block @@ -363,10 +376,12 @@ where fee_history_cache, blocking_task_guard: BlockingTaskGuard::new(proof_permits), raw_tx_sender, + raw_tx_forwarder, tx_resp_builder, next_env_builder: Box::new(next_env), tx_batch_sender, pending_block_kind, + send_raw_transaction_sync_timeout, } } } @@ -526,6 +541,18 @@ where pub const fn pending_block_kind(&self) -> PendingBlockKind { self.pending_block_kind } + + /// Returns a handle to the raw transaction forwarder. + #[inline] + pub const fn raw_tx_forwarder(&self) -> Option<&RpcClient> { + self.raw_tx_forwarder.as_ref() + } + + /// Returns the timeout duration for `send_raw_transaction_sync` RPC method. + #[inline] + pub const fn send_raw_transaction_sync_timeout(&self) -> Duration { + self.send_raw_transaction_sync_timeout + } } #[cfg(test)] diff --git a/crates/rpc/rpc/src/eth/filter.rs b/crates/rpc/rpc/src/eth/filter.rs index ff5841c3747..01b6a94158f 100644 --- a/crates/rpc/rpc/src/eth/filter.rs +++ b/crates/rpc/rpc/src/eth/filter.rs @@ -7,7 +7,11 @@ use alloy_rpc_types_eth::{ PendingTransactionFilterKind, }; use async_trait::async_trait; -use futures::future::TryFutureExt; +use futures::{ + future::TryFutureExt, + stream::{FuturesOrdered, StreamExt}, + Future, +}; use itertools::Itertools; use jsonrpsee::{core::RpcResult, server::IdProvider}; use reth_errors::ProviderError; @@ -30,9 +34,9 @@ use reth_transaction_pool::{NewSubpoolTransactionStream, PoolTransaction, Transa use std::{ collections::{HashMap, VecDeque}, fmt, - future::Future, iter::{Peekable, StepBy}, ops::RangeInclusive, + pin::Pin, sync::Arc, time::{Duration, Instant}, }; @@ -73,7 +77,7 @@ const BLOOM_ADJUSTMENT_MIN_BLOCKS: u64 = 100; const MAX_HEADERS_RANGE: u64 = 1_000; // with ~530bytes per header this is ~500kb /// Threshold for enabling parallel processing in range mode -const PARALLEL_PROCESSING_THRESHOLD: u64 = 1000; +const PARALLEL_PROCESSING_THRESHOLD: usize = 1000; /// Default concurrency for parallel processing const DEFAULT_PARALLEL_CONCURRENCY: usize = 4; @@ -344,7 +348,7 @@ where let stream = self.pool().new_pending_pool_transactions_listener(); let full_txs_receiver = FullTransactionsReceiver::new( stream, - self.inner.eth_api.tx_resp_builder().clone(), + dyn_clone::clone(self.inner.eth_api.tx_resp_builder()), ); FilterKind::PendingTransaction(PendingTransactionKind::FullTransaction(Arc::new( full_txs_receiver, @@ -496,8 +500,17 @@ where .map(|num| self.provider().convert_block_number(num)) .transpose()? .flatten(); + + if let Some(f) = from && + f > info.best_number + { + // start block higher than local head, can return empty + return Ok(Vec::new()); + } + let (from_block_number, to_block_number) = logs_utils::get_filter_block_range(from, to, start_block, info); + self.get_logs_in_block_range(filter, from_block_number, to_block_number, limits) .await } @@ -645,22 +658,23 @@ where // size check but only if range is multiple blocks, so we always return all // logs of a single block let is_multi_block_range = from_block != to_block; - if let Some(max_logs_per_response) = limits.max_logs_per_response { - if is_multi_block_range && all_logs.len() > max_logs_per_response { - debug!( - target: "rpc::eth::filter", - logs_found = all_logs.len(), - max_logs_per_response, - from_block, - to_block = num_hash.number.saturating_sub(1), - "Query exceeded max logs per response limit" - ); - return Err(EthFilterError::QueryExceedsMaxResults { - max_logs: max_logs_per_response, - from_block, - to_block: num_hash.number.saturating_sub(1), - }); - } + if let Some(max_logs_per_response) = limits.max_logs_per_response && + is_multi_block_range && + all_logs.len() > max_logs_per_response + { + debug!( + target: "rpc::eth::filter", + logs_found = all_logs.len(), + max_logs_per_response, + from_block, + to_block = num_hash.number.saturating_sub(1), + "Query exceeded max logs per response limit" + ); + return Err(EthFilterError::QueryExceedsMaxResults { + max_logs: max_logs_per_response, + from_block, + to_block: num_hash.number.saturating_sub(1), + }); } } @@ -934,6 +948,7 @@ impl< iter: sealed_headers.into_iter().peekable(), next: VecDeque::new(), max_range: max_headers_range as usize, + pending_tasks: FuturesOrdered::new(), }) } } @@ -1006,6 +1021,10 @@ impl< } } +/// Type alias for parallel receipt fetching task futures used in `RangeBlockMode` +type ReceiptFetchFuture

= + Pin>, EthFilterError>> + Send>>; + /// Mode for processing blocks using range queries for older blocks struct RangeBlockMode< Eth: RpcNodeCoreExt + EthApiTypes + 'static, @@ -1014,6 +1033,8 @@ struct RangeBlockMode< iter: Peekable::Header>>>, next: VecDeque>, max_range: usize, + // Stream of ongoing receipt fetching tasks + pending_tasks: FuturesOrdered>, } impl< @@ -1021,41 +1042,67 @@ impl< > RangeBlockMode { async fn next(&mut self) -> Result>, EthFilterError> { - if let Some(result) = self.next.pop_front() { - return Ok(Some(result)); - } + loop { + // First, try to return any already processed result from buffer + if let Some(result) = self.next.pop_front() { + return Ok(Some(result)); + } - let Some(next_header) = self.iter.next() else { - return Ok(None); - }; + // Try to get a completed task result if there are pending tasks + if let Some(task_result) = self.pending_tasks.next().await { + self.next.extend(task_result?); + continue; + } - let mut range_headers = Vec::with_capacity(self.max_range); - range_headers.push(next_header); + // No pending tasks - try to generate more work + let Some(next_header) = self.iter.next() else { + // No more headers to process + return Ok(None); + }; - // Collect consecutive blocks up to max_range size - while range_headers.len() < self.max_range { - let Some(peeked) = self.iter.peek() else { break }; - let Some(last_header) = range_headers.last() else { break }; + let mut range_headers = Vec::with_capacity(self.max_range); + range_headers.push(next_header); - let expected_next = last_header.header().number() + 1; - if peeked.header().number() != expected_next { - break; // Non-consecutive block, stop here - } + // Collect consecutive blocks up to max_range size + while range_headers.len() < self.max_range { + let Some(peeked) = self.iter.peek() else { break }; + let Some(last_header) = range_headers.last() else { break }; - let Some(next_header) = self.iter.next() else { break }; - range_headers.push(next_header); - } + let expected_next = last_header.number() + 1; + if peeked.number() != expected_next { + debug!( + target: "rpc::eth::filter", + last_block = last_header.number(), + next_block = peeked.number(), + expected = expected_next, + range_size = range_headers.len(), + "Non-consecutive block detected, stopping range collection" + ); + break; // Non-consecutive block, stop here + } - // Check if we should use parallel processing for large ranges - let remaining_headers = self.iter.len() + range_headers.len(); - if remaining_headers >= PARALLEL_PROCESSING_THRESHOLD as usize { - self.process_large_range(range_headers).await - } else { - self.process_small_range(range_headers).await + let Some(next_header) = self.iter.next() else { break }; + range_headers.push(next_header); + } + + // Check if we should use parallel processing for large ranges + let remaining_headers = self.iter.len() + range_headers.len(); + if remaining_headers >= PARALLEL_PROCESSING_THRESHOLD { + self.spawn_parallel_tasks(range_headers); + // Continue loop to await the spawned tasks + } else { + // Process small range sequentially and add results to buffer + if let Some(result) = self.process_small_range(range_headers).await? { + return Ok(Some(result)); + } + // Continue loop to check for more work + } } } - /// Process small range headers + /// Process a small range of headers sequentially + /// + /// This is used when the remaining headers count is below [`PARALLEL_PROCESSING_THRESHOLD`]. async fn process_small_range( &mut self, range_headers: Vec::Header>>, @@ -1072,7 +1119,7 @@ impl< let receipts = match maybe_receipts { Some(receipts) => receipts, None => { - // Not cached - fetch directly from provider without queuing + // Not cached - fetch directly from provider match self.filter_inner.provider().receipts_by_block(header.hash().into())? { Some(receipts) => Arc::new(receipts), None => continue, // No receipts found @@ -1092,11 +1139,14 @@ impl< Ok(self.next.pop_front()) } - /// Process large range headers - async fn process_large_range( + /// Spawn parallel tasks for processing a large range of headers + /// + /// This is used when the remaining headers count is at or above + /// [`PARALLEL_PROCESSING_THRESHOLD`]. + fn spawn_parallel_tasks( &mut self, range_headers: Vec::Header>>, - ) -> Result>, EthFilterError> { + ) { // Split headers into chunks let chunk_size = std::cmp::max(range_headers.len() / DEFAULT_PARALLEL_CONCURRENCY, 1); let header_chunks = range_headers @@ -1106,52 +1156,49 @@ impl< .map(|chunk| chunk.collect::>()) .collect::>(); - // Process chunks in parallel - let mut tasks = Vec::new(); + // Spawn each chunk as a separate task directly into the FuturesOrdered stream for chunk_headers in header_chunks { let filter_inner = self.filter_inner.clone(); - let task = tokio::task::spawn_blocking(move || { - let mut chunk_results = Vec::new(); - - for header in chunk_headers { - // Fetch directly from provider - RangeMode is used for older blocks unlikely to - // be cached - let receipts = - match filter_inner.provider().receipts_by_block(header.hash().into())? { + let chunk_task = Box::pin(async move { + let chunk_task = tokio::task::spawn_blocking(move || { + let mut chunk_results = Vec::new(); + + for header in chunk_headers { + // Fetch directly from provider - RangeMode is used for older blocks + // unlikely to be cached + let receipts = match filter_inner + .provider() + .receipts_by_block(header.hash().into())? + { Some(receipts) => Arc::new(receipts), None => continue, // No receipts found }; - if !receipts.is_empty() { - chunk_results.push(ReceiptBlockResult { - receipts, - recovered_block: None, - header, - }); + if !receipts.is_empty() { + chunk_results.push(ReceiptBlockResult { + receipts, + recovered_block: None, + header, + }); + } } - } - Ok::>, EthFilterError>(chunk_results) - }); - tasks.push(task); - } + Ok(chunk_results) + }); - let results = futures::future::join_all(tasks).await; - for result in results { - match result { - Ok(Ok(chunk_results)) => { - for result in chunk_results { - self.next.push_back(result); + // Await the blocking task and handle the result + match chunk_task.await { + Ok(Ok(chunk_results)) => Ok(chunk_results), + Ok(Err(e)) => Err(e), + Err(join_err) => { + trace!(target: "rpc::eth::filter", error = ?join_err, "Task join error"); + Err(EthFilterError::InternalError) } } - Ok(Err(e)) => return Err(e), - Err(_join_err) => { - return Err(EthFilterError::InternalError); - } - } - } + }); - Ok(self.next.pop_front()) + self.pending_tasks.push_back(chunk_task); + } } } @@ -1234,6 +1281,7 @@ mod tests { iter: headers.into_iter().peekable(), next: VecDeque::new(), max_range, + pending_tasks: FuturesOrdered::new(), }; let result = range_mode.next().await; @@ -1311,6 +1359,7 @@ mod tests { iter: headers.into_iter().peekable(), next: VecDeque::from([mock_result_1, mock_result_2]), // Queue two results max_range: 100, + pending_tasks: FuturesOrdered::new(), }; // first call should return the first queued result (FIFO order) @@ -1380,6 +1429,7 @@ mod tests { iter: headers.into_iter().peekable(), next: VecDeque::new(), max_range: 100, + pending_tasks: FuturesOrdered::new(), }; let result = range_mode.next().await; @@ -1450,6 +1500,7 @@ mod tests { iter: headers.into_iter().peekable(), next: VecDeque::new(), max_range: 3, // include the 3 blocks in the first queried results + pending_tasks: FuturesOrdered::new(), }; // first call should fetch receipts from provider and return first block with receipts @@ -1502,6 +1553,27 @@ mod tests { #[tokio::test] async fn test_range_block_mode_iterator_exhaustion() { let provider = MockEthProvider::default(); + + let header_100 = alloy_consensus::Header { number: 100, ..Default::default() }; + let header_101 = alloy_consensus::Header { number: 101, ..Default::default() }; + + let block_hash_100 = FixedBytes::random(); + let block_hash_101 = FixedBytes::random(); + + // Associate headers with hashes first + provider.add_header(block_hash_100, header_100.clone()); + provider.add_header(block_hash_101, header_101.clone()); + + // Add mock receipts so headers are actually processed + let mock_receipt = reth_ethereum_primitives::Receipt { + tx_type: TxType::Legacy, + cumulative_gas_used: 21_000, + logs: vec![], + success: true, + }; + provider.add_receipts(100, vec![mock_receipt.clone()]); + provider.add_receipts(101, vec![mock_receipt.clone()]); + let eth_api = build_test_eth_api(provider); let eth_filter = super::EthFilter::new( @@ -1512,14 +1584,8 @@ mod tests { let filter_inner = eth_filter.inner; let headers = vec![ - SealedHeader::new( - alloy_consensus::Header { number: 100, ..Default::default() }, - FixedBytes::random(), - ), - SealedHeader::new( - alloy_consensus::Header { number: 101, ..Default::default() }, - FixedBytes::random(), - ), + SealedHeader::new(header_100, block_hash_100), + SealedHeader::new(header_101, block_hash_101), ]; let mut range_mode = RangeBlockMode { @@ -1527,15 +1593,18 @@ mod tests { iter: headers.into_iter().peekable(), next: VecDeque::new(), max_range: 1, + pending_tasks: FuturesOrdered::new(), }; let result1 = range_mode.next().await; assert!(result1.is_ok()); + assert!(result1.unwrap().is_some()); // Should have processed block 100 - assert!(range_mode.iter.peek().is_some()); + assert!(range_mode.iter.peek().is_some()); // Should still have block 101 let result2 = range_mode.next().await; assert!(result2.is_ok()); + assert!(result2.unwrap().is_some()); // Should have processed block 101 // now iterator should be exhausted assert!(range_mode.iter.peek().is_none()); diff --git a/crates/rpc/rpc/src/eth/helpers/call.rs b/crates/rpc/rpc/src/eth/helpers/call.rs index 8a8377f7abc..a76e146042d 100644 --- a/crates/rpc/rpc/src/eth/helpers/call.rs +++ b/crates/rpc/rpc/src/eth/helpers/call.rs @@ -1,7 +1,7 @@ //! Contains RPC handler implementations specific to endpoints that call/execute within evm. use crate::EthApi; -use reth_evm::TxEnvFor; +use reth_evm::{SpecFor, TxEnvFor}; use reth_rpc_convert::RpcConvert; use reth_rpc_eth_api::{ helpers::{estimate::EstimateCall, Call, EthCall}, @@ -13,7 +13,12 @@ impl EthCall for EthApi where N: RpcNodeCore, EthApiError: FromEvmError, - Rpc: RpcConvert>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = EthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, { } @@ -21,7 +26,12 @@ impl Call for EthApi where N: RpcNodeCore, EthApiError: FromEvmError, - Rpc: RpcConvert>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = EthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, { #[inline] fn call_gas_limit(&self) -> u64 { @@ -38,6 +48,11 @@ impl EstimateCall for EthApi where N: RpcNodeCore, EthApiError: FromEvmError, - Rpc: RpcConvert>, + Rpc: RpcConvert< + Primitives = N::Primitives, + Error = EthApiError, + TxEnv = TxEnvFor, + Spec = SpecFor, + >, { } diff --git a/crates/rpc/rpc/src/eth/helpers/state.rs b/crates/rpc/rpc/src/eth/helpers/state.rs index 5d767d2ede5..3d9cc763097 100644 --- a/crates/rpc/rpc/src/eth/helpers/state.rs +++ b/crates/rpc/rpc/src/eth/helpers/state.rs @@ -1,17 +1,17 @@ //! Contains RPC handler implementations specific to state. +use crate::EthApi; use reth_rpc_convert::RpcConvert; use reth_rpc_eth_api::{ - helpers::{EthState, LoadState}, + helpers::{EthState, LoadPendingBlock, LoadState}, RpcNodeCore, }; -use crate::EthApi; - impl EthState for EthApi where N: RpcNodeCore, Rpc: RpcConvert, + Self: LoadPendingBlock, { fn max_proof_window(&self) -> u64 { self.inner.eth_proof_window() @@ -22,6 +22,7 @@ impl LoadState for EthApi where N: RpcNodeCore, Rpc: RpcConvert, + Self: LoadPendingBlock, { } diff --git a/crates/rpc/rpc/src/eth/helpers/transaction.rs b/crates/rpc/rpc/src/eth/helpers/transaction.rs index b3a3614447b..4fa39112166 100644 --- a/crates/rpc/rpc/src/eth/helpers/transaction.rs +++ b/crates/rpc/rpc/src/eth/helpers/transaction.rs @@ -1,7 +1,9 @@ //! Contains RPC handler implementations specific to transactions +use std::time::Duration; + use crate::EthApi; -use alloy_primitives::{Bytes, B256}; +use alloy_primitives::{hex, Bytes, B256}; use reth_rpc_convert::RpcConvert; use reth_rpc_eth_api::{ helpers::{spec::SignersForRpc, EthTransactions, LoadTransaction}, @@ -21,17 +23,42 @@ where self.inner.signers() } + #[inline] + fn send_raw_transaction_sync_timeout(&self) -> Duration { + self.inner.send_raw_transaction_sync_timeout() + } + /// Decodes and recovers the transaction and submits it to the pool. /// /// Returns the hash of the transaction. async fn send_raw_transaction(&self, tx: Bytes) -> Result { let recovered = recover_raw_transaction(&tx)?; + let pool_transaction = ::Transaction::from_pooled(recovered); + + // forward the transaction to the specific endpoint if configured. + if let Some(client) = self.raw_tx_forwarder() { + tracing::debug!(target: "rpc::eth", hash = %pool_transaction.hash(), "forwarding raw transaction to forwarder"); + let rlp_hex = hex::encode_prefixed(&tx); + + // broadcast raw transaction to subscribers if there is any. + self.broadcast_raw_transaction(tx); + + let hash = + client.request("eth_sendRawTransaction", (rlp_hex,)).await.inspect_err(|err| { + tracing::debug!(target: "rpc::eth", %err, hash=% *pool_transaction.hash(), "failed to forward raw transaction"); + }).map_err(EthApiError::other)?; + + // Retain tx in local tx pool after forwarding, for local RPC usage. + let _ = self.inner.add_pool_transaction(pool_transaction).await; + + return Ok(hash); + } + // broadcast raw transaction to subscribers if there is any. self.broadcast_raw_transaction(tx); - let pool_transaction = ::Transaction::from_pooled(recovered); - + // submit the transaction to the pool with a `Local` origin let AddedTransactionOutcome { hash, .. } = self.inner.add_pool_transaction(pool_transaction).await?; diff --git a/crates/rpc/rpc/src/eth/sim_bundle.rs b/crates/rpc/rpc/src/eth/sim_bundle.rs index 67d94d8140f..f7043821754 100644 --- a/crates/rpc/rpc/src/eth/sim_bundle.rs +++ b/crates/rpc/rpc/src/eth/sim_bundle.rs @@ -1,6 +1,6 @@ //! `Eth` Sim bundle implementation and helpers. -use alloy_consensus::BlockHeader; +use alloy_consensus::{transaction::TxHashRef, BlockHeader}; use alloy_eips::BlockNumberOrTag; use alloy_evm::overrides::apply_block_overrides; use alloy_primitives::U256; @@ -11,7 +11,7 @@ use alloy_rpc_types_mev::{ }; use jsonrpsee::core::RpcResult; use reth_evm::{ConfigureEvm, Evm}; -use reth_primitives_traits::{Recovered, SignedTransaction}; +use reth_primitives_traits::Recovered; use reth_revm::{database::StateProviderDatabase, db::CacheDB}; use reth_rpc_api::MevSimApiServer; use reth_rpc_eth_api::{ diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index d2905095900..d8e2f36b3ee 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -33,6 +33,7 @@ use pin_project as _; use tower as _; mod admin; +mod aliases; mod debug; mod engine; pub mod eth; @@ -47,6 +48,7 @@ mod validation; mod web3; pub use admin::AdminApi; +pub use aliases::*; pub use debug::DebugApi; pub use engine::{EngineApi, EngineEthApi}; pub use eth::{helpers::SyncListener, EthApi, EthApiBuilder, EthBundle, EthFilter, EthPubSub}; diff --git a/crates/rpc/rpc/src/trace.rs b/crates/rpc/rpc/src/trace.rs index 62dd6b32bc5..4ed42bc721d 100644 --- a/crates/rpc/rpc/src/trace.rs +++ b/crates/rpc/rpc/src/trace.rs @@ -1,7 +1,10 @@ use alloy_consensus::BlockHeader as _; use alloy_eips::BlockId; use alloy_evm::block::calc::{base_block_reward_pre_merge, block_reward, ommer_reward}; -use alloy_primitives::{map::HashSet, Bytes, B256, U256}; +use alloy_primitives::{ + map::{HashMap, HashSet}, + Address, BlockHash, Bytes, B256, U256, +}; use alloy_rpc_types_eth::{ state::{EvmOverrides, StateOverride}, BlockOverrides, Index, @@ -31,8 +34,10 @@ use reth_transaction_pool::{PoolPooledTx, PoolTransaction, TransactionPool}; use revm::DatabaseCommit; use revm_inspectors::{ opcode::OpcodeGasInspector, + storage::StorageInspector, tracing::{parity::populate_state_diff, TracingInspector, TracingInspectorConfig}, }; +use serde::{Deserialize, Serialize}; use std::sync::Arc; use tokio::sync::{AcquireError, OwnedSemaphorePermit}; @@ -485,14 +490,14 @@ where let mut maybe_traces = maybe_traces.map(|traces| traces.into_iter().flatten().collect::>()); - if let (Some(block), Some(traces)) = (maybe_block, maybe_traces.as_mut()) { - if let Some(base_block_reward) = self.calculate_base_block_reward(block.header())? { - traces.extend(self.extract_reward_traces( - block.header(), - block.body().ommers(), - base_block_reward, - )); - } + if let (Some(block), Some(traces)) = (maybe_block, maybe_traces.as_mut()) && + let Some(base_block_reward) = self.calculate_base_block_reward(block.header())? + { + traces.extend(self.extract_reward_traces( + block.header(), + block.body().ommers(), + base_block_reward, + )); } Ok(maybe_traces) @@ -566,6 +571,41 @@ where transactions, })) } + + /// Returns all storage slots accessed during transaction execution along with their access + /// counts. + pub async fn trace_block_storage_access( + &self, + block_id: BlockId, + ) -> Result, Eth::Error> { + let res = self + .eth_api() + .trace_block_inspector( + block_id, + None, + StorageInspector::default, + move |tx_info, ctx| { + let trace = TransactionStorageAccess { + transaction_hash: tx_info.hash.expect("tx hash is set"), + storage_access: ctx.inspector.accessed_slots().clone(), + unique_loads: ctx.inspector.unique_loads(), + warm_loads: ctx.inspector.warm_loads(), + }; + Ok(trace) + }, + ) + .await?; + + let Some(transactions) = res else { return Ok(None) }; + + let Some(block) = self.eth_api().recovered_block(block_id).await? else { return Ok(None) }; + + Ok(Some(BlockStorageAccess { + block_hash: block.hash(), + block_number: block.number(), + transactions, + })) + } } #[async_trait] @@ -712,6 +752,33 @@ struct TraceApiInner { eth_config: EthConfig, } +/// Response type for storage tracing that contains all accessed storage slots +/// for a transaction. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionStorageAccess { + /// Hash of the transaction + pub transaction_hash: B256, + /// Tracks storage slots and access counter. + pub storage_access: HashMap>, + /// Number of unique storage loads + pub unique_loads: u64, + /// Number of warm storage loads + pub warm_loads: u64, +} + +/// Response type for storage tracing that contains all accessed storage slots +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockStorageAccess { + /// The block hash + pub block_hash: BlockHash, + /// The block's number + pub block_number: u64, + /// All executed transactions in the block in the order they were executed + pub transactions: Vec, +} + /// Helper to construct a [`LocalizedTransactionTrace`] that describes a reward to the block /// beneficiary. fn reward_trace(header: &H, reward: RewardAction) -> LocalizedTransactionTrace { diff --git a/crates/rpc/rpc/src/validation.rs b/crates/rpc/rpc/src/validation.rs index 0b484fd13a8..d03846a4279 100644 --- a/crates/rpc/rpc/src/validation.rs +++ b/crates/rpc/rpc/src/validation.rs @@ -17,6 +17,7 @@ use jsonrpsee::core::RpcResult; use jsonrpsee_types::error::ErrorObject; use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; use reth_consensus::{Consensus, FullConsensus}; +use reth_consensus_common::validation::MAX_RLP_BLOCK_SIZE; use reth_engine_primitives::PayloadValidator; use reth_errors::{BlockExecutionError, ConsensusError, ProviderError}; use reth_evm::{execute::Executor, ConfigureEvm}; @@ -142,10 +143,10 @@ where if self.disallow.contains(sender) { return Err(ValidationApiError::Blacklist(*sender)) } - if let Some(to) = tx.to() { - if self.disallow.contains(&to) { - return Err(ValidationApiError::Blacklist(to)) - } + if let Some(to) = tx.to() && + self.disallow.contains(&to) + { + return Err(ValidationApiError::Blacklist(to)) } } } @@ -333,10 +334,10 @@ where return Err(ValidationApiError::ProposerPayment) } - if let Some(block_base_fee) = block.header().base_fee_per_gas() { - if tx.effective_tip_per_gas(block_base_fee).unwrap_or_default() != 0 { - return Err(ValidationApiError::ProposerPayment) - } + if let Some(block_base_fee) = block.header().base_fee_per_gas() && + tx.effective_tip_per_gas(block_base_fee).unwrap_or_default() != 0 + { + return Err(ValidationApiError::ProposerPayment) } Ok(()) @@ -454,6 +455,17 @@ where ), })?; + // Check block size as per EIP-7934 (only applies when Osaka hardfork is active) + let chain_spec = self.provider.chain_spec(); + if chain_spec.is_osaka_active_at_timestamp(block.timestamp()) && + block.rlp_length() > MAX_RLP_BLOCK_SIZE + { + return Err(ValidationApiError::Consensus(ConsensusError::BlockTooLarge { + rlp_length: block.rlp_length(), + max_rlp_length: MAX_RLP_BLOCK_SIZE, + })); + } + self.validate_message_against_block( block, request.request.message, diff --git a/crates/stages/api/src/pipeline/mod.rs b/crates/stages/api/src/pipeline/mod.rs index 61c6755be9f..9bc60634403 100644 --- a/crates/stages/api/src/pipeline/mod.rs +++ b/crates/stages/api/src/pipeline/mod.rs @@ -9,7 +9,7 @@ use reth_primitives_traits::constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH; use reth_provider::{ providers::ProviderNodeTypes, writer::UnifiedStorageWriter, BlockHashReader, BlockNumReader, ChainStateBlockReader, ChainStateBlockWriter, DatabaseProviderFactory, ProviderFactory, - StageCheckpointReader, StageCheckpointWriter, + PruneCheckpointReader, StageCheckpointReader, StageCheckpointWriter, }; use reth_prune::PrunerBuilder; use reth_static_file::StaticFileProducer; @@ -305,7 +305,8 @@ impl Pipeline { // Get the actual pruning configuration let prune_modes = provider.prune_modes_ref(); - prune_modes.ensure_unwind_target_unpruned(latest_block, to)?; + let checkpoints = provider.get_prune_checkpoints()?; + prune_modes.ensure_unwind_target_unpruned(latest_block, to, &checkpoints)?; // Unwind stages in reverse order of execution let unwind_pipeline = self.stages.iter_mut().rev(); diff --git a/crates/stages/api/src/pipeline/set.rs b/crates/stages/api/src/pipeline/set.rs index 8aea87ba035..c39dafae99f 100644 --- a/crates/stages/api/src/pipeline/set.rs +++ b/crates/stages/api/src/pipeline/set.rs @@ -73,16 +73,15 @@ impl StageSetBuilder { fn upsert_stage_state(&mut self, stage: Box>, added_at_index: usize) { let stage_id = stage.id(); - if self.stages.insert(stage.id(), StageEntry { stage, enabled: true }).is_some() { - if let Some(to_remove) = self + if self.stages.insert(stage.id(), StageEntry { stage, enabled: true }).is_some() && + let Some(to_remove) = self .order .iter() .enumerate() .find(|(i, id)| *i != added_at_index && **id == stage_id) .map(|(i, _)| i) - { - self.order.remove(to_remove); - } + { + self.order.remove(to_remove); } } @@ -264,10 +263,10 @@ impl StageSetBuilder { pub fn build(mut self) -> Vec>> { let mut stages = Vec::new(); for id in &self.order { - if let Some(entry) = self.stages.remove(id) { - if entry.enabled { - stages.push(entry.stage); - } + if let Some(entry) = self.stages.remove(id) && + entry.enabled + { + stages.push(entry.stage); } } stages diff --git a/crates/stages/stages/Cargo.toml b/crates/stages/stages/Cargo.toml index 1500c2944e1..32114c58e1b 100644 --- a/crates/stages/stages/Cargo.toml +++ b/crates/stages/stages/Cargo.toml @@ -70,7 +70,6 @@ reth-db = { workspace = true, features = ["test-utils", "mdbx"] } reth-ethereum-primitives = { workspace = true, features = ["test-utils"] } reth-ethereum-consensus.workspace = true reth-evm-ethereum.workspace = true -reth-execution-errors.workspace = true reth-consensus = { workspace = true, features = ["test-utils"] } reth-network-p2p = { workspace = true, features = ["test-utils"] } reth-downloaders.workspace = true @@ -80,7 +79,6 @@ reth-testing-utils.workspace = true reth-trie = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } reth-network-peers.workspace = true -reth-tracing.workspace = true alloy-primitives = { workspace = true, features = ["getrandom", "rand"] } alloy-rlp.workspace = true diff --git a/crates/stages/stages/benches/setup/mod.rs b/crates/stages/stages/benches/setup/mod.rs index 074e239da75..d5ea62ba4e0 100644 --- a/crates/stages/stages/benches/setup/mod.rs +++ b/crates/stages/stages/benches/setup/mod.rs @@ -161,8 +161,9 @@ pub(crate) fn txs_testdata(num_blocks: u64) -> TestStageDB { let offset = transitions.len() as u64; - let provider_rw = db.factory.provider_rw().unwrap(); db.insert_changesets(transitions, None).unwrap(); + + let provider_rw = db.factory.provider_rw().unwrap(); provider_rw.write_trie_updates(&updates).unwrap(); provider_rw.commit().unwrap(); diff --git a/crates/stages/stages/src/stages/bodies.rs b/crates/stages/stages/src/stages/bodies.rs index e503d8b5d5c..4eca51d00a7 100644 --- a/crates/stages/stages/src/stages/bodies.rs +++ b/crates/stages/stages/src/stages/bodies.rs @@ -702,11 +702,10 @@ mod tests { // Validate sequentiality only after prev progress, // since the data before is mocked and can contain gaps - if number > prev_progress { - if let Some(prev_key) = prev_number { + if number > prev_progress + && let Some(prev_key) = prev_number { assert_eq!(prev_key + 1, number, "Body entries must be sequential"); } - } // Validate that the current entry is below or equals to the highest allowed block assert!( diff --git a/crates/stages/stages/src/stages/era.rs b/crates/stages/stages/src/stages/era.rs index 38b7f0c0db7..436ee769659 100644 --- a/crates/stages/stages/src/stages/era.rs +++ b/crates/stages/stages/src/stages/era.rs @@ -4,7 +4,7 @@ use futures_util::{Stream, StreamExt}; use reqwest::{Client, Url}; use reth_config::config::EtlConfig; use reth_db_api::{table::Value, transaction::DbTxMut}; -use reth_era::era1_file::Era1Reader; +use reth_era::{era1_file::Era1Reader, era_file_ops::StreamReader}; use reth_era_downloader::{read_dir, EraClient, EraMeta, EraStream, EraStreamConfig}; use reth_era_utils as era; use reth_etl::Collector; @@ -150,18 +150,17 @@ where return Poll::Ready(Ok(())); } - if self.stream.is_none() { - if let Some(source) = self.source.clone() { - self.stream.replace(source.create(input)?); - } + if self.stream.is_none() && + let Some(source) = self.source.clone() + { + self.stream.replace(source.create(input)?); } - if let Some(stream) = &mut self.stream { - if let Some(next) = ready!(stream.poll_next_unpin(cx)) + if let Some(stream) = &mut self.stream && + let Some(next) = ready!(stream.poll_next_unpin(cx)) .transpose() .map_err(|e| StageError::Fatal(e.into()))? - { - self.item.replace(next); - } + { + self.item.replace(next); } Poll::Ready(Ok(())) @@ -546,11 +545,10 @@ mod tests { // Validate sequentiality only after prev progress, // since the data before is mocked and can contain gaps - if number > prev_progress { - if let Some(prev_key) = prev_number { + if number > prev_progress + && let Some(prev_key) = prev_number { assert_eq!(prev_key + 1, number, "Body entries must be sequential"); } - } // Validate that the current entry is below or equals to the highest allowed block assert!( diff --git a/crates/stages/stages/src/stages/execution.rs b/crates/stages/stages/src/stages/execution.rs index f447ee8ff2f..1270033b885 100644 --- a/crates/stages/stages/src/stages/execution.rs +++ b/crates/stages/stages/src/stages/execution.rs @@ -8,7 +8,7 @@ use reth_db::{static_file::HeaderMask, tables}; use reth_evm::{execute::Executor, metrics::ExecutorMetrics, ConfigureEvm}; use reth_execution_types::Chain; use reth_exex::{ExExManagerHandle, ExExNotification, ExExNotificationSource}; -use reth_primitives_traits::{format_gas_throughput, Block, BlockBody, NodePrimitives}; +use reth_primitives_traits::{format_gas_throughput, BlockBody, NodePrimitives}; use reth_provider::{ providers::{StaticFileProvider, StaticFileWriter}, BlockHashReader, BlockReader, DBProvider, ExecutionOutcome, HeaderProvider, @@ -531,9 +531,8 @@ where if let Some(stage_checkpoint) = stage_checkpoint.as_mut() { for block_number in range { stage_checkpoint.progress.processed -= provider - .block_by_number(block_number)? + .header_by_number(block_number)? .ok_or_else(|| ProviderError::HeaderNotFound(block_number.into()))? - .header() .gas_used(); } } diff --git a/crates/stages/stages/src/stages/headers.rs b/crates/stages/stages/src/stages/headers.rs index bfe7a460da1..9147ebb844c 100644 --- a/crates/stages/stages/src/stages/headers.rs +++ b/crates/stages/stages/src/stages/headers.rs @@ -145,19 +145,18 @@ where let mut cursor_header_numbers = provider.tx_ref().cursor_write::>()?; - let mut first_sync = false; - // If we only have the genesis block hash, then we are at first sync, and we can remove it, // add it to the collector and use tx.append on all hashes. - if provider.tx_ref().entries::>()? == 1 { - if let Some((hash, block_number)) = cursor_header_numbers.last()? { - if block_number.value()? == 0 { - self.hash_collector.insert(hash.key()?, 0)?; - cursor_header_numbers.delete_current()?; - first_sync = true; - } - } - } + let first_sync = if provider.tx_ref().entries::>()? == 1 && + let Some((hash, block_number)) = cursor_header_numbers.last()? && + block_number.value()? == 0 + { + self.hash_collector.insert(hash.key()?, 0)?; + cursor_header_numbers.delete_current()?; + true + } else { + false + }; // Since ETL sorts all entries by hashes, we are either appending (first sync) or inserting // in order (further syncs). @@ -408,7 +407,7 @@ mod tests { use reth_provider::{BlockWriter, ProviderFactory, StaticFileProviderFactory}; use reth_stages_api::StageUnitCheckpoint; use reth_testing_utils::generators::{self, random_header, random_header_range}; - use reth_trie::{updates::TrieUpdates, HashedPostStateSorted}; + use reth_trie::HashedPostStateSorted; use std::sync::Arc; use test_runner::HeadersTestRunner; @@ -651,7 +650,6 @@ mod tests { sealed_blocks, &ExecutionOutcome::default(), HashedPostStateSorted::default(), - TrieUpdates::default(), ) .unwrap(); provider.commit().unwrap(); diff --git a/crates/stages/stages/src/stages/index_account_history.rs b/crates/stages/stages/src/stages/index_account_history.rs index 37db4f5f9fd..c8d6464cf3f 100644 --- a/crates/stages/stages/src/stages/index_account_history.rs +++ b/crates/stages/stages/src/stages/index_account_history.rs @@ -67,23 +67,22 @@ where ) }) .transpose()? - .flatten() + .flatten() && + target_prunable_block > input.checkpoint().block_number { - if target_prunable_block > input.checkpoint().block_number { - input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); - - // Save prune checkpoint only if we don't have one already. - // Otherwise, pruner may skip the unpruned range of blocks. - if provider.get_prune_checkpoint(PruneSegment::AccountHistory)?.is_none() { - provider.save_prune_checkpoint( - PruneSegment::AccountHistory, - PruneCheckpoint { - block_number: Some(target_prunable_block), - tx_number: None, - prune_mode, - }, - )?; - } + input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); + + // Save prune checkpoint only if we don't have one already. + // Otherwise, pruner may skip the unpruned range of blocks. + if provider.get_prune_checkpoint(PruneSegment::AccountHistory)?.is_none() { + provider.save_prune_checkpoint( + PruneSegment::AccountHistory, + PruneCheckpoint { + block_number: Some(target_prunable_block), + tx_number: None, + prune_mode, + }, + )?; } } diff --git a/crates/stages/stages/src/stages/index_storage_history.rs b/crates/stages/stages/src/stages/index_storage_history.rs index 09c9030cb39..2ec4094c1ec 100644 --- a/crates/stages/stages/src/stages/index_storage_history.rs +++ b/crates/stages/stages/src/stages/index_storage_history.rs @@ -70,23 +70,22 @@ where ) }) .transpose()? - .flatten() + .flatten() && + target_prunable_block > input.checkpoint().block_number { - if target_prunable_block > input.checkpoint().block_number { - input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); - - // Save prune checkpoint only if we don't have one already. - // Otherwise, pruner may skip the unpruned range of blocks. - if provider.get_prune_checkpoint(PruneSegment::StorageHistory)?.is_none() { - provider.save_prune_checkpoint( - PruneSegment::StorageHistory, - PruneCheckpoint { - block_number: Some(target_prunable_block), - tx_number: None, - prune_mode, - }, - )?; - } + input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); + + // Save prune checkpoint only if we don't have one already. + // Otherwise, pruner may skip the unpruned range of blocks. + if provider.get_prune_checkpoint(PruneSegment::StorageHistory)?.is_none() { + provider.save_prune_checkpoint( + PruneSegment::StorageHistory, + PruneCheckpoint { + block_number: Some(target_prunable_block), + tx_number: None, + prune_mode, + }, + )?; } } diff --git a/crates/stages/stages/src/stages/merkle.rs b/crates/stages/stages/src/stages/merkle.rs index 54fc5b2477c..c5115316243 100644 --- a/crates/stages/stages/src/stages/merkle.rs +++ b/crates/stages/stages/src/stages/merkle.rs @@ -50,7 +50,7 @@ pub const MERKLE_STAGE_DEFAULT_INCREMENTAL_THRESHOLD: u64 = 7_000; /// The merkle hashing stage uses input from /// [`AccountHashingStage`][crate::stages::AccountHashingStage] and -/// [`StorageHashingStage`][crate::stages::AccountHashingStage] to calculate intermediate hashes +/// [`StorageHashingStage`][crate::stages::StorageHashingStage] to calculate intermediate hashes /// and state roots. /// /// This stage should be run with the above two stages, otherwise it is a no-op. @@ -196,10 +196,11 @@ where .ok_or_else(|| ProviderError::HeaderNotFound(to_block.into()))?; let target_block_root = target_block.state_root(); - let mut checkpoint = self.get_execution_checkpoint(provider)?; let (trie_root, entities_checkpoint) = if range.is_empty() { (target_block_root, input.checkpoint().entities_stage_checkpoint().unwrap_or_default()) } else if to_block - from_block > threshold || from_block == 1 { + let mut checkpoint = self.get_execution_checkpoint(provider)?; + // if there are more blocks than threshold it is faster to rebuild the trie let mut entities_checkpoint = if let Some(checkpoint) = checkpoint.as_ref().filter(|c| c.target_block == to_block) @@ -401,10 +402,19 @@ where // Validation passed, apply unwind changes to the database. provider.write_trie_updates(&updates)?; - // TODO(alexey): update entities checkpoint + // Update entities checkpoint to reflect the unwind operation + // Since we're unwinding, we need to recalculate the total entities at the target block + let accounts = tx.entries::()?; + let storages = tx.entries::()?; + let total = (accounts + storages) as u64; + entities_checkpoint.total = total; + entities_checkpoint.processed = total; } - Ok(UnwindOutput { checkpoint: StageCheckpoint::new(input.unwind_to) }) + Ok(UnwindOutput { + checkpoint: StageCheckpoint::new(input.unwind_to) + .with_entities_stage_checkpoint(entities_checkpoint), + }) } } diff --git a/crates/stages/stages/src/stages/tx_lookup.rs b/crates/stages/stages/src/stages/tx_lookup.rs index 84dae251671..20a0770d8c8 100644 --- a/crates/stages/stages/src/stages/tx_lookup.rs +++ b/crates/stages/stages/src/stages/tx_lookup.rs @@ -88,28 +88,27 @@ where ) }) .transpose()? - .flatten() + .flatten() && + target_prunable_block > input.checkpoint().block_number { - if target_prunable_block > input.checkpoint().block_number { - input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); - - // Save prune checkpoint only if we don't have one already. - // Otherwise, pruner may skip the unpruned range of blocks. - if provider.get_prune_checkpoint(PruneSegment::TransactionLookup)?.is_none() { - let target_prunable_tx_number = provider - .block_body_indices(target_prunable_block)? - .ok_or(ProviderError::BlockBodyIndicesNotFound(target_prunable_block))? - .last_tx_num(); - - provider.save_prune_checkpoint( - PruneSegment::TransactionLookup, - PruneCheckpoint { - block_number: Some(target_prunable_block), - tx_number: Some(target_prunable_tx_number), - prune_mode, - }, - )?; - } + input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); + + // Save prune checkpoint only if we don't have one already. + // Otherwise, pruner may skip the unpruned range of blocks. + if provider.get_prune_checkpoint(PruneSegment::TransactionLookup)?.is_none() { + let target_prunable_tx_number = provider + .block_body_indices(target_prunable_block)? + .ok_or(ProviderError::BlockBodyIndicesNotFound(target_prunable_block))? + .last_tx_num(); + + provider.save_prune_checkpoint( + PruneSegment::TransactionLookup, + PruneCheckpoint { + block_number: Some(target_prunable_block), + tx_number: Some(target_prunable_tx_number), + prune_mode, + }, + )?; } } if input.target_reached() { @@ -213,10 +212,10 @@ where // Delete all transactions that belong to this block for tx_id in body.tx_num_range() { // First delete the transaction and hash to id mapping - if let Some(transaction) = static_file_provider.transaction_by_id(tx_id)? { - if tx_hash_number_cursor.seek_exact(transaction.trie_hash())?.is_some() { - tx_hash_number_cursor.delete_current()?; - } + if let Some(transaction) = static_file_provider.transaction_by_id(tx_id)? && + tx_hash_number_cursor.seek_exact(transaction.trie_hash())?.is_some() + { + tx_hash_number_cursor.delete_current()?; } } } @@ -538,11 +537,10 @@ mod tests { }) .transpose() .expect("prune target block for transaction lookup") - .flatten() + .flatten() && + target_prunable_block > input.checkpoint().block_number { - if target_prunable_block > input.checkpoint().block_number { - input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); - } + input.checkpoint = Some(StageCheckpoint::new(target_prunable_block)); } let start_block = input.next_block(); let end_block = output.checkpoint.block_number; diff --git a/crates/stages/stages/src/stages/utils.rs b/crates/stages/stages/src/stages/utils.rs index 55d59606a2e..f4bb960e7aa 100644 --- a/crates/stages/stages/src/stages/utils.rs +++ b/crates/stages/stages/src/stages/utils.rs @@ -156,12 +156,11 @@ where // If it's not the first sync, there might an existing shard already, so we need to // merge it with the one coming from the collector - if !append_only { - if let Some((_, last_database_shard)) = + if !append_only && + let Some((_, last_database_shard)) = write_cursor.seek_exact(sharded_key_factory(current_partial, u64::MAX))? - { - current_list.extend(last_database_shard.iter()); - } + { + current_list.extend(last_database_shard.iter()); } } @@ -265,10 +264,10 @@ where // To be extra safe, we make sure that the last tx num matches the last block from its indices. // If not, get it. loop { - if let Some(indices) = provider.block_body_indices(last_block)? { - if indices.last_tx_num() <= last_tx_num { - break - } + if let Some(indices) = provider.block_body_indices(last_block)? && + indices.last_tx_num() <= last_tx_num + { + break } if last_block == 0 { break diff --git a/crates/stages/types/Cargo.toml b/crates/stages/types/Cargo.toml index c88f53dcdaa..03145965219 100644 --- a/crates/stages/types/Cargo.toml +++ b/crates/stages/types/Cargo.toml @@ -26,7 +26,6 @@ modular-bitfield = { workspace = true, optional = true } reth-codecs.workspace = true alloy-primitives = { workspace = true, features = ["arbitrary", "rand"] } arbitrary = { workspace = true, features = ["derive"] } -modular-bitfield.workspace = true proptest.workspace = true proptest-arbitrary-interop.workspace = true test-fuzz.workspace = true diff --git a/crates/stages/types/src/id.rs b/crates/stages/types/src/id.rs index 86dd9ced5c7..78d7e0ec1b6 100644 --- a/crates/stages/types/src/id.rs +++ b/crates/stages/types/src/id.rs @@ -1,3 +1,7 @@ +use alloc::vec::Vec; +#[cfg(feature = "std")] +use std::{collections::HashMap, sync::OnceLock}; + /// Stage IDs for all known stages. /// /// For custom stages, use [`StageId::Other`] @@ -27,6 +31,12 @@ pub enum StageId { Other(&'static str), } +/// One-time-allocated stage ids encoded as raw Vecs, useful for database +/// clients to reference them for queries instead of encoding anew per query +/// (sad heap allocation required). +#[cfg(feature = "std")] +static ENCODED_STAGE_IDS: OnceLock>> = OnceLock::new(); + impl StageId { /// All supported Stages pub const ALL: [Self; 15] = [ @@ -98,6 +108,25 @@ impl StageId { pub const fn is_finish(&self) -> bool { matches!(self, Self::Finish) } + + /// Get a pre-encoded raw Vec, for example, to be used as the DB key for + /// `tables::StageCheckpoints` and `tables::StageCheckpointProgresses` + pub fn get_pre_encoded(&self) -> Option<&Vec> { + #[cfg(not(feature = "std"))] + { + None + } + #[cfg(feature = "std")] + ENCODED_STAGE_IDS + .get_or_init(|| { + let mut map = HashMap::with_capacity(Self::ALL.len()); + for stage_id in Self::ALL { + map.insert(stage_id, stage_id.to_string().into_bytes()); + } + map + }) + .get(self) + } } impl core::fmt::Display for StageId { diff --git a/crates/stateless/src/trie.rs b/crates/stateless/src/trie.rs index f5c570b425d..49d1f6cf0fd 100644 --- a/crates/stateless/src/trie.rs +++ b/crates/stateless/src/trie.rs @@ -167,8 +167,8 @@ impl StatelessTrie for StatelessSparseTrie { /// The bytecode has a separate mapping because the [`SparseStateTrie`] does not store the /// contract bytecode, only the hash of it (code hash). /// -/// If the roots do not match, it returns `None`, indicating the witness is invalid -/// for the given `pre_state_root`. +/// If the roots do not match, it returns an error indicating the witness is invalid +/// for the given `pre_state_root` (see `StatelessValidationError::PreStateRootMismatch`). // Note: This approach might be inefficient for ZKVMs requiring minimal memory operations, which // would explain why they have for the most part re-implemented this function. fn verify_execution_witness( @@ -286,7 +286,6 @@ fn calculate_state_root( state.accounts.into_iter().sorted_unstable_by_key(|(addr, _)| *addr) { let nibbles = Nibbles::unpack(hashed_address); - let account = account.unwrap_or_default(); // Determine which storage root should be used for this account let storage_root = if let Some(storage_trie) = trie.storage_trie_mut(&hashed_address) { @@ -298,12 +297,12 @@ fn calculate_state_root( }; // Decide whether to remove or update the account leaf - if account.is_empty() && storage_root == EMPTY_ROOT_HASH { - trie.remove_account_leaf(&nibbles, &provider_factory)?; - } else { + if let Some(account) = account { account_rlp_buf.clear(); account.into_trie_account(storage_root).encode(&mut account_rlp_buf); trie.update_account_leaf(nibbles, account_rlp_buf.clone(), &provider_factory)?; + } else { + trie.remove_account_leaf(&nibbles, &provider_factory)?; } } diff --git a/crates/static-file/static-file/Cargo.toml b/crates/static-file/static-file/Cargo.toml index 38cfac36207..7ea23e0132f 100644 --- a/crates/static-file/static-file/Cargo.toml +++ b/crates/static-file/static-file/Cargo.toml @@ -31,7 +31,6 @@ rayon.workspace = true parking_lot = { workspace = true, features = ["send_guard", "arc_lock"] } [dev-dependencies] -reth-db = { workspace = true, features = ["test-utils"] } reth-stages = { workspace = true, features = ["test-utils"] } reth-testing-utils.workspace = true diff --git a/crates/storage/codecs/derive/src/arbitrary.rs b/crates/storage/codecs/derive/src/arbitrary.rs index 5713bb9b0ff..552e7d592d2 100644 --- a/crates/storage/codecs/derive/src/arbitrary.rs +++ b/crates/storage/codecs/derive/src/arbitrary.rs @@ -23,11 +23,11 @@ pub fn maybe_generate_tests( let mut iter = args.into_iter().peekable(); // we check if there's a crate argument which is used from inside the codecs crate directly - if let Some(arg) = iter.peek() { - if arg.to_string() == "crate" { - is_crate = true; - iter.next(); - } + if let Some(arg) = iter.peek() && + arg.to_string() == "crate" + { + is_crate = true; + iter.next(); } for arg in iter { diff --git a/crates/storage/codecs/derive/src/compact/mod.rs b/crates/storage/codecs/derive/src/compact/mod.rs index ae349cd06e5..30082a32126 100644 --- a/crates/storage/codecs/derive/src/compact/mod.rs +++ b/crates/storage/codecs/derive/src/compact/mod.rs @@ -171,28 +171,14 @@ fn load_field_from_segments( /// /// If so, we use another impl to code/decode its data. fn should_use_alt_impl(ftype: &str, segment: &syn::PathSegment) -> bool { - if ftype == "Vec" || ftype == "Option" { - if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments { - if let Some(syn::GenericArgument::Type(syn::Type::Path(arg_path))) = args.args.last() { - if let (Some(path), 1) = - (arg_path.path.segments.first(), arg_path.path.segments.len()) - { - if [ - "B256", - "Address", - "Address", - "Bloom", - "TxHash", - "BlockHash", - "CompactPlaceholder", - ] - .contains(&path.ident.to_string().as_str()) - { - return true - } - } - } - } + if (ftype == "Vec" || ftype == "Option") && + let syn::PathArguments::AngleBracketed(ref args) = segment.arguments && + let Some(syn::GenericArgument::Type(syn::Type::Path(arg_path))) = args.args.last() && + let (Some(path), 1) = (arg_path.path.segments.first(), arg_path.path.segments.len()) && + ["B256", "Address", "Address", "Bloom", "TxHash", "BlockHash", "CompactPlaceholder"] + .contains(&path.ident.to_string().as_str()) + { + return true } false } diff --git a/crates/storage/codecs/derive/src/lib.rs b/crates/storage/codecs/derive/src/lib.rs index a835e8fab3c..84d3d336573 100644 --- a/crates/storage/codecs/derive/src/lib.rs +++ b/crates/storage/codecs/derive/src/lib.rs @@ -69,8 +69,8 @@ pub fn derive_zstd(input: TokenStream) -> TokenStream { let mut decompressor = None; for attr in &input.attrs { - if attr.path().is_ident("reth_zstd") { - if let Err(err) = attr.parse_nested_meta(|meta| { + if attr.path().is_ident("reth_zstd") && + let Err(err) = attr.parse_nested_meta(|meta| { if meta.path.is_ident("compressor") { let value = meta.value()?; let path: syn::Path = value.parse()?; @@ -83,9 +83,9 @@ pub fn derive_zstd(input: TokenStream) -> TokenStream { return Err(meta.error("unsupported attribute")) } Ok(()) - }) { - return err.to_compile_error().into() - } + }) + { + return err.to_compile_error().into() } } diff --git a/crates/storage/db-api/src/mock.rs b/crates/storage/db-api/src/mock.rs index d37ffa289b9..4a8440cb950 100644 --- a/crates/storage/db-api/src/mock.rs +++ b/crates/storage/db-api/src/mock.rs @@ -16,7 +16,6 @@ use core::ops::Bound; use std::{collections::BTreeMap, ops::RangeBounds}; /// Mock database used for testing with inner `BTreeMap` structure -// TODO #[derive(Clone, Debug, Default)] pub struct DatabaseMock { /// Main data. TODO (Make it table aware) diff --git a/crates/storage/db-api/src/models/accounts.rs b/crates/storage/db-api/src/models/accounts.rs index ad6e37e0ecb..e363aff2f70 100644 --- a/crates/storage/db-api/src/models/accounts.rs +++ b/crates/storage/db-api/src/models/accounts.rs @@ -107,13 +107,13 @@ impl_fixed_arbitrary!((BlockNumberAddress, 28), (AddressStorageKey, 52)); #[cfg(test)] mod tests { use super::*; + use alloy_primitives::address; use rand::{rng, Rng}; - use std::str::FromStr; #[test] fn test_block_number_address() { let num = 1u64; - let hash = Address::from_str("ba5e000000000000000000000000000000000000").unwrap(); + let hash = address!("0xba5e000000000000000000000000000000000000"); let key = BlockNumberAddress((num, hash)); let mut bytes = [0u8; 28]; @@ -138,7 +138,7 @@ mod tests { #[test] fn test_address_storage_key() { let storage_key = StorageKey::random(); - let address = Address::from_str("ba5e000000000000000000000000000000000000").unwrap(); + let address = address!("0xba5e000000000000000000000000000000000000"); let key = AddressStorageKey((address, storage_key)); let mut bytes = [0u8; 52]; diff --git a/crates/storage/db-api/src/models/sharded_key.rs b/crates/storage/db-api/src/models/sharded_key.rs index d1de1bd400c..fdd583f0f55 100644 --- a/crates/storage/db-api/src/models/sharded_key.rs +++ b/crates/storage/db-api/src/models/sharded_key.rs @@ -55,7 +55,7 @@ impl Encode for ShardedKey { impl Decode for ShardedKey { fn decode(value: &[u8]) -> Result { - let (key, highest_tx_number) = value.split_last_chunk().unwrap(); + let (key, highest_tx_number) = value.split_last_chunk().ok_or(DatabaseError::Decode)?; let key = T::decode(key)?; let highest_tx_number = u64::from_be_bytes(*highest_tx_number); Ok(Self::new(key, highest_tx_number)) diff --git a/crates/storage/db-common/src/init.rs b/crates/storage/db-common/src/init.rs index d39d56b5c85..87bb2ce98a0 100644 --- a/crates/storage/db-common/src/init.rs +++ b/crates/storage/db-common/src/init.rs @@ -2,7 +2,7 @@ use alloy_consensus::BlockHeader; use alloy_genesis::GenesisAccount; -use alloy_primitives::{map::HashMap, Address, B256, U256}; +use alloy_primitives::{keccak256, map::HashMap, Address, B256, U256}; use reth_chainspec::EthChainSpec; use reth_codecs::Compact; use reth_config::config::EtlConfig; @@ -19,7 +19,10 @@ use reth_provider::{ }; use reth_stages_types::{StageCheckpoint, StageId}; use reth_static_file_types::StaticFileSegment; -use reth_trie::{IntermediateStateRootState, StateRoot as StateRootComputer, StateRootProgress}; +use reth_trie::{ + prefix_set::{TriePrefixSets, TriePrefixSetsMut}, + IntermediateStateRootState, Nibbles, StateRoot as StateRootComputer, StateRootProgress, +}; use reth_trie_db::DatabaseStateRoot; use serde::{Deserialize, Serialize}; use std::io::BufRead; @@ -144,7 +147,7 @@ where insert_genesis_state(&provider_rw, alloc.iter())?; // compute state root to populate trie tables - compute_state_root(&provider_rw)?; + compute_state_root(&provider_rw, None)?; // insert sync stage for stage in StageId::ALL { @@ -425,11 +428,14 @@ where // remaining lines are accounts let collector = parse_accounts(&mut reader, etl_config)?; - // write state to db - dump_state(collector, provider_rw, block)?; + // write state to db and collect prefix sets + let mut prefix_sets = TriePrefixSetsMut::default(); + dump_state(collector, provider_rw, block, &mut prefix_sets)?; + + info!(target: "reth::cli", "All accounts written to database, starting state root computation (may take some time)"); // compute and compare state root. this advances the stage checkpoints. - let computed_state_root = compute_state_root(provider_rw)?; + let computed_state_root = compute_state_root(provider_rw, Some(prefix_sets.freeze()))?; if computed_state_root == expected_state_root { info!(target: "reth::cli", ?computed_state_root, @@ -505,6 +511,7 @@ fn dump_state( mut collector: Collector, provider_rw: &Provider, block: u64, + prefix_sets: &mut TriePrefixSetsMut, ) -> Result<(), eyre::Error> where Provider: StaticFileProviderFactory @@ -524,6 +531,22 @@ where let (address, _) = Address::from_compact(address.as_slice(), address.len()); let (account, _) = GenesisAccount::from_compact(account.as_slice(), account.len()); + // Add to prefix sets + let hashed_address = keccak256(address); + prefix_sets.account_prefix_set.insert(Nibbles::unpack(hashed_address)); + + // Add storage keys to prefix sets if storage exists + if let Some(ref storage) = account.storage { + for key in storage.keys() { + let hashed_key = keccak256(key); + prefix_sets + .storage_prefix_sets + .entry(hashed_address) + .or_default() + .insert(Nibbles::unpack(hashed_key)); + } + } + accounts.push((address, account)); if (index > 0 && index.is_multiple_of(AVERAGE_COUNT_ACCOUNTS_PER_GB_STATE_DUMP)) || @@ -563,7 +586,10 @@ where /// Computes the state root (from scratch) based on the accounts and storages present in the /// database. -fn compute_state_root(provider: &Provider) -> Result +fn compute_state_root( + provider: &Provider, + prefix_sets: Option, +) -> Result where Provider: DBProvider + TrieWriter, { @@ -574,10 +600,14 @@ where let mut total_flushed_updates = 0; loop { - match StateRootComputer::from_tx(tx) - .with_intermediate_state(intermediate_state) - .root_with_progress()? - { + let mut state_root = + StateRootComputer::from_tx(tx).with_intermediate_state(intermediate_state); + + if let Some(sets) = prefix_sets.clone() { + state_root = state_root.with_prefix_sets(sets); + } + + match state_root.root_with_progress()? { StateRootProgress::Progress(state, _, updates) => { let updated_len = provider.write_trie_updates(&updates)?; total_flushed_updates += updated_len; diff --git a/crates/storage/db-models/Cargo.toml b/crates/storage/db-models/Cargo.toml index eb74e227e6d..34ec472c7a6 100644 --- a/crates/storage/db-models/Cargo.toml +++ b/crates/storage/db-models/Cargo.toml @@ -36,7 +36,6 @@ reth-primitives-traits = { workspace = true, features = ["arbitrary", "reth-code reth-codecs.workspace = true bytes.workspace = true -modular-bitfield.workspace = true arbitrary = { workspace = true, features = ["derive"] } proptest.workspace = true diff --git a/crates/storage/db/Cargo.toml b/crates/storage/db/Cargo.toml index 719c7b785c1..5e2c2f31b9b 100644 --- a/crates/storage/db/Cargo.toml +++ b/crates/storage/db/Cargo.toml @@ -46,6 +46,7 @@ strum = { workspace = true, features = ["derive"], optional = true } [dev-dependencies] # reth libs with arbitrary reth-primitives-traits = { workspace = true, features = ["reth-codec"] } +reth-prune-types.workspace = true alloy-primitives = { workspace = true, features = ["getrandom"] } alloy-consensus.workspace = true @@ -81,6 +82,7 @@ test-utils = [ "reth-db-api/test-utils", "reth-nippy-jar/test-utils", "reth-primitives-traits/test-utils", + "reth-prune-types/test-utils", ] bench = ["reth-db-api/bench"] arbitrary = [ @@ -88,6 +90,7 @@ arbitrary = [ "alloy-primitives/arbitrary", "alloy-consensus/arbitrary", "reth-primitives-traits/arbitrary", + "reth-prune-types/arbitrary", ] op = [ "reth-db-api/op", diff --git a/crates/storage/db/src/implementation/mdbx/mod.rs b/crates/storage/db/src/implementation/mdbx/mod.rs index faa784de698..def7c90ca42 100644 --- a/crates/storage/db/src/implementation/mdbx/mod.rs +++ b/crates/storage/db/src/implementation/mdbx/mod.rs @@ -23,6 +23,7 @@ use reth_libmdbx::{ use reth_storage_errors::db::LogLevel; use reth_tracing::tracing::error; use std::{ + collections::HashMap, ops::{Deref, Range}, path::Path, sync::Arc, @@ -116,7 +117,7 @@ impl DatabaseArguments { Self { client_version, geometry: Geometry { - size: Some(0..(4 * TERABYTE)), + size: Some(0..(8 * TERABYTE)), growth_step: Some(4 * GIGABYTE as isize), shrink_threshold: Some(0), page_size: Some(PageSize::Set(default_page_size())), @@ -190,6 +191,12 @@ impl DatabaseArguments { pub struct DatabaseEnv { /// Libmdbx-sys environment. inner: Environment, + /// Opened DBIs for reuse. + /// Important: Do not manually close these DBIs, like via `mdbx_dbi_close`. + /// More generally, do not dynamically create, re-open, or drop tables at + /// runtime. It's better to perform table creation and migration only once + /// at startup. + dbis: Arc>, /// Cache for metric handles. If `None`, metrics are not recorded. metrics: Option>, /// Write lock for when dealing with a read-write environment. @@ -201,16 +208,18 @@ impl Database for DatabaseEnv { type TXMut = tx::Tx; fn tx(&self) -> Result { - Tx::new_with_metrics( + Tx::new( self.inner.begin_ro_txn().map_err(|e| DatabaseError::InitTx(e.into()))?, + self.dbis.clone(), self.metrics.clone(), ) .map_err(|e| DatabaseError::InitTx(e.into())) } fn tx_mut(&self) -> Result { - Tx::new_with_metrics( + Tx::new( self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?, + self.dbis.clone(), self.metrics.clone(), ) .map_err(|e| DatabaseError::InitTx(e.into())) @@ -445,6 +454,7 @@ impl DatabaseEnv { let env = Self { inner: inner_env.open(path).map_err(|e| DatabaseError::Open(e.into()))?, + dbis: Arc::default(), metrics: None, _lock_file, }; @@ -459,25 +469,60 @@ impl DatabaseEnv { } /// Creates all the tables defined in [`Tables`], if necessary. - pub fn create_tables(&self) -> Result<(), DatabaseError> { - self.create_tables_for::() + /// + /// This keeps tracks of the created table handles and stores them for better efficiency. + pub fn create_tables(&mut self) -> Result<(), DatabaseError> { + self.create_and_track_tables_for::() } /// Creates all the tables defined in the given [`TableSet`], if necessary. - pub fn create_tables_for(&self) -> Result<(), DatabaseError> { + /// + /// This keeps tracks of the created table handles and stores them for better efficiency. + pub fn create_and_track_tables_for(&mut self) -> Result<(), DatabaseError> { + let handles = self._create_tables::()?; + // Note: This is okay because self has mutable access here and `DatabaseEnv` must be Arc'ed + // before it can be shared. + let dbis = Arc::make_mut(&mut self.dbis); + dbis.extend(handles); + + Ok(()) + } + + /// Creates all the tables defined in [`Tables`], if necessary. + /// + /// If this type is unique the created handle for the tables will be updated. + /// + /// This is recommended to be called during initialization to create and track additional tables + /// after the default [`Self::create_tables`] are created. + pub fn create_tables_for(self: &mut Arc) -> Result<(), DatabaseError> { + let handles = self._create_tables::()?; + if let Some(db) = Arc::get_mut(self) { + // Note: The db is unique and the dbis as well, and they can also be cloned. + let dbis = Arc::make_mut(&mut db.dbis); + dbis.extend(handles); + } + Ok(()) + } + + /// Creates the tables and returns the identifiers of the tables. + fn _create_tables( + &self, + ) -> Result, DatabaseError> { + let mut handles = Vec::new(); let tx = self.inner.begin_rw_txn().map_err(|e| DatabaseError::InitTx(e.into()))?; for table in TS::tables() { let flags = if table.is_dupsort() { DatabaseFlags::DUP_SORT } else { DatabaseFlags::default() }; - tx.create_db(Some(table.name()), flags) + let db = tx + .create_db(Some(table.name()), flags) .map_err(|e| DatabaseError::CreateTable(e.into()))?; + handles.push((table.name(), db.dbi())); } tx.commit().map_err(|e| DatabaseError::Commit(e.into()))?; - - Ok(()) + Ok(handles) } /// Records version that accesses the database with write privileges. @@ -543,8 +588,9 @@ mod tests { /// Create database for testing with specified path fn create_test_db_with_path(kind: DatabaseEnvKind, path: &Path) -> DatabaseEnv { - let env = DatabaseEnv::open(path, kind, DatabaseArguments::new(ClientVersion::default())) - .expect(ERROR_DB_CREATION); + let mut env = + DatabaseEnv::open(path, kind, DatabaseArguments::new(ClientVersion::default())) + .expect(ERROR_DB_CREATION); env.create_tables().expect(ERROR_TABLE_CREATION); env } diff --git a/crates/storage/db/src/implementation/mdbx/tx.rs b/crates/storage/db/src/implementation/mdbx/tx.rs index d2b20f5ae38..1d3e3124306 100644 --- a/crates/storage/db/src/implementation/mdbx/tx.rs +++ b/crates/storage/db/src/implementation/mdbx/tx.rs @@ -14,6 +14,7 @@ use reth_storage_errors::db::{DatabaseWriteError, DatabaseWriteOperation}; use reth_tracing::tracing::{debug, trace, warn}; use std::{ backtrace::Backtrace, + collections::HashMap, marker::PhantomData, sync::{ atomic::{AtomicBool, Ordering}, @@ -31,6 +32,9 @@ pub struct Tx { /// Libmdbx-sys transaction. pub inner: Transaction, + /// Cached MDBX DBIs for reuse. + dbis: Arc>, + /// Handler for metrics with its own [Drop] implementation for cases when the transaction isn't /// closed by [`Tx::commit`] or [`Tx::abort`], but we still need to report it in the metrics. /// @@ -39,17 +43,12 @@ pub struct Tx { } impl Tx { - /// Creates new `Tx` object with a `RO` or `RW` transaction. - #[inline] - pub const fn new(inner: Transaction) -> Self { - Self::new_inner(inner, None) - } - /// Creates new `Tx` object with a `RO` or `RW` transaction and optionally enables metrics. #[inline] #[track_caller] - pub(crate) fn new_with_metrics( + pub(crate) fn new( inner: Transaction, + dbis: Arc>, env_metrics: Option>, ) -> reth_libmdbx::Result { let metrics_handler = env_metrics @@ -60,12 +59,7 @@ impl Tx { Ok(handler) }) .transpose()?; - Ok(Self::new_inner(inner, metrics_handler)) - } - - #[inline] - const fn new_inner(inner: Transaction, metrics_handler: Option>) -> Self { - Self { inner, metrics_handler } + Ok(Self { inner, dbis, metrics_handler }) } /// Gets this transaction ID. @@ -75,10 +69,14 @@ impl Tx { /// Gets a table database handle if it exists, otherwise creates it. pub fn get_dbi(&self) -> Result { - self.inner - .open_db(Some(T::NAME)) - .map(|db| db.dbi()) - .map_err(|e| DatabaseError::Open(e.into())) + if let Some(dbi) = self.dbis.get(T::NAME) { + Ok(*dbi) + } else { + self.inner + .open_db(Some(T::NAME)) + .map(|db| db.dbi()) + .map_err(|e| DatabaseError::Open(e.into())) + } } /// Create db Cursor diff --git a/crates/storage/db/src/lockfile.rs b/crates/storage/db/src/lockfile.rs index 4f862d1f170..5e25d14ae3a 100644 --- a/crates/storage/db/src/lockfile.rs +++ b/crates/storage/db/src/lockfile.rs @@ -44,17 +44,18 @@ impl StorageLock { #[cfg(any(test, not(feature = "disable-lock")))] fn try_acquire_file_lock(path: &Path) -> Result { let file_path = path.join(LOCKFILE_NAME); - if let Some(process_lock) = ProcessUID::parse(&file_path)? { - if process_lock.pid != (process::id() as usize) && process_lock.is_active() { - reth_tracing::tracing::error!( - target: "reth::db::lockfile", - path = ?file_path, - pid = process_lock.pid, - start_time = process_lock.start_time, - "Storage lock already taken." - ); - return Err(StorageLockError::Taken(process_lock.pid)) - } + if let Some(process_lock) = ProcessUID::parse(&file_path)? && + process_lock.pid != (process::id() as usize) && + process_lock.is_active() + { + reth_tracing::tracing::error!( + target: "reth::db::lockfile", + path = ?file_path, + pid = process_lock.pid, + start_time = process_lock.start_time, + "Storage lock already taken." + ); + return Err(StorageLockError::Taken(process_lock.pid)) } Ok(Self(Arc::new(StorageLockInner::new(file_path)?))) @@ -141,15 +142,15 @@ impl ProcessUID { /// Parses [`Self`] from a file. fn parse(path: &Path) -> Result, StorageLockError> { - if path.exists() { - if let Ok(contents) = reth_fs_util::read_to_string(path) { - let mut lines = contents.lines(); - if let (Some(Ok(pid)), Some(Ok(start_time))) = ( - lines.next().map(str::trim).map(str::parse), - lines.next().map(str::trim).map(str::parse), - ) { - return Ok(Some(Self { pid, start_time })); - } + if path.exists() && + let Ok(contents) = reth_fs_util::read_to_string(path) + { + let mut lines = contents.lines(); + if let (Some(Ok(pid)), Some(Ok(start_time))) = ( + lines.next().map(str::trim).map(str::parse), + lines.next().map(str::trim).map(str::parse), + ) { + return Ok(Some(Self { pid, start_time })); } } Ok(None) diff --git a/crates/storage/db/src/mdbx.rs b/crates/storage/db/src/mdbx.rs index 9042299afdc..fb0fd8501e3 100644 --- a/crates/storage/db/src/mdbx.rs +++ b/crates/storage/db/src/mdbx.rs @@ -41,8 +41,8 @@ pub fn init_db_for, TS: TableSet>( args: DatabaseArguments, ) -> eyre::Result { let client_version = args.client_version().clone(); - let db = create_db(path, args)?; - db.create_tables_for::()?; + let mut db = create_db(path, args)?; + db.create_and_track_tables_for::()?; db.record_client_version(client_version)?; Ok(db) } diff --git a/crates/storage/db/src/static_file/mod.rs b/crates/storage/db/src/static_file/mod.rs index cbcf87d8939..f2c9ce45fbc 100644 --- a/crates/storage/db/src/static_file/mod.rs +++ b/crates/storage/db/src/static_file/mod.rs @@ -33,25 +33,22 @@ pub fn iter_static_files(path: &Path) -> Result::load(&entry.path())?; + { + let jar = NippyJar::::load(&entry.path())?; - let (block_range, tx_range) = ( - jar.user_header().block_range().copied(), - jar.user_header().tx_range().copied(), - ); + let (block_range, tx_range) = + (jar.user_header().block_range().copied(), jar.user_header().tx_range().copied()); - if let Some(block_range) = block_range { - match static_files.entry(segment) { - Entry::Occupied(mut entry) => { - entry.get_mut().push((block_range, tx_range)); - } - Entry::Vacant(entry) => { - entry.insert(vec![(block_range, tx_range)]); - } + if let Some(block_range) = block_range { + match static_files.entry(segment) { + Entry::Occupied(mut entry) => { + entry.get_mut().push((block_range, tx_range)); + } + Entry::Vacant(entry) => { + entry.insert(vec![(block_range, tx_range)]); } } } diff --git a/crates/storage/libmdbx-rs/Cargo.toml b/crates/storage/libmdbx-rs/Cargo.toml index 6b7956f4675..8fa931a3495 100644 --- a/crates/storage/libmdbx-rs/Cargo.toml +++ b/crates/storage/libmdbx-rs/Cargo.toml @@ -17,7 +17,6 @@ reth-mdbx-sys.workspace = true bitflags.workspace = true byteorder.workspace = true derive_more.workspace = true -indexmap.workspace = true parking_lot.workspace = true smallvec.workspace = true thiserror.workspace = true diff --git a/crates/storage/libmdbx-rs/benches/transaction.rs b/crates/storage/libmdbx-rs/benches/transaction.rs index 35c403606df..311e5b5c184 100644 --- a/crates/storage/libmdbx-rs/benches/transaction.rs +++ b/crates/storage/libmdbx-rs/benches/transaction.rs @@ -66,8 +66,6 @@ fn bench_put_rand(c: &mut Criterion) { let txn = env.begin_ro_txn().unwrap(); let db = txn.open_db(None).unwrap(); - txn.prime_for_permaopen(db); - let db = txn.commit_and_rebind_open_dbs().unwrap().2.remove(0); let mut items: Vec<(String, String)> = (0..n).map(|n| (get_key(n), get_data(n))).collect(); items.shuffle(&mut StdRng::from_seed(Default::default())); diff --git a/crates/storage/libmdbx-rs/src/codec.rs b/crates/storage/libmdbx-rs/src/codec.rs index c78f79db9f9..c0b2f0f1cf7 100644 --- a/crates/storage/libmdbx-rs/src/codec.rs +++ b/crates/storage/libmdbx-rs/src/codec.rs @@ -17,7 +17,7 @@ pub trait TableObject: Sized { _: *const ffi::MDBX_txn, data_val: ffi::MDBX_val, ) -> Result { - let s = slice::from_raw_parts(data_val.iov_base as *const u8, data_val.iov_len); + let s = unsafe { slice::from_raw_parts(data_val.iov_base as *const u8, data_val.iov_len) }; Self::decode(s) } } @@ -32,7 +32,7 @@ impl TableObject for Cow<'_, [u8]> { _txn: *const ffi::MDBX_txn, data_val: ffi::MDBX_val, ) -> Result { - let s = slice::from_raw_parts(data_val.iov_base as *const u8, data_val.iov_len); + let s = unsafe { slice::from_raw_parts(data_val.iov_base as *const u8, data_val.iov_len) }; #[cfg(feature = "return-borrowed")] { diff --git a/crates/storage/libmdbx-rs/src/flags.rs b/crates/storage/libmdbx-rs/src/flags.rs index 1457195be78..71bd77b55d2 100644 --- a/crates/storage/libmdbx-rs/src/flags.rs +++ b/crates/storage/libmdbx-rs/src/flags.rs @@ -2,11 +2,12 @@ use bitflags::bitflags; use ffi::*; /// MDBX sync mode -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub enum SyncMode { /// Default robust and durable sync mode. /// Metadata is written and flushed to disk after a data is written and flushed, which /// guarantees the integrity of the database in the event of a crash at any time. + #[default] Durable, /// Don't sync the meta-page after commit. @@ -100,12 +101,6 @@ pub enum SyncMode { UtterlyNoSync, } -impl Default for SyncMode { - fn default() -> Self { - Self::Durable - } -} - #[derive(Clone, Copy, Debug)] pub enum Mode { ReadOnly, diff --git a/crates/storage/libmdbx-rs/src/transaction.rs b/crates/storage/libmdbx-rs/src/transaction.rs index a19e7095660..e47e71ac261 100644 --- a/crates/storage/libmdbx-rs/src/transaction.rs +++ b/crates/storage/libmdbx-rs/src/transaction.rs @@ -7,7 +7,6 @@ use crate::{ Cursor, Error, Stat, TableObject, }; use ffi::{MDBX_txn_flags_t, MDBX_TXN_RDONLY, MDBX_TXN_READWRITE}; -use indexmap::IndexSet; use parking_lot::{Mutex, MutexGuard}; use std::{ ffi::{c_uint, c_void}, @@ -94,7 +93,6 @@ where let inner = TransactionInner { txn, - primed_dbis: Mutex::new(IndexSet::new()), committed: AtomicBool::new(false), env, _marker: Default::default(), @@ -173,50 +171,25 @@ where /// /// Any pending operations will be saved. pub fn commit(self) -> Result<(bool, CommitLatency)> { - self.commit_and_rebind_open_dbs().map(|v| (v.0, v.1)) - } - - pub fn prime_for_permaopen(&self, db: Database) { - self.inner.primed_dbis.lock().insert(db.dbi()); - } + let result = self.txn_execute(|txn| { + if K::IS_READ_ONLY { + #[cfg(feature = "read-tx-timeouts")] + self.env().txn_manager().remove_active_read_transaction(txn); - /// Commits the transaction and returns table handles permanently open until dropped. - pub fn commit_and_rebind_open_dbs(self) -> Result<(bool, CommitLatency, Vec)> { - let result = { - let result = self.txn_execute(|txn| { - if K::IS_READ_ONLY { - #[cfg(feature = "read-tx-timeouts")] - self.env().txn_manager().remove_active_read_transaction(txn); - - let mut latency = CommitLatency::new(); - mdbx_result(unsafe { - ffi::mdbx_txn_commit_ex(txn, latency.mdb_commit_latency()) - }) + let mut latency = CommitLatency::new(); + mdbx_result(unsafe { ffi::mdbx_txn_commit_ex(txn, latency.mdb_commit_latency()) }) .map(|v| (v, latency)) - } else { - let (sender, rx) = sync_channel(0); - self.env() - .txn_manager() - .send_message(TxnManagerMessage::Commit { tx: TxnPtr(txn), sender }); - rx.recv().unwrap() - } - })?; + } else { + let (sender, rx) = sync_channel(0); + self.env() + .txn_manager() + .send_message(TxnManagerMessage::Commit { tx: TxnPtr(txn), sender }); + rx.recv().unwrap() + } + })?; - self.inner.set_committed(); - result - }; - result.map(|(v, latency)| { - ( - v, - latency, - self.inner - .primed_dbis - .lock() - .iter() - .map(|&dbi| Database::new_from_ptr(dbi, self.env().clone())) - .collect(), - ) - }) + self.inner.set_committed(); + result } /// Opens a handle to an MDBX database. @@ -308,8 +281,6 @@ where { /// The transaction pointer itself. txn: TransactionPtr, - /// A set of database handles that are primed for permaopen. - primed_dbis: Mutex>, /// Whether the transaction has committed. committed: AtomicBool, env: Environment, @@ -505,7 +476,7 @@ impl Transaction { /// Caller must close ALL other [Database] and [Cursor] instances pointing to the same dbi /// BEFORE calling this function. pub unsafe fn drop_db(&self, db: Database) -> Result<()> { - mdbx_result(self.txn_execute(|txn| ffi::mdbx_drop(txn, db.dbi(), true))?)?; + mdbx_result(self.txn_execute(|txn| unsafe { ffi::mdbx_drop(txn, db.dbi(), true) })?)?; Ok(()) } @@ -518,7 +489,7 @@ impl Transaction { /// Caller must close ALL other [Database] and [Cursor] instances pointing to the same dbi /// BEFORE calling this function. pub unsafe fn close_db(&self, db: Database) -> Result<()> { - mdbx_result(ffi::mdbx_dbi_close(self.env().env_ptr(), db.dbi()))?; + mdbx_result(unsafe { ffi::mdbx_dbi_close(self.env().env_ptr(), db.dbi()) })?; Ok(()) } diff --git a/crates/storage/nippy-jar/src/lib.rs b/crates/storage/nippy-jar/src/lib.rs index f3d1944d3b4..b47042f8b9a 100644 --- a/crates/storage/nippy-jar/src/lib.rs +++ b/crates/storage/nippy-jar/src/lib.rs @@ -309,10 +309,10 @@ impl NippyJar { return Err(NippyJarError::ColumnLenMismatch(self.columns, columns.len())) } - if let Some(compression) = &self.compressor { - if !compression.is_ready() { - return Err(NippyJarError::CompressorNotReady) - } + if let Some(compression) = &self.compressor && + !compression.is_ready() + { + return Err(NippyJarError::CompressorNotReady) } Ok(()) diff --git a/crates/storage/nippy-jar/src/writer.rs b/crates/storage/nippy-jar/src/writer.rs index 1069c6e67da..cf899791eed 100644 --- a/crates/storage/nippy-jar/src/writer.rs +++ b/crates/storage/nippy-jar/src/writer.rs @@ -404,10 +404,10 @@ impl NippyJarWriter { // Appends new offsets to disk for offset in self.offsets.drain(..) { - if let Some(last_offset_ondisk) = last_offset_ondisk.take() { - if last_offset_ondisk == offset { - continue - } + if let Some(last_offset_ondisk) = last_offset_ondisk.take() && + last_offset_ondisk == offset + { + continue } self.offsets_file.write_all(&offset.to_le_bytes())?; } diff --git a/crates/storage/provider/src/providers/blockchain_provider.rs b/crates/storage/provider/src/providers/blockchain_provider.rs index 0dc828fdf9b..f6c3d30150e 100644 --- a/crates/storage/provider/src/providers/blockchain_provider.rs +++ b/crates/storage/provider/src/providers/blockchain_provider.rs @@ -514,6 +514,37 @@ impl StateProviderFactory for BlockchainProvider { } } + /// Returns a [`StateProviderBox`] indexed by the given block number or tag. + fn state_by_block_number_or_tag( + &self, + number_or_tag: BlockNumberOrTag, + ) -> ProviderResult { + match number_or_tag { + BlockNumberOrTag::Latest => self.latest(), + BlockNumberOrTag::Finalized => { + // we can only get the finalized state by hash, not by num + let hash = + self.finalized_block_hash()?.ok_or(ProviderError::FinalizedBlockNotFound)?; + self.state_by_block_hash(hash) + } + BlockNumberOrTag::Safe => { + // we can only get the safe state by hash, not by num + let hash = self.safe_block_hash()?.ok_or(ProviderError::SafeBlockNotFound)?; + self.state_by_block_hash(hash) + } + BlockNumberOrTag::Earliest => { + self.history_by_block_number(self.earliest_block_number()?) + } + BlockNumberOrTag::Pending => self.pending(), + BlockNumberOrTag::Number(num) => { + let hash = self + .block_hash(num)? + .ok_or_else(|| ProviderError::HeaderNotFound(num.into()))?; + self.state_by_block_hash(hash) + } + } + } + fn history_by_block_number( &self, block_number: BlockNumber, @@ -563,43 +594,20 @@ impl StateProviderFactory for BlockchainProvider { } fn pending_state_by_hash(&self, block_hash: B256) -> ProviderResult> { - if let Some(pending) = self.canonical_in_memory_state.pending_state() { - if pending.hash() == block_hash { - return Ok(Some(Box::new(self.block_state_provider(&pending)?))); - } + if let Some(pending) = self.canonical_in_memory_state.pending_state() && + pending.hash() == block_hash + { + return Ok(Some(Box::new(self.block_state_provider(&pending)?))); } Ok(None) } - /// Returns a [`StateProviderBox`] indexed by the given block number or tag. - fn state_by_block_number_or_tag( - &self, - number_or_tag: BlockNumberOrTag, - ) -> ProviderResult { - match number_or_tag { - BlockNumberOrTag::Latest => self.latest(), - BlockNumberOrTag::Finalized => { - // we can only get the finalized state by hash, not by num - let hash = - self.finalized_block_hash()?.ok_or(ProviderError::FinalizedBlockNotFound)?; - self.state_by_block_hash(hash) - } - BlockNumberOrTag::Safe => { - // we can only get the safe state by hash, not by num - let hash = self.safe_block_hash()?.ok_or(ProviderError::SafeBlockNotFound)?; - self.state_by_block_hash(hash) - } - BlockNumberOrTag::Earliest => { - self.history_by_block_number(self.earliest_block_number()?) - } - BlockNumberOrTag::Pending => self.pending(), - BlockNumberOrTag::Number(num) => { - let hash = self - .block_hash(num)? - .ok_or_else(|| ProviderError::HeaderNotFound(num.into()))?; - self.state_by_block_hash(hash) - } + fn maybe_pending(&self) -> ProviderResult> { + if let Some(pending) = self.canonical_in_memory_state.pending_state() { + return Ok(Some(Box::new(self.block_state_provider(&pending)?))) } + + Ok(None) } } @@ -957,26 +965,26 @@ mod tests { ) { let hook_provider = provider.clone(); provider.database.db_ref().set_post_transaction_hook(Box::new(move || { - if let Some(state) = hook_provider.canonical_in_memory_state.head_state() { - if state.anchor().number + 1 == block_number { - let mut lowest_memory_block = - state.parent_state_chain().last().expect("qed").block(); - let num_hash = lowest_memory_block.recovered_block().num_hash(); - - let mut execution_output = (*lowest_memory_block.execution_output).clone(); - execution_output.first_block = lowest_memory_block.recovered_block().number; - lowest_memory_block.execution_output = Arc::new(execution_output); - - // Push to disk - let provider_rw = hook_provider.database_provider_rw().unwrap(); - UnifiedStorageWriter::from(&provider_rw, &hook_provider.static_file_provider()) - .save_blocks(vec![lowest_memory_block]) - .unwrap(); - UnifiedStorageWriter::commit(provider_rw).unwrap(); - - // Remove from memory - hook_provider.canonical_in_memory_state.remove_persisted_blocks(num_hash); - } + if let Some(state) = hook_provider.canonical_in_memory_state.head_state() && + state.anchor().number + 1 == block_number + { + let mut lowest_memory_block = + state.parent_state_chain().last().expect("qed").block(); + let num_hash = lowest_memory_block.recovered_block().num_hash(); + + let mut execution_output = (*lowest_memory_block.execution_output).clone(); + execution_output.first_block = lowest_memory_block.recovered_block().number; + lowest_memory_block.execution_output = Arc::new(execution_output); + + // Push to disk + let provider_rw = hook_provider.database_provider_rw().unwrap(); + UnifiedStorageWriter::from(&provider_rw, &hook_provider.static_file_provider()) + .save_blocks(vec![lowest_memory_block]) + .unwrap(); + UnifiedStorageWriter::commit(provider_rw).unwrap(); + + // Remove from memory + hook_provider.canonical_in_memory_state.remove_persisted_blocks(num_hash); } })); } @@ -1694,7 +1702,6 @@ mod tests { ..Default::default() }, Default::default(), - Default::default(), )?; provider_rw.commit()?; @@ -2025,7 +2032,7 @@ mod tests { "partial mem data" ); - // Test range in in-memory to unbounded end + // Test range in memory to unbounded end assert_eq!(provider.$method(in_mem_range.start() + 1..)?, &in_memory_data[1..], "unbounded mem data"); // Test last element in-memory diff --git a/crates/storage/provider/src/providers/consistent.rs b/crates/storage/provider/src/providers/consistent.rs index f617c3f6fa4..4a4a2df2779 100644 --- a/crates/storage/provider/src/providers/consistent.rs +++ b/crates/storage/provider/src/providers/consistent.rs @@ -536,10 +536,10 @@ impl ConsistentProvider { // If the transaction number is less than the first in-memory transaction number, make a // database lookup - if let HashOrNumber::Number(id) = id { - if id < in_memory_tx_num { - return fetch_from_db(provider) - } + if let HashOrNumber::Number(id) = id && + id < in_memory_tx_num + { + return fetch_from_db(provider) } // Iterate from the lowest block to the highest @@ -816,14 +816,14 @@ impl BlockReader for ConsistentProvider { hash: B256, source: BlockSource, ) -> ProviderResult> { - if matches!(source, BlockSource::Canonical | BlockSource::Any) { - if let Some(block) = self.get_in_memory_or_storage_by_block( + if matches!(source, BlockSource::Canonical | BlockSource::Any) && + let Some(block) = self.get_in_memory_or_storage_by_block( hash.into(), |db_provider| db_provider.find_block_by_hash(hash, BlockSource::Canonical), |block_state| Ok(Some(block_state.block_ref().recovered_block().clone_block())), - )? { - return Ok(Some(block)) - } + )? + { + return Ok(Some(block)) } if matches!(source, BlockSource::Pending | BlockSource::Any) { @@ -1133,14 +1133,14 @@ impl ReceiptProviderIdExt for ConsistentProvider { match block { BlockId::Hash(rpc_block_hash) => { let mut receipts = self.receipts_by_block(rpc_block_hash.block_hash.into())?; - if receipts.is_none() && !rpc_block_hash.require_canonical.unwrap_or(false) { - if let Some(state) = self + if receipts.is_none() && + !rpc_block_hash.require_canonical.unwrap_or(false) && + let Some(state) = self .head_block .as_ref() .and_then(|b| b.block_on_chain(rpc_block_hash.block_hash.into())) - { - receipts = Some(state.executed_block_receipts()); - } + { + receipts = Some(state.executed_block_receipts()); } Ok(receipts) } @@ -1769,7 +1769,6 @@ mod tests { ..Default::default() }, Default::default(), - Default::default(), )?; provider_rw.commit()?; diff --git a/crates/storage/provider/src/providers/consistent_view.rs b/crates/storage/provider/src/providers/consistent_view.rs index 2afaacfa5d9..8edf062d269 100644 --- a/crates/storage/provider/src/providers/consistent_view.rs +++ b/crates/storage/provider/src/providers/consistent_view.rs @@ -67,10 +67,10 @@ where // // To ensure this doesn't happen, we just have to make sure that we fetch from the same // data source that we used during initialization. In this case, that is static files - if let Some((hash, number)) = self.tip { - if provider_ro.sealed_header(number)?.is_none_or(|header| header.hash() != hash) { - return Err(ConsistentViewError::Reorged { block: hash }.into()) - } + if let Some((hash, number)) = self.tip && + provider_ro.sealed_header(number)?.is_none_or(|header| header.hash() != hash) + { + return Err(ConsistentViewError::Reorged { block: hash }.into()) } Ok(provider_ro) diff --git a/crates/storage/provider/src/providers/database/chain.rs b/crates/storage/provider/src/providers/database/chain.rs index 9d0e0158a58..2da32d9a05f 100644 --- a/crates/storage/provider/src/providers/database/chain.rs +++ b/crates/storage/provider/src/providers/database/chain.rs @@ -3,7 +3,7 @@ use reth_db_api::transaction::{DbTx, DbTxMut}; use reth_node_types::FullNodePrimitives; use reth_primitives_traits::{FullBlockHeader, FullSignedTx}; -use reth_storage_api::{ChainStorageReader, ChainStorageWriter, EthStorage}; +use reth_storage_api::{ChainStorageReader, ChainStorageWriter, EmptyBodyStorage, EthStorage}; /// Trait that provides access to implementations of [`ChainStorage`] pub trait ChainStorage: Send + Sync { @@ -47,3 +47,31 @@ where self } } + +impl ChainStorage for EmptyBodyStorage +where + T: FullSignedTx, + H: FullBlockHeader, + N: FullNodePrimitives< + Block = alloy_consensus::Block, + BlockHeader = H, + BlockBody = alloy_consensus::BlockBody, + SignedTx = T, + >, +{ + fn reader(&self) -> impl ChainStorageReader, N> + where + TX: DbTx + 'static, + Types: NodeTypesForProvider, + { + self + } + + fn writer(&self) -> impl ChainStorageWriter, N> + where + TX: DbTxMut + DbTx + 'static, + Types: NodeTypesForProvider, + { + self + } +} diff --git a/crates/storage/provider/src/providers/database/metrics.rs b/crates/storage/provider/src/providers/database/metrics.rs index 4ee8f1ce5b1..1d14ecc4bf0 100644 --- a/crates/storage/provider/src/providers/database/metrics.rs +++ b/crates/storage/provider/src/providers/database/metrics.rs @@ -36,9 +36,6 @@ impl DurationsRecorder { #[derive(Debug, Copy, Clone)] pub(crate) enum Action { - InsertStorageHashing, - InsertAccountHashing, - InsertMerkleTree, InsertBlock, InsertState, InsertHashes, @@ -58,12 +55,6 @@ pub(crate) enum Action { #[derive(Metrics)] #[metrics(scope = "storage.providers.database")] struct DatabaseProviderMetrics { - /// Duration of insert storage hashing - insert_storage_hashing: Histogram, - /// Duration of insert account hashing - insert_account_hashing: Histogram, - /// Duration of insert merkle tree - insert_merkle_tree: Histogram, /// Duration of insert block insert_block: Histogram, /// Duration of insert state @@ -96,9 +87,6 @@ impl DatabaseProviderMetrics { /// Records the duration for the given action. pub(crate) fn record_duration(&self, action: Action, duration: Duration) { match action { - Action::InsertStorageHashing => self.insert_storage_hashing.record(duration), - Action::InsertAccountHashing => self.insert_account_hashing.record(duration), - Action::InsertMerkleTree => self.insert_merkle_tree.record(duration), Action::InsertBlock => self.insert_block.record(duration), Action::InsertState => self.insert_state.record(duration), Action::InsertHashes => self.insert_hashes.record(duration), diff --git a/crates/storage/provider/src/providers/database/provider.rs b/crates/storage/provider/src/providers/database/provider.rs index 5028ffcc88b..6a16dbcbf5f 100644 --- a/crates/storage/provider/src/providers/database/provider.rs +++ b/crates/storage/provider/src/providers/database/provider.rs @@ -19,7 +19,7 @@ use crate::{ TransactionsProviderExt, TrieWriter, }; use alloy_consensus::{ - transaction::{SignerRecoverable, TransactionMeta}, + transaction::{SignerRecoverable, TransactionMeta, TxHashRef}, BlockHeader, Header, TxReceipt, }; use alloy_eips::{eip2718::Encodable2718, BlockHashOrNumber}; @@ -47,7 +47,7 @@ use reth_execution_types::{Chain, ExecutionOutcome}; use reth_node_types::{BlockTy, BodyTy, HeaderTy, NodeTypes, ReceiptTy, TxTy}; use reth_primitives_traits::{ Account, Block as _, BlockBody as _, Bytecode, GotExpected, NodePrimitives, RecoveredBlock, - SealedHeader, SignedTransaction, StorageEntry, + SealedHeader, StorageEntry, }; use reth_prune_types::{ PruneCheckpoint, PruneMode, PruneModes, PruneSegment, MINIMUM_PRUNING_DISTANCE, @@ -1020,12 +1020,12 @@ impl HeaderProvider for DatabasePro } fn header_td_by_number(&self, number: BlockNumber) -> ProviderResult> { - if self.chain_spec.is_paris_active_at_block(number) { - if let Some(td) = self.chain_spec.final_paris_total_difficulty() { - // if this block is higher than the final paris(merge) block, return the final paris - // difficulty - return Ok(Some(td)) - } + if self.chain_spec.is_paris_active_at_block(number) && + let Some(td) = self.chain_spec.final_paris_total_difficulty() + { + // if this block is higher than the final paris(merge) block, return the final paris + // difficulty + return Ok(Some(td)) } self.static_file_provider.get_with_static_file_or_database( @@ -1180,25 +1180,25 @@ impl BlockReader for DatabaseProvid /// If the header is found, but the transactions either do not exist, or are not indexed, this /// will return None. fn block(&self, id: BlockHashOrNumber) -> ProviderResult> { - if let Some(number) = self.convert_hash_or_number(id)? { - if let Some(header) = self.header_by_number(number)? { - // If the body indices are not found, this means that the transactions either do not - // exist in the database yet, or they do exit but are not indexed. - // If they exist but are not indexed, we don't have enough - // information to return the block anyways, so we return `None`. - let Some(transactions) = self.transactions_by_block(number.into())? else { - return Ok(None) - }; + if let Some(number) = self.convert_hash_or_number(id)? && + let Some(header) = self.header_by_number(number)? + { + // If the body indices are not found, this means that the transactions either do not + // exist in the database yet, or they do exit but are not indexed. + // If they exist but are not indexed, we don't have enough + // information to return the block anyways, so we return `None`. + let Some(transactions) = self.transactions_by_block(number.into())? else { + return Ok(None) + }; - let body = self - .storage - .reader() - .read_block_bodies(self, vec![(&header, transactions)])? - .pop() - .ok_or(ProviderError::InvalidStorageOutput)?; + let body = self + .storage + .reader() + .read_block_bodies(self, vec![(&header, transactions)])? + .pop() + .ok_or(ProviderError::InvalidStorageOutput)?; - return Ok(Some(Self::Block::new(header, body))) - } + return Ok(Some(Self::Block::new(header, body))) } Ok(None) @@ -1416,34 +1416,31 @@ impl TransactionsProvider for Datab tx_hash: TxHash, ) -> ProviderResult> { let mut transaction_cursor = self.tx.cursor_read::()?; - if let Some(transaction_id) = self.transaction_id(tx_hash)? { - if let Some(transaction) = self.transaction_by_id_unhashed(transaction_id)? { - if let Some(block_number) = - transaction_cursor.seek(transaction_id).map(|b| b.map(|(_, bn)| bn))? - { - if let Some(sealed_header) = self.sealed_header(block_number)? { - let (header, block_hash) = sealed_header.split(); - if let Some(block_body) = self.block_body_indices(block_number)? { - // the index of the tx in the block is the offset: - // len([start..tx_id]) - // NOTE: `transaction_id` is always `>=` the block's first - // index - let index = transaction_id - block_body.first_tx_num(); - - let meta = TransactionMeta { - tx_hash, - index, - block_hash, - block_number, - base_fee: header.base_fee_per_gas(), - excess_blob_gas: header.excess_blob_gas(), - timestamp: header.timestamp(), - }; - - return Ok(Some((transaction, meta))) - } - } - } + if let Some(transaction_id) = self.transaction_id(tx_hash)? && + let Some(transaction) = self.transaction_by_id_unhashed(transaction_id)? && + let Some(block_number) = + transaction_cursor.seek(transaction_id).map(|b| b.map(|(_, bn)| bn))? && + let Some(sealed_header) = self.sealed_header(block_number)? + { + let (header, block_hash) = sealed_header.split(); + if let Some(block_body) = self.block_body_indices(block_number)? { + // the index of the tx in the block is the offset: + // len([start..tx_id]) + // NOTE: `transaction_id` is always `>=` the block's first + // index + let index = transaction_id - block_body.first_tx_num(); + + let meta = TransactionMeta { + tx_hash, + index, + block_hash, + block_number, + base_fee: header.base_fee_per_gas(), + excess_blob_gas: header.excess_blob_gas(), + timestamp: header.timestamp(), + }; + + return Ok(Some((transaction, meta))) } } @@ -1461,14 +1458,14 @@ impl TransactionsProvider for Datab ) -> ProviderResult>> { let mut tx_cursor = self.tx.cursor_read::>()?; - if let Some(block_number) = self.convert_hash_or_number(id)? { - if let Some(body) = self.block_body_indices(block_number)? { - let tx_range = body.tx_num_range(); - return if tx_range.is_empty() { - Ok(Some(Vec::new())) - } else { - Ok(Some(self.transactions_by_tx_range_with_cursor(tx_range, &mut tx_cursor)?)) - } + if let Some(block_number) = self.convert_hash_or_number(id)? && + let Some(body) = self.block_body_indices(block_number)? + { + let tx_range = body.tx_num_range(); + return if tx_range.is_empty() { + Ok(Some(Vec::new())) + } else { + Ok(Some(self.transactions_by_tx_range_with_cursor(tx_range, &mut tx_cursor)?)) } } Ok(None) @@ -1543,14 +1540,14 @@ impl ReceiptProvider for DatabasePr &self, block: BlockHashOrNumber, ) -> ProviderResult>> { - if let Some(number) = self.convert_hash_or_number(block)? { - if let Some(body) = self.block_body_indices(number)? { - let tx_range = body.tx_num_range(); - return if tx_range.is_empty() { - Ok(Some(Vec::new())) - } else { - self.receipts_by_tx_range(tx_range).map(Some) - } + if let Some(number) = self.convert_hash_or_number(block)? && + let Some(body) = self.block_body_indices(number)? + { + let tx_range = body.tx_num_range(); + return if tx_range.is_empty() { + Ok(Some(Vec::new())) + } else { + self.receipts_by_tx_range(tx_range).map(Some) } } Ok(None) @@ -1642,7 +1639,11 @@ impl BlockBodyIndicesProvider impl StageCheckpointReader for DatabaseProvider { fn get_stage_checkpoint(&self, id: StageId) -> ProviderResult> { - Ok(self.tx.get::(id.to_string())?) + Ok(if let Some(encoded) = id.get_pre_encoded() { + self.tx.get_by_encoded_key::(encoded)? + } else { + self.tx.get::(id.to_string())? + }) } /// Get stage checkpoint progress. @@ -1996,10 +1997,10 @@ impl StateWriter for entry in storage { tracing::trace!(?address, ?entry.key, "Updating plain state storage"); - if let Some(db_entry) = storages_cursor.seek_by_key_subkey(address, entry.key)? { - if db_entry.key == entry.key { - storages_cursor.delete_current()?; - } + if let Some(db_entry) = storages_cursor.seek_by_key_subkey(address, entry.key)? && + db_entry.key == entry.key + { + storages_cursor.delete_current()?; } if !entry.value.is_zero() { @@ -2034,11 +2035,10 @@ impl StateWriter for (hashed_slot, value) in storage.storage_slots_sorted() { let entry = StorageEntry { key: hashed_slot, value }; if let Some(db_entry) = - hashed_storage_cursor.seek_by_key_subkey(*hashed_address, entry.key)? + hashed_storage_cursor.seek_by_key_subkey(*hashed_address, entry.key)? && + db_entry.key == entry.key { - if db_entry.key == entry.key { - hashed_storage_cursor.delete_current()?; - } + hashed_storage_cursor.delete_current()?; } if !entry.value.is_zero() { @@ -2332,7 +2332,7 @@ impl TrieWriter for DatabaseProvider } } - num_entries += self.write_storage_trie_updates(trie_updates.storage_tries_ref())?; + num_entries += self.write_storage_trie_updates(trie_updates.storage_tries_ref().iter())?; Ok(num_entries) } @@ -2341,12 +2341,12 @@ impl TrieWriter for DatabaseProvider impl StorageTrieWriter for DatabaseProvider { /// Writes storage trie updates from the given storage trie map. First sorts the storage trie /// updates by the hashed address, writing in sorted order. - fn write_storage_trie_updates( + fn write_storage_trie_updates<'a>( &self, - storage_tries: &B256Map, + storage_tries: impl Iterator, ) -> ProviderResult { let mut num_entries = 0; - let mut storage_tries = Vec::from_iter(storage_tries); + let mut storage_tries = storage_tries.collect::>(); storage_tries.sort_unstable_by(|a, b| a.0.cmp(b.0)); let mut cursor = self.tx_ref().cursor_dup_write::()?; for (hashed_address, storage_trie_updates) in storage_tries { @@ -2359,20 +2359,6 @@ impl StorageTrieWriter for DatabaseP Ok(num_entries) } - - fn write_individual_storage_trie_updates( - &self, - hashed_address: B256, - updates: &StorageTrieUpdates, - ) -> ProviderResult { - if updates.is_empty() { - return Ok(0) - } - - let cursor = self.tx_ref().cursor_dup_write::()?; - let mut trie_db_cursor = DatabaseStorageTrieCursor::new(cursor, hashed_address); - Ok(trie_db_cursor.write_storage_trie_updates(updates)?) - } } impl HashingWriter for DatabaseProvider { @@ -2522,82 +2508,6 @@ impl HashingWriter for DatabaseProvi Ok(hashed_storage_keys) } - - fn insert_hashes( - &self, - range: RangeInclusive, - end_block_hash: B256, - expected_state_root: B256, - ) -> ProviderResult<()> { - // Initialize prefix sets. - let mut account_prefix_set = PrefixSetMut::default(); - let mut storage_prefix_sets: HashMap = HashMap::default(); - let mut destroyed_accounts = HashSet::default(); - - let mut durations_recorder = metrics::DurationsRecorder::default(); - - // storage hashing stage - { - let lists = self.changed_storages_with_range(range.clone())?; - let storages = self.plain_state_storages(lists)?; - let storage_entries = self.insert_storage_for_hashing(storages)?; - for (hashed_address, hashed_slots) in storage_entries { - account_prefix_set.insert(Nibbles::unpack(hashed_address)); - for slot in hashed_slots { - storage_prefix_sets - .entry(hashed_address) - .or_default() - .insert(Nibbles::unpack(slot)); - } - } - } - durations_recorder.record_relative(metrics::Action::InsertStorageHashing); - - // account hashing stage - { - let lists = self.changed_accounts_with_range(range.clone())?; - let accounts = self.basic_accounts(lists)?; - let hashed_addresses = self.insert_account_for_hashing(accounts)?; - for (hashed_address, account) in hashed_addresses { - account_prefix_set.insert(Nibbles::unpack(hashed_address)); - if account.is_none() { - destroyed_accounts.insert(hashed_address); - } - } - } - durations_recorder.record_relative(metrics::Action::InsertAccountHashing); - - // merkle tree - { - // This is the same as `StateRoot::incremental_root_with_updates`, only the prefix sets - // are pre-loaded. - let prefix_sets = TriePrefixSets { - account_prefix_set: account_prefix_set.freeze(), - storage_prefix_sets: storage_prefix_sets - .into_iter() - .map(|(k, v)| (k, v.freeze())) - .collect(), - destroyed_accounts, - }; - let (state_root, trie_updates) = StateRoot::from_tx(&self.tx) - .with_prefix_sets(prefix_sets) - .root_with_updates() - .map_err(reth_db_api::DatabaseError::from)?; - if state_root != expected_state_root { - return Err(ProviderError::StateRootMismatch(Box::new(RootMismatch { - root: GotExpected { got: state_root, expected: expected_state_root }, - block_number: *range.end(), - block_hash: end_block_hash, - }))) - } - self.write_trie_updates(&trie_updates)?; - } - durations_recorder.record_relative(metrics::Action::InsertMerkleTree); - - debug!(target: "providers::db", ?range, actions = ?durations_recorder.actions, "Inserted hashes"); - - Ok(()) - } } impl HistoryWriter for DatabaseProvider { @@ -3044,7 +2954,6 @@ impl BlockWrite blocks: Vec>, execution_outcome: &ExecutionOutcome, hashed_state: HashedPostStateSorted, - trie_updates: TrieUpdates, ) -> ProviderResult<()> { if blocks.is_empty() { debug!(target: "providers::db", "Attempted to append empty block range"); @@ -3072,7 +2981,6 @@ impl BlockWrite // insert hashes and intermediate merkle nodes self.write_hashed_state(&hashed_state)?; - self.write_trie_updates(&trie_updates)?; durations_recorder.record_relative(metrics::Action::InsertHashes); self.update_history_indices(first_number..=last_block_number)?; diff --git a/crates/storage/provider/src/providers/state/latest.rs b/crates/storage/provider/src/providers/state/latest.rs index 5c838b0da3e..de8eef2cc9c 100644 --- a/crates/storage/provider/src/providers/state/latest.rs +++ b/crates/storage/provider/src/providers/state/latest.rs @@ -158,10 +158,10 @@ impl StateProvider storage_key: StorageKey, ) -> ProviderResult> { let mut cursor = self.tx().cursor_dup_read::()?; - if let Some(entry) = cursor.seek_by_key_subkey(account, storage_key)? { - if entry.key == storage_key { - return Ok(Some(entry.value)) - } + if let Some(entry) = cursor.seek_by_key_subkey(account, storage_key)? && + entry.key == storage_key + { + return Ok(Some(entry.value)) } Ok(None) } diff --git a/crates/storage/provider/src/providers/static_file/jar.rs b/crates/storage/provider/src/providers/static_file/jar.rs index 74ec074dba3..ab19fbf732c 100644 --- a/crates/storage/provider/src/providers/static_file/jar.rs +++ b/crates/storage/provider/src/providers/static_file/jar.rs @@ -314,10 +314,10 @@ impl ProviderResult> { - if let Some(tx_static_file) = &self.auxiliary_jar { - if let Some(num) = tx_static_file.transaction_id(hash)? { - return self.receipt(num) - } + if let Some(tx_static_file) = &self.auxiliary_jar && + let Some(num) = tx_static_file.transaction_id(hash)? + { + return self.receipt(num) } Ok(None) } diff --git a/crates/storage/provider/src/providers/static_file/manager.rs b/crates/storage/provider/src/providers/static_file/manager.rs index 4c597b7a9af..fc2c94a1ba1 100644 --- a/crates/storage/provider/src/providers/static_file/manager.rs +++ b/crates/storage/provider/src/providers/static_file/manager.rs @@ -18,7 +18,7 @@ use alloy_primitives::{ use dashmap::DashMap; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; use parking_lot::RwLock; -use reth_chainspec::{ChainInfo, ChainSpecProvider, EthChainSpec}; +use reth_chainspec::{ChainInfo, ChainSpecProvider, EthChainSpec, NamedChain}; use reth_db::{ lockfile::StorageLock, static_file::{ @@ -781,6 +781,16 @@ impl StaticFileProvider { continue } + if segment.is_receipts() && + (NamedChain::Gnosis == provider.chain_spec().chain_id() || + NamedChain::Chiado == provider.chain_spec().chain_id()) + { + // Gnosis and Chiado's historical import is broken and does not work with this + // check. They are importing receipts along with importing + // headers/bodies. + continue; + } + let initial_highest_block = self.get_highest_static_file_block(segment); // File consistency is broken if: @@ -940,12 +950,11 @@ impl StaticFileProvider { } } - if let Some((db_last_entry, _)) = db_cursor.last()? { - if highest_static_file_entry + if let Some((db_last_entry, _)) = db_cursor.last()? && + highest_static_file_entry .is_none_or(|highest_entry| db_last_entry > highest_entry) - { - return Ok(None) - } + { + return Ok(None) } } @@ -1271,16 +1280,15 @@ impl StaticFileProvider { self.get_highest_static_file_block(segment) } else { self.get_highest_static_file_tx(segment) - } { - if block_or_tx_range.start <= static_file_upper_bound { - let end = block_or_tx_range.end.min(static_file_upper_bound + 1); - data.extend(fetch_from_static_file( - self, - block_or_tx_range.start..end, - &mut predicate, - )?); - block_or_tx_range.start = end; - } + } && block_or_tx_range.start <= static_file_upper_bound + { + let end = block_or_tx_range.end.min(static_file_upper_bound + 1); + data.extend(fetch_from_static_file( + self, + block_or_tx_range.start..end, + &mut predicate, + )?); + block_or_tx_range.start = end; } if block_or_tx_range.end > block_or_tx_range.start { diff --git a/crates/storage/provider/src/providers/static_file/metrics.rs b/crates/storage/provider/src/providers/static_file/metrics.rs index ad738334837..8d7269e3d7e 100644 --- a/crates/storage/provider/src/providers/static_file/metrics.rs +++ b/crates/storage/provider/src/providers/static_file/metrics.rs @@ -66,18 +66,15 @@ impl StaticFileProviderMetrics { operation: StaticFileProviderOperation, duration: Option, ) { - self.segment_operations + let segment_operation = self + .segment_operations .get(&(segment, operation)) - .expect("segment operation metrics should exist") - .calls_total - .increment(1); + .expect("segment operation metrics should exist"); + + segment_operation.calls_total.increment(1); if let Some(duration) = duration { - self.segment_operations - .get(&(segment, operation)) - .expect("segment operation metrics should exist") - .write_duration_seconds - .record(duration.as_secs_f64()); + segment_operation.write_duration_seconds.record(duration.as_secs_f64()); } } diff --git a/crates/storage/provider/src/providers/static_file/mod.rs b/crates/storage/provider/src/providers/static_file/mod.rs index 2bf9cf66f9c..97a8ea95433 100644 --- a/crates/storage/provider/src/providers/static_file/mod.rs +++ b/crates/storage/provider/src/providers/static_file/mod.rs @@ -74,7 +74,7 @@ mod tests { fn assert_eyre(got: T, expected: T, msg: &str) -> eyre::Result<()> { if got != expected { - eyre::bail!("{msg} | got: {got:?} expected: {expected:?})"); + eyre::bail!("{msg} | got: {got:?} expected: {expected:?}"); } Ok(()) } diff --git a/crates/storage/provider/src/test_utils/mock.rs b/crates/storage/provider/src/test_utils/mock.rs index 07bc8026616..af7a3c069af 100644 --- a/crates/storage/provider/src/test_utils/mock.rs +++ b/crates/storage/provider/src/test_utils/mock.rs @@ -5,7 +5,11 @@ use crate::{ StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, TransactionVariant, TransactionsProvider, }; -use alloy_consensus::{constants::EMPTY_ROOT_HASH, transaction::TransactionMeta, BlockHeader}; +use alloy_consensus::{ + constants::EMPTY_ROOT_HASH, + transaction::{TransactionMeta, TxHashRef}, + BlockHeader, +}; use alloy_eips::{BlockHashOrNumber, BlockId, BlockNumberOrTag}; use alloy_primitives::{ keccak256, map::HashMap, Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, @@ -22,7 +26,7 @@ use reth_ethereum_primitives::EthPrimitives; use reth_execution_types::ExecutionOutcome; use reth_primitives_traits::{ Account, Block, BlockBody, Bytecode, GotExpected, NodePrimitives, RecoveredBlock, SealedHeader, - SignedTransaction, SignerRecoverable, + SignerRecoverable, }; use reth_prune_types::PruneModes; use reth_stages_types::{StageCheckpoint, StageId}; @@ -944,6 +948,10 @@ impl StatePr fn pending_state_by_hash(&self, _block_hash: B256) -> ProviderResult> { Ok(Some(Box::new(self.clone()))) } + + fn maybe_pending(&self) -> ProviderResult> { + Ok(Some(Box::new(self.clone()))) + } } impl BlockBodyIndicesProvider diff --git a/crates/storage/provider/src/writer/mod.rs b/crates/storage/provider/src/writer/mod.rs index bca2a4cdb4c..02f5bdabd76 100644 --- a/crates/storage/provider/src/writer/mod.rs +++ b/crates/storage/provider/src/writer/mod.rs @@ -1354,7 +1354,7 @@ mod tests { assert_eq!(storage_root, storage_root_prehashed(init_storage.storage)); assert!(!storage_updates.is_empty()); provider_rw - .write_individual_storage_trie_updates(hashed_address, &storage_updates) + .write_storage_trie_updates(core::iter::once((&hashed_address, &storage_updates))) .unwrap(); // destroy the storage and re-create with new slots diff --git a/crates/storage/rpc-provider/src/lib.rs b/crates/storage/rpc-provider/src/lib.rs index 1e3c288e8a4..86908932096 100644 --- a/crates/storage/rpc-provider/src/lib.rs +++ b/crates/storage/rpc-provider/src/lib.rs @@ -803,6 +803,10 @@ where // RPC provider doesn't support pending state by hash Err(ProviderError::UnsupportedProvider) } + + fn maybe_pending(&self) -> Result, ProviderError> { + Ok(None) + } } impl DatabaseProviderFactory for RpcBlockchainProvider @@ -812,8 +816,8 @@ where Node: NodeTypes, { type DB = DatabaseMock; - type ProviderRW = RpcBlockchainStateProvider; type Provider = RpcBlockchainStateProvider; + type ProviderRW = RpcBlockchainStateProvider; fn database_provider_ro(&self) -> Result { // RPC provider returns a new state provider @@ -1363,14 +1367,14 @@ where TxMock::default() } - fn prune_modes_ref(&self) -> &reth_prune_types::PruneModes { - unimplemented!("prune modes not supported for RPC provider") - } - fn disable_long_read_transaction_safety(self) -> Self { // No-op for RPC provider self } + + fn prune_modes_ref(&self) -> &reth_prune_types::PruneModes { + unimplemented!("prune modes not supported for RPC provider") + } } impl BlockNumReader for RpcBlockchainStateProvider @@ -1817,6 +1821,10 @@ where // RPC provider doesn't support pending state by hash Err(ProviderError::UnsupportedProvider) } + + fn maybe_pending(&self) -> ProviderResult> { + Ok(None) + } } impl ChainSpecProvider for RpcBlockchainStateProvider diff --git a/crates/storage/storage-api/Cargo.toml b/crates/storage/storage-api/Cargo.toml index e8601e9667d..a62193a5dd8 100644 --- a/crates/storage/storage-api/Cargo.toml +++ b/crates/storage/storage-api/Cargo.toml @@ -65,7 +65,6 @@ serde = [ "reth-stages-types/serde", "reth-trie-common/serde", "revm-database/serde", - "reth-ethereum-primitives/serde", "alloy-eips/serde", "alloy-primitives/serde", "alloy-consensus/serde", @@ -73,7 +72,6 @@ serde = [ ] serde-bincode-compat = [ - "reth-ethereum-primitives/serde-bincode-compat", "reth-execution-types/serde-bincode-compat", "reth-primitives-traits/serde-bincode-compat", "reth-trie-common/serde-bincode-compat", diff --git a/crates/storage/storage-api/src/block_writer.rs b/crates/storage/storage-api/src/block_writer.rs index 552491b922a..476b0bd8dbc 100644 --- a/crates/storage/storage-api/src/block_writer.rs +++ b/crates/storage/storage-api/src/block_writer.rs @@ -5,7 +5,7 @@ use reth_db_models::StoredBlockBodyIndices; use reth_execution_types::{Chain, ExecutionOutcome}; use reth_primitives_traits::{Block, NodePrimitives, RecoveredBlock}; use reth_storage_errors::provider::ProviderResult; -use reth_trie_common::{updates::TrieUpdates, HashedPostStateSorted}; +use reth_trie_common::HashedPostStateSorted; /// `BlockExecution` Writer pub trait BlockExecutionWriter: @@ -107,7 +107,7 @@ pub trait BlockWriter: Send + Sync { /// updates the post-state. /// /// Inserts the blocks into the database and updates the state with - /// provided `BundleState`. + /// provided `BundleState`. The database's trie state is _not_ updated. /// /// # Parameters /// @@ -122,6 +122,5 @@ pub trait BlockWriter: Send + Sync { blocks: Vec>, execution_outcome: &ExecutionOutcome, hashed_state: HashedPostStateSorted, - trie_updates: TrieUpdates, ) -> ProviderResult<()>; } diff --git a/crates/storage/storage-api/src/chain.rs b/crates/storage/storage-api/src/chain.rs index 5c66d055e18..e78a59afefc 100644 --- a/crates/storage/storage-api/src/chain.rs +++ b/crates/storage/storage-api/src/chain.rs @@ -3,7 +3,7 @@ use alloc::vec::Vec; use alloy_consensus::Header; use alloy_primitives::BlockNumber; use core::marker::PhantomData; -use reth_chainspec::{ChainSpecProvider, EthereumHardforks}; +use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; use reth_db_api::{ cursor::{DbCursorRO, DbCursorRW}, models::StoredBlockOmmers, @@ -120,11 +120,10 @@ where } // Write withdrawals if any - if let Some(withdrawals) = body.withdrawals { - if !withdrawals.is_empty() { - withdrawals_cursor - .append(block_number, &StoredBlockWithdrawals { withdrawals })?; - } + if let Some(withdrawals) = body.withdrawals && + !withdrawals.is_empty() + { + withdrawals_cursor.append(block_number, &StoredBlockWithdrawals { withdrawals })?; } } @@ -138,7 +137,7 @@ where _remove_from: StorageLocation, ) -> ProviderResult<()> { provider.tx_ref().unwind_table_by_num::(block)?; - provider.tx_ref().unwind_table_by_num::(block)?; + provider.tx_ref().unwind_table_by_num::>(block)?; Ok(()) } @@ -193,3 +192,75 @@ where Ok(bodies) } } + +/// A noop storage for chains that don’t have custom body storage. +/// +/// This will never read nor write additional body content such as withdrawals or ommers. +/// But will respect the optionality of withdrawals if activated and fill them if the corresponding +/// hardfork is activated. +#[derive(Debug, Clone, Copy)] +pub struct EmptyBodyStorage(PhantomData<(T, H)>); + +impl Default for EmptyBodyStorage { + fn default() -> Self { + Self(PhantomData) + } +} + +impl BlockBodyWriter> + for EmptyBodyStorage +where + Provider: DBProvider, + T: SignedTransaction, + H: FullBlockHeader, +{ + fn write_block_bodies( + &self, + _provider: &Provider, + _bodies: Vec<(u64, Option>)>, + _write_to: StorageLocation, + ) -> ProviderResult<()> { + // noop + Ok(()) + } + + fn remove_block_bodies_above( + &self, + _provider: &Provider, + _block: BlockNumber, + _remove_from: StorageLocation, + ) -> ProviderResult<()> { + // noop + Ok(()) + } +} + +impl BlockBodyReader for EmptyBodyStorage +where + Provider: ChainSpecProvider + DBProvider, + T: SignedTransaction, + H: FullBlockHeader, +{ + type Block = alloy_consensus::Block; + + fn read_block_bodies( + &self, + provider: &Provider, + inputs: Vec>, + ) -> ProviderResult::Body>> { + let chain_spec = provider.chain_spec(); + + Ok(inputs + .into_iter() + .map(|(header, transactions)| { + alloy_consensus::BlockBody { + transactions, + ommers: vec![], // Empty storage never has ommers + withdrawals: chain_spec + .is_shanghai_active_at_timestamp(header.timestamp()) + .then(Default::default), + } + }) + .collect()) + } +} diff --git a/crates/storage/storage-api/src/hashing.rs b/crates/storage/storage-api/src/hashing.rs index 38964a244cd..dfbb00ab8f9 100644 --- a/crates/storage/storage-api/src/hashing.rs +++ b/crates/storage/storage-api/src/hashing.rs @@ -1,7 +1,7 @@ use alloc::collections::{BTreeMap, BTreeSet}; use alloy_primitives::{map::HashMap, Address, BlockNumber, B256}; use auto_impl::auto_impl; -use core::ops::{RangeBounds, RangeInclusive}; +use core::ops::RangeBounds; use reth_db_api::models::BlockNumberAddress; use reth_db_models::AccountBeforeTx; use reth_primitives_traits::{Account, StorageEntry}; @@ -69,17 +69,4 @@ pub trait HashingWriter: Send + Sync { &self, storages: impl IntoIterator)>, ) -> ProviderResult>>; - - /// Calculate the hashes of all changed accounts and storages, and finally calculate the state - /// root. - /// - /// The hashes are calculated from `fork_block_number + 1` to `current_block_number`. - /// - /// The resulting state root is compared with `expected_state_root`. - fn insert_hashes( - &self, - range: RangeInclusive, - end_block_hash: B256, - expected_state_root: B256, - ) -> ProviderResult<()>; } diff --git a/crates/storage/storage-api/src/noop.rs b/crates/storage/storage-api/src/noop.rs index 1cb924ce113..ca66ac6931c 100644 --- a/crates/storage/storage-api/src/noop.rs +++ b/crates/storage/storage-api/src/noop.rs @@ -557,6 +557,10 @@ impl StateProviderFactory for NoopP fn pending_state_by_hash(&self, _block_hash: B256) -> ProviderResult> { Ok(Some(Box::new(self.clone()))) } + + fn maybe_pending(&self) -> ProviderResult> { + Ok(Some(Box::new(self.clone()))) + } } impl StageCheckpointReader for NoopProvider { diff --git a/crates/storage/storage-api/src/state.rs b/crates/storage/storage-api/src/state.rs index 6f508289d5f..dc8241fb95f 100644 --- a/crates/storage/storage-api/src/state.rs +++ b/crates/storage/storage-api/src/state.rs @@ -194,4 +194,9 @@ pub trait StateProviderFactory: BlockIdReader + Send + Sync { /// /// If the block couldn't be found, returns `None`. fn pending_state_by_hash(&self, block_hash: B256) -> ProviderResult>; + + /// Returns a pending [`StateProvider`] if it exists. + /// + /// This will return `None` if there's no pending state. + fn maybe_pending(&self) -> ProviderResult>; } diff --git a/crates/storage/storage-api/src/trie.rs b/crates/storage/storage-api/src/trie.rs index 9ae8ebee9a0..3f39cf3838d 100644 --- a/crates/storage/storage-api/src/trie.rs +++ b/crates/storage/storage-api/src/trie.rs @@ -1,5 +1,5 @@ use alloc::vec::Vec; -use alloy_primitives::{map::B256Map, Address, Bytes, B256}; +use alloy_primitives::{Address, Bytes, B256}; use reth_storage_errors::provider::ProviderResult; use reth_trie_common::{ updates::{StorageTrieUpdates, TrieUpdates}, @@ -106,15 +106,8 @@ pub trait StorageTrieWriter: Send + Sync { /// First sorts the storage trie updates by the hashed address key, writing in sorted order. /// /// Returns the number of entries modified. - fn write_storage_trie_updates( + fn write_storage_trie_updates<'a>( &self, - storage_tries: &B256Map, - ) -> ProviderResult; - - /// Writes storage trie updates for the given hashed address. - fn write_individual_storage_trie_updates( - &self, - hashed_address: B256, - updates: &StorageTrieUpdates, + storage_tries: impl Iterator, ) -> ProviderResult; } diff --git a/crates/storage/zstd-compressors/src/lib.rs b/crates/storage/zstd-compressors/src/lib.rs index d7f2b65904d..0e9b800b1fd 100644 --- a/crates/storage/zstd-compressors/src/lib.rs +++ b/crates/storage/zstd-compressors/src/lib.rs @@ -118,10 +118,10 @@ impl ReusableDecompressor { // source. if !reserved_upper_bound { reserved_upper_bound = true; - if let Some(upper_bound) = Decompressor::upper_bound(src) { - if let Some(additional) = upper_bound.checked_sub(self.buf.capacity()) { - break 'b additional - } + if let Some(upper_bound) = Decompressor::upper_bound(src) && + let Some(additional) = upper_bound.checked_sub(self.buf.capacity()) + { + break 'b additional } } diff --git a/crates/tasks/src/pool.rs b/crates/tasks/src/pool.rs index 10fedccedd1..6c77c9886ad 100644 --- a/crates/tasks/src/pool.rs +++ b/crates/tasks/src/pool.rs @@ -72,7 +72,7 @@ impl BlockingTaskPool { /// Uses [`rayon::ThreadPoolBuilder::build`](rayon::ThreadPoolBuilder::build) defaults but /// increases the stack size to 8MB. pub fn build() -> Result { - Self::builder().build().map(Self::new) + Self::builder().thread_name(|i| format!("rayon-{i}")).build().map(Self::new) } /// Asynchronous wrapper around Rayon's diff --git a/crates/tracing/src/layers.rs b/crates/tracing/src/layers.rs index f7d1a0346f6..5b9c93b5fb6 100644 --- a/crates/tracing/src/layers.rs +++ b/crates/tracing/src/layers.rs @@ -18,8 +18,6 @@ pub type FileWorkerGuard = tracing_appender::non_blocking::WorkerGuard; /// A boxed tracing [Layer]. pub(crate) type BoxedLayer = Box + Send + Sync>; -const RETH_LOG_FILE_NAME: &str = "reth.log"; - /// Default [directives](Directive) for [`EnvFilter`] which disables high-frequency debug logs from /// `hyper`, `hickory-resolver`, `jsonrpsee-server`, and `discv5`. const DEFAULT_ENV_FILTER_DIRECTIVES: [&str; 5] = [ @@ -140,8 +138,13 @@ pub struct FileInfo { impl FileInfo { /// Creates a new `FileInfo` instance. - pub fn new(dir: PathBuf, max_size_bytes: u64, max_files: usize) -> Self { - Self { dir, file_name: RETH_LOG_FILE_NAME.to_string(), max_size_bytes, max_files } + pub const fn new( + dir: PathBuf, + file_name: String, + max_size_bytes: u64, + max_files: usize, + ) -> Self { + Self { dir, file_name, max_size_bytes, max_files } } /// Creates the log directory if it doesn't exist. diff --git a/crates/transaction-pool/Cargo.toml b/crates/transaction-pool/Cargo.toml index 02030719840..10a7301d010 100644 --- a/crates/transaction-pool/Cargo.toml +++ b/crates/transaction-pool/Cargo.toml @@ -15,6 +15,7 @@ workspace = true # reth reth-chain-state.workspace = true reth-ethereum-primitives.workspace = true +reth-optimism-primitives.workspace = true reth-chainspec.workspace = true reth-eth-wire-types.workspace = true reth-primitives-traits.workspace = true @@ -24,6 +25,7 @@ reth-storage-api.workspace = true reth-tasks.workspace = true revm-interpreter.workspace = true revm-primitives.workspace = true +reth-gas-station.workspace = true # ethereum alloy-eips = { workspace = true, features = ["kzg"] } @@ -90,6 +92,7 @@ serde = [ "revm-primitives/serde", "reth-primitives-traits/serde", "reth-ethereum-primitives/serde", + "reth-optimism-primitives/serde", "reth-chain-state/serde", "reth-storage-api/serde", ] diff --git a/crates/transaction-pool/src/batcher.rs b/crates/transaction-pool/src/batcher.rs index dcf59c9ea6d..75280e68b3c 100644 --- a/crates/transaction-pool/src/batcher.rs +++ b/crates/transaction-pool/src/batcher.rs @@ -10,7 +10,7 @@ use pin_project::pin_project; use std::{ future::Future, pin::Pin, - task::{Context, Poll}, + task::{ready, Context, Poll}, }; use tokio::sync::{mpsc, oneshot}; @@ -44,6 +44,7 @@ where pub struct BatchTxProcessor { pool: Pool, max_batch_size: usize, + buf: Vec>, #[pin] request_rx: mpsc::UnboundedReceiver>, } @@ -59,13 +60,24 @@ where ) -> (Self, mpsc::UnboundedSender>) { let (request_tx, request_rx) = mpsc::unbounded_channel(); - let processor = Self { pool, max_batch_size, request_rx }; + let processor = Self { pool, max_batch_size, buf: Vec::with_capacity(1), request_rx }; (processor, request_tx) } + async fn process_request(pool: &Pool, req: BatchTxRequest) { + let BatchTxRequest { pool_tx, response_tx } = req; + let pool_result = pool.add_transaction(TransactionOrigin::Local, pool_tx).await; + let _ = response_tx.send(pool_result); + } + /// Process a batch of transaction requests, grouped by origin - async fn process_batch(pool: &Pool, batch: Vec>) { + async fn process_batch(pool: &Pool, mut batch: Vec>) { + if batch.len() == 1 { + Self::process_request(pool, batch.remove(0)).await; + return + } + let (pool_transactions, response_tx): (Vec<_>, Vec<_>) = batch.into_iter().map(|req| (req.pool_tx, req.response_tx)).unzip(); @@ -88,21 +100,15 @@ where loop { // Drain all available requests from the receiver - let mut batch = Vec::with_capacity(1); - while let Poll::Ready(Some(request)) = this.request_rx.poll_recv(cx) { - batch.push(request); - - // Check if the max batch size threshold has been reached - if batch.len() >= *this.max_batch_size { - break; - } - } + ready!(this.request_rx.poll_recv_many(cx, this.buf, *this.max_batch_size)); - if !batch.is_empty() { + if !this.buf.is_empty() { + let batch = std::mem::take(this.buf); let pool = this.pool.clone(); tokio::spawn(async move { Self::process_batch(&pool, batch).await; }); + this.buf.reserve(1); continue; } diff --git a/crates/transaction-pool/src/config.rs b/crates/transaction-pool/src/config.rs index db792a5162f..c6fb4ecc88b 100644 --- a/crates/transaction-pool/src/config.rs +++ b/crates/transaction-pool/src/config.rs @@ -31,6 +31,9 @@ pub const REPLACE_BLOB_PRICE_BUMP: u128 = 100; /// Default maximum new transactions for broadcasting. pub const MAX_NEW_PENDING_TXS_NOTIFICATIONS: usize = 200; +/// Default maximum allowed in flight delegated transactions per account. +pub const DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS: usize = 1; + /// Configuration options for the Transaction pool. #[derive(Debug, Clone)] pub struct PoolConfig { @@ -65,9 +68,38 @@ pub struct PoolConfig { pub max_new_pending_txs_notifications: usize, /// Maximum lifetime for transactions in the pool pub max_queued_lifetime: Duration, + /// The maximum allowed inflight transactions a delegated sender can have. + /// + /// This restricts how many executable transaction a delegated sender can stack. + pub max_inflight_delegated_slot_limit: usize, } impl PoolConfig { + /// Sets the minimal protocol base fee to 0, effectively disabling checks that enforce that a + /// transaction's fee must be higher than the [`MIN_PROTOCOL_BASE_FEE`] which is the lowest + /// value the ethereum EIP-1559 base fee can reach. + pub const fn with_disabled_protocol_base_fee(self) -> Self { + self.with_protocol_base_fee(0) + } + + /// Configures the minimal protocol base fee that should be enforced. + /// + /// Ethereum's EIP-1559 base fee can't drop below [`MIN_PROTOCOL_BASE_FEE`] hence this is + /// enforced by default in the pool. + pub const fn with_protocol_base_fee(mut self, protocol_base_fee: u64) -> Self { + self.minimal_protocol_basefee = protocol_base_fee; + self + } + + /// Configures how many slots are available for a delegated sender. + pub const fn with_max_inflight_delegated_slots( + mut self, + max_inflight_delegation_limit: usize, + ) -> Self { + self.max_inflight_delegated_slot_limit = max_inflight_delegation_limit; + self + } + /// Returns whether the size and amount constraints in any sub-pools are exceeded. #[inline] pub const fn is_exceeded(&self, pool_size: PoolSize) -> bool { @@ -96,6 +128,7 @@ impl Default for PoolConfig { new_tx_listener_buffer_size: NEW_TX_LISTENER_BUFFER_SIZE, max_new_pending_txs_notifications: MAX_NEW_PENDING_TXS_NOTIFICATIONS, max_queued_lifetime: MAX_QUEUED_TRANSACTION_LIFETIME, + max_inflight_delegated_slot_limit: DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS, } } } diff --git a/crates/transaction-pool/src/error.rs b/crates/transaction-pool/src/error.rs index b499c57aebd..a6283afea9d 100644 --- a/crates/transaction-pool/src/error.rs +++ b/crates/transaction-pool/src/error.rs @@ -93,7 +93,7 @@ impl PoolError { /// /// Not all error variants are caused by the incorrect composition of the transaction (See also /// [`InvalidPoolTransactionError`]) and can be caused by the current state of the transaction - /// pool. For example the transaction pool is already full or the error was caused my an + /// pool. For example the transaction pool is already full or the error was caused by an /// internal error, such as database errors. /// /// This function returns true only if the transaction will never make it into the pool because @@ -319,7 +319,11 @@ impl InvalidPoolTransactionError { InvalidTransactionError::Eip7702Disabled => { // settings false - } + }, + InvalidTransactionError::GaslessValidationError(_) => { + // gasless validation failed + false + }, InvalidTransactionError::OldLegacyChainId | InvalidTransactionError::ChainIdMismatch | InvalidTransactionError::GasUintOverflow | diff --git a/crates/transaction-pool/src/lib.rs b/crates/transaction-pool/src/lib.rs index e5e2df416fb..c543d412842 100644 --- a/crates/transaction-pool/src/lib.rs +++ b/crates/transaction-pool/src/lib.rs @@ -274,7 +274,8 @@ pub use crate::{ batcher::{BatchTxProcessor, BatchTxRequest}, blobstore::{BlobStore, BlobStoreError}, config::{ - LocalTransactionConfig, PoolConfig, PriceBumpConfig, SubPoolLimit, DEFAULT_PRICE_BUMP, + LocalTransactionConfig, PoolConfig, PriceBumpConfig, SubPoolLimit, + DEFAULT_MAX_INFLIGHT_DELEGATED_SLOTS, DEFAULT_PRICE_BUMP, DEFAULT_TXPOOL_ADDITIONAL_VALIDATION_TASKS, MAX_NEW_PENDING_TXS_NOTIFICATIONS, REPLACE_BLOB_PRICE_BUMP, TXPOOL_MAX_ACCOUNT_SLOTS_PER_SENDER, TXPOOL_SUBPOOL_MAX_SIZE_MB_DEFAULT, TXPOOL_SUBPOOL_MAX_TXS_DEFAULT, @@ -368,12 +369,8 @@ where &self, origin: TransactionOrigin, transaction: V::Transaction, - ) -> (TxHash, TransactionValidationOutcome) { - let hash = *transaction.hash(); - - let outcome = self.pool.validator().validate_transaction(origin, transaction).await; - - (hash, outcome) + ) -> TransactionValidationOutcome { + self.pool.validator().validate_transaction(origin, transaction).await } /// Returns future that validates all transactions in the given iterator. @@ -383,14 +380,8 @@ where &self, origin: TransactionOrigin, transactions: impl IntoIterator + Send, - ) -> Vec<(TxHash, TransactionValidationOutcome)> { - self.pool - .validator() - .validate_transactions_with_origin(origin, transactions) - .await - .into_iter() - .map(|tx| (tx.tx_hash(), tx)) - .collect() + ) -> Vec> { + self.pool.validator().validate_transactions_with_origin(origin, transactions).await } /// Validates all transactions with their individual origins. @@ -400,6 +391,11 @@ where &self, transactions: Vec<(TransactionOrigin, V::Transaction)>, ) -> Vec<(TransactionOrigin, TransactionValidationOutcome)> { + if transactions.len() == 1 { + let (origin, tx) = transactions.into_iter().next().unwrap(); + let res = self.pool.validator().validate_transaction(origin, tx).await; + return vec![(origin, res)] + } let origins: Vec<_> = transactions.iter().map(|(origin, _)| *origin).collect(); let tx_outcomes = self.pool.validator().validate_transactions(transactions).await; origins.into_iter().zip(tx_outcomes).collect() @@ -493,7 +489,7 @@ where origin: TransactionOrigin, transaction: Self::Transaction, ) -> PoolResult { - let (_, tx) = self.validate(origin, transaction).await; + let tx = self.validate(origin, transaction).await; self.pool.add_transaction_and_subscribe(origin, tx) } @@ -502,7 +498,7 @@ where origin: TransactionOrigin, transaction: Self::Transaction, ) -> PoolResult { - let (_, tx) = self.validate(origin, transaction).await; + let tx = self.validate(origin, transaction).await; let mut results = self.pool.add_transactions(origin, std::iter::once(tx)); results.pop().expect("result length is the same as the input") } @@ -517,7 +513,7 @@ where } let validated = self.validate_all(origin, transactions).await; - self.pool.add_transactions(origin, validated.into_iter().map(|(_, tx)| tx)) + self.pool.add_transactions(origin, validated.into_iter()) } async fn add_transactions_with_origins( diff --git a/crates/transaction-pool/src/maintain.rs b/crates/transaction-pool/src/maintain.rs index 300ff6eb410..732d55d0c3f 100644 --- a/crates/transaction-pool/src/maintain.rs +++ b/crates/transaction-pool/src/maintain.rs @@ -7,7 +7,7 @@ use crate::{ traits::{CanonicalStateUpdate, EthPoolTransaction, TransactionPool, TransactionPoolExt}, BlockInfo, PoolTransaction, PoolUpdateKind, TransactionOrigin, }; -use alloy_consensus::{BlockHeader, Typed2718}; +use alloy_consensus::{transaction::TxHashRef, BlockHeader, Typed2718}; use alloy_eips::{BlockNumberOrTag, Decodable2718, Encodable2718}; use alloy_primitives::{Address, BlockHash, BlockNumber}; use alloy_rlp::{Bytes, Encodable}; @@ -229,21 +229,19 @@ pub async fn maintain_transaction_pool( // check if we have a new finalized block if let Some(finalized) = - last_finalized_block.update(client.finalized_block_number().ok().flatten()) - { - if let BlobStoreUpdates::Finalized(blobs) = + last_finalized_block.update(client.finalized_block_number().ok().flatten()) && + let BlobStoreUpdates::Finalized(blobs) = blob_store_tracker.on_finalized_block(finalized) - { - metrics.inc_deleted_tracked_blobs(blobs.len()); - // remove all finalized blobs from the blob store - pool.delete_blobs(blobs); - // and also do periodic cleanup - let pool = pool.clone(); - task_spawner.spawn_blocking(Box::pin(async move { - debug!(target: "txpool", finalized_block = %finalized, "cleaning up blob store"); - pool.cleanup_blobs(); - })); - } + { + metrics.inc_deleted_tracked_blobs(blobs.len()); + // remove all finalized blobs from the blob store + pool.delete_blobs(blobs); + // and also do periodic cleanup + let pool = pool.clone(); + task_spawner.spawn_blocking(Box::pin(async move { + debug!(target: "txpool", finalized_block = %finalized, "cleaning up blob store"); + pool.cleanup_blobs(); + })); } // outcomes of the futures we are waiting on @@ -628,10 +626,11 @@ where tx_backups .into_iter() .filter_map(|backup| { - let tx_signed = ::Consensus::decode_2718( - &mut backup.rlp.as_ref(), - ) - .ok()?; + let tx_signed = + ::Consensus::decode_2718_exact( + backup.rlp.as_ref(), + ) + .ok()?; let recovered = tx_signed.try_into_recovered().ok()?; let pool_tx = ::try_from_consensus(recovered).ok()?; @@ -800,7 +799,7 @@ mod tests { let validator = EthTransactionValidatorBuilder::new(provider).build(blob_store.clone()); let txpool = Pool::new( - validator.clone(), + validator, CoinbaseTipOrdering::default(), blob_store.clone(), Default::default(), diff --git a/crates/transaction-pool/src/metrics.rs b/crates/transaction-pool/src/metrics.rs index 85a78663d24..d9926dafa02 100644 --- a/crates/transaction-pool/src/metrics.rs +++ b/crates/transaction-pool/src/metrics.rs @@ -140,3 +140,11 @@ pub struct TxPoolValidationMetrics { /// How long to successfully validate a blob pub(crate) blob_validation_duration: Histogram, } + +/// Transaction pool validator task metrics +#[derive(Metrics)] +#[metrics(scope = "transaction_pool")] +pub struct TxPoolValidatorMetrics { + /// Number of in-flight validation job sends waiting for channel capacity + pub(crate) inflight_validation_jobs: Gauge, +} diff --git a/crates/transaction-pool/src/ordering.rs b/crates/transaction-pool/src/ordering.rs index c6554220336..2106cc4e3bf 100644 --- a/crates/transaction-pool/src/ordering.rs +++ b/crates/transaction-pool/src/ordering.rs @@ -1,5 +1,6 @@ use crate::traits::PoolTransaction; use alloy_primitives::U256; +use reth_optimism_primitives::is_gasless; use std::{cmp::Ordering, fmt::Debug, marker::PhantomData}; /// Priority of the transaction that can be missing. @@ -71,7 +72,7 @@ impl TransactionOrdering for CoinbaseTipOrdering where T: PoolTransaction + 'static, { - type PriorityValue = U256; + type PriorityValue = u128; type Transaction = T; /// Source: . @@ -82,7 +83,11 @@ where transaction: &Self::Transaction, base_fee: u64, ) -> Priority { - transaction.effective_tip_per_gas(base_fee).map(U256::from).into() + if is_gasless(transaction) { + Priority::Value(u128::MAX) + } else { + transaction.effective_tip_per_gas(base_fee).into() + } } } diff --git a/crates/transaction-pool/src/pool/best.rs b/crates/transaction-pool/src/pool/best.rs index 0066a51aaf6..8448493d9ae 100644 --- a/crates/transaction-pool/src/pool/best.rs +++ b/crates/transaction-pool/src/pool/best.rs @@ -15,6 +15,7 @@ use std::{ }; use tokio::sync::broadcast::{error::TryRecvError, Receiver}; use tracing::debug; +use reth_optimism_primitives::is_gasless; /// An iterator that returns transactions that can be executed on the current state (*best* /// transactions). @@ -54,8 +55,10 @@ impl Iterator for BestTransactionsWithFees { loop { let best = Iterator::next(&mut self.best)?; // If both the base fee and blob fee (if applicable for EIP-4844) are satisfied, return - // the transaction - if best.transaction.max_fee_per_gas() >= self.base_fee as u128 && + // the transaction. For gasless transactions (priority fee or legacy price == 0) we + // bypass base fee filtering entirely so they can be considered for inclusion. + if (is_gasless(&best.transaction) || + best.transaction.max_fee_per_gas() >= self.base_fee as u128) && best.transaction .max_fee_per_blob_gas() .is_none_or(|fee| fee >= self.base_fee_per_blob_gas as u128) @@ -127,12 +130,12 @@ impl BestTransactions { loop { match self.new_transaction_receiver.as_mut()?.try_recv() { Ok(tx) => { - if let Some(last_priority) = &self.last_priority { - if &tx.priority > last_priority { - // we skip transactions if we already yielded a transaction with lower - // priority - return None - } + if let Some(last_priority) = &self.last_priority && + &tx.priority > last_priority + { + // we skip transactions if we already yielded a transaction with lower + // priority + return None } return Some(tx) } @@ -394,7 +397,6 @@ mod tests { test_utils::{MockOrdering, MockTransaction, MockTransactionFactory}, BestTransactions, Priority, }; - use alloy_primitives::U256; #[test] fn test_best_iter() { @@ -665,7 +667,7 @@ mod tests { let pending_tx = PendingTransaction { submission_id: 10, transaction: Arc::new(valid_new_tx.clone()), - priority: Priority::Value(U256::from(1000)), + priority: Priority::Value(1000), }; tx_sender.send(pending_tx.clone()).unwrap(); @@ -712,7 +714,7 @@ mod tests { let pending_tx1 = PendingTransaction { submission_id: 10, transaction: Arc::new(valid_new_tx1.clone()), - priority: Priority::Value(U256::from(1000)), + priority: Priority::Value(1000), }; tx_sender.send(pending_tx1.clone()).unwrap(); @@ -735,7 +737,7 @@ mod tests { let pending_tx2 = PendingTransaction { submission_id: 11, // Different submission ID transaction: Arc::new(valid_new_tx2.clone()), - priority: Priority::Value(U256::from(1000)), + priority: Priority::Value(1000), }; tx_sender.send(pending_tx2.clone()).unwrap(); @@ -981,7 +983,7 @@ mod tests { let pending_tx = PendingTransaction { submission_id: 10, transaction: Arc::new(valid_new_higher_fee_tx.clone()), - priority: Priority::Value(U256::from(u64::MAX)), + priority: Priority::Value(u128::MAX), }; tx_sender.send(pending_tx).unwrap(); diff --git a/crates/transaction-pool/src/pool/mod.rs b/crates/transaction-pool/src/pool/mod.rs index 2387a78d607..04f0e6e0b31 100644 --- a/crates/transaction-pool/src/pool/mod.rs +++ b/crates/transaction-pool/src/pool/mod.rs @@ -113,7 +113,7 @@ mod best; mod blob; mod listener; mod parked; -pub(crate) mod pending; +pub mod pending; pub(crate) mod size; pub(crate) mod state; pub mod txpool; @@ -510,10 +510,7 @@ where let added = pool.add_transaction(tx, balance, state_nonce, bytecode_hash)?; let hash = *added.hash(); - let state = match added.subpool() { - SubPool::Pending => AddedTransactionState::Pending, - _ => AddedTransactionState::Queued, - }; + let state = added.transaction_state(); // transaction was successfully inserted into the pool if let Some(sidecar) = maybe_sidecar { @@ -615,10 +612,10 @@ where // A newly added transaction may be immediately discarded, so we need to // adjust the result here for res in &mut added { - if let Ok(AddedTransactionOutcome { hash, .. }) = res { - if discarded_hashes.contains(hash) { - *res = Err(PoolError::new(*hash, PoolErrorKind::DiscardedOnInsert)) - } + if let Ok(AddedTransactionOutcome { hash, .. }) = res && + discarded_hashes.contains(hash) + { + *res = Err(PoolError::new(*hash, PoolErrorKind::DiscardedOnInsert)) } } } @@ -1160,6 +1157,8 @@ pub enum AddedTransaction { replaced: Option>>, /// The subpool it was moved to. subpool: SubPool, + /// The specific reason why the transaction is queued (if applicable). + queued_reason: Option, }, } @@ -1229,19 +1228,81 @@ impl AddedTransaction { Self::Parked { transaction, .. } => transaction.id(), } } + + /// Returns the queued reason if the transaction is parked with a queued reason. + pub(crate) const fn queued_reason(&self) -> Option<&QueuedReason> { + match self { + Self::Pending(_) => None, + Self::Parked { queued_reason, .. } => queued_reason.as_ref(), + } + } + + /// Returns the transaction state based on the subpool and queued reason. + pub(crate) fn transaction_state(&self) -> AddedTransactionState { + match self.subpool() { + SubPool::Pending => AddedTransactionState::Pending, + _ => { + // For non-pending transactions, use the queued reason directly from the + // AddedTransaction + if let Some(reason) = self.queued_reason() { + AddedTransactionState::Queued(reason.clone()) + } else { + // Fallback - this shouldn't happen with the new implementation + AddedTransactionState::Queued(QueuedReason::NonceGap) + } + } + } + } +} + +/// The specific reason why a transaction is queued (not ready for execution) +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum QueuedReason { + /// Transaction has a nonce gap - missing prior transactions + NonceGap, + /// Transaction has parked ancestors - waiting for other transactions to be mined + ParkedAncestors, + /// Sender has insufficient balance to cover the transaction cost + InsufficientBalance, + /// Transaction exceeds the block gas limit + TooMuchGas, + /// Transaction doesn't meet the base fee requirement + InsufficientBaseFee, + /// Transaction doesn't meet the blob fee requirement (EIP-4844) + InsufficientBlobFee, } /// The state of a transaction when is was added to the pool -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum AddedTransactionState { /// Ready for execution Pending, - /// Not ready for execution due to a nonce gap or insufficient balance - Queued, // TODO: Break it down to missing nonce, insufficient balance, etc. + /// Not ready for execution due to a specific condition + Queued(QueuedReason), +} + +impl AddedTransactionState { + /// Returns whether the transaction was submitted as queued. + pub const fn is_queued(&self) -> bool { + matches!(self, Self::Queued(_)) + } + + /// Returns whether the transaction was submitted as pending. + pub const fn is_pending(&self) -> bool { + matches!(self, Self::Pending) + } + + /// Returns the specific queued reason if the transaction is queued. + pub const fn queued_reason(&self) -> Option<&QueuedReason> { + match self { + Self::Queued(reason) => Some(reason), + Self::Pending => None, + } + } } /// The outcome of a successful transaction addition -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct AddedTransactionOutcome { /// The hash of the transaction pub hash: TxHash, @@ -1249,6 +1310,18 @@ pub struct AddedTransactionOutcome { pub state: AddedTransactionState, } +impl AddedTransactionOutcome { + /// Returns whether the transaction was submitted as queued. + pub const fn is_queued(&self) -> bool { + self.state.is_queued() + } + + /// Returns whether the transaction was submitted as pending. + pub const fn is_pending(&self) -> bool { + self.state.is_pending() + } +} + /// Contains all state changes after a [`CanonicalStateUpdate`] was processed #[derive(Debug)] pub(crate) struct OnNewCanonicalStateOutcome { diff --git a/crates/transaction-pool/src/pool/parked.rs b/crates/transaction-pool/src/pool/parked.rs index 86f02ae0b8e..539aeaa9e2c 100644 --- a/crates/transaction-pool/src/pool/parked.rs +++ b/crates/transaction-pool/src/pool/parked.rs @@ -44,13 +44,9 @@ pub struct ParkedPool { impl ParkedPool { /// Adds a new transactions to the pending queue. - /// - /// # Panics - /// - /// If the transaction is already included. pub fn add_transaction(&mut self, tx: Arc>) { let id = *tx.id(); - assert!( + debug_assert!( !self.contains(&id), "transaction already included {:?}", self.get(&id).unwrap().transaction.transaction @@ -295,18 +291,43 @@ impl ParkedPool> { transactions } - /// Removes all transactions and their dependent transaction from the subpool that no longer - /// satisfy the given basefee. + /// Removes all transactions from this subpool that can afford the given basefee, + /// invoking the provided handler for each transaction as it is removed. + /// + /// This method enforces the basefee constraint by identifying transactions that now + /// satisfy the basefee requirement (typically after a basefee decrease) and processing + /// them via the provided transaction handler closure. + /// + /// Respects per-sender nonce ordering: if the lowest-nonce transaction for a sender + /// still cannot afford the basefee, higher-nonce transactions from that sender are skipped. /// /// Note: the transactions are not returned in a particular order. - pub(crate) fn enforce_basefee(&mut self, basefee: u64) -> Vec>> { + pub(crate) fn enforce_basefee_with(&mut self, basefee: u64, mut tx_handler: F) + where + F: FnMut(Arc>), + { let to_remove = self.satisfy_base_fee_ids(basefee as u128); - let mut removed = Vec::with_capacity(to_remove.len()); for id in to_remove { - removed.push(self.remove_transaction(&id).expect("transaction exists")); + if let Some(tx) = self.remove_transaction(&id) { + tx_handler(tx); + } } + } + /// Removes all transactions and their dependent transaction from the subpool that no longer + /// satisfy the given basefee. + /// + /// Legacy method maintained for compatibility with read-only queries. + /// For basefee enforcement, prefer `enforce_basefee_with` for better performance. + /// + /// Note: the transactions are not returned in a particular order. + #[cfg(test)] + pub(crate) fn enforce_basefee(&mut self, basefee: u64) -> Vec>> { + let mut removed = Vec::new(); + self.enforce_basefee_with(basefee, |tx| { + removed.push(tx); + }); removed } } @@ -1039,4 +1060,68 @@ mod tests { assert!(removed.is_some()); assert!(!pool.contains(&tx_id)); } + + #[test] + fn test_enforce_basefee_with_handler_zero_allocation() { + let mut f = MockTransactionFactory::default(); + let mut pool = ParkedPool::>::default(); + + // Add multiple transactions across different fee ranges + let sender_a = address!("0x000000000000000000000000000000000000000a"); + let sender_b = address!("0x000000000000000000000000000000000000000b"); + + // Add transactions where nonce ordering allows proper processing: + // Sender A: both transactions can afford basefee (500 >= 400, 600 >= 400) + // Sender B: transaction cannot afford basefee (300 < 400) + let txs = vec![ + f.validated_arc( + MockTransaction::eip1559() + .set_sender(sender_a) + .set_nonce(0) + .set_max_fee(500) + .clone(), + ), + f.validated_arc( + MockTransaction::eip1559() + .set_sender(sender_a) + .set_nonce(1) + .set_max_fee(600) + .clone(), + ), + f.validated_arc( + MockTransaction::eip1559() + .set_sender(sender_b) + .set_nonce(0) + .set_max_fee(300) + .clone(), + ), + ]; + + let expected_affordable = vec![txs[0].clone(), txs[1].clone()]; // Both sender A txs + for tx in txs { + pool.add_transaction(tx); + } + + // Test the handler approach with zero allocations + let mut processed_txs = Vec::new(); + let mut handler_call_count = 0; + + pool.enforce_basefee_with(400, |tx| { + processed_txs.push(tx); + handler_call_count += 1; + }); + + // Verify correct number of transactions processed + assert_eq!(handler_call_count, 2); + assert_eq!(processed_txs.len(), 2); + + // Verify the correct transactions were processed (those with fee >= 400) + let processed_ids: Vec<_> = processed_txs.iter().map(|tx| *tx.id()).collect(); + for expected_tx in expected_affordable { + assert!(processed_ids.contains(expected_tx.id())); + } + + // Verify transactions were removed from pool + assert_eq!(pool.len(), 1); // Only the 300 fee tx should remain + } } diff --git a/crates/transaction-pool/src/pool/pending.rs b/crates/transaction-pool/src/pool/pending.rs index a77dda61253..c28f66c58ec 100644 --- a/crates/transaction-pool/src/pool/pending.rs +++ b/crates/transaction-pool/src/pool/pending.rs @@ -1,3 +1,5 @@ +//! Pending transactions + use crate::{ identifier::{SenderId, TransactionId}, pool::{ @@ -182,7 +184,8 @@ impl PendingPool { // Drain and iterate over all transactions. let mut transactions_iter = self.clear_transactions().into_iter().peekable(); while let Some((id, tx)) = transactions_iter.next() { - if tx.transaction.max_fee_per_blob_gas() < Some(blob_fee) { + if tx.transaction.is_eip4844() && tx.transaction.max_fee_per_blob_gas() < Some(blob_fee) + { // Add this tx to the removed collection since it no longer satisfies the blob fee // condition. Decrease the total pool size. removed.push(Arc::clone(&tx.transaction)); @@ -326,13 +329,13 @@ impl PendingPool { &mut self, id: &TransactionId, ) -> Option>> { - if let Some(lowest) = self.independent_transactions.get(&id.sender) { - if lowest.transaction.nonce() == id.nonce { - self.independent_transactions.remove(&id.sender); - // mark the next as independent if it exists - if let Some(unlocked) = self.get(&id.descendant()) { - self.independent_transactions.insert(id.sender, unlocked.clone()); - } + if let Some(lowest) = self.independent_transactions.get(&id.sender) && + lowest.transaction.nonce() == id.nonce + { + self.independent_transactions.remove(&id.sender); + // mark the next as independent if it exists + if let Some(unlocked) = self.get(&id.descendant()) { + self.independent_transactions.insert(id.sender, unlocked.clone()); } } @@ -510,6 +513,21 @@ impl PendingPool { self.by_id.len() } + /// All transactions grouped by id + pub const fn by_id(&self) -> &BTreeMap> { + &self.by_id + } + + /// Independent transactions + pub const fn independent_transactions(&self) -> &FxHashMap> { + &self.independent_transactions + } + + /// Subscribes to new transactions + pub fn new_transaction_receiver(&self) -> broadcast::Receiver> { + self.new_transaction_notifier.subscribe() + } + /// Whether the pool is empty #[cfg(test)] pub(crate) fn is_empty(&self) -> bool { @@ -569,18 +587,18 @@ impl PendingPool { /// A transaction that is ready to be included in a block. #[derive(Debug)] -pub(crate) struct PendingTransaction { +pub struct PendingTransaction { /// Identifier that tags when transaction was submitted in the pool. - pub(crate) submission_id: u64, + pub submission_id: u64, /// Actual transaction. - pub(crate) transaction: Arc>, + pub transaction: Arc>, /// The priority value assigned by the used `Ordering` function. - pub(crate) priority: Priority, + pub priority: Priority, } impl PendingTransaction { /// The next transaction of the sender: `nonce + 1` - pub(crate) fn unlocks(&self) -> TransactionId { + pub fn unlocks(&self) -> TransactionId { self.transaction.transaction_id.descendant() } } @@ -747,7 +765,7 @@ mod tests { // the independent set is the roots of each of these tx chains, these are the highest // nonces for each sender - let expected_highest_nonces = vec![d[0].clone(), c[2].clone(), b[2].clone(), a[3].clone()] + let expected_highest_nonces = [d[0].clone(), c[2].clone(), b[2].clone(), a[3].clone()] .iter() .map(|tx| (tx.sender(), tx.nonce())) .collect::>(); diff --git a/crates/transaction-pool/src/pool/state.rs b/crates/transaction-pool/src/pool/state.rs index e04b463343e..187d472f5ae 100644 --- a/crates/transaction-pool/src/pool/state.rs +++ b/crates/transaction-pool/src/pool/state.rs @@ -1,3 +1,5 @@ +use crate::pool::QueuedReason; + bitflags::bitflags! { /// Marker to represents the current state of a transaction in the pool and from which the corresponding sub-pool is derived, depending on what bits are set. /// @@ -68,6 +70,56 @@ impl TxState { pub(crate) const fn has_nonce_gap(&self) -> bool { !self.intersects(Self::NO_NONCE_GAPS) } + + /// Adds the transaction into the pool. + /// + /// This pool consists of four sub-pools: `Queued`, `Pending`, `BaseFee`, and `Blob`. + /// + /// The `Queued` pool contains transactions with gaps in its dependency tree: It requires + /// additional transactions that are note yet present in the pool. And transactions that the + /// sender can not afford with the current balance. + /// + /// The `Pending` pool contains all transactions that have no nonce gaps, and can be afforded by + /// the sender. It only contains transactions that are ready to be included in the pending + /// block. The pending pool contains all transactions that could be listed currently, but not + /// necessarily independently. However, this pool never contains transactions with nonce gaps. A + /// transaction is considered `ready` when it has the lowest nonce of all transactions from the + /// same sender. Which is equals to the chain nonce of the sender in the pending pool. + /// + /// The `BaseFee` pool contains transactions that currently can't satisfy the dynamic fee + /// requirement. With EIP-1559, transactions can become executable or not without any changes to + /// the sender's balance or nonce and instead their `feeCap` determines whether the + /// transaction is _currently_ (on the current state) ready or needs to be parked until the + /// `feeCap` satisfies the block's `baseFee`. + /// + /// The `Blob` pool contains _blob_ transactions that currently can't satisfy the dynamic fee + /// requirement, or blob fee requirement. Transactions become executable only if the + /// transaction `feeCap` is greater than the block's `baseFee` and the `maxBlobFee` is greater + /// than the block's `blobFee`. + /// + /// Determines the specific reason why a transaction is queued based on its subpool and state. + pub(crate) const fn determine_queued_reason(&self, subpool: SubPool) -> Option { + match subpool { + SubPool::Pending => None, // Not queued + SubPool::Queued => { + // Check state flags to determine specific reason + if !self.contains(Self::NO_NONCE_GAPS) { + Some(QueuedReason::NonceGap) + } else if !self.contains(Self::ENOUGH_BALANCE) { + Some(QueuedReason::InsufficientBalance) + } else if !self.contains(Self::NO_PARKED_ANCESTORS) { + Some(QueuedReason::ParkedAncestors) + } else if !self.contains(Self::NOT_TOO_MUCH_GAS) { + Some(QueuedReason::TooMuchGas) + } else { + // Fallback for unexpected queued state + Some(QueuedReason::NonceGap) + } + } + SubPool::BaseFee => Some(QueuedReason::InsufficientBaseFee), + SubPool::Blob => Some(QueuedReason::InsufficientBlobFee), + } + } } /// Identifier for the transaction Sub-pool diff --git a/crates/transaction-pool/src/pool/txpool.rs b/crates/transaction-pool/src/pool/txpool.rs index c3f2233f442..c77ade271f7 100644 --- a/crates/transaction-pool/src/pool/txpool.rs +++ b/crates/transaction-pool/src/pool/txpool.rs @@ -40,7 +40,8 @@ use std::{ ops::Bound::{Excluded, Unbounded}, sync::Arc, }; -use tracing::trace; +use reth_optimism_primitives::is_gasless; +use tracing::{trace, warn}; #[cfg_attr(doc, aquamarine::aquamarine)] // TODO: Inlined diagram due to a bug in aquamarine library, should become an include when it's @@ -118,6 +119,9 @@ pub struct TxPool { metrics: TxPoolMetrics, /// The last update kind that was applied to the pool. latest_update_kind: Option, + /// Pending credit usage for gasless transactions by destination contract. + /// This is increased when a gasless tx to `to` is inserted and decreased when removed. + pending_credit_usage: FxHashMap, } // === impl TxPool === @@ -138,6 +142,7 @@ impl TxPool { config, metrics: Default::default(), latest_update_kind: None, + pending_credit_usage: Default::default(), } } @@ -175,7 +180,7 @@ impl TxPool { let mut next_expected_nonce = on_chain.nonce; for (id, tx) in self.all().descendant_txs_inclusive(&on_chain) { if next_expected_nonce != id.nonce { - break + break; } next_expected_nonce = id.next_nonce(); last_consecutive_tx = Some(tx); @@ -209,6 +214,45 @@ impl TxPool { } } + /// Increment pending usage for gasless txs + fn maybe_inc_gasless_pending_usage(&mut self, tx: &Arc>) { + // destination address + let to = match tx.to() { + Some(addr) => addr, + None => return, + }; + // detect gasless + let is_gasless = match tx.tx_type() { + LEGACY_TX_TYPE_ID => tx.priority_fee_or_price() == 0, + EIP1559_TX_TYPE_ID => tx.max_fee_per_gas() == 0 && tx.priority_fee_or_price() == 0, + _ => false, + }; + if !is_gasless { + return; + } + let entry = self.pending_credit_usage.entry(to).or_insert(U256::ZERO); + *entry = (*entry).saturating_add(U256::from(tx.gas_limit())); + } + + /// Decrement pending usage for gasless txs + fn maybe_dec_gasless_pending_usage(&mut self, tx: &Arc>) { + let to = match tx.to() { + Some(addr) => addr, + None => return, + }; + let is_gasless = match tx.tx_type() { + LEGACY_TX_TYPE_ID => tx.priority_fee_or_price() == 0, + EIP1559_TX_TYPE_ID => tx.max_fee_per_gas() == 0 && tx.priority_fee_or_price() == 0, + _ => false, + }; + if !is_gasless { + return; + } + if let Some(entry) = self.pending_credit_usage.get_mut(&to) { + *entry = entry.saturating_sub(U256::from(tx.gas_limit())); + } + } + /// Returns the currently tracked block values pub const fn block_info(&self) -> BlockInfo { BlockInfo { @@ -221,7 +265,14 @@ impl TxPool { } /// Updates the tracked blob fee - fn update_blob_fee(&mut self, mut pending_blob_fee: u128, base_fee_update: Ordering) { + fn update_blob_fee( + &mut self, + mut pending_blob_fee: u128, + base_fee_update: Ordering, + mut on_promoted: F, + ) where + F: FnMut(&Arc>), + { std::mem::swap(&mut self.all_transactions.pending_fees.blob_fee, &mut pending_blob_fee); match (self.all_transactions.pending_fees.blob_fee.cmp(&pending_blob_fee), base_fee_update) { @@ -250,15 +301,20 @@ impl TxPool { let removed = self.blob_pool.enforce_pending_fees(&self.all_transactions.pending_fees); for tx in removed { - let to = { - let tx = + let subpool = { + let tx_meta = self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set"); - tx.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK); - tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); - tx.subpool = tx.state.into(); - tx.subpool + tx_meta.state.insert(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK); + tx_meta.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); + tx_meta.subpool = tx_meta.state.into(); + tx_meta.subpool }; - self.add_transaction_to_subpool(to, tx); + + if subpool == SubPool::Pending { + on_promoted(&tx); + } + + self.add_transaction_to_subpool(subpool, tx); } } } @@ -268,7 +324,10 @@ impl TxPool { /// /// Depending on the change in direction of the basefee, this will promote or demote /// transactions from the basefee pool. - fn update_basefee(&mut self, mut pending_basefee: u64) -> Ordering { + fn update_basefee(&mut self, mut pending_basefee: u64, mut on_promoted: F) -> Ordering + where + F: FnMut(&Arc>), + { std::mem::swap(&mut self.all_transactions.pending_fees.base_fee, &mut pending_basefee); match self.all_transactions.pending_fees.base_fee.cmp(&pending_basefee) { Ordering::Equal => { @@ -293,19 +352,45 @@ impl TxPool { Ordering::Greater } Ordering::Less => { - // decreased base fee: recheck basefee pool and promote all that are now valid - let removed = - self.basefee_pool.enforce_basefee(self.all_transactions.pending_fees.base_fee); - for tx in removed { - let to = { - let tx = + // Base fee decreased: recheck BaseFee and promote. + // Invariants: + // - BaseFee contains only non-blob txs (blob txs live in Blob) and they already + // have ENOUGH_BLOB_FEE_CAP_BLOCK. + // - PENDING_POOL_BITS = BASE_FEE_POOL_BITS | ENOUGH_FEE_CAP_BLOCK | + // ENOUGH_BLOB_FEE_CAP_BLOCK. + // With the lower base fee they gain ENOUGH_FEE_CAP_BLOCK, so we can set the bit and + // insert directly into Pending (skip generic routing). + let current_base_fee = self.all_transactions.pending_fees.base_fee; + self.basefee_pool.enforce_basefee_with(current_base_fee, |tx| { + // Update transaction state — guaranteed Pending by the invariants above + let subpool = { + let meta = self.all_transactions.txs.get_mut(tx.id()).expect("tx exists in set"); - tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); - tx.subpool = tx.state.into(); - tx.subpool + meta.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); + meta.subpool = meta.state.into(); + meta.subpool }; - self.add_transaction_to_subpool(to, tx); - } + + if subpool == SubPool::Pending { + on_promoted(&tx); + } + + trace!(target: "txpool", hash=%tx.transaction.hash(), pool=?subpool, "Adding transaction to a subpool"); + match subpool { + SubPool::Queued => self.queued_pool.add_transaction(tx), + SubPool::Pending => { + self.pending_pool.add_transaction(tx, current_base_fee); + } + SubPool::Blob => { + self.blob_pool.add_transaction(tx); + } + SubPool::BaseFee => { + // This should be unreachable as transactions from BaseFee pool with decreased + // basefee are guaranteed to become Pending + warn!(target: "txpool", "BaseFee transactions should become Pending after basefee decrease"); + } + } + }); Ordering::Less } @@ -314,24 +399,15 @@ impl TxPool { /// Sets the current block info for the pool. /// - /// This will also apply updates to the pool based on the new base fee + /// This will also apply updates to the pool based on the new base fee and blob fee pub fn set_block_info(&mut self, info: BlockInfo) { - let BlockInfo { - block_gas_limit, - last_seen_block_hash, - last_seen_block_number, - pending_basefee, - pending_blob_fee, - } = info; - self.all_transactions.last_seen_block_hash = last_seen_block_hash; - self.all_transactions.last_seen_block_number = last_seen_block_number; - let basefee_ordering = self.update_basefee(pending_basefee); - - self.all_transactions.block_gas_limit = block_gas_limit; - - if let Some(blob_fee) = pending_blob_fee { - self.update_blob_fee(blob_fee, basefee_ordering) + // first update the subpools based on the new values + let basefee_ordering = self.update_basefee(info.pending_basefee, |_| {}); + if let Some(blob_fee) = info.pending_blob_fee { + self.update_blob_fee(blob_fee, basefee_ordering, |_| {}) } + // then update tracked values + self.all_transactions.set_block_info(info); } /// Returns an iterator that yields transactions that are ready to be included in the block with @@ -534,6 +610,59 @@ impl TxPool { self.all_transactions.txs_iter(sender).map(|(_, tx)| Arc::clone(&tx.transaction)).collect() } + /// Updates only the pending fees without triggering subpool updates. + /// Returns the previous base fee and blob fee values. + const fn update_pending_fees_only( + &mut self, + mut new_base_fee: u64, + new_blob_fee: Option, + ) -> (u64, u128) { + std::mem::swap(&mut self.all_transactions.pending_fees.base_fee, &mut new_base_fee); + + let prev_blob_fee = if let Some(mut blob_fee) = new_blob_fee { + std::mem::swap(&mut self.all_transactions.pending_fees.blob_fee, &mut blob_fee); + blob_fee + } else { + self.all_transactions.pending_fees.blob_fee + }; + + (new_base_fee, prev_blob_fee) + } + + /// Applies fee-based promotion updates based on the previous fees. + /// + /// Records promoted transactions based on fee swings. + /// + /// Caution: This expects that the fees were previously already updated via + /// [`Self::update_pending_fees_only`]. + fn apply_fee_updates( + &mut self, + prev_base_fee: u64, + prev_blob_fee: u128, + outcome: &mut UpdateOutcome, + ) { + let new_base_fee = self.all_transactions.pending_fees.base_fee; + let new_blob_fee = self.all_transactions.pending_fees.blob_fee; + + if new_base_fee == prev_base_fee && new_blob_fee == prev_blob_fee { + // nothing to update + return; + } + + // IMPORTANT: + // Restore previous fees so that the update fee functions correctly handle fee swings + self.all_transactions.pending_fees.base_fee = prev_base_fee; + self.all_transactions.pending_fees.blob_fee = prev_blob_fee; + + let base_fee_ordering = self.update_basefee(new_base_fee, |tx| { + outcome.promoted.push(tx.clone()); + }); + + self.update_blob_fee(new_blob_fee, base_fee_ordering, |tx| { + outcome.promoted.push(tx.clone()); + }); + } + /// Updates the transactions for the changed senders. pub(crate) fn update_accounts( &mut self, @@ -554,8 +683,8 @@ impl TxPool { /// Updates the entire pool after a new block was mined. /// - /// This removes all mined transactions, updates according to the new base fee and rechecks - /// sender allowance. + /// This removes all mined transactions, updates according to the new base fee and blob fee and + /// rechecks sender allowance based on the given changed sender infos. pub(crate) fn on_canonical_state_change( &mut self, block_info: BlockInfo, @@ -565,7 +694,6 @@ impl TxPool { ) -> OnNewCanonicalStateOutcome { // update block info let block_hash = block_info.last_seen_block_hash; - self.all_transactions.set_block_info(block_info); // Remove all transaction that were included in the block let mut removed_txs_count = 0; @@ -578,7 +706,22 @@ impl TxPool { // Update removed transactions metric self.metrics.removed_transactions.increment(removed_txs_count); - let UpdateOutcome { promoted, discarded } = self.update_accounts(changed_senders); + // Update fees internally first without triggering subpool updates based on fee movements + // This must happen before we update the changed so that all account updates use the new fee + // values, this way all changed accounts remain unaffected by the fee updates that are + // performed in next step and we don't collect promotions twice + let (prev_base_fee, prev_blob_fee) = + self.update_pending_fees_only(block_info.pending_basefee, block_info.pending_blob_fee); + + // Now update accounts with the new fees already set + let mut outcome = self.update_accounts(changed_senders); + + // Apply subpool updates based on fee changes + // This will record any additional promotions based on fee movements + self.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome); + + // Update the rest of block info (without triggering fee updates again) + self.all_transactions.set_block_info(block_info); self.update_transaction_type_metrics(); self.metrics.performed_state_updates.increment(1); @@ -586,7 +729,12 @@ impl TxPool { // Update the latest update kind self.latest_update_kind = Some(update_kind); - OnNewCanonicalStateOutcome { block_hash, mined: mined_transactions, promoted, discarded } + OnNewCanonicalStateOutcome { + block_hash, + mined: mined_transactions, + promoted: outcome.promoted, + discarded: outcome.discarded, + } } /// Update sub-pools size metrics. @@ -629,31 +777,6 @@ impl TxPool { self.metrics.total_eip7702_transactions.set(eip7702_count as f64); } - /// Adds the transaction into the pool. - /// - /// This pool consists of four sub-pools: `Queued`, `Pending`, `BaseFee`, and `Blob`. - /// - /// The `Queued` pool contains transactions with gaps in its dependency tree: It requires - /// additional transactions that are note yet present in the pool. And transactions that the - /// sender can not afford with the current balance. - /// - /// The `Pending` pool contains all transactions that have no nonce gaps, and can be afforded by - /// the sender. It only contains transactions that are ready to be included in the pending - /// block. The pending pool contains all transactions that could be listed currently, but not - /// necessarily independently. However, this pool never contains transactions with nonce gaps. A - /// transaction is considered `ready` when it has the lowest nonce of all transactions from the - /// same sender. Which is equals to the chain nonce of the sender in the pending pool. - /// - /// The `BaseFee` pool contains transactions that currently can't satisfy the dynamic fee - /// requirement. With EIP-1559, transactions can become executable or not without any changes to - /// the sender's balance or nonce and instead their `feeCap` determines whether the - /// transaction is _currently_ (on the current state) ready or needs to be parked until the - /// `feeCap` satisfies the block's `baseFee`. - /// - /// The `Blob` pool contains _blob_ transactions that currently can't satisfy the dynamic fee - /// requirement, or blob fee requirement. Transactions become executable only if the - /// transaction `feeCap` is greater than the block's `baseFee` and the `maxBlobFee` is greater - /// than the block's `blobFee`. pub(crate) fn add_transaction( &mut self, tx: ValidPoolTransaction, @@ -662,7 +785,7 @@ impl TxPool { on_chain_code_hash: Option, ) -> PoolResult> { if self.contains(tx.hash()) { - return Err(PoolError::new(*tx.hash(), PoolErrorKind::AlreadyImported)) + return Err(PoolError::new(*tx.hash(), PoolErrorKind::AlreadyImported)); } self.validate_auth(&tx, on_chain_nonce, on_chain_code_hash)?; @@ -674,7 +797,7 @@ impl TxPool { .update(on_chain_nonce, on_chain_balance); match self.all_transactions.insert_tx(tx, on_chain_balance, on_chain_nonce) { - Ok(InsertOk { transaction, move_to, replaced_tx, updates, .. }) => { + Ok(InsertOk { transaction, move_to, replaced_tx, updates, state }) => { // replace the new tx and remove the replaced in the subpool(s) self.add_new_transaction(transaction.clone(), replaced_tx.clone(), move_to); // Update inserted transactions metric @@ -683,6 +806,15 @@ impl TxPool { let replaced = replaced_tx.map(|(tx, _)| tx); + // Track pending credit usage for gasless transactions + self.maybe_inc_gasless_pending_usage(&transaction); + if let Some(ref r) = replaced { + self.maybe_dec_gasless_pending_usage(r); + } + for d in &discarded { + self.maybe_dec_gasless_pending_usage(d); + } + // This transaction was moved to the pending pool. let res = if move_to.is_pending() { AddedTransaction::Pending(AddedPendingTransaction { @@ -692,7 +824,14 @@ impl TxPool { replaced, }) } else { - AddedTransaction::Parked { transaction, subpool: move_to, replaced } + // Determine the specific queued reason based on the transaction state + let queued_reason = state.determine_queued_reason(move_to); + AddedTransaction::Parked { + transaction, + subpool: move_to, + replaced, + queued_reason, + } }; // Update size metrics after adding and potentially moving transactions. @@ -759,8 +898,8 @@ impl TxPool { } /// Determines if the tx sender is delegated or has a pending delegation, and if so, ensures - /// they have at most one in-flight **executable** transaction, e.g. disallow stacked and - /// nonce-gapped transactions from the account. + /// they have at most one configured amount of in-flight **executable** transactions (default at + /// most one), e.g. disallow stacked and nonce-gapped transactions from the account. fn check_delegation_limit( &self, transaction: &ValidPoolTransaction, @@ -768,10 +907,10 @@ impl TxPool { on_chain_code_hash: Option, ) -> Result<(), PoolError> { // Short circuit if the sender has neither delegation nor pending delegation. - if (on_chain_code_hash.is_none() || on_chain_code_hash == Some(KECCAK_EMPTY)) && - !self.all_transactions.auths.contains_key(&transaction.sender_id()) + if (on_chain_code_hash.is_none() || on_chain_code_hash == Some(KECCAK_EMPTY)) + && !self.all_transactions.auths.contains_key(&transaction.sender_id()) { - return Ok(()) + return Ok(()); } let mut txs_by_sender = @@ -779,19 +918,32 @@ impl TxPool { if txs_by_sender.peek().is_none() { // Transaction with gapped nonce is not supported for delegated accounts - if transaction.nonce() > on_chain_nonce { + // but transaction can arrive out of order if more slots are allowed + // by default with a slot limit of 1 this will fail if the transaction's nonce > + // on_chain + let nonce_gap_distance = transaction.nonce().saturating_sub(on_chain_nonce); + if nonce_gap_distance >= self.config.max_inflight_delegated_slot_limit as u64 { return Err(PoolError::new( *transaction.hash(), PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702( Eip7702PoolTransactionError::OutOfOrderTxFromDelegated, )), - )) + )); } - return Ok(()) + return Ok(()); } - if txs_by_sender.any(|id| id == &transaction.transaction_id) { - // Transaction replacement is supported + let mut count = 0; + for id in txs_by_sender { + if id == &transaction.transaction_id { + // Transaction replacement is supported + return Ok(()) + } + count += 1; + } + + if count < self.config.max_inflight_delegated_slot_limit { + // account still has an available slot return Ok(()) } @@ -806,30 +958,31 @@ impl TxPool { /// This verifies that the transaction complies with code authorization /// restrictions brought by EIP-7702 transaction type: /// 1. Any account with a deployed delegation or an in-flight authorization to deploy a - /// delegation will only be allowed a single transaction slot instead of the standard limit. - /// This is due to the possibility of the account being sweeped by an unrelated account. - /// 2. In case the pool is tracking a pending / queued transaction from a specific account, it - /// will reject new transactions with delegations from that account with standard in-flight - /// transactions. + /// delegation will only be allowed a certain amount of transaction slots (default 1) instead + /// of the standard limit. This is due to the possibility of the account being sweeped by an + /// unrelated account. + /// 2. In case the pool is tracking a pending / queued transaction from a specific account, at + /// most one in-flight transaction is allowed; any additional delegated transactions from + /// that account will be rejected. fn validate_auth( &self, transaction: &ValidPoolTransaction, on_chain_nonce: u64, on_chain_code_hash: Option, ) -> Result<(), PoolError> { - // Allow at most one in-flight tx for delegated accounts or those with a - // pending authorization. + // Ensure in-flight limit for delegated accounts or those with a pending authorization. self.check_delegation_limit(transaction, on_chain_nonce, on_chain_code_hash)?; if let Some(authority_list) = &transaction.authority_ids { for sender_id in authority_list { - if self.all_transactions.txs_iter(*sender_id).next().is_some() { + // Ensure authority has at most 1 inflight transaction. + if self.all_transactions.txs_iter(*sender_id).nth(1).is_some() { return Err(PoolError::new( *transaction.hash(), PoolErrorKind::InvalidTransaction(InvalidPoolTransactionError::Eip7702( Eip7702PoolTransactionError::AuthorityReserved, )), - )) + )); } } } @@ -854,11 +1007,11 @@ impl TxPool { Destination::Pool(move_to) => { debug_assert_ne!(&move_to, ¤t, "destination must be different"); let moved = self.move_transaction(current, move_to, &id); - if matches!(move_to, SubPool::Pending) { - if let Some(tx) = moved { - trace!(target: "txpool", hash=%tx.transaction.hash(), "Promoted transaction to pending"); - outcome.promoted.push(tx); - } + if matches!(move_to, SubPool::Pending) && + let Some(tx) = moved + { + trace!(target: "txpool", hash=%tx.transaction.hash(), "Promoted transaction to pending"); + outcome.promoted.push(tx); } } } @@ -996,6 +1149,11 @@ impl TxPool { SubPool::Blob => self.blob_pool.remove_transaction(tx), }; + if let Some(ref arc_tx) = tx { + // Update pending usage for gasless transactions on any removal path + self.maybe_dec_gasless_pending_usage(arc_tx); + } + if let Some(ref tx) = tx { // We trace here instead of in subpool structs directly, because the `ParkedPool` type // is generic and it would not be possible to distinguish whether a transaction is @@ -1026,7 +1184,7 @@ impl TxPool { } id = descendant; } else { - return + return; } } } @@ -1188,18 +1346,19 @@ impl Drop for TxPool { } } -// Additional test impls -#[cfg(any(test, feature = "test-utils"))] impl TxPool { - pub(crate) const fn pending(&self) -> &PendingPool { + /// Pending subpool + pub const fn pending(&self) -> &PendingPool { &self.pending_pool } - pub(crate) const fn base_fee(&self) -> &ParkedPool> { + /// Base fee subpool + pub const fn base_fee(&self) -> &ParkedPool> { &self.basefee_pool } - pub(crate) const fn queued(&self) -> &ParkedPool> { + /// Queued sub pool + pub const fn queued(&self) -> &ParkedPool> { &self.queued_pool } } @@ -1295,7 +1454,7 @@ impl AllTransactions { if *count == 1 { entry.remove(); self.metrics.all_transactions_by_all_senders.decrement(1.0); - return + return; } *count -= 1; self.metrics.all_transactions_by_all_senders.decrement(1.0); @@ -1370,7 +1529,7 @@ impl AllTransactions { ($iter:ident) => { 'this: while let Some((peek, _)) = iter.peek() { if peek.sender != id.sender { - break 'this + break 'this; } iter.next(); } @@ -1387,7 +1546,7 @@ impl AllTransactions { current: tx.subpool, destination: Destination::Discard, }); - continue 'transactions + continue 'transactions; } let ancestor = TransactionId::ancestor(id.nonce, info.state_nonce, id.sender); @@ -1412,7 +1571,7 @@ impl AllTransactions { // If there's a nonce gap, we can shortcircuit, because there's nothing to update yet. if tx.state.has_nonce_gap() { next_sender!(iter); - continue 'transactions + continue 'transactions; } // Since this is the first transaction of the sender, it has no parked ancestors @@ -1435,7 +1594,7 @@ impl AllTransactions { while let Some((peek, tx)) = iter.peek_mut() { if peek.sender != id.sender { // Found the next sender we need to check - continue 'transactions + continue 'transactions; } if tx.transaction.nonce() == next_nonce_in_line { @@ -1444,7 +1603,7 @@ impl AllTransactions { } else { // can short circuit if there's still a nonce gap next_sender!(iter); - continue 'transactions + continue 'transactions; } // update for next iteration of this sender's loop @@ -1504,6 +1663,12 @@ impl AllTransactions { /// Rechecks the transaction's dynamic fee condition. fn update_tx_base_fee(pending_block_base_fee: u64, tx: &mut PoolInternalTransaction) { // Recheck dynamic fee condition. + // Gasless transactions (no effective tip) should always satisfy fee-cap for selection. + if is_gasless(&tx.transaction.transaction) { + tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); + return + } + match tx.transaction.max_fee_per_gas().cmp(&(pending_block_base_fee as u128)) { Ordering::Greater | Ordering::Equal => { tx.state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); @@ -1699,7 +1864,7 @@ impl AllTransactions { if current_txs >= self.max_account_slots && transaction.nonce() > on_chain_nonce { return Err(InsertErr::ExceededSenderTransactionsCapacity { transaction: Arc::new(transaction), - }) + }); } } if transaction.gas_limit() > self.block_gas_limit { @@ -1707,12 +1872,12 @@ impl AllTransactions { block_gas_limit: self.block_gas_limit, tx_gas_limit: transaction.gas_limit(), transaction: Arc::new(transaction), - }) + }); } if self.contains_conflicting_transaction(&transaction) { // blob vs non blob transactions are mutually exclusive for the same sender - return Err(InsertErr::TxTypeConflict { transaction: Arc::new(transaction) }) + return Err(InsertErr::TxTypeConflict { transaction: Arc::new(transaction) }); } Ok(transaction) @@ -1733,13 +1898,13 @@ impl AllTransactions { let Some(ancestor_tx) = self.txs.get(&ancestor) else { // ancestor tx is missing, so we can't insert the new blob self.metrics.blob_transactions_nonce_gaps.increment(1); - return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) }) + return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) }); }; if ancestor_tx.state.has_nonce_gap() { // the ancestor transaction already has a nonce gap, so we can't insert the new // blob self.metrics.blob_transactions_nonce_gaps.increment(1); - return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) }) + return Err(InsertErr::BlobTxHasNonceGap { transaction: Arc::new(new_blob_tx) }); } // the max cost executing this transaction requires @@ -1748,31 +1913,31 @@ impl AllTransactions { // check if the new blob would go into overdraft if cumulative_cost > on_chain_balance { // the transaction would go into overdraft - return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) }) + return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) }); } // ensure that a replacement would not shift already propagated blob transactions into // overdraft let id = new_blob_tx.transaction_id; let mut descendants = self.descendant_txs_inclusive(&id).peekable(); - if let Some((maybe_replacement, _)) = descendants.peek() { - if **maybe_replacement == new_blob_tx.transaction_id { - // replacement transaction - descendants.next(); - - // check if any of descendant blob transactions should be shifted into overdraft - for (_, tx) in descendants { - cumulative_cost += tx.transaction.cost(); - if tx.transaction.is_eip4844() && cumulative_cost > on_chain_balance { - // the transaction would shift - return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) }) - } + if let Some((maybe_replacement, _)) = descendants.peek() && + **maybe_replacement == new_blob_tx.transaction_id + { + // replacement transaction + descendants.next(); + + // check if any of descendant blob transactions should be shifted into overdraft + for (_, tx) in descendants { + cumulative_cost += tx.transaction.cost(); + if tx.transaction.is_eip4844() && cumulative_cost > on_chain_balance { + // the transaction would shift + return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) }) } } } } else if new_blob_tx.cost() > &on_chain_balance { // the transaction would go into overdraft - return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) }) + return Err(InsertErr::Overdraft { transaction: Arc::new(new_blob_tx) }); } Ok(new_blob_tx) @@ -1859,14 +2024,22 @@ impl AllTransactions { state.insert(TxState::NO_PARKED_ANCESTORS); } - // Check dynamic fee + // Check dynamic fee. Bypass protocol basefee check for gasless transactions (0/0 or legacy 0). let fee_cap = transaction.max_fee_per_gas(); - if fee_cap < self.minimal_protocol_basefee as u128 { - return Err(InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap }) - } - if fee_cap >= self.pending_fees.base_fee as u128 { + // Gasless transactions (no effective tip) should always be eligible with respect to + // basefee, provided they meet the minimal protocol fee cap requirement. This ensures they + // are returned by best_transactions. + if is_gasless(&transaction.transaction) { + // Mark as satisfying fee cap regardless of current pending base fee. state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); + } else { + if fee_cap < self.minimal_protocol_basefee as u128 { + return Err(InsertErr::FeeCapBelowMinimumProtocolFeeCap { transaction, fee_cap }) + } + if fee_cap >= self.pending_fees.base_fee as u128 { + state.insert(TxState::ENOUGH_FEE_CAP_BLOCK); + } } // placeholder for the replaced transaction, if any @@ -1896,7 +2069,7 @@ impl AllTransactions { return Err(InsertErr::Underpriced { transaction: pool_tx.transaction, existing: *entry.get().transaction.hash(), - }) + }); } let new_hash = *pool_tx.transaction.hash(); let new_transaction = pool_tx.transaction.clone(); @@ -1936,7 +2109,7 @@ impl AllTransactions { // If there's a nonce gap, we can shortcircuit if next_nonce != id.nonce { - break + break; } // close the nonce gap @@ -2101,7 +2274,6 @@ pub(crate) struct InsertOk { /// Where to move the transaction to. move_to: SubPool, /// Current state of the inserted tx. - #[cfg_attr(not(test), expect(dead_code))] state: TxState, /// The transaction that was replaced by this. replaced_tx: Option<(Arc>, SubPool)>, @@ -2585,6 +2757,239 @@ mod tests { assert!(inserted.state.intersects(expected_state)); } + #[test] + // Test that on_canonical_state_change doesn't double-process transactions + // when both fee and account updates would affect the same transaction + fn test_on_canonical_state_change_no_double_processing() { + let mut tx_factory = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + // Setup: Create a sender with a transaction in basefee pool + let tx = MockTransaction::eip1559().with_gas_price(50).with_gas_limit(30_000); + let sender = tx.sender(); + + // Set high base fee initially + let mut block_info = pool.block_info(); + block_info.pending_basefee = 100; + pool.set_block_info(block_info); + + let validated = tx_factory.validated(tx); + pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap(); + + // Get sender_id after the transaction has been added + let sender_id = tx_factory.ids.sender_id(&sender).unwrap(); + + assert_eq!(pool.basefee_pool.len(), 1); + assert_eq!(pool.pending_pool.len(), 0); + + // Now simulate a canonical state change with: + // 1. Lower base fee (would promote tx) + // 2. Account balance update (would also evaluate tx) + block_info.pending_basefee = 40; + + let mut changed_senders = FxHashMap::default(); + changed_senders.insert( + sender_id, + SenderInfo { + state_nonce: 0, + balance: U256::from(20_000_000), // Increased balance + }, + ); + + let outcome = pool.on_canonical_state_change( + block_info, + vec![], // no mined transactions + changed_senders, + PoolUpdateKind::Commit, + ); + + // Transaction should be promoted exactly once + assert_eq!(pool.pending_pool.len(), 1, "Transaction should be in pending pool"); + assert_eq!(pool.basefee_pool.len(), 0, "Transaction should not be in basefee pool"); + assert_eq!(outcome.promoted.len(), 1, "Should report exactly one promotion"); + } + + #[test] + // Regression test: ensure we don't double-count promotions when base fee + // decreases and account is updated. This test would fail before the fix. + fn test_canonical_state_change_with_basefee_update_regression() { + let mut tx_factory = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + // Create transactions from different senders to test independently + let sender_balance = U256::from(100_000_000); + + // Sender 1: tx will be promoted (gas price 60 > new base fee 50) + let tx1 = + MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000).with_nonce(0); + let sender1 = tx1.sender(); + + // Sender 2: tx will be promoted (gas price 55 > new base fee 50) + let tx2 = + MockTransaction::eip1559().with_gas_price(55).with_gas_limit(21_000).with_nonce(0); + let sender2 = tx2.sender(); + + // Sender 3: tx will NOT be promoted (gas price 45 < new base fee 50) + let tx3 = + MockTransaction::eip1559().with_gas_price(45).with_gas_limit(21_000).with_nonce(0); + let sender3 = tx3.sender(); + + // Set high initial base fee (all txs will go to basefee pool) + let mut block_info = pool.block_info(); + block_info.pending_basefee = 70; + pool.set_block_info(block_info); + + // Add all transactions + let validated1 = tx_factory.validated(tx1); + let validated2 = tx_factory.validated(tx2); + let validated3 = tx_factory.validated(tx3); + + pool.add_transaction(validated1, sender_balance, 0, None).unwrap(); + pool.add_transaction(validated2, sender_balance, 0, None).unwrap(); + pool.add_transaction(validated3, sender_balance, 0, None).unwrap(); + + let sender1_id = tx_factory.ids.sender_id(&sender1).unwrap(); + let sender2_id = tx_factory.ids.sender_id(&sender2).unwrap(); + let sender3_id = tx_factory.ids.sender_id(&sender3).unwrap(); + + // All should be in basefee pool initially + assert_eq!(pool.basefee_pool.len(), 3, "All txs should be in basefee pool"); + assert_eq!(pool.pending_pool.len(), 0, "No txs should be in pending pool"); + + // Now decrease base fee to 50 - this should promote tx1 and tx2 (prices 60 and 55) + // but not tx3 (price 45) + block_info.pending_basefee = 50; + + // Update all senders' balances (simulating account state changes) + let mut changed_senders = FxHashMap::default(); + changed_senders.insert( + sender1_id, + SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) }, + ); + changed_senders.insert( + sender2_id, + SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) }, + ); + changed_senders.insert( + sender3_id, + SenderInfo { state_nonce: 0, balance: sender_balance + U256::from(1000) }, + ); + + let outcome = pool.on_canonical_state_change( + block_info, + vec![], + changed_senders, + PoolUpdateKind::Commit, + ); + + // Check final state + assert_eq!(pool.pending_pool.len(), 2, "tx1 and tx2 should be promoted"); + assert_eq!(pool.basefee_pool.len(), 1, "tx3 should remain in basefee"); + + // CRITICAL: Should report exactly 2 promotions, not 4 (which would happen with + // double-processing) + assert_eq!( + outcome.promoted.len(), + 2, + "Should report exactly 2 promotions, not double-counted" + ); + + // Verify the correct transactions were promoted + let promoted_prices: Vec = + outcome.promoted.iter().map(|tx| tx.max_fee_per_gas()).collect(); + assert!(promoted_prices.contains(&60)); + assert!(promoted_prices.contains(&55)); + } + + #[test] + fn test_basefee_decrease_with_empty_senders() { + // Test that fee promotions still occur when basefee decreases + // even with no changed_senders + let mut tx_factory = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + // Create transaction that will be promoted when fee drops + let tx = MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000); + + // Set high initial base fee + let mut block_info = pool.block_info(); + block_info.pending_basefee = 100; + pool.set_block_info(block_info); + + // Add transaction - should go to basefee pool + let validated = tx_factory.validated(tx); + pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap(); + + assert_eq!(pool.basefee_pool.len(), 1); + assert_eq!(pool.pending_pool.len(), 0); + + // Decrease base fee with NO changed senders + block_info.pending_basefee = 50; + let outcome = pool.on_canonical_state_change( + block_info, + vec![], + FxHashMap::default(), // Empty changed_senders! + PoolUpdateKind::Commit, + ); + + // Transaction should still be promoted by fee-driven logic + assert_eq!(pool.pending_pool.len(), 1, "Fee decrease should promote tx"); + assert_eq!(pool.basefee_pool.len(), 0); + assert_eq!(outcome.promoted.len(), 1, "Should report promotion from fee update"); + } + + #[test] + fn test_basefee_decrease_account_makes_unfundable() { + // Test that when basefee decreases but account update makes tx unfundable, + // we don't get transient promote-then-discard double counting + let mut tx_factory = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + let tx = MockTransaction::eip1559().with_gas_price(60).with_gas_limit(21_000); + let sender = tx.sender(); + + // High initial base fee + let mut block_info = pool.block_info(); + block_info.pending_basefee = 100; + pool.set_block_info(block_info); + + let validated = tx_factory.validated(tx); + pool.add_transaction(validated, U256::from(10_000_000), 0, None).unwrap(); + let sender_id = tx_factory.ids.sender_id(&sender).unwrap(); + + assert_eq!(pool.basefee_pool.len(), 1); + + // Decrease base fee (would normally promote) but also drain account + block_info.pending_basefee = 50; + let mut changed_senders = FxHashMap::default(); + changed_senders.insert( + sender_id, + SenderInfo { + state_nonce: 0, + balance: U256::from(100), // Too low to pay for gas! + }, + ); + + let outcome = pool.on_canonical_state_change( + block_info, + vec![], + changed_senders, + PoolUpdateKind::Commit, + ); + + // With insufficient balance, transaction goes to queued pool + assert_eq!(pool.pending_pool.len(), 0, "Unfunded tx should not be in pending"); + assert_eq!(pool.basefee_pool.len(), 0, "Tx no longer in basefee pool"); + assert_eq!(pool.queued_pool.len(), 1, "Unfunded tx should be in queued pool"); + + // Transaction is not removed, just moved to queued + let tx_count = pool.all_transactions.txs.len(); + assert_eq!(tx_count, 1, "Transaction should still be in pool (in queued)"); + + assert_eq!(outcome.promoted.len(), 0, "Should not report promotion"); + assert_eq!(outcome.discarded.len(), 0, "Queued tx is not reported as discarded"); + } + #[test] fn insert_already_imported() { let on_chain_balance = U256::ZERO; @@ -2920,6 +3325,21 @@ mod tests { assert!(state.contains(TxState::NOT_TOO_MUCH_GAS)); } + #[test] + fn accept_legacy_zero_gas_price() { + let on_chain_balance = U256::ZERO; + let on_chain_nonce = 0; + let mut f = MockTransactionFactory::default(); + let mut pool = AllTransactions::default(); + + // Legacy (type 0) tx with gas price 0 and sufficient gas limit for a simple call + let tx = + MockTransaction::legacy().with_gas_price(0).with_gas_limit(21_000).with_value(U256::ZERO); + + // Should be accepted by the pool + let _ok = pool.insert_tx(f.validated(tx), on_chain_balance, on_chain_nonce).unwrap(); + } + #[test] fn update_basefee_subpools() { let mut f = MockTransactionFactory::default(); @@ -2932,7 +3352,7 @@ mod tests { assert_eq!(pool.pending_pool.len(), 1); - pool.update_basefee((tx.max_fee_per_gas() + 1) as u64); + pool.update_basefee((tx.max_fee_per_gas() + 1) as u64, |_| {}); assert!(pool.pending_pool.is_empty()); assert_eq!(pool.basefee_pool.len(), 1); @@ -2963,6 +3383,261 @@ mod tests { assert_eq!(pool.all_transactions.txs.get(&id).unwrap().subpool, SubPool::BaseFee) } + #[test] + fn basefee_decrease_promotes_affordable_and_keeps_unaffordable() { + use alloy_primitives::address; + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + // Create transactions that will be in basefee pool (can't afford initial high fee) + // Use different senders to avoid nonce gap issues + let sender_a = address!("0x000000000000000000000000000000000000000a"); + let sender_b = address!("0x000000000000000000000000000000000000000b"); + let sender_c = address!("0x000000000000000000000000000000000000000c"); + + let tx1 = MockTransaction::eip1559() + .set_sender(sender_a) + .set_nonce(0) + .set_max_fee(500) + .inc_limit(); + let tx2 = MockTransaction::eip1559() + .set_sender(sender_b) + .set_nonce(0) + .set_max_fee(600) + .inc_limit(); + let tx3 = MockTransaction::eip1559() + .set_sender(sender_c) + .set_nonce(0) + .set_max_fee(400) + .inc_limit(); + + // Set high initial basefee so transactions go to basefee pool + let mut block_info = pool.block_info(); + block_info.pending_basefee = 700; + pool.set_block_info(block_info); + + let validated1 = f.validated(tx1); + let validated2 = f.validated(tx2); + let validated3 = f.validated(tx3); + let id1 = *validated1.id(); + let id2 = *validated2.id(); + let id3 = *validated3.id(); + + // Add transactions - they should go to basefee pool due to high basefee + // All transactions have nonce 0 from different senders, so on_chain_nonce should be 0 for + // all + pool.add_transaction(validated1, U256::from(10_000), 0, None).unwrap(); + pool.add_transaction(validated2, U256::from(10_000), 0, None).unwrap(); + pool.add_transaction(validated3, U256::from(10_000), 0, None).unwrap(); + + // Debug: Check where transactions ended up + println!("Basefee pool len: {}", pool.basefee_pool.len()); + println!("Pending pool len: {}", pool.pending_pool.len()); + println!("tx1 subpool: {:?}", pool.all_transactions.txs.get(&id1).unwrap().subpool); + println!("tx2 subpool: {:?}", pool.all_transactions.txs.get(&id2).unwrap().subpool); + println!("tx3 subpool: {:?}", pool.all_transactions.txs.get(&id3).unwrap().subpool); + + // Verify they're in basefee pool + assert_eq!(pool.basefee_pool.len(), 3); + assert_eq!(pool.pending_pool.len(), 0); + assert_eq!(pool.all_transactions.txs.get(&id1).unwrap().subpool, SubPool::BaseFee); + assert_eq!(pool.all_transactions.txs.get(&id2).unwrap().subpool, SubPool::BaseFee); + assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee); + + // Now decrease basefee to trigger the zero-allocation optimization + let mut block_info = pool.block_info(); + block_info.pending_basefee = 450; // tx1 (500) and tx2 (600) can now afford it, tx3 (400) cannot + pool.set_block_info(block_info); + + // Verify the optimization worked correctly: + // - tx1 and tx2 should be promoted to pending (mathematical certainty) + // - tx3 should remain in basefee pool + // - All state transitions should be correct + assert_eq!(pool.basefee_pool.len(), 1); + assert_eq!(pool.pending_pool.len(), 2); + + // tx3 should still be in basefee pool (fee 400 < basefee 450) + assert_eq!(pool.all_transactions.txs.get(&id3).unwrap().subpool, SubPool::BaseFee); + + // tx1 and tx2 should be in pending pool with correct state bits + let tx1_meta = pool.all_transactions.txs.get(&id1).unwrap(); + let tx2_meta = pool.all_transactions.txs.get(&id2).unwrap(); + assert_eq!(tx1_meta.subpool, SubPool::Pending); + assert_eq!(tx2_meta.subpool, SubPool::Pending); + assert!(tx1_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK)); + assert!(tx2_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK)); + + // Verify that best_transactions returns the promoted transactions + let best: Vec<_> = pool.best_transactions().take(3).collect(); + assert_eq!(best.len(), 2); // Only tx1 and tx2 should be returned + assert!(best.iter().any(|tx| tx.id() == &id1)); + assert!(best.iter().any(|tx| tx.id() == &id2)); + } + + #[test] + fn apply_fee_updates_records_promotions_after_basefee_drop() { + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + let tx = MockTransaction::eip1559() + .with_gas_limit(21_000) + .with_max_fee(500) + .with_priority_fee(1); + let validated = f.validated(tx); + let id = *validated.id(); + pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap(); + + assert_eq!(pool.pending_pool.len(), 1); + + // Raise base fee beyond the transaction's cap so it gets parked in BaseFee pool. + pool.update_basefee(600, |_| {}); + assert!(pool.pending_pool.is_empty()); + assert_eq!(pool.basefee_pool.len(), 1); + + let prev_base_fee = 600; + let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee; + + // Simulate the canonical state path updating pending fees before applying promotions. + pool.all_transactions.pending_fees.base_fee = 400; + + let mut outcome = UpdateOutcome::default(); + pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome); + + assert_eq!(pool.pending_pool.len(), 1); + assert!(pool.basefee_pool.is_empty()); + assert_eq!(outcome.promoted.len(), 1); + assert_eq!(outcome.promoted[0].id(), &id); + assert_eq!(pool.all_transactions.pending_fees.base_fee, 400); + assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee); + + let tx_meta = pool.all_transactions.txs.get(&id).unwrap(); + assert_eq!(tx_meta.subpool, SubPool::Pending); + assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK)); + } + + #[test] + fn apply_fee_updates_records_promotions_after_blob_fee_drop() { + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee; + + let tx = MockTransaction::eip4844().with_blob_fee(initial_blob_fee + 100); + let validated = f.validated(tx.clone()); + let id = *validated.id(); + pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap(); + + assert_eq!(pool.pending_pool.len(), 1); + + // Raise blob fee beyond the transaction's cap so it gets parked in Blob pool. + let increased_blob_fee = tx.max_fee_per_blob_gas().unwrap() + 200; + pool.update_blob_fee(increased_blob_fee, Ordering::Equal, |_| {}); + assert!(pool.pending_pool.is_empty()); + assert_eq!(pool.blob_pool.len(), 1); + + let prev_base_fee = pool.all_transactions.pending_fees.base_fee; + let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee; + + // Simulate the canonical state path updating pending fees before applying promotions. + pool.all_transactions.pending_fees.blob_fee = tx.max_fee_per_blob_gas().unwrap(); + + let mut outcome = UpdateOutcome::default(); + pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome); + + assert_eq!(pool.pending_pool.len(), 1); + assert!(pool.blob_pool.is_empty()); + assert_eq!(outcome.promoted.len(), 1); + assert_eq!(outcome.promoted[0].id(), &id); + assert_eq!(pool.all_transactions.pending_fees.base_fee, prev_base_fee); + assert_eq!(pool.all_transactions.pending_fees.blob_fee, tx.max_fee_per_blob_gas().unwrap()); + + let tx_meta = pool.all_transactions.txs.get(&id).unwrap(); + assert_eq!(tx_meta.subpool, SubPool::Pending); + assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK)); + assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK)); + } + + #[test] + fn apply_fee_updates_promotes_blob_after_basefee_drop() { + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + let initial_blob_fee = pool.all_transactions.pending_fees.blob_fee; + + let tx = MockTransaction::eip4844() + .with_max_fee(500) + .with_priority_fee(1) + .with_blob_fee(initial_blob_fee + 100); + let validated = f.validated(tx); + let id = *validated.id(); + pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap(); + + assert_eq!(pool.pending_pool.len(), 1); + + // Raise base fee beyond the transaction's cap so it gets parked in Blob pool. + let high_base_fee = 600; + pool.update_basefee(high_base_fee, |_| {}); + assert!(pool.pending_pool.is_empty()); + assert_eq!(pool.blob_pool.len(), 1); + + let prev_base_fee = high_base_fee; + let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee; + + // Simulate applying a lower base fee while keeping blob fee unchanged. + pool.all_transactions.pending_fees.base_fee = 400; + + let mut outcome = UpdateOutcome::default(); + pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome); + + assert_eq!(pool.pending_pool.len(), 1); + assert!(pool.blob_pool.is_empty()); + assert_eq!(outcome.promoted.len(), 1); + assert_eq!(outcome.promoted[0].id(), &id); + assert_eq!(pool.all_transactions.pending_fees.base_fee, 400); + assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee); + + let tx_meta = pool.all_transactions.txs.get(&id).unwrap(); + assert_eq!(tx_meta.subpool, SubPool::Pending); + assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK)); + assert!(tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK)); + } + + #[test] + fn apply_fee_updates_demotes_after_basefee_rise() { + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + let tx = MockTransaction::eip1559() + .with_gas_limit(21_000) + .with_max_fee(400) + .with_priority_fee(1); + let validated = f.validated(tx); + let id = *validated.id(); + pool.add_transaction(validated, U256::from(1_000_000), 0, None).unwrap(); + + assert_eq!(pool.pending_pool.len(), 1); + + let prev_base_fee = pool.all_transactions.pending_fees.base_fee; + let prev_blob_fee = pool.all_transactions.pending_fees.blob_fee; + + // Simulate canonical path raising the base fee beyond the transaction's cap. + let new_base_fee = prev_base_fee + 1_000; + pool.all_transactions.pending_fees.base_fee = new_base_fee; + + let mut outcome = UpdateOutcome::default(); + pool.apply_fee_updates(prev_base_fee, prev_blob_fee, &mut outcome); + + assert!(pool.pending_pool.is_empty()); + assert_eq!(pool.basefee_pool.len(), 1); + assert!(outcome.promoted.is_empty()); + assert_eq!(pool.all_transactions.pending_fees.base_fee, new_base_fee); + assert_eq!(pool.all_transactions.pending_fees.blob_fee, prev_blob_fee); + + let tx_meta = pool.all_transactions.txs.get(&id).unwrap(); + assert_eq!(tx_meta.subpool, SubPool::BaseFee); + assert!(!tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK)); + } + #[test] fn get_highest_transaction_by_sender_and_nonce() { // Set up a mock transaction factory and a new transaction pool. @@ -3120,7 +3795,7 @@ mod tests { // set the base fee of the pool let pool_base_fee = 100; - pool.update_basefee(pool_base_fee); + pool.update_basefee(pool_base_fee, |_| {}); // 2 txs, that should put the pool over the size limit but not max txs let a_txs = MockTransactionSet::dependent(a_sender, 0, 3, TxType::Eip1559) @@ -3783,7 +4458,7 @@ mod tests { let mut f = MockTransactionFactory::default(); let mut pool = TxPool::new(MockOrdering::default(), Default::default()); - let sender = address!("1234567890123456789012345678901234567890"); + let sender = address!("0x1234567890123456789012345678901234567890"); let tx0 = f.validated_arc( MockTransaction::legacy().with_sender(sender).with_nonce(0).with_gas_price(10), ); @@ -3837,4 +4512,108 @@ mod tests { assert_eq!(t2.id(), tx2.id()); assert_eq!(t3.id(), tx3.id()); } + + #[test] + fn test_non_4844_blob_fee_bit_invariant() { + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + let non_4844_tx = MockTransaction::eip1559().set_max_fee(200).inc_limit(); + let validated = f.validated(non_4844_tx.clone()); + + assert!(!non_4844_tx.is_eip4844()); + pool.add_transaction(validated.clone(), U256::from(10_000), 0, None).unwrap(); + + // Core invariant: Non-4844 transactions must ALWAYS have ENOUGH_BLOB_FEE_CAP_BLOCK bit + let tx_meta = pool.all_transactions.txs.get(validated.id()).unwrap(); + assert!(tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK)); + assert_eq!(tx_meta.subpool, SubPool::Pending); + } + + #[test] + fn test_blob_fee_enforcement_only_applies_to_eip4844() { + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + // Set blob fee higher than EIP-4844 tx can afford + let mut block_info = pool.block_info(); + block_info.pending_blob_fee = Some(160); + block_info.pending_basefee = 100; + pool.set_block_info(block_info); + + let eip4844_tx = MockTransaction::eip4844() + .with_sender(address!("0x000000000000000000000000000000000000000a")) + .with_max_fee(200) + .with_blob_fee(150) // Less than block blob fee (160) + .inc_limit(); + + let non_4844_tx = MockTransaction::eip1559() + .with_sender(address!("0x000000000000000000000000000000000000000b")) + .set_max_fee(200) + .inc_limit(); + + let validated_4844 = f.validated(eip4844_tx); + let validated_non_4844 = f.validated(non_4844_tx); + + pool.add_transaction(validated_4844.clone(), U256::from(10_000), 0, None).unwrap(); + pool.add_transaction(validated_non_4844.clone(), U256::from(10_000), 0, None).unwrap(); + + let tx_4844_meta = pool.all_transactions.txs.get(validated_4844.id()).unwrap(); + let tx_non_4844_meta = pool.all_transactions.txs.get(validated_non_4844.id()).unwrap(); + + // EIP-4844: blob fee enforcement applies - insufficient blob fee removes bit + assert!(!tx_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK)); + assert_eq!(tx_4844_meta.subpool, SubPool::Blob); + + // Non-4844: blob fee enforcement does NOT apply - bit always remains true + assert!(tx_non_4844_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK)); + assert_eq!(tx_non_4844_meta.subpool, SubPool::Pending); + } + + #[test] + fn test_basefee_decrease_preserves_non_4844_blob_fee_bit() { + let mut f = MockTransactionFactory::default(); + let mut pool = TxPool::new(MockOrdering::default(), Default::default()); + + // Create non-4844 transaction with fee that initially can't afford high basefee + let non_4844_tx = MockTransaction::eip1559() + .with_sender(address!("0x000000000000000000000000000000000000000a")) + .set_max_fee(500) // Can't afford basefee of 600 + .inc_limit(); + + // Set high basefee so transaction goes to BaseFee pool initially + pool.update_basefee(600, |_| {}); + + let validated = f.validated(non_4844_tx); + let tx_id = *validated.id(); + pool.add_transaction(validated, U256::from(10_000), 0, None).unwrap(); + + // Initially should be in BaseFee pool but STILL have blob fee bit (critical invariant) + let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap(); + assert_eq!(tx_meta.subpool, SubPool::BaseFee); + assert!( + tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK), + "Non-4844 tx in BaseFee pool must retain ENOUGH_BLOB_FEE_CAP_BLOCK bit" + ); + + // Decrease basefee - transaction should be promoted to Pending + // This is where PR #18215 bug would manifest: blob fee bit incorrectly removed + pool.update_basefee(400, |_| {}); + + // After basefee decrease: should be promoted to Pending with blob fee bit preserved + let tx_meta = pool.all_transactions.txs.get(&tx_id).unwrap(); + assert_eq!( + tx_meta.subpool, + SubPool::Pending, + "Non-4844 tx should be promoted from BaseFee to Pending after basefee decrease" + ); + assert!( + tx_meta.state.contains(TxState::ENOUGH_BLOB_FEE_CAP_BLOCK), + "Non-4844 tx must NEVER lose ENOUGH_BLOB_FEE_CAP_BLOCK bit during basefee promotion" + ); + assert!( + tx_meta.state.contains(TxState::ENOUGH_FEE_CAP_BLOCK), + "Non-4844 tx should gain ENOUGH_FEE_CAP_BLOCK bit after basefee decrease" + ); + } } diff --git a/crates/transaction-pool/src/test_utils/mock.rs b/crates/transaction-pool/src/test_utils/mock.rs index 4c0c5909839..2983c6ea343 100644 --- a/crates/transaction-pool/src/test_utils/mock.rs +++ b/crates/transaction-pool/src/test_utils/mock.rs @@ -54,7 +54,31 @@ pub fn mock_tx_pool() -> MockTxPool { /// Sets the value for the field macro_rules! set_value { - ($this:ident => $field:ident) => { + // For mutable references + (&mut $this:expr => $field:ident) => {{ + let new_value = $field; + match $this { + MockTransaction::Legacy { $field, .. } => { + *$field = new_value; + } + MockTransaction::Eip1559 { $field, .. } => { + *$field = new_value; + } + MockTransaction::Eip4844 { $field, .. } => { + *$field = new_value; + } + MockTransaction::Eip2930 { $field, .. } => { + *$field = new_value; + } + MockTransaction::Eip7702 { $field, .. } => { + *$field = new_value; + } + } + // Ensure the tx cost is always correct after each mutation. + $this.update_cost(); + }}; + // For owned values + ($this:expr => $field:ident) => {{ let new_value = $field; match $this { MockTransaction::Legacy { ref mut $field, .. } | @@ -67,7 +91,7 @@ macro_rules! set_value { } // Ensure the tx cost is always correct after each mutation. $this.update_cost(); - }; + }}; } /// Gets the value for the field @@ -89,7 +113,7 @@ macro_rules! make_setters_getters { paste! {$( /// Sets the value of the specified field. pub fn [](&mut self, $name: $t) -> &mut Self { - set_value!(self => $name); + set_value!(&mut self => $name); self } diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index f2ed3822a91..9552646652b 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -60,7 +60,7 @@ use crate::{ validate::ValidPoolTransaction, AddedTransactionOutcome, AllTransactionsEvents, }; -use alloy_consensus::{error::ValueError, BlockHeader, Signed, Typed2718}; +use alloy_consensus::{error::ValueError, transaction::TxHashRef, BlockHeader, Signed, Typed2718}; use alloy_eips::{ eip2718::{Encodable2718, WithEncoded}, eip2930::AccessList, diff --git a/crates/transaction-pool/src/validate/eth.rs b/crates/transaction-pool/src/validate/eth.rs index b32401f2cbb..27232e30dda 100644 --- a/crates/transaction-pool/src/validate/eth.rs +++ b/crates/transaction-pool/src/validate/eth.rs @@ -9,9 +9,11 @@ use crate::{ metrics::TxPoolValidationMetrics, traits::TransactionOrigin, validate::{ValidTransaction, ValidationTask, MAX_INIT_CODE_BYTE_SIZE}, - EthBlobTransactionSidecar, EthPoolTransaction, LocalTransactionConfig, - TransactionValidationOutcome, TransactionValidationTaskExecutor, TransactionValidator, + Address, BlobTransactionSidecarVariant, EthBlobTransactionSidecar, EthPoolTransaction, + LocalTransactionConfig, TransactionValidationOutcome, TransactionValidationTaskExecutor, + TransactionValidator, }; + use alloy_consensus::{ constants::{ EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, @@ -24,11 +26,12 @@ use alloy_eips::{ eip7840::BlobParams, }; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; +use reth_optimism_primitives::is_gasless; use reth_primitives_traits::{ - constants::MAX_TX_GAS_LIMIT_OSAKA, transaction::error::InvalidTransactionError, Block, + constants::MAX_TX_GAS_LIMIT_OSAKA, transaction::error::InvalidTransactionError, Account, Block, GotExpected, SealedBlock, }; -use reth_storage_api::{AccountInfoReader, StateProviderFactory}; +use reth_storage_api::{AccountInfoReader, BytecodeReader, StateProviderFactory}; use reth_tasks::TaskSpawner; use std::{ marker::PhantomData, @@ -39,13 +42,59 @@ use std::{ time::Instant, }; use tokio::sync::Mutex; +use reth_gas_station::{validate_gasless_tx, GasStationConfig}; +use reth_primitives_traits::transaction::gasless_error::GaslessValidationError; -/// Validator for Ethereum transactions. -/// It is a [`TransactionValidator`] implementation that validates ethereum transaction. -#[derive(Debug, Clone)] +/// A [`TransactionValidator`] implementation that validates ethereum transaction. +/// +/// It supports all known ethereum transaction types: +/// - Legacy +/// - EIP-2718 +/// - EIP-1559 +/// - EIP-4844 +/// - EIP-7702 +/// +/// And enforces additional constraints such as: +/// - Maximum transaction size +/// - Maximum gas limit +/// +/// And adheres to the configured [`LocalTransactionConfig`]. +#[derive(Debug)] pub struct EthTransactionValidator { - /// The type that performs the actual validation. - inner: Arc>, + /// This type fetches account info from the db + client: Client, + /// Blobstore used for fetching re-injected blob transactions. + blob_store: Box, + /// tracks activated forks relevant for transaction validation + fork_tracker: ForkTracker, + /// Fork indicator whether we are using EIP-2718 type transactions. + eip2718: bool, + /// Fork indicator whether we are using EIP-1559 type transactions. + eip1559: bool, + /// Fork indicator whether we are using EIP-4844 blob transactions. + eip4844: bool, + /// Fork indicator whether we are using EIP-7702 type transactions. + eip7702: bool, + /// The current max gas limit + block_gas_limit: AtomicU64, + /// The current tx fee cap limit in wei locally submitted into the pool. + tx_fee_cap: Option, + /// Minimum priority fee to enforce for acceptance into the pool. + minimum_priority_fee: Option, + /// Stores the setup and parameters needed for validating KZG proofs. + kzg_settings: EnvKzgSettings, + /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions. + local_transactions_config: LocalTransactionConfig, + /// Maximum size in bytes a single transaction can have in order to be accepted into the pool. + max_tx_input_bytes: usize, + /// Maximum gas limit for individual transactions + max_tx_gas_limit: Option, + /// Disable balance checks during transaction validation + disable_balance_check: bool, + /// Marker for the transaction type + _marker: PhantomData, + /// Metrics for tsx pool validation + validation_metrics: TxPoolValidationMetrics, } impl EthTransactionValidator { @@ -57,60 +106,73 @@ impl EthTransactionValidator { self.client().chain_spec() } + /// Returns the configured chain id + pub fn chain_id(&self) -> u64 + where + Client: ChainSpecProvider, + { + self.client().chain_spec().chain().id() + } + /// Returns the configured client - pub fn client(&self) -> &Client { - &self.inner.client + pub const fn client(&self) -> &Client { + &self.client } /// Returns the tracks activated forks relevant for transaction validation - pub fn fork_tracker(&self) -> &ForkTracker { - &self.inner.fork_tracker + pub const fn fork_tracker(&self) -> &ForkTracker { + &self.fork_tracker } /// Returns if there are EIP-2718 type transactions - pub fn eip2718(&self) -> bool { - self.inner.eip2718 + pub const fn eip2718(&self) -> bool { + self.eip2718 } /// Returns if there are EIP-1559 type transactions - pub fn eip1559(&self) -> bool { - self.inner.eip1559 + pub const fn eip1559(&self) -> bool { + self.eip1559 } /// Returns if there are EIP-4844 blob transactions - pub fn eip4844(&self) -> bool { - self.inner.eip4844 + pub const fn eip4844(&self) -> bool { + self.eip4844 } /// Returns if there are EIP-7702 type transactions - pub fn eip7702(&self) -> bool { - self.inner.eip7702 + pub const fn eip7702(&self) -> bool { + self.eip7702 } /// Returns the current tx fee cap limit in wei locally submitted into the pool - pub fn tx_fee_cap(&self) -> &Option { - &self.inner.tx_fee_cap + pub const fn tx_fee_cap(&self) -> &Option { + &self.tx_fee_cap } /// Returns the minimum priority fee to enforce for acceptance into the pool - pub fn minimum_priority_fee(&self) -> &Option { - &self.inner.minimum_priority_fee + pub const fn minimum_priority_fee(&self) -> &Option { + &self.minimum_priority_fee } /// Returns the setup and parameters needed for validating KZG proofs. - pub fn kzg_settings(&self) -> &EnvKzgSettings { - &self.inner.kzg_settings + pub const fn kzg_settings(&self) -> &EnvKzgSettings { + &self.kzg_settings } /// Returns the config to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions.. - pub fn local_transactions_config(&self) -> &LocalTransactionConfig { - &self.inner.local_transactions_config + pub const fn local_transactions_config(&self) -> &LocalTransactionConfig { + &self.local_transactions_config } /// Returns the maximum size in bytes a single transaction can have in order to be accepted into /// the pool. - pub fn max_tx_input_bytes(&self) -> usize { - self.inner.max_tx_input_bytes + pub const fn max_tx_input_bytes(&self) -> usize { + self.max_tx_input_bytes + } + + /// Returns whether balance checks are disabled for this validator. + pub const fn disable_balance_check(&self) -> bool { + self.disable_balance_check } } @@ -121,7 +183,7 @@ where { /// Returns the current max gas limit pub fn block_gas_limit(&self) -> u64 { - self.inner.max_gas_limit() + self.max_gas_limit() } /// Validates a single transaction. @@ -132,7 +194,7 @@ where origin: TransactionOrigin, transaction: Tx, ) -> TransactionValidationOutcome { - self.inner.validate_one_with_provider(origin, transaction, &mut None) + self.validate_one_with_provider(origin, transaction, &mut None) } /// Validates a single transaction with the provided state provider. @@ -147,115 +209,7 @@ where transaction: Tx, state: &mut Option>, ) -> TransactionValidationOutcome { - self.inner.validate_one_with_provider(origin, transaction, state) - } -} - -impl TransactionValidator for EthTransactionValidator -where - Client: ChainSpecProvider + StateProviderFactory, - Tx: EthPoolTransaction, -{ - type Transaction = Tx; - - async fn validate_transaction( - &self, - origin: TransactionOrigin, - transaction: Self::Transaction, - ) -> TransactionValidationOutcome { - self.validate_one(origin, transaction) - } - - async fn validate_transactions( - &self, - transactions: Vec<(TransactionOrigin, Self::Transaction)>, - ) -> Vec> { - self.inner.validate_batch(transactions) - } - - async fn validate_transactions_with_origin( - &self, - origin: TransactionOrigin, - transactions: impl IntoIterator + Send, - ) -> Vec> { - self.inner.validate_batch_with_origin(origin, transactions) - } - - fn on_new_head_block(&self, new_tip_block: &SealedBlock) - where - B: Block, - { - self.inner.on_new_head_block(new_tip_block.header()) - } -} - -/// A [`TransactionValidator`] implementation that validates ethereum transaction. -/// -/// It supports all known ethereum transaction types: -/// - Legacy -/// - EIP-2718 -/// - EIP-1559 -/// - EIP-4844 -/// - EIP-7702 -/// -/// And enforces additional constraints such as: -/// - Maximum transaction size -/// - Maximum gas limit -/// -/// And adheres to the configured [`LocalTransactionConfig`]. -#[derive(Debug)] -pub(crate) struct EthTransactionValidatorInner { - /// This type fetches account info from the db - client: Client, - /// Blobstore used for fetching re-injected blob transactions. - blob_store: Box, - /// tracks activated forks relevant for transaction validation - fork_tracker: ForkTracker, - /// Fork indicator whether we are using EIP-2718 type transactions. - eip2718: bool, - /// Fork indicator whether we are using EIP-1559 type transactions. - eip1559: bool, - /// Fork indicator whether we are using EIP-4844 blob transactions. - eip4844: bool, - /// Fork indicator whether we are using EIP-7702 type transactions. - eip7702: bool, - /// The current max gas limit - block_gas_limit: AtomicU64, - /// The current tx fee cap limit in wei locally submitted into the pool. - tx_fee_cap: Option, - /// Minimum priority fee to enforce for acceptance into the pool. - minimum_priority_fee: Option, - /// Stores the setup and parameters needed for validating KZG proofs. - kzg_settings: EnvKzgSettings, - /// How to handle [`TransactionOrigin::Local`](TransactionOrigin) transactions. - local_transactions_config: LocalTransactionConfig, - /// Maximum size in bytes a single transaction can have in order to be accepted into the pool. - max_tx_input_bytes: usize, - /// Maximum gas limit for individual transactions - max_tx_gas_limit: Option, - /// Marker for the transaction type - _marker: PhantomData, - /// Metrics for tsx pool validation - validation_metrics: TxPoolValidationMetrics, -} - -// === impl EthTransactionValidatorInner === - -impl EthTransactionValidatorInner { - /// Returns the configured chain id - pub(crate) fn chain_id(&self) -> u64 { - self.client.chain_spec().chain().id() - } -} - -impl EthTransactionValidatorInner -where - Client: ChainSpecProvider + StateProviderFactory, - Tx: EthPoolTransaction, -{ - /// Returns the configured chain spec - fn chain_spec(&self) -> Arc { - self.client.chain_spec() + self.validate_one_with_provider(origin, transaction, state) } /// Validates a single transaction using an optional cached state provider. @@ -388,10 +342,10 @@ where } // Check whether the init code size has been exceeded. - if self.fork_tracker.is_shanghai_activated() { - if let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE) { - return Err(TransactionValidationOutcome::Invalid(transaction, err)) - } + if self.fork_tracker.is_shanghai_activated() && + let Err(err) = transaction.ensure_max_init_code_size(MAX_INIT_CODE_BYTE_SIZE) + { + return Err(TransactionValidationOutcome::Invalid(transaction, err)) } // Checks for gas limit @@ -408,16 +362,16 @@ where } // Check individual transaction gas limit if configured - if let Some(max_tx_gas_limit) = self.max_tx_gas_limit { - if transaction_gas_limit > max_tx_gas_limit { - return Err(TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::MaxTxGasLimitExceeded( - transaction_gas_limit, - max_tx_gas_limit, - ), - )) - } + if let Some(max_tx_gas_limit) = self.max_tx_gas_limit && + transaction_gas_limit > max_tx_gas_limit + { + return Err(TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::MaxTxGasLimitExceeded( + transaction_gas_limit, + max_tx_gas_limit, + ), + )) } // Ensure max_priority_fee_per_gas (if EIP1559) is less than max_fee_per_gas if any. @@ -455,31 +409,34 @@ where } // Drop non-local transactions with a fee lower than the configured fee for acceptance into - // the pool. - if !is_local && - transaction.is_dynamic_fee() && - transaction.max_priority_fee_per_gas() < self.minimum_priority_fee + // the pool. Bypass for gasless transactions (legacy gas_price == 0 or 1559 caps == 0). + if !is_local && transaction.is_dynamic_fee() { + let is_gasless_1559 = transaction.max_fee_per_gas() == 0 + && transaction.max_priority_fee_per_gas() == Some(0); + if !is_gasless_1559 + && transaction.max_priority_fee_per_gas() < self.minimum_priority_fee + { + return Err(TransactionValidationOutcome::Invalid( + transaction, + InvalidPoolTransactionError::PriorityFeeBelowMinimum { + minimum_priority_fee: self + .minimum_priority_fee + .expect("minimum priority fee is expected inside if statement"), + }, + )); + } + } + + // Checks for chainid + if let Some(chain_id) = transaction.chain_id() && + chain_id != self.chain_id() { return Err(TransactionValidationOutcome::Invalid( transaction, - InvalidPoolTransactionError::PriorityFeeBelowMinimum { - minimum_priority_fee: self - .minimum_priority_fee - .expect("minimum priority fee is expected inside if statement"), - }, + InvalidTransactionError::ChainIdMismatch.into(), )) } - // Checks for chainid - if let Some(chain_id) = transaction.chain_id() { - if chain_id != self.chain_id() { - return Err(TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::ChainIdMismatch.into(), - )) - } - } - if transaction.is_eip7702() { // Prague fork is required for 7702 txs if !self.fork_tracker.is_prague_activated() { @@ -567,21 +524,101 @@ where } }; + // check for bytecode + match self.validate_sender_bytecode(&transaction, &account, &state) { + Err(outcome) => return outcome, + Ok(Err(err)) => return TransactionValidationOutcome::Invalid(transaction, err), + _ => {} + }; + + // Checks for nonce + if let Err(err) = self.validate_sender_nonce(&transaction, &account) { + return TransactionValidationOutcome::Invalid(transaction, err) + } + + // checks for max cost not exceedng account_balance + if let Err(err) = self.validate_sender_balance(&transaction, &account) { + return TransactionValidationOutcome::Invalid(transaction, err) + } + + // heavy blob tx validation + let maybe_blob_sidecar = match self.validate_eip4844(&mut transaction) { + Err(err) => return TransactionValidationOutcome::Invalid(transaction, err), + Ok(sidecar) => sidecar, + }; + + // Validate gasless + if is_gasless(&transaction) { + let full_state = self.client.latest().unwrap(); + let to = match transaction.to() { + Some(to) => *to, + None => { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::GaslessValidationError( + GaslessValidationError::Create, + ).into(), + ); + } + }; + + if let Err(err) = validate_gasless_tx( + &GasStationConfig::default(), + &full_state, + to.into(), + *transaction.sender_ref(), + transaction.gas_limit(), + None, + ) { + return TransactionValidationOutcome::Invalid( + transaction, + InvalidTransactionError::GaslessValidationError(err).into(), + ); + } + } + + let authorities = self.recover_authorities(&transaction); + + // Return the valid transaction + TransactionValidationOutcome::Valid { + balance: account.balance, + state_nonce: account.nonce, + bytecode_hash: account.bytecode_hash, + transaction: ValidTransaction::new(transaction, maybe_blob_sidecar), + // by this point assume all external transactions should be propagated + propagate: match origin { + TransactionOrigin::External => true, + TransactionOrigin::Local => { + self.local_transactions_config.propagate_local_transactions + } + TransactionOrigin::Private => false, + }, + authorities, + } + } + + /// Validates that the sender’s account has valid or no bytecode. + pub fn validate_sender_bytecode( + &self, + transaction: &Tx, + sender: &Account, + state: impl BytecodeReader, + ) -> Result, TransactionValidationOutcome> { // Unless Prague is active, the signer account shouldn't have bytecode. // // If Prague is active, only EIP-7702 bytecode is allowed for the sender. // // Any other case means that the account is not an EOA, and should not be able to send // transactions. - if let Some(code_hash) = &account.bytecode_hash { + if let Some(code_hash) = &sender.bytecode_hash { let is_eip7702 = if self.fork_tracker.is_prague_activated() { match state.bytecode_by_hash(code_hash) { Ok(bytecode) => bytecode.unwrap_or_default().is_eip7702(), Err(err) => { - return TransactionValidationOutcome::Error( + return Err(TransactionValidationOutcome::Error( *transaction.hash(), Box::new(err), - ) + )) } } } else { @@ -589,38 +626,53 @@ where }; if !is_eip7702 { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::SignerAccountHasBytecode.into(), - ) + return Ok(Err(InvalidTransactionError::SignerAccountHasBytecode.into())) } } + Ok(Ok(())) + } + /// Checks if the transaction nonce is valid. + pub fn validate_sender_nonce( + &self, + transaction: &Tx, + sender: &Account, + ) -> Result<(), InvalidPoolTransactionError> { let tx_nonce = transaction.nonce(); - // Checks for nonce - if tx_nonce < account.nonce { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::NonceNotConsistent { tx: tx_nonce, state: account.nonce } - .into(), - ) + if tx_nonce < sender.nonce { + return Err(InvalidTransactionError::NonceNotConsistent { + tx: tx_nonce, + state: sender.nonce, + } + .into()) } + Ok(()) + } + /// Ensures the sender has sufficient account balance. + pub fn validate_sender_balance( + &self, + transaction: &Tx, + sender: &Account, + ) -> Result<(), InvalidPoolTransactionError> { let cost = transaction.cost(); - // Checks for max cost - if cost > &account.balance { + if !self.disable_balance_check && cost > &sender.balance { let expected = *cost; - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::InsufficientFunds( - GotExpected { got: account.balance, expected }.into(), - ) - .into(), + return Err(InvalidTransactionError::InsufficientFunds( + GotExpected { got: sender.balance, expected }.into(), ) + .into()) } + Ok(()) + } + /// Validates EIP-4844 blob sidecar data and returns the extracted sidecar, if any. + pub fn validate_eip4844( + &self, + transaction: &mut Tx, + ) -> Result, InvalidPoolTransactionError> { let mut maybe_blob_sidecar = None; // heavy blob tx validation @@ -629,25 +681,19 @@ where match transaction.take_blob() { EthBlobTransactionSidecar::None => { // this should not happen - return TransactionValidationOutcome::Invalid( - transaction, - InvalidTransactionError::TxTypeNotSupported.into(), - ) + return Err(InvalidTransactionError::TxTypeNotSupported.into()) } EthBlobTransactionSidecar::Missing => { // This can happen for re-injected blob transactions (on re-org), since the blob // is stripped from the transaction and not included in a block. // check if the blob is in the store, if it's included we previously validated // it and inserted it - if matches!(self.blob_store.contains(*transaction.hash()), Ok(true)) { + if self.blob_store.contains(*transaction.hash()).is_ok_and(|c| c) { // validated transaction is already in the store } else { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::MissingEip4844BlobSidecar, - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::MissingEip4844BlobSidecar, + )) } } EthBlobTransactionSidecar::Present(sidecar) => { @@ -655,30 +701,21 @@ where if self.fork_tracker.is_osaka_activated() { if sidecar.is_eip4844() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka, - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::UnexpectedEip4844SidecarAfterOsaka, + )) } } else if sidecar.is_eip7594() { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka, - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::UnexpectedEip7594SidecarBeforeOsaka, + )) } // validate the blob if let Err(err) = transaction.validate_blob(&sidecar, self.kzg_settings.get()) { - return TransactionValidationOutcome::Invalid( - transaction, - InvalidPoolTransactionError::Eip4844( - Eip4844PoolTransactionError::InvalidEip4844Blob(err), - ), - ) + return Err(InvalidPoolTransactionError::Eip4844( + Eip4844PoolTransactionError::InvalidEip4844Blob(err), + )) } // Record the duration of successful blob validation as histogram self.validation_metrics.blob_validation_duration.record(now.elapsed()); @@ -687,26 +724,14 @@ where } } } + Ok(maybe_blob_sidecar) + } - let authorities = transaction.authorization_list().map(|auths| { - auths.iter().flat_map(|auth| auth.recover_authority()).collect::>() - }); - // Return the valid transaction - TransactionValidationOutcome::Valid { - balance: account.balance, - state_nonce: account.nonce, - bytecode_hash: account.bytecode_hash, - transaction: ValidTransaction::new(transaction, maybe_blob_sidecar), - // by this point assume all external transactions should be propagated - propagate: match origin { - TransactionOrigin::External => true, - TransactionOrigin::Local => { - self.local_transactions_config.propagate_local_transactions - } - TransactionOrigin::Private => false, - }, - authorities, - } + /// Returns the recovered authorities for the given transaction + fn recover_authorities(&self, transaction: &Tx) -> std::option::Option> { + transaction + .authorization_list() + .map(|auths| auths.iter().flat_map(|auth| auth.recover_authority()).collect::>()) } /// Validates all given transactions. @@ -768,6 +793,44 @@ where } } +impl TransactionValidator for EthTransactionValidator +where + Client: ChainSpecProvider + StateProviderFactory, + Tx: EthPoolTransaction, +{ + type Transaction = Tx; + + async fn validate_transaction( + &self, + origin: TransactionOrigin, + transaction: Self::Transaction, + ) -> TransactionValidationOutcome { + self.validate_one(origin, transaction) + } + + async fn validate_transactions( + &self, + transactions: Vec<(TransactionOrigin, Self::Transaction)>, + ) -> Vec> { + self.validate_batch(transactions) + } + + async fn validate_transactions_with_origin( + &self, + origin: TransactionOrigin, + transactions: impl IntoIterator + Send, + ) -> Vec> { + self.validate_batch_with_origin(origin, transactions) + } + + fn on_new_head_block(&self, new_tip_block: &SealedBlock) + where + B: Block, + { + self.on_new_head_block(new_tip_block.header()) + } +} + /// A builder for [`EthTransactionValidator`] and [`TransactionValidationTaskExecutor`] #[derive(Debug)] pub struct EthTransactionValidatorBuilder { @@ -809,6 +872,8 @@ pub struct EthTransactionValidatorBuilder { max_tx_input_bytes: usize, /// Maximum gas limit for individual transactions max_tx_gas_limit: Option, + /// Disable balance checks during transaction validation + disable_balance_check: bool, } impl EthTransactionValidatorBuilder { @@ -852,6 +917,9 @@ impl EthTransactionValidatorBuilder { // max blob count is prague by default max_blob_count: BlobParams::prague().max_blobs_per_tx, + + // balance checks are enabled by default + disable_balance_check: false, } } @@ -961,7 +1029,8 @@ impl EthTransactionValidatorBuilder { /// Configures validation rules based on the head block's timestamp. /// - /// For example, whether the Shanghai and Cancun hardfork is activated at launch. + /// For example, whether the Shanghai and Cancun hardfork is activated at launch, or max blob + /// counts. pub fn with_head_timestamp(mut self, timestamp: u64) -> Self where Client: ChainSpecProvider, @@ -1007,6 +1076,12 @@ impl EthTransactionValidatorBuilder { self } + /// Disables balance checks during transaction validation + pub const fn disable_balance_check(mut self) -> Self { + self.disable_balance_check = true; + self + } + /// Builds a the [`EthTransactionValidator`] without spawning validator tasks. pub fn build(self, blob_store: S) -> EthTransactionValidator where @@ -1029,15 +1104,11 @@ impl EthTransactionValidatorBuilder { local_transactions_config, max_tx_input_bytes, max_tx_gas_limit, - .. + disable_balance_check, + max_blob_count, + additional_tasks: _, } = self; - let max_blob_count = if prague { - BlobParams::prague().max_blobs_per_tx - } else { - BlobParams::cancun().max_blobs_per_tx - }; - let fork_tracker = ForkTracker { shanghai: AtomicBool::new(shanghai), cancun: AtomicBool::new(cancun), @@ -1046,7 +1117,7 @@ impl EthTransactionValidatorBuilder { max_blob_count: AtomicU64::new(max_blob_count), }; - let inner = EthTransactionValidatorInner { + EthTransactionValidator { client, eip2718, eip1559, @@ -1061,11 +1132,10 @@ impl EthTransactionValidatorBuilder { local_transactions_config, max_tx_input_bytes, max_tx_gas_limit, + disable_balance_check, _marker: Default::default(), validation_metrics: TxPoolValidationMetrics::default(), - }; - - EthTransactionValidator { inner: Arc::new(inner) } + } } /// Builds a [`EthTransactionValidator`] and spawns validation tasks via the @@ -1107,7 +1177,7 @@ impl EthTransactionValidatorBuilder { let to_validation_task = Arc::new(Mutex::new(tx)); - TransactionValidationTaskExecutor { validator, to_validation_task } + TransactionValidationTaskExecutor { validator: Arc::new(validator), to_validation_task } } } @@ -1610,4 +1680,40 @@ mod tests { let invalid = outcome.as_invalid().unwrap(); assert!(invalid.is_oversized()); } + + #[tokio::test] + async fn valid_with_disabled_balance_check() { + let transaction = get_transaction(); + let provider = MockEthProvider::default(); + + // Set account with 0 balance + provider.add_account( + transaction.sender(), + ExtendedAccount::new(transaction.nonce(), alloy_primitives::U256::ZERO), + ); + + // Valdiate with balance check enabled + let validator = EthTransactionValidatorBuilder::new(provider.clone()) + .build(InMemoryBlobStore::default()); + + let outcome = validator.validate_one(TransactionOrigin::External, transaction.clone()); + let expected_cost = *transaction.cost(); + if let TransactionValidationOutcome::Invalid(_, err) = outcome { + assert!(matches!( + err, + InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(ref funds_err)) + if funds_err.got == alloy_primitives::U256::ZERO && funds_err.expected == expected_cost + )); + } else { + panic!("Expected Invalid outcome with InsufficientFunds error"); + } + + // Valdiate with balance check disabled + let validator = EthTransactionValidatorBuilder::new(provider) + .disable_balance_check() // This should allow the transaction through despite zero balance + .build(InMemoryBlobStore::default()); + + let outcome = validator.validate_one(TransactionOrigin::External, transaction); + assert!(outcome.is_valid()); // Should be valid because balance check is disabled + } } diff --git a/crates/transaction-pool/src/validate/mod.rs b/crates/transaction-pool/src/validate/mod.rs index e1104f713ee..725f83c392c 100644 --- a/crates/transaction-pool/src/validate/mod.rs +++ b/crates/transaction-pool/src/validate/mod.rs @@ -335,12 +335,12 @@ impl ValidPoolTransaction { } /// Returns the internal identifier for the sender of this transaction - pub(crate) const fn sender_id(&self) -> SenderId { + pub const fn sender_id(&self) -> SenderId { self.transaction_id.sender } /// Returns the internal identifier for this transaction. - pub(crate) const fn id(&self) -> &TransactionId { + pub const fn id(&self) -> &TransactionId { &self.transaction_id } diff --git a/crates/transaction-pool/src/validate/task.rs b/crates/transaction-pool/src/validate/task.rs index 93f16a585b0..fc22ce4ceb1 100644 --- a/crates/transaction-pool/src/validate/task.rs +++ b/crates/transaction-pool/src/validate/task.rs @@ -2,6 +2,7 @@ use crate::{ blobstore::BlobStore, + metrics::TxPoolValidatorMetrics, validate::{EthTransactionValidatorBuilder, TransactionValidatorError}, EthTransactionValidator, PoolTransaction, TransactionOrigin, TransactionValidationOutcome, TransactionValidator, @@ -33,10 +34,18 @@ pub struct ValidationTask { } impl ValidationTask { - /// Creates a new cloneable task pair + /// Creates a new cloneable task pair. + /// + /// The sender sends new (transaction) validation tasks to an available validation task. pub fn new() -> (ValidationJobSender, Self) { - let (tx, rx) = mpsc::channel(1); - (ValidationJobSender { tx }, Self::with_receiver(rx)) + Self::with_capacity(1) + } + + /// Creates a new cloneable task pair with the given channel capacity. + pub fn with_capacity(capacity: usize) -> (ValidationJobSender, Self) { + let (tx, rx) = mpsc::channel(capacity); + let metrics = TxPoolValidatorMetrics::default(); + (ValidationJobSender { tx, metrics }, Self::with_receiver(rx)) } /// Creates a new task with the given receiver. @@ -64,6 +73,7 @@ impl std::fmt::Debug for ValidationTask { #[derive(Debug)] pub struct ValidationJobSender { tx: mpsc::Sender + Send>>>, + metrics: TxPoolValidatorMetrics, } impl ValidationJobSender { @@ -72,20 +82,36 @@ impl ValidationJobSender { &self, job: Pin + Send>>, ) -> Result<(), TransactionValidatorError> { - self.tx.send(job).await.map_err(|_| TransactionValidatorError::ValidationServiceUnreachable) + self.metrics.inflight_validation_jobs.increment(1); + let res = self + .tx + .send(job) + .await + .map_err(|_| TransactionValidatorError::ValidationServiceUnreachable); + self.metrics.inflight_validation_jobs.decrement(1); + res } } /// A [`TransactionValidator`] implementation that validates ethereum transaction. /// This validator is non-blocking, all validation work is done in a separate task. -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct TransactionValidationTaskExecutor { /// The validator that will validate transactions on a separate task. - pub validator: V, + pub validator: Arc, /// The sender half to validation tasks that perform the actual validation. pub to_validation_task: Arc>, } +impl Clone for TransactionValidationTaskExecutor { + fn clone(&self) -> Self { + Self { + validator: self.validator.clone(), + to_validation_task: self.to_validation_task.clone(), + } + } +} + // === impl TransactionValidationTaskExecutor === impl TransactionValidationTaskExecutor<()> { @@ -102,13 +128,13 @@ impl TransactionValidationTaskExecutor { F: FnMut(V) -> T, { TransactionValidationTaskExecutor { - validator: f(self.validator), + validator: Arc::new(f(Arc::into_inner(self.validator).unwrap())), to_validation_task: self.to_validation_task, } } /// Returns the validator. - pub const fn validator(&self) -> &V { + pub fn validator(&self) -> &V { &self.validator } } @@ -156,13 +182,13 @@ impl TransactionValidationTaskExecutor { /// validation tasks. pub fn new(validator: V) -> Self { let (tx, _) = ValidationTask::new(); - Self { validator, to_validation_task: Arc::new(sync::Mutex::new(tx)) } + Self { validator: Arc::new(validator), to_validation_task: Arc::new(sync::Mutex::new(tx)) } } } impl TransactionValidator for TransactionValidationTaskExecutor where - V: TransactionValidator + Clone + 'static, + V: TransactionValidator + 'static, { type Transaction = ::Transaction; @@ -244,6 +270,14 @@ where } } + async fn validate_transactions_with_origin( + &self, + origin: TransactionOrigin, + transactions: impl IntoIterator + Send, + ) -> Vec> { + self.validate_transactions(transactions.into_iter().map(|tx| (origin, tx)).collect()).await + } + fn on_new_head_block(&self, new_tip_block: &SealedBlock) where B: Block, diff --git a/crates/trie/common/src/added_removed_keys.rs b/crates/trie/common/src/added_removed_keys.rs new file mode 100644 index 00000000000..8e61423718a --- /dev/null +++ b/crates/trie/common/src/added_removed_keys.rs @@ -0,0 +1,218 @@ +//! Tracking of keys having been added and removed from the tries. + +use crate::HashedPostState; +use alloy_primitives::{map::B256Map, B256}; +use alloy_trie::proof::AddedRemovedKeys; + +/// Tracks added and removed keys across account and storage tries. +#[derive(Debug, Clone)] +pub struct MultiAddedRemovedKeys { + account: AddedRemovedKeys, + storages: B256Map, +} + +/// Returns [`AddedRemovedKeys`] with default parameters. This is necessary while we are not yet +/// tracking added keys. +fn default_added_removed_keys() -> AddedRemovedKeys { + AddedRemovedKeys::default().with_assume_added(true) +} + +impl Default for MultiAddedRemovedKeys { + fn default() -> Self { + Self::new() + } +} + +impl MultiAddedRemovedKeys { + /// Returns a new instance. + pub fn new() -> Self { + Self { account: default_added_removed_keys(), storages: Default::default() } + } + + /// Updates the set of removed keys based on a [`HashedPostState`]. + /// + /// Storage keys set to [`alloy_primitives::U256::ZERO`] are added to the set for their + /// respective account. Keys set to any other value are removed from their respective + /// account. + pub fn update_with_state(&mut self, update: &HashedPostState) { + for (hashed_address, storage) in &update.storages { + let account = update + .accounts + .get(hashed_address) + .map(|entry| entry.unwrap_or_default()) + .unwrap_or_default(); + + if storage.wiped { + self.storages.remove(hashed_address); + if account.is_empty() { + self.account.insert_removed(*hashed_address); + } + continue + } + + let storage_removed_keys = + self.storages.entry(*hashed_address).or_insert_with(default_added_removed_keys); + + for (key, val) in &storage.storage { + if val.is_zero() { + storage_removed_keys.insert_removed(*key); + } else { + storage_removed_keys.remove_removed(key); + } + } + + if !account.is_empty() { + self.account.remove_removed(hashed_address); + } + } + } + + /// Returns a [`AddedRemovedKeys`] for the storage trie of a particular account, if any. + pub fn get_storage(&self, hashed_address: &B256) -> Option<&AddedRemovedKeys> { + self.storages.get(hashed_address) + } + + /// Returns an [`AddedRemovedKeys`] for tracking account-level changes. + pub const fn get_accounts(&self) -> &AddedRemovedKeys { + &self.account + } + + /// Marks an account as existing, and therefore having storage. + pub fn touch_accounts(&mut self, addresses: impl Iterator) { + for address in addresses { + self.storages.entry(address).or_insert_with(default_added_removed_keys); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::HashedStorage; + use alloy_primitives::U256; + use reth_primitives_traits::Account; + + #[test] + fn test_update_with_state_storage_keys_non_zero() { + let mut multi_keys = MultiAddedRemovedKeys::new(); + let mut update = HashedPostState::default(); + + let addr = B256::random(); + let slot1 = B256::random(); + let slot2 = B256::random(); + + // First mark slots as removed + let mut storage = HashedStorage::default(); + storage.storage.insert(slot1, U256::ZERO); + storage.storage.insert(slot2, U256::ZERO); + update.storages.insert(addr, storage); + multi_keys.update_with_state(&update); + + // Verify they are removed + assert!(multi_keys.get_storage(&addr).unwrap().is_removed(&slot1)); + assert!(multi_keys.get_storage(&addr).unwrap().is_removed(&slot2)); + + // Now update with non-zero values + let mut update2 = HashedPostState::default(); + let mut storage2 = HashedStorage::default(); + storage2.storage.insert(slot1, U256::from(100)); + storage2.storage.insert(slot2, U256::from(200)); + update2.storages.insert(addr, storage2); + multi_keys.update_with_state(&update2); + + // Slots should no longer be marked as removed + let storage_keys = multi_keys.get_storage(&addr).unwrap(); + assert!(!storage_keys.is_removed(&slot1)); + assert!(!storage_keys.is_removed(&slot2)); + } + + #[test] + fn test_update_with_state_wiped_storage() { + let mut multi_keys = MultiAddedRemovedKeys::new(); + let mut update = HashedPostState::default(); + + let addr = B256::random(); + let slot1 = B256::random(); + + // First add some removed keys + let mut storage = HashedStorage::default(); + storage.storage.insert(slot1, U256::ZERO); + update.storages.insert(addr, storage); + multi_keys.update_with_state(&update); + assert!(multi_keys.get_storage(&addr).is_some()); + + // Now wipe the storage + let mut update2 = HashedPostState::default(); + let wiped_storage = HashedStorage::new(true); + update2.storages.insert(addr, wiped_storage); + multi_keys.update_with_state(&update2); + + // Storage and account should be removed + assert!(multi_keys.get_storage(&addr).is_none()); + assert!(multi_keys.get_accounts().is_removed(&addr)); + } + + #[test] + fn test_update_with_state_account_tracking() { + let mut multi_keys = MultiAddedRemovedKeys::new(); + let mut update = HashedPostState::default(); + + let addr = B256::random(); + let slot = B256::random(); + + // Add storage with zero value and empty account + let mut storage = HashedStorage::default(); + storage.storage.insert(slot, U256::ZERO); + update.storages.insert(addr, storage); + // Account is implicitly empty (not in accounts map) + + multi_keys.update_with_state(&update); + + // Storage should have removed keys but account should not be removed + assert!(multi_keys.get_storage(&addr).unwrap().is_removed(&slot)); + assert!(!multi_keys.get_accounts().is_removed(&addr)); + + // Now clear all removed storage keys and keep account empty + let mut update2 = HashedPostState::default(); + let mut storage2 = HashedStorage::default(); + storage2.storage.insert(slot, U256::from(100)); // Non-zero removes from removed set + update2.storages.insert(addr, storage2); + + multi_keys.update_with_state(&update2); + + // Account should not be marked as removed still + assert!(!multi_keys.get_accounts().is_removed(&addr)); + } + + #[test] + fn test_update_with_state_account_with_balance() { + let mut multi_keys = MultiAddedRemovedKeys::new(); + let mut update = HashedPostState::default(); + + let addr = B256::random(); + + // Add account with non-empty state (has balance) + let account = Account { balance: U256::from(1000), nonce: 0, bytecode_hash: None }; + update.accounts.insert(addr, Some(account)); + + // Add empty storage + let storage = HashedStorage::default(); + update.storages.insert(addr, storage); + + multi_keys.update_with_state(&update); + + // Account should not be marked as removed because it has balance + assert!(!multi_keys.get_accounts().is_removed(&addr)); + + // Now wipe the storage + let mut update2 = HashedPostState::default(); + let wiped_storage = HashedStorage::new(true); + update2.storages.insert(addr, wiped_storage); + update2.accounts.insert(addr, Some(account)); + multi_keys.update_with_state(&update2); + + // Storage should be None, but account should not be removed. + assert!(multi_keys.get_storage(&addr).is_none()); + assert!(!multi_keys.get_accounts().is_removed(&addr)); + } +} diff --git a/crates/trie/common/src/hashed_state.rs b/crates/trie/common/src/hashed_state.rs index 8e4ca75e808..eba725ad5c4 100644 --- a/crates/trie/common/src/hashed_state.rs +++ b/crates/trie/common/src/hashed_state.rs @@ -1,6 +1,7 @@ use core::ops::Not; use crate::{ + added_removed_keys::MultiAddedRemovedKeys, prefix_set::{PrefixSetMut, TriePrefixSetsMut}, KeyHasher, MultiProofTargets, Nibbles, }; @@ -207,15 +208,23 @@ impl HashedPostState { /// /// CAUTION: The state updates are expected to be applied in order, so that the storage wipes /// are done correctly. - pub fn partition_by_targets(mut self, targets: &MultiProofTargets) -> (Self, Self) { + pub fn partition_by_targets( + mut self, + targets: &MultiProofTargets, + added_removed_keys: &MultiAddedRemovedKeys, + ) -> (Self, Self) { let mut state_updates_not_in_targets = Self::default(); self.storages.retain(|&address, storage| { + let storage_added_removed_keys = added_removed_keys.get_storage(&address); + let (retain, storage_not_in_targets) = match targets.get(&address) { Some(storage_in_targets) => { let mut storage_not_in_targets = HashedStorage::default(); storage.storage.retain(|&slot, value| { - if storage_in_targets.contains(&slot) { + if storage_in_targets.contains(&slot) && + !storage_added_removed_keys.is_some_and(|k| k.is_removed(&slot)) + { return true } @@ -975,7 +984,8 @@ mod tests { }; let targets = MultiProofTargets::from_iter([(addr1, HashSet::from_iter([slot1]))]); - let (with_targets, without_targets) = state.partition_by_targets(&targets); + let (with_targets, without_targets) = + state.partition_by_targets(&targets, &MultiAddedRemovedKeys::new()); assert_eq!( with_targets, diff --git a/crates/trie/common/src/lib.rs b/crates/trie/common/src/lib.rs index a710d1f4983..7694b60c9da 100644 --- a/crates/trie/common/src/lib.rs +++ b/crates/trie/common/src/lib.rs @@ -55,6 +55,8 @@ pub mod root; /// Buffer for trie updates. pub mod updates; +pub mod added_removed_keys; + /// Bincode-compatible serde implementations for trie types. /// /// `bincode` crate allows for more efficient serialization of trie types, because it allows diff --git a/crates/trie/common/src/prefix_set.rs b/crates/trie/common/src/prefix_set.rs index c8d3ac74547..6714893f16d 100644 --- a/crates/trie/common/src/prefix_set.rs +++ b/crates/trie/common/src/prefix_set.rs @@ -55,7 +55,7 @@ impl TriePrefixSetsMut { } /// Collection of trie prefix sets. -#[derive(Default, Debug)] +#[derive(Default, Debug, Clone)] pub struct TriePrefixSets { /// A set of account prefixes that have changed. pub account_prefix_set: PrefixSet, diff --git a/crates/trie/common/src/proofs.rs b/crates/trie/common/src/proofs.rs index 5c3b55b0920..b7961f047a4 100644 --- a/crates/trie/common/src/proofs.rs +++ b/crates/trie/common/src/proofs.rs @@ -229,18 +229,16 @@ impl MultiProof { // Inspect the last node in the proof. If it's a leaf node with matching suffix, // then the node contains the encoded trie account. let info = 'info: { - if let Some(last) = proof.last() { - if let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? { - if nibbles.ends_with(&leaf.key) { - let account = TrieAccount::decode(&mut &leaf.value[..])?; - break 'info Some(Account { - balance: account.balance, - nonce: account.nonce, - bytecode_hash: (account.code_hash != KECCAK_EMPTY) - .then_some(account.code_hash), - }) - } - } + if let Some(last) = proof.last() && + let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? && + nibbles.ends_with(&leaf.key) + { + let account = TrieAccount::decode(&mut &leaf.value[..])?; + break 'info Some(Account { + balance: account.balance, + nonce: account.nonce, + bytecode_hash: (account.code_hash != KECCAK_EMPTY).then_some(account.code_hash), + }) } None }; @@ -360,16 +358,15 @@ impl DecodedMultiProof { // Inspect the last node in the proof. If it's a leaf node with matching suffix, // then the node contains the encoded trie account. let info = 'info: { - if let Some(TrieNode::Leaf(leaf)) = proof.last() { - if nibbles.ends_with(&leaf.key) { - let account = TrieAccount::decode(&mut &leaf.value[..])?; - break 'info Some(Account { - balance: account.balance, - nonce: account.nonce, - bytecode_hash: (account.code_hash != KECCAK_EMPTY) - .then_some(account.code_hash), - }) - } + if let Some(TrieNode::Leaf(leaf)) = proof.last() && + nibbles.ends_with(&leaf.key) + { + let account = TrieAccount::decode(&mut &leaf.value[..])?; + break 'info Some(Account { + balance: account.balance, + nonce: account.nonce, + bytecode_hash: (account.code_hash != KECCAK_EMPTY).then_some(account.code_hash), + }) } None }; @@ -486,12 +483,11 @@ impl StorageMultiProof { // Inspect the last node in the proof. If it's a leaf node with matching suffix, // then the node contains the encoded slot value. let value = 'value: { - if let Some(last) = proof.last() { - if let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? { - if nibbles.ends_with(&leaf.key) { - break 'value U256::decode(&mut &leaf.value[..])? - } - } + if let Some(last) = proof.last() && + let TrieNode::Leaf(leaf) = TrieNode::decode(&mut &last[..])? && + nibbles.ends_with(&leaf.key) + { + break 'value U256::decode(&mut &leaf.value[..])? } U256::ZERO }; @@ -539,10 +535,10 @@ impl DecodedStorageMultiProof { // Inspect the last node in the proof. If it's a leaf node with matching suffix, // then the node contains the encoded slot value. let value = 'value: { - if let Some(TrieNode::Leaf(leaf)) = proof.last() { - if nibbles.ends_with(&leaf.key) { - break 'value U256::decode(&mut &leaf.value[..])? - } + if let Some(TrieNode::Leaf(leaf)) = proof.last() && + nibbles.ends_with(&leaf.key) + { + break 'value U256::decode(&mut &leaf.value[..])? } U256::ZERO }; @@ -989,8 +985,8 @@ mod tests { // populate some targets let (addr1, addr2) = (B256::random(), B256::random()); let (slot1, slot2) = (B256::random(), B256::random()); - targets.insert(addr1, vec![slot1].into_iter().collect()); - targets.insert(addr2, vec![slot2].into_iter().collect()); + targets.insert(addr1, std::iter::once(slot1).collect()); + targets.insert(addr2, std::iter::once(slot2).collect()); let mut retained = targets.clone(); retained.retain_difference(&Default::default()); diff --git a/crates/trie/common/src/storage.rs b/crates/trie/common/src/storage.rs index 3ebcc4e810e..187a097bfd4 100644 --- a/crates/trie/common/src/storage.rs +++ b/crates/trie/common/src/storage.rs @@ -25,8 +25,8 @@ impl reth_codecs::Compact for StorageTrieEntry { } fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) { - let (nibbles, buf) = StoredNibblesSubKey::from_compact(buf, 33); - let (node, buf) = BranchNodeCompact::from_compact(buf, len - 33); + let (nibbles, buf) = StoredNibblesSubKey::from_compact(buf, 65); + let (node, buf) = BranchNodeCompact::from_compact(buf, len - 65); let this = Self { nibbles, node }; (this, buf) } diff --git a/crates/trie/common/src/updates.rs b/crates/trie/common/src/updates.rs index a752fd06d73..5f32f388c0c 100644 --- a/crates/trie/common/src/updates.rs +++ b/crates/trie/common/src/updates.rs @@ -107,15 +107,8 @@ impl TrieUpdates { } /// Converts trie updates into [`TrieUpdatesSorted`]. - pub fn into_sorted(self) -> TrieUpdatesSorted { - let mut account_nodes = Vec::from_iter(self.account_nodes); - account_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - let storage_tries = self - .storage_tries - .into_iter() - .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted())) - .collect(); - TrieUpdatesSorted { removed_nodes: self.removed_nodes, account_nodes, storage_tries } + pub fn into_sorted(mut self) -> TrieUpdatesSorted { + self.drain_into_sorted() } /// Converts trie updates into [`TrieUpdatesSorted`], but keeping the maps allocated by @@ -126,7 +119,17 @@ impl TrieUpdates { /// This allows us to reuse the allocated space. This allocates new space for the sorted /// updates, like `into_sorted`. pub fn drain_into_sorted(&mut self) -> TrieUpdatesSorted { - let mut account_nodes = self.account_nodes.drain().collect::>(); + let mut account_nodes = self + .account_nodes + .drain() + .map(|(path, node)| { + // Updated nodes take precedence over removed nodes. + self.removed_nodes.remove(&path); + (path, Some(node)) + }) + .collect::>(); + + account_nodes.extend(self.removed_nodes.drain().map(|path| (path, None))); account_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0)); let storage_tries = self @@ -134,12 +137,7 @@ impl TrieUpdates { .drain() .map(|(hashed_address, updates)| (hashed_address, updates.into_sorted())) .collect(); - - TrieUpdatesSorted { - removed_nodes: self.removed_nodes.clone(), - account_nodes, - storage_tries, - } + TrieUpdatesSorted { account_nodes, storage_tries } } /// Converts trie updates into [`TrieUpdatesSortedRef`]. @@ -266,14 +264,21 @@ impl StorageTrieUpdates { } /// Convert storage trie updates into [`StorageTrieUpdatesSorted`]. - pub fn into_sorted(self) -> StorageTrieUpdatesSorted { - let mut storage_nodes = Vec::from_iter(self.storage_nodes); + pub fn into_sorted(mut self) -> StorageTrieUpdatesSorted { + let mut storage_nodes = self + .storage_nodes + .into_iter() + .map(|(path, node)| { + // Updated nodes take precedence over removed nodes. + self.removed_nodes.remove(&path); + (path, Some(node)) + }) + .collect::>(); + + storage_nodes.extend(self.removed_nodes.into_iter().map(|path| (path, None))); storage_nodes.sort_unstable_by(|a, b| a.0.cmp(&b.0)); - StorageTrieUpdatesSorted { - is_deleted: self.is_deleted, - removed_nodes: self.removed_nodes, - storage_nodes, - } + + StorageTrieUpdatesSorted { is_deleted: self.is_deleted, storage_nodes } } /// Convert storage trie updates into [`StorageTrieUpdatesSortedRef`]. @@ -425,25 +430,19 @@ pub struct TrieUpdatesSortedRef<'a> { #[derive(PartialEq, Eq, Clone, Default, Debug)] #[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] pub struct TrieUpdatesSorted { - /// Sorted collection of updated state nodes with corresponding paths. - pub account_nodes: Vec<(Nibbles, BranchNodeCompact)>, - /// The set of removed state node keys. - pub removed_nodes: HashSet, + /// Sorted collection of updated state nodes with corresponding paths. None indicates that a + /// node was removed. + pub account_nodes: Vec<(Nibbles, Option)>, /// Storage tries stored by hashed address of the account the trie belongs to. pub storage_tries: B256Map, } impl TrieUpdatesSorted { /// Returns reference to updated account nodes. - pub fn account_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] { + pub fn account_nodes_ref(&self) -> &[(Nibbles, Option)] { &self.account_nodes } - /// Returns reference to removed account nodes. - pub const fn removed_nodes_ref(&self) -> &HashSet { - &self.removed_nodes - } - /// Returns reference to updated storage tries. pub const fn storage_tries_ref(&self) -> &B256Map { &self.storage_tries @@ -468,10 +467,9 @@ pub struct StorageTrieUpdatesSortedRef<'a> { pub struct StorageTrieUpdatesSorted { /// Flag indicating whether the trie has been deleted/wiped. pub is_deleted: bool, - /// Sorted collection of updated storage nodes with corresponding paths. - pub storage_nodes: Vec<(Nibbles, BranchNodeCompact)>, - /// The set of removed storage node keys. - pub removed_nodes: HashSet, + /// Sorted collection of updated storage nodes with corresponding paths. None indicates a node + /// is removed. + pub storage_nodes: Vec<(Nibbles, Option)>, } impl StorageTrieUpdatesSorted { @@ -481,14 +479,9 @@ impl StorageTrieUpdatesSorted { } /// Returns reference to updated storage nodes. - pub fn storage_nodes_ref(&self) -> &[(Nibbles, BranchNodeCompact)] { + pub fn storage_nodes_ref(&self) -> &[(Nibbles, Option)] { &self.storage_nodes } - - /// Returns reference to removed storage nodes. - pub const fn removed_nodes_ref(&self) -> &HashSet { - &self.removed_nodes - } } /// Excludes empty nibbles from the given iterator. diff --git a/crates/trie/db/Cargo.toml b/crates/trie/db/Cargo.toml index f13acf5ad7f..09ccd301192 100644 --- a/crates/trie/db/Cargo.toml +++ b/crates/trie/db/Cargo.toml @@ -30,7 +30,6 @@ reth-chainspec.workspace = true reth-primitives-traits = { workspace = true, features = ["test-utils", "arbitrary"] } reth-db = { workspace = true, features = ["test-utils"] } reth-provider = { workspace = true, features = ["test-utils"] } -reth-storage-errors.workspace = true reth-trie-common = { workspace = true, features = ["test-utils", "arbitrary"] } reth-trie = { workspace = true, features = ["test-utils"] } diff --git a/crates/trie/db/src/witness.rs b/crates/trie/db/src/witness.rs index a240734f8ce..3afb8c340c9 100644 --- a/crates/trie/db/src/witness.rs +++ b/crates/trie/db/src/witness.rs @@ -44,6 +44,7 @@ impl<'a, TX: DbTx> DatabaseTrieWitness<'a, TX> &state_sorted, )) .with_prefix_sets_mut(input.prefix_sets) + .always_include_root_node() .compute(target) } } diff --git a/crates/trie/db/tests/trie.rs b/crates/trie/db/tests/trie.rs index 4b56911b518..e9fcb5a1c48 100644 --- a/crates/trie/db/tests/trie.rs +++ b/crates/trie/db/tests/trie.rs @@ -1,7 +1,9 @@ #![allow(missing_docs)] use alloy_consensus::EMPTY_ROOT_HASH; -use alloy_primitives::{hex_literal::hex, keccak256, map::HashMap, Address, B256, U256}; +use alloy_primitives::{ + address, b256, hex_literal::hex, keccak256, map::HashMap, Address, B256, U256, +}; use alloy_rlp::Encodable; use proptest::{prelude::ProptestConfig, proptest}; use proptest_arbitrary_interop::arb; @@ -79,7 +81,7 @@ fn incremental_vs_full_root(inputs: &[&str], modified: &str) { let modified_root = loader.root().unwrap(); // Update the intermediate roots table so that we can run the incremental verification - tx.write_individual_storage_trie_updates(hashed_address, &trie_updates).unwrap(); + tx.write_storage_trie_updates(core::iter::once((&hashed_address, &trie_updates))).unwrap(); // 3. Calculate the incremental root let mut storage_changes = PrefixSetMut::default(); @@ -295,7 +297,7 @@ fn storage_root_regression() { let factory = create_test_provider_factory(); let tx = factory.provider_rw().unwrap(); // Some address whose hash starts with 0xB041 - let address3 = Address::from_str("16b07afd1c635f77172e842a000ead9a2a222459").unwrap(); + let address3 = address!("0x16b07afd1c635f77172e842a000ead9a2a222459"); let key3 = keccak256(address3); assert_eq!(key3[0], 0xB0); assert_eq!(key3[1], 0x41); @@ -346,14 +348,13 @@ fn account_and_storage_trie() { let mut hash_builder = HashBuilder::default(); // Insert first account - let key1 = - B256::from_str("b000000000000000000000000000000000000000000000000000000000000000").unwrap(); + let key1 = b256!("0xb000000000000000000000000000000000000000000000000000000000000000"); let account1 = Account { nonce: 0, balance: U256::from(3).mul(ether), bytecode_hash: None }; hashed_account_cursor.upsert(key1, &account1).unwrap(); hash_builder.add_leaf(Nibbles::unpack(key1), &encode_account(account1, None)); // Some address whose hash starts with 0xB040 - let address2 = Address::from_str("7db3e81b72d2695e19764583f6d219dbee0f35ca").unwrap(); + let address2 = address!("0x7db3e81b72d2695e19764583f6d219dbee0f35ca"); let key2 = keccak256(address2); assert_eq!(key2[0], 0xB0); assert_eq!(key2[1], 0x40); @@ -362,12 +363,11 @@ fn account_and_storage_trie() { hash_builder.add_leaf(Nibbles::unpack(key2), &encode_account(account2, None)); // Some address whose hash starts with 0xB041 - let address3 = Address::from_str("16b07afd1c635f77172e842a000ead9a2a222459").unwrap(); + let address3 = address!("0x16b07afd1c635f77172e842a000ead9a2a222459"); let key3 = keccak256(address3); assert_eq!(key3[0], 0xB0); assert_eq!(key3[1], 0x41); - let code_hash = - B256::from_str("5be74cad16203c4905c068b012a2e9fb6d19d036c410f16fd177f337541440dd").unwrap(); + let code_hash = b256!("0x5be74cad16203c4905c068b012a2e9fb6d19d036c410f16fd177f337541440dd"); let account3 = Account { nonce: 0, balance: U256::from(2).mul(ether), bytecode_hash: Some(code_hash) }; hashed_account_cursor.upsert(key3, &account3).unwrap(); @@ -386,27 +386,23 @@ fn account_and_storage_trie() { hash_builder .add_leaf(Nibbles::unpack(key3), &encode_account(account3, Some(account3_storage_root))); - let key4a = - B256::from_str("B1A0000000000000000000000000000000000000000000000000000000000000").unwrap(); + let key4a = b256!("0xB1A0000000000000000000000000000000000000000000000000000000000000"); let account4a = Account { nonce: 0, balance: U256::from(4).mul(ether), ..Default::default() }; hashed_account_cursor.upsert(key4a, &account4a).unwrap(); hash_builder.add_leaf(Nibbles::unpack(key4a), &encode_account(account4a, None)); - let key5 = - B256::from_str("B310000000000000000000000000000000000000000000000000000000000000").unwrap(); + let key5 = b256!("0xB310000000000000000000000000000000000000000000000000000000000000"); let account5 = Account { nonce: 0, balance: U256::from(8).mul(ether), ..Default::default() }; hashed_account_cursor.upsert(key5, &account5).unwrap(); hash_builder.add_leaf(Nibbles::unpack(key5), &encode_account(account5, None)); - let key6 = - B256::from_str("B340000000000000000000000000000000000000000000000000000000000000").unwrap(); + let key6 = b256!("0xB340000000000000000000000000000000000000000000000000000000000000"); let account6 = Account { nonce: 0, balance: U256::from(1).mul(ether), ..Default::default() }; hashed_account_cursor.upsert(key6, &account6).unwrap(); hash_builder.add_leaf(Nibbles::unpack(key6), &encode_account(account6, None)); // Populate account & storage trie DB tables - let expected_root = - B256::from_str("72861041bc90cd2f93777956f058a545412b56de79af5eb6b8075fe2eabbe015").unwrap(); + let expected_root = b256!("0x72861041bc90cd2f93777956f058a545412b56de79af5eb6b8075fe2eabbe015"); let computed_expected_root: B256 = triehash::trie_root::([ (key1, encode_account(account1, None)), (key2, encode_account(account2, None)), @@ -432,6 +428,7 @@ fn account_and_storage_trie() { let (nibbles1a, node1a) = account_updates.first().unwrap(); assert_eq!(nibbles1a.to_vec(), vec![0xB]); + let node1a = node1a.as_ref().unwrap(); assert_eq!(node1a.state_mask, TrieMask::new(0b1011)); assert_eq!(node1a.tree_mask, TrieMask::new(0b0001)); assert_eq!(node1a.hash_mask, TrieMask::new(0b1001)); @@ -440,6 +437,7 @@ fn account_and_storage_trie() { let (nibbles2a, node2a) = account_updates.last().unwrap(); assert_eq!(nibbles2a.to_vec(), vec![0xB, 0x0]); + let node2a = node2a.as_ref().unwrap(); assert_eq!(node2a.state_mask, TrieMask::new(0b10001)); assert_eq!(node2a.tree_mask, TrieMask::new(0b00000)); assert_eq!(node2a.hash_mask, TrieMask::new(0b10000)); @@ -448,7 +446,7 @@ fn account_and_storage_trie() { // Add an account // Some address whose hash starts with 0xB1 - let address4b = Address::from_str("4f61f2d5ebd991b85aa1677db97307caf5215c91").unwrap(); + let address4b = address!("0x4f61f2d5ebd991b85aa1677db97307caf5215c91"); let key4b = keccak256(address4b); assert_eq!(key4b.0[0], key4a.0[0]); let account4b = Account { nonce: 0, balance: U256::from(5).mul(ether), bytecode_hash: None }; @@ -458,7 +456,7 @@ fn account_and_storage_trie() { prefix_set.insert(Nibbles::unpack(key4b)); let expected_state_root = - B256::from_str("8e263cd4eefb0c3cbbb14e5541a66a755cad25bcfab1e10dd9d706263e811b28").unwrap(); + b256!("0x8e263cd4eefb0c3cbbb14e5541a66a755cad25bcfab1e10dd9d706263e811b28"); let (root, trie_updates) = StateRoot::from_tx(tx.tx_ref()) .with_prefix_sets(TriePrefixSets { @@ -475,6 +473,7 @@ fn account_and_storage_trie() { let (nibbles1b, node1b) = account_updates.first().unwrap(); assert_eq!(nibbles1b.to_vec(), vec![0xB]); + let node1b = node1b.as_ref().unwrap(); assert_eq!(node1b.state_mask, TrieMask::new(0b1011)); assert_eq!(node1b.tree_mask, TrieMask::new(0b0001)); assert_eq!(node1b.hash_mask, TrieMask::new(0b1011)); @@ -485,6 +484,7 @@ fn account_and_storage_trie() { let (nibbles2b, node2b) = account_updates.last().unwrap(); assert_eq!(nibbles2b.to_vec(), vec![0xB, 0x0]); + let node2b = node2b.as_ref().unwrap(); assert_eq!(node2a, node2b); tx.commit().unwrap(); @@ -524,8 +524,9 @@ fn account_and_storage_trie() { assert_eq!(trie_updates.account_nodes_ref().len(), 1); - let (nibbles1c, node1c) = trie_updates.account_nodes_ref().iter().next().unwrap(); - assert_eq!(nibbles1c.to_vec(), vec![0xB]); + let entry = trie_updates.account_nodes_ref().iter().next().unwrap(); + assert_eq!(entry.0.to_vec(), vec![0xB]); + let node1c = entry.1; assert_eq!(node1c.state_mask, TrieMask::new(0b1011)); assert_eq!(node1c.tree_mask, TrieMask::new(0b0000)); @@ -582,8 +583,9 @@ fn account_and_storage_trie() { assert_eq!(trie_updates.account_nodes_ref().len(), 1); - let (nibbles1d, node1d) = trie_updates.account_nodes_ref().iter().next().unwrap(); - assert_eq!(nibbles1d.to_vec(), vec![0xB]); + let entry = trie_updates.account_nodes_ref().iter().next().unwrap(); + assert_eq!(entry.0.to_vec(), vec![0xB]); + let node1d = entry.1; assert_eq!(node1d.state_mask, TrieMask::new(0b1011)); assert_eq!(node1d.tree_mask, TrieMask::new(0b0000)); diff --git a/crates/trie/db/tests/walker.rs b/crates/trie/db/tests/walker.rs index 22316cd5ad4..edc69e330b7 100644 --- a/crates/trie/db/tests/walker.rs +++ b/crates/trie/db/tests/walker.rs @@ -60,7 +60,7 @@ fn test_cursor(mut trie: T, expected: &[Vec]) where T: TrieCursor, { - let mut walker = TrieWalker::state_trie(&mut trie, Default::default()); + let mut walker = TrieWalker::<_>::state_trie(&mut trie, Default::default()); assert!(walker.key().unwrap().is_empty()); // We're traversing the path in lexicographical order. @@ -114,7 +114,7 @@ fn cursor_rootnode_with_changesets() { let mut trie = DatabaseStorageTrieCursor::new(cursor, hashed_address); // No changes - let mut cursor = TrieWalker::state_trie(&mut trie, Default::default()); + let mut cursor = TrieWalker::<_>::state_trie(&mut trie, Default::default()); assert_eq!(cursor.key().copied(), Some(Nibbles::new())); // root assert!(cursor.can_skip_current_node); // due to root_hash cursor.advance().unwrap(); // skips to the end of trie @@ -123,7 +123,7 @@ fn cursor_rootnode_with_changesets() { // We insert something that's not part of the existing trie/prefix. let mut changed = PrefixSetMut::default(); changed.insert(Nibbles::from_nibbles([0xF, 0x1])); - let mut cursor = TrieWalker::state_trie(&mut trie, changed.freeze()); + let mut cursor = TrieWalker::<_>::state_trie(&mut trie, changed.freeze()); // Root node assert_eq!(cursor.key().copied(), Some(Nibbles::new())); diff --git a/crates/trie/parallel/src/proof.rs b/crates/trie/parallel/src/proof.rs index a7d76860a81..63ef762000a 100644 --- a/crates/trie/parallel/src/proof.rs +++ b/crates/trie/parallel/src/proof.rs @@ -28,7 +28,10 @@ use reth_trie::{ DecodedMultiProof, DecodedStorageMultiProof, HashBuilder, HashedPostStateSorted, MultiProofTargets, Nibbles, TRIE_ACCOUNT_RLP_MAX_SIZE, }; -use reth_trie_common::proof::{DecodedProofNodes, ProofRetainer}; +use reth_trie_common::{ + added_removed_keys::MultiAddedRemovedKeys, + proof::{DecodedProofNodes, ProofRetainer}, +}; use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory}; use std::sync::{mpsc::Receiver, Arc}; use tracing::debug; @@ -52,6 +55,8 @@ pub struct ParallelProof { pub prefix_sets: Arc, /// Flag indicating whether to include branch node masks in the proof. collect_branch_node_masks: bool, + /// Provided by the user to give the necessary context to retain extra proofs. + multi_added_removed_keys: Option>, /// Handle to the storage proof task. storage_proof_task_handle: ProofTaskManagerHandle>, #[cfg(feature = "metrics")] @@ -73,6 +78,7 @@ impl ParallelProof { state_sorted, prefix_sets, collect_branch_node_masks: false, + multi_added_removed_keys: None, storage_proof_task_handle, #[cfg(feature = "metrics")] metrics: ParallelTrieMetrics::new_with_labels(&[("type", "proof")]), @@ -84,6 +90,16 @@ impl ParallelProof { self.collect_branch_node_masks = branch_node_masks; self } + + /// Configure the `ParallelProof` with a [`MultiAddedRemovedKeys`], allowing for retaining + /// extra proofs needed to add and remove leaf nodes from the tries. + pub fn with_multi_added_removed_keys( + mut self, + multi_added_removed_keys: Option>, + ) -> Self { + self.multi_added_removed_keys = multi_added_removed_keys; + self + } } impl ParallelProof @@ -102,6 +118,7 @@ where prefix_set, target_slots, self.collect_branch_node_masks, + self.multi_added_removed_keys.clone(), ); let (sender, receiver) = std::sync::mpsc::channel(); @@ -217,15 +234,23 @@ where &self.state_sorted, ); + let accounts_added_removed_keys = + self.multi_added_removed_keys.as_ref().map(|keys| keys.get_accounts()); + // Create the walker. - let walker = TrieWalker::state_trie( + let walker = TrieWalker::<_>::state_trie( trie_cursor_factory.account_trie_cursor().map_err(ProviderError::Database)?, prefix_sets.account_prefix_set, ) + .with_added_removed_keys(accounts_added_removed_keys) .with_deletions_retained(true); // Create a hash builder to rebuild the root node since it is not available in the database. - let retainer: ProofRetainer = targets.keys().map(Nibbles::unpack).collect(); + let retainer = targets + .keys() + .map(Nibbles::unpack) + .collect::() + .with_added_removed_keys(accounts_added_removed_keys); let mut hash_builder = HashBuilder::default() .with_proof_retainer(retainer) .with_updates(self.collect_branch_node_masks); diff --git a/crates/trie/parallel/src/proof_task.rs b/crates/trie/parallel/src/proof_task.rs index e986bf2da82..0934159f79e 100644 --- a/crates/trie/parallel/src/proof_task.rs +++ b/crates/trie/parallel/src/proof_task.rs @@ -24,7 +24,10 @@ use reth_trie::{ updates::TrieUpdatesSorted, DecodedStorageMultiProof, HashedPostStateSorted, Nibbles, }; -use reth_trie_common::prefix_set::{PrefixSet, PrefixSetMut}; +use reth_trie_common::{ + added_removed_keys::MultiAddedRemovedKeys, + prefix_set::{PrefixSet, PrefixSetMut}, +}; use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory}; use reth_trie_sparse::provider::{RevealedNode, TrieNodeProvider, TrieNodeProviderFactory}; use std::{ @@ -133,7 +136,7 @@ where let provider_ro = self.view.provider_ro()?; let tx = provider_ro.into_tx(); self.total_transactions += 1; - return Ok(Some(ProofTaskTx::new(tx, self.task_ctx.clone()))); + return Ok(Some(ProofTaskTx::new(tx, self.task_ctx.clone(), self.total_transactions))); } Ok(None) @@ -219,12 +222,17 @@ pub struct ProofTaskTx { /// Trie updates, prefix sets, and state updates task_ctx: ProofTaskCtx, + + /// Identifier for the tx within the context of a single [`ProofTaskManager`], used only for + /// tracing. + id: usize, } impl ProofTaskTx { - /// Initializes a [`ProofTaskTx`] using the given transaction anda[`ProofTaskCtx`]. - const fn new(tx: Tx, task_ctx: ProofTaskCtx) -> Self { - Self { tx, task_ctx } + /// Initializes a [`ProofTaskTx`] using the given transaction and a [`ProofTaskCtx`]. The id is + /// used only for tracing. + const fn new(tx: Tx, task_ctx: ProofTaskCtx, id: usize) -> Self { + Self { tx, task_ctx, id } } } @@ -265,9 +273,24 @@ where ); let (trie_cursor_factory, hashed_cursor_factory) = self.create_factories(); + let multi_added_removed_keys = input + .multi_added_removed_keys + .unwrap_or_else(|| Arc::new(MultiAddedRemovedKeys::new())); + let added_removed_keys = multi_added_removed_keys.get_storage(&input.hashed_address); + + let span = tracing::trace_span!( + target: "trie::proof_task", + "Storage proof calculation", + hashed_address=?input.hashed_address, + // Add a unique id because we often have parallel storage proof calculations for the + // same hashed address, and we want to differentiate them during trace analysis. + span_id=self.id, + ); + let span_guard = span.enter(); let target_slots_len = input.target_slots.len(); let proof_start = Instant::now(); + let raw_proof_result = StorageProof::new_hashed( trie_cursor_factory, hashed_cursor_factory, @@ -275,9 +298,12 @@ where ) .with_prefix_set_mut(PrefixSetMut::from(input.prefix_set.iter().copied())) .with_branch_node_masks(input.with_branch_node_masks) + .with_added_removed_keys(added_removed_keys) .storage_multiproof(input.target_slots) .map_err(|e| ParallelStateRootError::Other(e.to_string())); + drop(span_guard); + let decoded_result = raw_proof_result.and_then(|raw_proof| { raw_proof.try_into().map_err(|e: alloy_rlp::Error| { ParallelStateRootError::Other(format!( @@ -413,6 +439,8 @@ pub struct StorageProofInput { target_slots: B256Set, /// Whether or not to collect branch node masks with_branch_node_masks: bool, + /// Provided by the user to give the necessary context to retain extra proofs. + multi_added_removed_keys: Option>, } impl StorageProofInput { @@ -423,8 +451,15 @@ impl StorageProofInput { prefix_set: PrefixSet, target_slots: B256Set, with_branch_node_masks: bool, + multi_added_removed_keys: Option>, ) -> Self { - Self { hashed_address, prefix_set, target_slots, with_branch_node_masks } + Self { + hashed_address, + prefix_set, + target_slots, + with_branch_node_masks, + multi_added_removed_keys, + } } } diff --git a/crates/trie/parallel/src/root.rs b/crates/trie/parallel/src/root.rs index ccc1856e1f7..3b84442cc41 100644 --- a/crates/trie/parallel/src/root.rs +++ b/crates/trie/parallel/src/root.rs @@ -20,7 +20,10 @@ use reth_trie::{ use reth_trie_db::{DatabaseHashedCursorFactory, DatabaseTrieCursorFactory}; use std::{ collections::HashMap, - sync::{mpsc, Arc, OnceLock}, + sync::{ + atomic::{AtomicUsize, Ordering}, + mpsc, Arc, OnceLock, + }, time::Duration, }; use thiserror::Error; @@ -155,7 +158,7 @@ where &hashed_state_sorted, ); - let walker = TrieWalker::state_trie( + let walker = TrieWalker::<_>::state_trie( trie_cursor_factory.account_trie_cursor().map_err(ProviderError::Database)?, prefix_sets.account_prefix_set, ) @@ -283,6 +286,7 @@ fn get_runtime_handle() -> Handle { Handle::try_current().unwrap_or_else(|_| { // Create a new runtime if no runtime is available static RT: OnceLock = OnceLock::new(); + static THREAD_COUNTER: AtomicUsize = AtomicUsize::new(0); let rt = RT.get_or_init(|| { Builder::new_multi_thread() @@ -290,6 +294,10 @@ fn get_runtime_handle() -> Handle { // This prevents the costly process of spawning new threads on every // new block, and instead reuses the existing threads. .thread_keep_alive(Duration::from_secs(15)) + .thread_name_fn(|| { + let id = THREAD_COUNTER.fetch_add(1, Ordering::Relaxed); + format!("tokio-trie-{id}") + }) .build() .expect("Failed to create tokio runtime") }); diff --git a/crates/trie/sparse-parallel/src/trie.rs b/crates/trie/sparse-parallel/src/trie.rs index 7c1f8a02bc9..d973d705de2 100644 --- a/crates/trie/sparse-parallel/src/trie.rs +++ b/crates/trie/sparse-parallel/src/trie.rs @@ -21,7 +21,7 @@ use std::{ cmp::{Ord, Ordering, PartialOrd}, sync::mpsc, }; -use tracing::{instrument, trace}; +use tracing::{debug, instrument, trace}; /// The maximum length of a path, in nibbles, which belongs to the upper subtrie of a /// [`ParallelSparseTrie`]. All longer paths belong to a lower subtrie. @@ -30,6 +30,18 @@ pub const UPPER_TRIE_MAX_DEPTH: usize = 2; /// Number of lower subtries which are managed by the [`ParallelSparseTrie`]. pub const NUM_LOWER_SUBTRIES: usize = 16usize.pow(UPPER_TRIE_MAX_DEPTH as u32); +/// Configuration for controlling when parallelism is enabled in [`ParallelSparseTrie`] operations. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct ParallelismThresholds { + /// Minimum number of nodes to reveal before parallel processing is enabled. + /// When `reveal_nodes` has fewer nodes than this threshold, they will be processed serially. + pub min_revealed_nodes: usize, + /// Minimum number of changed keys (prefix set length) before parallel processing is enabled + /// for hash updates. When updating subtrie hashes with fewer changed keys than this threshold, + /// the updates will be processed serially. + pub min_updated_nodes: usize, +} + /// A revealed sparse trie with subtries that can be updated in parallel. /// /// ## Structure @@ -109,6 +121,8 @@ pub struct ParallelSparseTrie { /// Reusable buffer pool used for collecting [`SparseTrieUpdatesAction`]s during hash /// computations. update_actions_buffers: Vec>, + /// Thresholds controlling when parallelism is enabled for different operations. + parallelism_thresholds: ParallelismThresholds, /// Metrics for the parallel sparse trie. #[cfg(feature = "metrics")] metrics: crate::metrics::ParallelSparseTrieMetrics, @@ -127,6 +141,7 @@ impl Default for ParallelSparseTrie { branch_node_tree_masks: HashMap::default(), branch_node_hash_masks: HashMap::default(), update_actions_buffers: Vec::default(), + parallelism_thresholds: Default::default(), #[cfg(feature = "metrics")] metrics: Default::default(), } @@ -200,19 +215,20 @@ impl SparseTrieInterface for ParallelSparseTrie { self.reveal_upper_node(node.path, &node.node, node.masks)?; } - #[cfg(not(feature = "std"))] - // Reveal lower subtrie nodes serially if nostd - { + if !self.is_reveal_parallelism_enabled(lower_nodes.len()) { for node in lower_nodes { if let Some(subtrie) = self.lower_subtrie_for_path_mut(&node.path) { - subtrie.reveal_node(node.path, &node.node, &node.masks)?; + subtrie.reveal_node(node.path, &node.node, node.masks)?; } else { panic!("upper subtrie node {node:?} found amongst lower nodes"); } } - Ok(()) + return Ok(()) } + #[cfg(not(feature = "std"))] + unreachable!("nostd is checked by is_reveal_parallelism_enabled"); + #[cfg(feature = "std")] // Reveal lower subtrie nodes in parallel { @@ -334,6 +350,12 @@ impl SparseTrieInterface for ParallelSparseTrie { if let Some(reveal_path) = reveal_path { let subtrie = self.subtrie_for_path_mut(&reveal_path); if subtrie.nodes.get(&reveal_path).expect("node must exist").is_hash() { + debug!( + target: "trie::parallel_sparse", + child_path = ?reveal_path, + leaf_full_path = ?full_path, + "Extension node child not revealed in update_leaf, falling back to db", + ); if let Some(RevealedNode { node, tree_mask, hash_mask }) = provider.trie_node(&reveal_path)? { @@ -344,7 +366,7 @@ impl SparseTrieInterface for ParallelSparseTrie { ?decoded, ?tree_mask, ?hash_mask, - "Revealing child", + "Revealing child (from upper)", ); subtrie.reveal_node( reveal_path, @@ -379,12 +401,9 @@ impl SparseTrieInterface for ParallelSparseTrie { self.upper_subtrie.nodes.remove(node_path).expect("node belongs to upper subtrie"); // If it's a leaf node, extract its value before getting mutable reference to subtrie. - // We also add the leaf the prefix set, so that whichever lower subtrie it belongs to - // will have its hash recalculated as part of `update_subtrie_hashes`. let leaf_value = if let SparseNode::Leaf { key, .. } = &node { let mut leaf_full_path = *node_path; leaf_full_path.extend(key); - self.prefix_set.insert(leaf_full_path); Some(( leaf_full_path, self.upper_subtrie @@ -531,15 +550,14 @@ impl SparseTrieInterface for ParallelSparseTrie { // If we were previously looking at the upper trie, and the new path is in the // lower trie, we need to pull out a ref to the lower trie. - if curr_subtrie_is_upper { - if let SparseSubtrieType::Lower(idx) = + if curr_subtrie_is_upper && + let SparseSubtrieType::Lower(idx) = SparseSubtrieType::from_path(&curr_path) - { - curr_subtrie = self.lower_subtries[idx] - .as_revealed_mut() - .expect("lower subtrie is revealed"); - curr_subtrie_is_upper = false; - } + { + curr_subtrie = self.lower_subtries[idx] + .as_revealed_mut() + .expect("lower subtrie is revealed"); + curr_subtrie_is_upper = false; } } }; @@ -580,7 +598,7 @@ impl SparseTrieInterface for ParallelSparseTrie { // If there is a parent branch node (very likely, unless the leaf is at the root) execute // any required changes for that node, relative to the removed leaf. - if let (Some(branch_path), Some(SparseNode::Branch { mut state_mask, .. })) = + if let (Some(branch_path), &Some(SparseNode::Branch { mut state_mask, .. })) = (&branch_parent_path, &branch_parent_node) { let child_nibble = leaf_path.get_unchecked(branch_path.len()); @@ -612,10 +630,11 @@ impl SparseTrieInterface for ParallelSparseTrie { let remaining_child_node = match remaining_child_subtrie.nodes.get(&remaining_child_path).unwrap() { SparseNode::Hash(_) => { - trace!( + debug!( target: "trie::parallel_sparse", - ?remaining_child_path, - "Retrieving remaining blinded branch child", + child_path = ?remaining_child_path, + leaf_full_path = ?full_path, + "Branch node child not revealed in remove_leaf, falling back to db", ); if let Some(RevealedNode { node, tree_mask, hash_mask }) = provider.trie_node(&remaining_child_path)? @@ -721,76 +740,62 @@ impl SparseTrieInterface for ParallelSparseTrie { // Take changed subtries according to the prefix set let mut prefix_set = core::mem::take(&mut self.prefix_set).freeze(); - let (subtries, unchanged_prefix_set) = self.take_changed_lower_subtries(&mut prefix_set); + let num_changed_keys = prefix_set.len(); + let (mut changed_subtries, unchanged_prefix_set) = + self.take_changed_lower_subtries(&mut prefix_set); // update metrics #[cfg(feature = "metrics")] - self.metrics.subtries_updated.record(subtries.len() as f64); + self.metrics.subtries_updated.record(changed_subtries.len() as f64); // Update the prefix set with the keys that didn't have matching subtries self.prefix_set = unchanged_prefix_set; - let (tx, rx) = mpsc::channel(); + // Update subtrie hashes serially parallelism is not enabled + if !self.is_update_parallelism_enabled(num_changed_keys) { + for changed_subtrie in &mut changed_subtries { + changed_subtrie.subtrie.update_hashes( + &mut changed_subtrie.prefix_set, + &mut changed_subtrie.update_actions_buf, + &self.branch_node_tree_masks, + &self.branch_node_hash_masks, + ); + } - #[cfg(not(feature = "std"))] - // Update subtrie hashes serially if nostd - for ChangedSubtrie { index, mut subtrie, mut prefix_set, mut update_actions_buf } in - subtries - { - subtrie.update_hashes( - &mut prefix_set, - &mut update_actions_buf, - &self.branch_node_tree_masks, - &self.branch_node_hash_masks, - ); - tx.send((index, subtrie, update_actions_buf)).unwrap(); + self.insert_changed_subtries(changed_subtries); + return } + #[cfg(not(feature = "std"))] + unreachable!("nostd is checked by is_update_parallelism_enabled"); + #[cfg(feature = "std")] // Update subtrie hashes in parallel { use rayon::iter::{IntoParallelIterator, ParallelIterator}; + let (tx, rx) = mpsc::channel(); + let branch_node_tree_masks = &self.branch_node_tree_masks; let branch_node_hash_masks = &self.branch_node_hash_masks; - subtries + changed_subtries .into_par_iter() - .map( - |ChangedSubtrie { - index, - mut subtrie, - mut prefix_set, - mut update_actions_buf, - }| { - #[cfg(feature = "metrics")] - let start = std::time::Instant::now(); - subtrie.update_hashes( - &mut prefix_set, - &mut update_actions_buf, - branch_node_tree_masks, - branch_node_hash_masks, - ); - #[cfg(feature = "metrics")] - self.metrics.subtrie_hash_update_latency.record(start.elapsed()); - (index, subtrie, update_actions_buf) - }, - ) + .map(|mut changed_subtrie| { + #[cfg(feature = "metrics")] + let start = std::time::Instant::now(); + changed_subtrie.subtrie.update_hashes( + &mut changed_subtrie.prefix_set, + &mut changed_subtrie.update_actions_buf, + branch_node_tree_masks, + branch_node_hash_masks, + ); + #[cfg(feature = "metrics")] + self.metrics.subtrie_hash_update_latency.record(start.elapsed()); + changed_subtrie + }) .for_each_init(|| tx.clone(), |tx, result| tx.send(result).unwrap()); - } - - drop(tx); - - // Return updated subtries back to the trie after executing any actions required on the - // top-level `SparseTrieUpdates`. - for (index, subtrie, update_actions_buf) in rx { - if let Some(mut update_actions_buf) = update_actions_buf { - self.apply_subtrie_update_actions( - #[allow(clippy::iter_with_drain)] - update_actions_buf.drain(..), - ); - self.update_actions_buffers.push(update_actions_buf); - } - self.lower_subtries[index] = LowerSparseSubtrie::Revealed(subtrie); + drop(tx); + self.insert_changed_subtries(rx); } } @@ -810,6 +815,7 @@ impl SparseTrieInterface for ParallelSparseTrie { self.upper_subtrie.wipe(); self.lower_subtries = [const { LowerSparseSubtrie::Blind(None) }; NUM_LOWER_SUBTRIES]; self.prefix_set = PrefixSetMut::all(); + self.updates = self.updates.is_some().then(SparseTrieUpdates::wiped); } fn clear(&mut self) { @@ -820,6 +826,8 @@ impl SparseTrieInterface for ParallelSparseTrie { } self.prefix_set.clear(); self.updates = None; + self.branch_node_tree_masks.clear(); + self.branch_node_hash_masks.clear(); // `update_actions_buffers` doesn't need to be cleared; we want to reuse the Vecs it has // buffered, and all of those are already inherently cleared when they get used. } @@ -876,11 +884,11 @@ impl SparseTrieInterface for ParallelSparseTrie { curr_path = next_path; // If we were previously looking at the upper trie, and the new path is in the // lower trie, we need to pull out a ref to the lower trie. - if curr_subtrie_is_upper { - if let Some(lower_subtrie) = self.lower_subtrie_for_path(&curr_path) { - curr_subtrie = lower_subtrie; - curr_subtrie_is_upper = false; - } + if curr_subtrie_is_upper && + let Some(lower_subtrie) = self.lower_subtrie_for_path(&curr_path) + { + curr_subtrie = lower_subtrie; + curr_subtrie_is_upper = false; } } } @@ -889,11 +897,35 @@ impl SparseTrieInterface for ParallelSparseTrie { } impl ParallelSparseTrie { + /// Sets the thresholds that control when parallelism is used during operations. + pub const fn with_parallelism_thresholds(mut self, thresholds: ParallelismThresholds) -> Self { + self.parallelism_thresholds = thresholds; + self + } + /// Returns true if retaining updates is enabled for the overall trie. const fn updates_enabled(&self) -> bool { self.updates.is_some() } + /// Returns true if parallelism should be enabled for revealing the given number of nodes. + /// Will always return false in nostd builds. + const fn is_reveal_parallelism_enabled(&self, num_nodes: usize) -> bool { + #[cfg(not(feature = "std"))] + return false; + + num_nodes >= self.parallelism_thresholds.min_revealed_nodes + } + + /// Returns true if parallelism should be enabled for updating hashes with the given number + /// of changed keys. Will always return false in nostd builds. + const fn is_update_parallelism_enabled(&self, num_changed_keys: usize) -> bool { + #[cfg(not(feature = "std"))] + return false; + + num_changed_keys >= self.parallelism_thresholds.min_updated_nodes + } + /// Creates a new revealed sparse trie from the given root node. /// /// This function initializes the internal structures and then reveals the root. @@ -1288,7 +1320,10 @@ impl ParallelSparseTrie { /// Returns: /// 1. List of lower [subtries](SparseSubtrie) that have changed according to the provided - /// [prefix set](PrefixSet). See documentation of [`ChangedSubtrie`] for more details. + /// [prefix set](PrefixSet). See documentation of [`ChangedSubtrie`] for more details. Lower + /// subtries whose root node is missing a hash will also be returned; this is required to + /// handle cases where extensions/leafs get shortened and therefore moved from the upper to a + /// lower subtrie. /// 2. Prefix set of keys that do not belong to any lower subtrie. /// /// This method helps optimize hash recalculations by identifying which specific @@ -1300,6 +1335,12 @@ impl ParallelSparseTrie { &mut self, prefix_set: &mut PrefixSet, ) -> (Vec, PrefixSetMut) { + // Fast-path: If the prefix set is empty then no subtries can have been changed. Just return + // empty values. + if prefix_set.is_empty() && !prefix_set.all() { + return Default::default(); + } + // Clone the prefix set to iterate over its keys. Cloning is cheap, it's just an Arc. let prefix_set_clone = prefix_set.clone(); let mut prefix_set_iter = prefix_set_clone.into_iter().copied().peekable(); @@ -1308,9 +1349,10 @@ impl ParallelSparseTrie { let updates_enabled = self.updates_enabled(); for (index, subtrie) in self.lower_subtries.iter_mut().enumerate() { - if let Some(subtrie) = - subtrie.take_revealed_if(|subtrie| prefix_set.contains(&subtrie.path)) - { + if let Some(subtrie) = subtrie.take_revealed_if(|subtrie| { + prefix_set.contains(&subtrie.path) || + subtrie.nodes.get(&subtrie.path).is_some_and(|n| n.hash().is_none()) + }) { let prefix_set = if prefix_set.all() { unchanged_prefix_set = PrefixSetMut::all(); PrefixSetMut::all() @@ -1442,6 +1484,25 @@ impl ParallelSparseTrie { Ok(()) } + + /// Return updated subtries back to the trie after executing any actions required on the + /// top-level `SparseTrieUpdates`. + fn insert_changed_subtries( + &mut self, + changed_subtries: impl IntoIterator, + ) { + for ChangedSubtrie { index, subtrie, update_actions_buf, .. } in changed_subtries { + if let Some(mut update_actions_buf) = update_actions_buf { + self.apply_subtrie_update_actions( + #[allow(clippy::iter_with_drain)] + update_actions_buf.drain(..), + ); + self.update_actions_buffers.push(update_actions_buf); + } + + self.lower_subtries[index] = LowerSparseSubtrie::Revealed(subtrie); + } + } } /// This is a subtrie of the [`ParallelSparseTrie`] that contains a map from path to sparse trie @@ -1529,31 +1590,37 @@ impl SparseSubtrie { current = Some(next_node); } LeafUpdateStep::Complete { reveal_path, .. } => { - if let Some(reveal_path) = reveal_path { - if self.nodes.get(&reveal_path).expect("node must exist").is_hash() { - if let Some(RevealedNode { node, tree_mask, hash_mask }) = - provider.trie_node(&reveal_path)? - { - let decoded = TrieNode::decode(&mut &node[..])?; - trace!( - target: "trie::parallel_sparse", - ?reveal_path, - ?decoded, - ?tree_mask, - ?hash_mask, - "Revealing child", - ); - self.reveal_node( - reveal_path, - &decoded, - TrieMasks { hash_mask, tree_mask }, - )?; - } else { - return Err(SparseTrieErrorKind::NodeNotFoundInProvider { - path: reveal_path, - } - .into()) + if let Some(reveal_path) = reveal_path && + self.nodes.get(&reveal_path).expect("node must exist").is_hash() + { + debug!( + target: "trie::parallel_sparse", + child_path = ?reveal_path, + leaf_full_path = ?full_path, + "Extension node child not revealed in update_leaf, falling back to db", + ); + if let Some(RevealedNode { node, tree_mask, hash_mask }) = + provider.trie_node(&reveal_path)? + { + let decoded = TrieNode::decode(&mut &node[..])?; + trace!( + target: "trie::parallel_sparse", + ?reveal_path, + ?decoded, + ?tree_mask, + ?hash_mask, + "Revealing child (from lower)", + ); + self.reveal_node( + reveal_path, + &decoded, + TrieMasks { hash_mask, tree_mask }, + )?; + } else { + return Err(SparseTrieErrorKind::NodeNotFoundInProvider { + path: reveal_path, } + .into()) } } @@ -2475,7 +2542,7 @@ mod tests { use crate::trie::ChangedSubtrie; use alloy_primitives::{ b256, hex, - map::{foldhash::fast::RandomState, B256Set, DefaultHashBuilder, HashMap}, + map::{B256Set, DefaultHashBuilder, HashMap}, B256, U256, }; use alloy_rlp::{Decodable, Encodable}; @@ -2531,7 +2598,7 @@ mod tests { impl MockTrieNodeProvider { /// Creates a new empty mock provider fn new() -> Self { - Self { nodes: HashMap::with_hasher(RandomState::default()) } + Self { nodes: HashMap::default() } } /// Adds a revealed node at the specified path @@ -2791,8 +2858,8 @@ mod tests { let mut prefix_set = PrefixSetMut::default(); prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles)); prefix_set.extend_keys(destroyed_accounts.iter().map(Nibbles::unpack)); - let walker = - TrieWalker::state_trie(trie_cursor, prefix_set.freeze()).with_deletions_retained(true); + let walker = TrieWalker::<_>::state_trie(trie_cursor, prefix_set.freeze()) + .with_deletions_retained(true); let hashed_post_state = HashedPostState::default() .with_accounts(state.into_iter().map(|(nibbles, account)| { (nibbles.pack().into_inner().unwrap().into(), Some(account)) @@ -3283,27 +3350,36 @@ mod tests { } #[test] - fn test_update_subtrie_hashes() { + fn test_update_subtrie_hashes_prefix_set_matching() { // Create a trie and reveal leaf nodes using reveal_nodes let mut trie = ParallelSparseTrie::default(); - // Create dummy leaf nodes that form an incorrect trie structure but enough to test the - // method + // Create dummy leaf nodes. let leaf_1_full_path = Nibbles::from_nibbles([0; 64]); let leaf_1_path = leaf_1_full_path.slice(..2); let leaf_1_key = leaf_1_full_path.slice(2..); - let leaf_2_full_path = Nibbles::from_nibbles([vec![1, 0], vec![0; 62]].concat()); + let leaf_2_full_path = Nibbles::from_nibbles([vec![0, 1], vec![0; 62]].concat()); let leaf_2_path = leaf_2_full_path.slice(..2); let leaf_2_key = leaf_2_full_path.slice(2..); - let leaf_3_full_path = Nibbles::from_nibbles([vec![3, 0], vec![0; 62]].concat()); + let leaf_3_full_path = Nibbles::from_nibbles([vec![0, 2], vec![0; 62]].concat()); let leaf_3_path = leaf_3_full_path.slice(..2); let leaf_3_key = leaf_3_full_path.slice(2..); let leaf_1 = create_leaf_node(leaf_1_key.to_vec(), 1); let leaf_2 = create_leaf_node(leaf_2_key.to_vec(), 2); let leaf_3 = create_leaf_node(leaf_3_key.to_vec(), 3); + // Create branch node with hashes for each leaf. + let child_hashes = [ + RlpNode::word_rlp(&B256::repeat_byte(0x00)), + RlpNode::word_rlp(&B256::repeat_byte(0x11)), + // deliberately omit hash for leaf_3 + ]; + let branch_path = Nibbles::from_nibbles([0x0]); + let branch_node = create_branch_node_with_children(&[0x0, 0x1, 0x2], child_hashes); + // Reveal nodes using reveal_nodes trie.reveal_nodes(vec![ + RevealedSparseNode { path: branch_path, node: branch_node, masks: TrieMasks::none() }, RevealedSparseNode { path: leaf_1_path, node: leaf_1, masks: TrieMasks::none() }, RevealedSparseNode { path: leaf_2_path, node: leaf_2, masks: TrieMasks::none() }, RevealedSparseNode { path: leaf_3_path, node: leaf_3, masks: TrieMasks::none() }, @@ -3315,16 +3391,16 @@ mod tests { let subtrie_2_index = SparseSubtrieType::from_path(&leaf_2_path).lower_index().unwrap(); let subtrie_3_index = SparseSubtrieType::from_path(&leaf_3_path).lower_index().unwrap(); - let unchanged_prefix_set = PrefixSetMut::from([ + let mut unchanged_prefix_set = PrefixSetMut::from([ Nibbles::from_nibbles([0x0]), leaf_2_full_path, - Nibbles::from_nibbles([0x2, 0x0, 0x0]), + Nibbles::from_nibbles([0x3, 0x0, 0x0]), ]); // Create a prefix set with the keys that match only the second subtrie let mut prefix_set = PrefixSetMut::from([ // Match second subtrie - Nibbles::from_nibbles([0x1, 0x0, 0x0]), - Nibbles::from_nibbles([0x1, 0x0, 0x1, 0x0]), + Nibbles::from_nibbles([0x0, 0x1, 0x0]), + Nibbles::from_nibbles([0x0, 0x1, 0x1, 0x0]), ]); prefix_set.extend(unchanged_prefix_set.clone()); trie.prefix_set = prefix_set; @@ -3332,8 +3408,16 @@ mod tests { // Update subtrie hashes trie.update_subtrie_hashes(); + // We expect that leaf 3 (0x02) should have been added to the prefix set, because it is + // missing a hash and is the root node of a lower subtrie, and therefore would need to have + // that hash calculated by `update_upper_subtrie_hashes`. + unchanged_prefix_set.insert(leaf_3_full_path); + // Check that the prefix set was updated - assert_eq!(trie.prefix_set, unchanged_prefix_set); + assert_eq!( + trie.prefix_set.clone().freeze().into_iter().collect::>(), + unchanged_prefix_set.freeze().into_iter().collect::>() + ); // Check that subtries were returned back to the array assert!(trie.lower_subtries[subtrie_1_index].as_revealed_ref().is_some()); assert!(trie.lower_subtries[subtrie_2_index].as_revealed_ref().is_some()); @@ -5653,7 +5737,7 @@ mod tests { // 0xXY: Leaf { key: 0xZ... } // Create leaves that will force multiple subtries - let leaves = vec![ + let leaves = [ ctx.create_test_leaf([0x0, 0x0, 0x1, 0x2], 1), ctx.create_test_leaf([0x0, 0x1, 0x3, 0x4], 2), ctx.create_test_leaf([0x0, 0x2, 0x5, 0x6], 3), @@ -6238,7 +6322,7 @@ mod tests { // Assert the root hash matches the expected value let expected_root = - b256!("29b07de8376e9ce7b3a69e9b102199869514d3f42590b5abc6f7d48ec9b8665c"); + b256!("0x29b07de8376e9ce7b3a69e9b102199869514d3f42590b5abc6f7d48ec9b8665c"); assert_eq!(trie.root(), expected_root); } diff --git a/crates/trie/sparse/benches/root.rs b/crates/trie/sparse/benches/root.rs index ed88921ecf2..9eaf54c2d0f 100644 --- a/crates/trie/sparse/benches/root.rs +++ b/crates/trie/sparse/benches/root.rs @@ -7,7 +7,7 @@ use proptest::{prelude::*, strategy::ValueTree, test_runner::TestRunner}; use reth_trie::{ hashed_cursor::{noop::NoopHashedStorageCursor, HashedPostStateStorageCursor}, node_iter::{TrieElement, TrieNodeIter}, - trie_cursor::{noop::NoopStorageTrieCursor, InMemoryStorageTrieCursor}, + trie_cursor::{noop::NoopStorageTrieCursor, InMemoryTrieCursor}, updates::StorageTrieUpdates, walker::TrieWalker, HashedStorage, @@ -133,11 +133,10 @@ fn calculate_root_from_leaves_repeated(c: &mut Criterion) { ) }; - let walker = TrieWalker::storage_trie( - InMemoryStorageTrieCursor::new( - B256::ZERO, - NoopStorageTrieCursor::default(), - Some(&trie_updates_sorted), + let walker = TrieWalker::<_>::storage_trie( + InMemoryTrieCursor::new( + Some(NoopStorageTrieCursor::default()), + &trie_updates_sorted.storage_nodes, ), prefix_set, ); diff --git a/crates/trie/sparse/src/metrics.rs b/crates/trie/sparse/src/metrics.rs index 44f9c9dc958..430a831a2f7 100644 --- a/crates/trie/sparse/src/metrics.rs +++ b/crates/trie/sparse/src/metrics.rs @@ -21,19 +21,20 @@ pub(crate) struct SparseStateTrieMetrics { impl SparseStateTrieMetrics { /// Record the metrics into the histograms - pub(crate) fn record(&self) { + pub(crate) fn record(&mut self) { + use core::mem::take; self.histograms .multiproof_skipped_account_nodes - .record(self.multiproof_skipped_account_nodes as f64); + .record(take(&mut self.multiproof_skipped_account_nodes) as f64); self.histograms .multiproof_total_account_nodes - .record(self.multiproof_total_account_nodes as f64); + .record(take(&mut self.multiproof_total_account_nodes) as f64); self.histograms .multiproof_skipped_storage_nodes - .record(self.multiproof_skipped_storage_nodes as f64); + .record(take(&mut self.multiproof_skipped_storage_nodes) as f64); self.histograms .multiproof_total_storage_nodes - .record(self.multiproof_total_storage_nodes as f64); + .record(take(&mut self.multiproof_total_storage_nodes) as f64); } /// Increment the skipped account nodes counter by the given count diff --git a/crates/trie/sparse/src/state.rs b/crates/trie/sparse/src/state.rs index c7c214a894c..fde4810da57 100644 --- a/crates/trie/sparse/src/state.rs +++ b/crates/trie/sparse/src/state.rs @@ -30,8 +30,8 @@ pub struct ClearedSparseStateTrie< impl ClearedSparseStateTrie where - A: SparseTrieInterface + Default, - S: SparseTrieInterface + Default, + A: SparseTrieInterface, + S: SparseTrieInterface, { /// Creates a [`ClearedSparseStateTrie`] by clearing all the existing internal state of a /// [`SparseStateTrie`] and then storing that instance for later re-use. @@ -108,12 +108,18 @@ impl SparseStateTrie { self.state = trie; self } + + /// Set the default trie which will be cloned when creating new storage [`SparseTrie`]s. + pub fn with_default_storage_trie(mut self, trie: SparseTrie) -> Self { + self.storage.default_trie = trie; + self + } } impl SparseStateTrie where A: SparseTrieInterface + Default, - S: SparseTrieInterface + Default, + S: SparseTrieInterface + Default + Clone, { /// Create new [`SparseStateTrie`] pub fn new() -> Self { @@ -671,15 +677,14 @@ where /// Update or remove trie account based on new account info. This method will either recompute /// the storage root based on update storage trie or look it up from existing leaf value. /// - /// If the new account info and storage trie are empty, the account leaf will be removed. + /// Returns false if the new account info and storage trie are empty, indicating the account + /// leaf should be removed. pub fn update_account( &mut self, address: B256, account: Account, provider_factory: impl TrieNodeProviderFactory, - ) -> SparseStateTrieResult<()> { - let nibbles = Nibbles::unpack(address); - + ) -> SparseStateTrieResult { let storage_root = if let Some(storage_trie) = self.storage.tries.get_mut(&address) { trace!(target: "trie::sparse", ?address, "Calculating storage root to update account"); storage_trie.root().ok_or(SparseTrieErrorKind::Blind)? @@ -698,27 +703,29 @@ where }; if account.is_empty() && storage_root == EMPTY_ROOT_HASH { - trace!(target: "trie::sparse", ?address, "Removing account"); - self.remove_account_leaf(&nibbles, provider_factory) - } else { - trace!(target: "trie::sparse", ?address, "Updating account"); - self.account_rlp_buf.clear(); - account.into_trie_account(storage_root).encode(&mut self.account_rlp_buf); - self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory) + return Ok(false); } + + trace!(target: "trie::sparse", ?address, "Updating account"); + let nibbles = Nibbles::unpack(address); + self.account_rlp_buf.clear(); + account.into_trie_account(storage_root).encode(&mut self.account_rlp_buf); + self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?; + + Ok(true) } /// Update the storage root of a revealed account. /// /// If the account doesn't exist in the trie, the function is a no-op. /// - /// If the new storage root is empty, and the account info was already empty, the account leaf - /// will be removed. + /// Returns false if the new storage root is empty, and the account info was already empty, + /// indicating the account leaf should be removed. pub fn update_account_storage_root( &mut self, address: B256, provider_factory: impl TrieNodeProviderFactory, - ) -> SparseStateTrieResult<()> { + ) -> SparseStateTrieResult { if !self.is_account_revealed(address) { return Err(SparseTrieErrorKind::Blind.into()) } @@ -730,7 +737,7 @@ where .transpose()? else { trace!(target: "trie::sparse", ?address, "Account not found in trie, skipping storage root update"); - return Ok(()) + return Ok(true) }; // Calculate the new storage root. If the storage trie doesn't exist, the storage root will @@ -745,20 +752,19 @@ where // Update the account with the new storage root. trie_account.storage_root = storage_root; - let nibbles = Nibbles::unpack(address); + // If the account is empty, indicate that it should be removed. if trie_account == TrieAccount::default() { - // If the account is empty, remove it. - trace!(target: "trie::sparse", ?address, "Removing account because the storage root is empty"); - self.remove_account_leaf(&nibbles, provider_factory)?; - } else { - // Otherwise, update the account leaf. - trace!(target: "trie::sparse", ?address, "Updating account with the new storage root"); - self.account_rlp_buf.clear(); - trie_account.encode(&mut self.account_rlp_buf); - self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?; + return Ok(false) } - Ok(()) + // Otherwise, update the account leaf. + trace!(target: "trie::sparse", ?address, "Updating account with the new storage root"); + let nibbles = Nibbles::unpack(address); + self.account_rlp_buf.clear(); + trie_account.encode(&mut self.account_rlp_buf); + self.update_account_leaf(nibbles, self.account_rlp_buf.clone(), provider_factory)?; + + Ok(true) } /// Remove the account leaf node. @@ -801,9 +807,11 @@ struct StorageTries { revealed_paths: B256Map>, /// Cleared revealed storage trie path collections, kept for re-use. cleared_revealed_paths: Vec>, + /// A default cleared trie instance, which will be cloned when creating new tries. + default_trie: SparseTrie, } -impl StorageTries { +impl StorageTries { /// Returns all fields to a cleared state, equivalent to the default state, keeping cleared /// collections for re-use later when possible. fn clear(&mut self) { @@ -813,7 +821,9 @@ impl StorageTries { set })); } +} +impl StorageTries { /// Returns the set of already revealed trie node paths for an account's storage, creating the /// set if it didn't previously exist. fn get_revealed_paths_mut(&mut self, account: B256) -> &mut HashSet { @@ -828,10 +838,9 @@ impl StorageTries { &mut self, account: B256, ) -> (&mut SparseTrie, &mut HashSet) { - let trie = self - .tries - .entry(account) - .or_insert_with(|| self.cleared_tries.pop().unwrap_or_default()); + let trie = self.tries.entry(account).or_insert_with(|| { + self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone()) + }); let revealed_paths = self .revealed_paths @@ -845,7 +854,9 @@ impl StorageTries { /// doesn't already exist. #[cfg(feature = "std")] fn take_or_create_trie(&mut self, account: &B256) -> SparseTrie { - self.tries.remove(account).unwrap_or_else(|| self.cleared_tries.pop().unwrap_or_default()) + self.tries.remove(account).unwrap_or_else(|| { + self.cleared_tries.pop().unwrap_or_else(|| self.default_trie.clone()) + }) } /// Takes the revealed paths set from the account from the internal `HashMap`, creating one if diff --git a/crates/trie/sparse/src/trie.rs b/crates/trie/sparse/src/trie.rs index 3189a8c3b66..76dadc8fc9c 100644 --- a/crates/trie/sparse/src/trie.rs +++ b/crates/trie/sparse/src/trie.rs @@ -24,7 +24,7 @@ use reth_trie_common::{ TrieNode, CHILD_INDEX_RANGE, EMPTY_ROOT_HASH, }; use smallvec::SmallVec; -use tracing::trace; +use tracing::{debug, trace}; /// The level below which the sparse trie hashes are calculated in /// [`SerialSparseTrie::update_subtrie_hashes`]. @@ -42,7 +42,7 @@ const SPARSE_TRIE_SUBTRIE_HASHES_LEVEL: usize = 2; /// 2. Update tracking - changes to the trie structure can be tracked and selectively persisted /// 3. Incremental operations - nodes can be revealed as needed without loading the entire trie. /// This is what gives rise to the notion of a "sparse" trie. -#[derive(PartialEq, Eq, Debug)] +#[derive(PartialEq, Eq, Debug, Clone)] pub enum SparseTrie { /// The trie is blind -- no nodes have been revealed /// @@ -132,6 +132,13 @@ impl SparseTrie { Self::Blind(None) } + /// Creates a new blind sparse trie, clearing and later reusing the given + /// [`SparseTrieInterface`]. + pub fn blind_from(mut trie: T) -> Self { + trie.clear(); + Self::Blind(Some(Box::new(trie))) + } + /// Returns `true` if the sparse trie has no revealed nodes. pub const fn is_blind(&self) -> bool { matches!(self, Self::Blind(_)) @@ -419,6 +426,8 @@ impl SparseTrieInterface for SerialSparseTrie { node: TrieNode, masks: TrieMasks, ) -> SparseTrieResult<()> { + trace!(target: "trie::sparse", ?path, ?node, ?masks, "reveal_node called"); + // If the node is already revealed and it's not a hash node, do nothing. if self.nodes.get(&path).is_some_and(|node| !node.is_hash()) { return Ok(()) @@ -570,6 +579,8 @@ impl SparseTrieInterface for SerialSparseTrie { value: Vec, provider: P, ) -> SparseTrieResult<()> { + trace!(target: "trie::sparse", ?full_path, ?value, "update_leaf called"); + self.prefix_set.insert(full_path); let existing = self.values.insert(full_path, value); if existing.is_some() { @@ -636,6 +647,12 @@ impl SparseTrieInterface for SerialSparseTrie { if self.updates.is_some() { // Check if the extension node child is a hash that needs to be revealed if self.nodes.get(¤t).unwrap().is_hash() { + debug!( + target: "trie::sparse", + leaf_full_path = ?full_path, + child_path = ?current, + "Extension node child not revealed in update_leaf, falling back to db", + ); if let Some(RevealedNode { node, tree_mask, hash_mask }) = provider.trie_node(¤t)? { @@ -700,6 +717,8 @@ impl SparseTrieInterface for SerialSparseTrie { full_path: &Nibbles, provider: P, ) -> SparseTrieResult<()> { + trace!(target: "trie::sparse", ?full_path, "remove_leaf called"); + if self.values.remove(full_path).is_none() { if let Some(&SparseNode::Hash(hash)) = self.nodes.get(full_path) { // Leaf is present in the trie, but it's blinded. @@ -803,7 +822,12 @@ impl SparseTrieInterface for SerialSparseTrie { trace!(target: "trie::sparse", ?removed_path, ?child_path, "Branch node has only one child"); if self.nodes.get(&child_path).unwrap().is_hash() { - trace!(target: "trie::sparse", ?child_path, "Retrieving remaining blinded branch child"); + debug!( + target: "trie::sparse", + ?child_path, + leaf_full_path = ?full_path, + "Branch node child not revealed in remove_leaf, falling back to db", + ); if let Some(RevealedNode { node, tree_mask, hash_mask }) = provider.trie_node(&child_path)? { @@ -951,14 +975,14 @@ impl SparseTrieInterface for SerialSparseTrie { expected_value: Option<&Vec>, path: &Nibbles, ) -> Result<(), LeafLookupError> { - if let Some(expected) = expected_value { - if actual_value != expected { - return Err(LeafLookupError::ValueMismatch { - path: *path, - expected: Some(expected.clone()), - actual: actual_value.clone(), - }); - } + if let Some(expected) = expected_value && + actual_value != expected + { + return Err(LeafLookupError::ValueMismatch { + path: *path, + expected: Some(expected.clone()), + actual: actual_value.clone(), + }); } Ok(()) } @@ -1921,8 +1945,6 @@ impl SparseTrieUpdates { mod find_leaf_tests { use super::*; use crate::provider::DefaultTrieNodeProvider; - use alloy_primitives::map::foldhash::fast::RandomState; - // Assuming this exists use alloy_rlp::Encodable; use assert_matches::assert_matches; use reth_primitives_traits::Account; @@ -2085,7 +2107,7 @@ mod find_leaf_tests { let blinded_hash = B256::repeat_byte(0xBB); let leaf_path = Nibbles::from_nibbles_unchecked([0x1, 0x2, 0x3, 0x4]); - let mut nodes = alloy_primitives::map::HashMap::with_hasher(RandomState::default()); + let mut nodes = alloy_primitives::map::HashMap::default(); // Create path to the blinded node nodes.insert( Nibbles::default(), @@ -2126,7 +2148,7 @@ mod find_leaf_tests { let path_to_blind = Nibbles::from_nibbles_unchecked([0x1]); let search_path = Nibbles::from_nibbles_unchecked([0x1, 0x2, 0x3, 0x4]); - let mut nodes = HashMap::with_hasher(RandomState::default()); + let mut nodes = HashMap::default(); // Root is a branch with child 0x1 (blinded) and 0x5 (revealed leaf) // So we set Bit 1 and Bit 5 in the state_mask @@ -2141,7 +2163,7 @@ mod find_leaf_tests { SparseNode::new_leaf(Nibbles::from_nibbles_unchecked([0x6, 0x7, 0x8])), ); - let mut values = HashMap::with_hasher(RandomState::default()); + let mut values = HashMap::default(); values.insert(path_revealed_leaf, VALUE_A()); let sparse = SerialSparseTrie { @@ -2286,8 +2308,8 @@ mod tests { let mut prefix_set = PrefixSetMut::default(); prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles)); prefix_set.extend_keys(destroyed_accounts.iter().map(Nibbles::unpack)); - let walker = - TrieWalker::state_trie(trie_cursor, prefix_set.freeze()).with_deletions_retained(true); + let walker = TrieWalker::<_>::state_trie(trie_cursor, prefix_set.freeze()) + .with_deletions_retained(true); let hashed_post_state = HashedPostState::default() .with_accounts(state.into_iter().map(|(nibbles, account)| { (nibbles.pack().into_inner().unwrap().into(), Some(account)) diff --git a/crates/trie/trie/Cargo.toml b/crates/trie/trie/Cargo.toml index adee3291b80..403d187e46a 100644 --- a/crates/trie/trie/Cargo.toml +++ b/crates/trie/trie/Cargo.toml @@ -57,6 +57,7 @@ revm-state.workspace = true triehash.workspace = true # misc +assert_matches.workspace = true criterion.workspace = true parking_lot.workspace = true pretty_assertions.workspace = true diff --git a/crates/trie/trie/src/lib.rs b/crates/trie/trie/src/lib.rs index 8accd447105..7efa00631d2 100644 --- a/crates/trie/trie/src/lib.rs +++ b/crates/trie/trie/src/lib.rs @@ -63,3 +63,6 @@ pub mod test_utils; /// Collection of mock types for testing. #[cfg(test)] pub mod mock; + +/// Verification of existing stored trie nodes against state data. +pub mod verify; diff --git a/crates/trie/trie/src/node_iter.rs b/crates/trie/trie/src/node_iter.rs index dfb140fdf98..5d0b7b496fc 100644 --- a/crates/trie/trie/src/node_iter.rs +++ b/crates/trie/trie/src/node_iter.rs @@ -2,6 +2,7 @@ use crate::{ hashed_cursor::HashedCursor, trie_cursor::TrieCursor, walker::TrieWalker, Nibbles, TrieType, }; use alloy_primitives::B256; +use alloy_trie::proof::AddedRemovedKeys; use reth_storage_errors::db::DatabaseError; use tracing::{instrument, trace}; @@ -43,11 +44,14 @@ struct SeekedHashedEntry { result: Option<(B256, V)>, } -/// An iterator over existing intermediate branch nodes and updated leaf nodes. +/// Iterates over trie nodes for hash building. +/// +/// This iterator depends on the ordering guarantees of [`TrieCursor`], +/// and additionally uses hashed cursor lookups when operating on storage tries. #[derive(Debug)] -pub struct TrieNodeIter { +pub struct TrieNodeIter { /// The walker over intermediate nodes. - pub walker: TrieWalker, + pub walker: TrieWalker, /// The cursor for the hashed entries. pub hashed_cursor: H, /// The type of the trie. @@ -74,22 +78,23 @@ pub struct TrieNodeIter { last_next_result: Option<(B256, H::Value)>, } -impl TrieNodeIter +impl TrieNodeIter where H::Value: Copy, + K: AsRef, { /// Creates a new [`TrieNodeIter`] for the state trie. - pub fn state_trie(walker: TrieWalker, hashed_cursor: H) -> Self { + pub fn state_trie(walker: TrieWalker, hashed_cursor: H) -> Self { Self::new(walker, hashed_cursor, TrieType::State) } /// Creates a new [`TrieNodeIter`] for the storage trie. - pub fn storage_trie(walker: TrieWalker, hashed_cursor: H) -> Self { + pub fn storage_trie(walker: TrieWalker, hashed_cursor: H) -> Self { Self::new(walker, hashed_cursor, TrieType::Storage) } /// Creates a new [`TrieNodeIter`]. - fn new(walker: TrieWalker, hashed_cursor: H, trie_type: TrieType) -> Self { + fn new(walker: TrieWalker, hashed_cursor: H, trie_type: TrieType) -> Self { Self { walker, hashed_cursor, @@ -117,16 +122,16 @@ where /// /// If `metrics` feature is enabled, also updates the metrics. fn seek_hashed_entry(&mut self, key: B256) -> Result, DatabaseError> { - if let Some((last_key, last_value)) = self.last_next_result { - if last_key == key { - trace!(target: "trie::node_iter", seek_key = ?key, "reusing result from last next() call instead of seeking"); - self.last_next_result = None; // Consume the cached value + if let Some((last_key, last_value)) = self.last_next_result && + last_key == key + { + trace!(target: "trie::node_iter", seek_key = ?key, "reusing result from last next() call instead of seeking"); + self.last_next_result = None; // Consume the cached value - let result = Some((last_key, last_value)); - self.last_seeked_hashed_entry = Some(SeekedHashedEntry { seeked_key: key, result }); + let result = Some((last_key, last_value)); + self.last_seeked_hashed_entry = Some(SeekedHashedEntry { seeked_key: key, result }); - return Ok(result); - } + return Ok(result); } if let Some(entry) = self @@ -167,11 +172,12 @@ where } } -impl TrieNodeIter +impl TrieNodeIter where C: TrieCursor, H: HashedCursor, H::Value: Copy, + K: AsRef, { /// Return the next trie node to be added to the hash builder. /// @@ -337,7 +343,7 @@ mod tests { let mut prefix_set = PrefixSetMut::default(); prefix_set.extend_keys(state.clone().into_iter().map(|(nibbles, _)| nibbles)); - let walker = TrieWalker::state_trie(NoopAccountTrieCursor, prefix_set.freeze()); + let walker = TrieWalker::<_>::state_trie(NoopAccountTrieCursor, prefix_set.freeze()); let hashed_post_state = HashedPostState::default() .with_accounts(state.into_iter().map(|(nibbles, account)| { @@ -466,8 +472,10 @@ mod tests { prefix_set.insert(Nibbles::unpack(account_3)); let prefix_set = prefix_set.freeze(); - let walker = - TrieWalker::state_trie(trie_cursor_factory.account_trie_cursor().unwrap(), prefix_set); + let walker = TrieWalker::<_>::state_trie( + trie_cursor_factory.account_trie_cursor().unwrap(), + prefix_set, + ); let hashed_cursor_factory = MockHashedCursorFactory::new( BTreeMap::from([ diff --git a/crates/trie/trie/src/proof/mod.rs b/crates/trie/trie/src/proof/mod.rs index 10439b804f6..dc18a24988d 100644 --- a/crates/trie/trie/src/proof/mod.rs +++ b/crates/trie/trie/src/proof/mod.rs @@ -12,6 +12,7 @@ use alloy_primitives::{ Address, B256, }; use alloy_rlp::{BufMut, Encodable}; +use alloy_trie::proof::AddedRemovedKeys; use reth_execution_errors::trie::StateProofError; use reth_trie_common::{ proof::ProofRetainer, AccountProof, MultiProof, MultiProofTargets, StorageMultiProof, @@ -111,7 +112,7 @@ where // Create the walker. let mut prefix_set = self.prefix_sets.account_prefix_set.clone(); prefix_set.extend_keys(targets.keys().map(Nibbles::unpack)); - let walker = TrieWalker::state_trie(trie_cursor, prefix_set.freeze()); + let walker = TrieWalker::<_>::state_trie(trie_cursor, prefix_set.freeze()); // Create a hash builder to rebuild the root node since it is not available in the database. let retainer = targets.keys().map(Nibbles::unpack).collect(); @@ -183,7 +184,7 @@ where /// Generates storage merkle proofs. #[derive(Debug)] -pub struct StorageProof { +pub struct StorageProof { /// The factory for traversing trie nodes. trie_cursor_factory: T, /// The factory for hashed cursors. @@ -194,6 +195,8 @@ pub struct StorageProof { prefix_set: PrefixSetMut, /// Flag indicating whether to include branch node masks in the proof. collect_branch_node_masks: bool, + /// Provided by the user to give the necessary context to retain extra proofs. + added_removed_keys: Option, } impl StorageProof { @@ -210,28 +213,36 @@ impl StorageProof { hashed_address, prefix_set: PrefixSetMut::default(), collect_branch_node_masks: false, + added_removed_keys: None, } } +} +impl StorageProof { /// Set the trie cursor factory. - pub fn with_trie_cursor_factory(self, trie_cursor_factory: TF) -> StorageProof { + pub fn with_trie_cursor_factory(self, trie_cursor_factory: TF) -> StorageProof { StorageProof { trie_cursor_factory, hashed_cursor_factory: self.hashed_cursor_factory, hashed_address: self.hashed_address, prefix_set: self.prefix_set, collect_branch_node_masks: self.collect_branch_node_masks, + added_removed_keys: self.added_removed_keys, } } /// Set the hashed cursor factory. - pub fn with_hashed_cursor_factory(self, hashed_cursor_factory: HF) -> StorageProof { + pub fn with_hashed_cursor_factory( + self, + hashed_cursor_factory: HF, + ) -> StorageProof { StorageProof { trie_cursor_factory: self.trie_cursor_factory, hashed_cursor_factory, hashed_address: self.hashed_address, prefix_set: self.prefix_set, collect_branch_node_masks: self.collect_branch_node_masks, + added_removed_keys: self.added_removed_keys, } } @@ -246,12 +257,32 @@ impl StorageProof { self.collect_branch_node_masks = branch_node_masks; self } + + /// Configures the retainer to retain proofs for certain nodes which would otherwise fall + /// outside the target set, when those nodes might be required to calculate the state root when + /// keys have been added or removed to the trie. + /// + /// If None is given then retention of extra proofs is disabled. + pub fn with_added_removed_keys( + self, + added_removed_keys: Option, + ) -> StorageProof { + StorageProof { + trie_cursor_factory: self.trie_cursor_factory, + hashed_cursor_factory: self.hashed_cursor_factory, + hashed_address: self.hashed_address, + prefix_set: self.prefix_set, + collect_branch_node_masks: self.collect_branch_node_masks, + added_removed_keys, + } + } } -impl StorageProof +impl StorageProof where T: TrieCursorFactory, H: HashedCursorFactory, + K: AsRef, { /// Generate an account proof from intermediate nodes. pub fn storage_proof( @@ -279,9 +310,11 @@ where self.prefix_set.extend_keys(target_nibbles.clone()); let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(self.hashed_address)?; - let walker = TrieWalker::storage_trie(trie_cursor, self.prefix_set.freeze()); + let walker = TrieWalker::<_>::storage_trie(trie_cursor, self.prefix_set.freeze()) + .with_added_removed_keys(self.added_removed_keys.as_ref()); - let retainer = ProofRetainer::from_iter(target_nibbles); + let retainer = ProofRetainer::from_iter(target_nibbles) + .with_added_removed_keys(self.added_removed_keys.as_ref()); let mut hash_builder = HashBuilder::default() .with_proof_retainer(retainer) .with_updates(self.collect_branch_node_masks); diff --git a/crates/trie/trie/src/trie.rs b/crates/trie/trie/src/trie.rs index f0ce3aac7cf..17cdd1f96c5 100644 --- a/crates/trie/trie/src/trie.rs +++ b/crates/trie/trie/src/trie.rs @@ -15,9 +15,10 @@ use crate::{ use alloy_consensus::EMPTY_ROOT_HASH; use alloy_primitives::{keccak256, Address, B256}; use alloy_rlp::{BufMut, Encodable}; +use alloy_trie::proof::AddedRemovedKeys; use reth_execution_errors::{StateRootError, StorageRootError}; use reth_primitives_traits::Account; -use tracing::{debug, trace, trace_span}; +use tracing::{debug, instrument, trace}; /// The default updates after which root algorithms should return intermediate progress rather than /// finishing the computation. @@ -172,7 +173,7 @@ where // resume account trie iteration let mut hash_builder = account_root_state.hash_builder.with_updates(retain_updates); - let walker = TrieWalker::state_trie_from_stack( + let walker = TrieWalker::<_>::state_trie_from_stack( trie_cursor, account_root_state.walker_stack, self.prefix_sets.account_prefix_set, @@ -355,9 +356,9 @@ impl StateRootContext { /// Creates a [`StateRootProgress`] when the threshold is hit, from the state of the current /// [`TrieNodeIter`], [`HashBuilder`], last hashed key and any storage root intermediate state. - fn create_progress_state( + fn create_progress_state( mut self, - account_node_iter: TrieNodeIter, + account_node_iter: TrieNodeIter, hash_builder: HashBuilder, last_hashed_key: B256, storage_state: Option, @@ -365,6 +366,7 @@ impl StateRootContext { where C: TrieCursor, H: HashedCursor, + K: AsRef, { let (walker_stack, walker_deleted_keys) = account_node_iter.walker.split(); self.trie_updates.removed_nodes.extend(walker_deleted_keys); @@ -382,14 +384,15 @@ impl StateRootContext { } /// Calculates the total number of updated nodes. - fn total_updates_len( + fn total_updates_len( &self, - account_node_iter: &TrieNodeIter, + account_node_iter: &TrieNodeIter, hash_builder: &HashBuilder, ) -> u64 where C: TrieCursor, H: HashedCursor, + K: AsRef, { (self.updated_storage_nodes + account_node_iter.walker.removed_keys_len() + @@ -608,10 +611,8 @@ where /// /// The storage root, number of walked entries and trie updates /// for a given address if requested. + #[instrument(skip_all, target = "trie::storage_root", name = "Storage trie", fields(hashed_address = ?self.hashed_address))] pub fn calculate(self, retain_updates: bool) -> Result { - let span = trace_span!(target: "trie::storage_root", "Storage trie", hashed_address = ?self.hashed_address); - let _enter = span.enter(); - trace!(target: "trie::storage_root", "calculating storage root"); let mut hashed_storage_cursor = @@ -634,7 +635,7 @@ where let (mut hash_builder, mut storage_node_iter) = match self.previous_state { Some(state) => { let hash_builder = state.hash_builder.with_updates(retain_updates); - let walker = TrieWalker::storage_trie_from_stack( + let walker = TrieWalker::<_>::storage_trie_from_stack( trie_cursor, state.walker_stack, self.prefix_set, diff --git a/crates/trie/trie/src/trie_cursor/depth_first.rs b/crates/trie/trie/src/trie_cursor/depth_first.rs new file mode 100644 index 00000000000..8e9b567ac68 --- /dev/null +++ b/crates/trie/trie/src/trie_cursor/depth_first.rs @@ -0,0 +1,401 @@ +use super::TrieCursor; +use crate::{BranchNodeCompact, Nibbles}; +use reth_storage_errors::db::DatabaseError; +use std::cmp::Ordering; +use tracing::trace; + +/// Compares two Nibbles in depth-first order. +/// +/// In depth-first ordering: +/// - Descendants come before their ancestors (children before parents) +/// - Siblings are ordered lexicographically +/// +/// # Example +/// +/// ```text +/// 0x11 comes before 0x1 (child before parent) +/// 0x12 comes before 0x1 (child before parent) +/// 0x11 comes before 0x12 (lexicographical among siblings) +/// 0x1 comes before 0x21 (lexicographical among siblings) +/// Result: 0x11, 0x12, 0x1, 0x21 +/// ``` +pub fn cmp(a: &Nibbles, b: &Nibbles) -> Ordering { + // If the two are equal length then compare them lexicographically + if a.len() == b.len() { + return a.cmp(b) + } + + // If one is a prefix of the other, then the other comes first + let common_prefix_len = a.common_prefix_length(b); + if a.len() == common_prefix_len { + return Ordering::Greater + } else if b.len() == common_prefix_len { + return Ordering::Less + } + + // Otherwise the nibble after the prefix determines the ordering. We know that neither is empty + // at this point, otherwise the previous if/else block would have caught it. + a.get_unchecked(common_prefix_len).cmp(&b.get_unchecked(common_prefix_len)) +} + +/// An iterator that traverses trie nodes in depth-first post-order. +/// +/// This iterator yields nodes in post-order traversal (children before parents), +/// which matches the `cmp` comparison function where descendants +/// come before their ancestors. +#[derive(Debug)] +pub struct DepthFirstTrieIterator { + /// The underlying trie cursor. + cursor: C, + /// Set to true once the trie cursor has done its initial seek to the root node. + initialized: bool, + /// Stack of nodes which have been fetched. Each node's path is a prefix of the next's. + stack: Vec<(Nibbles, BranchNodeCompact)>, + /// Nodes which are ready to be yielded from `next`. + next: Vec<(Nibbles, BranchNodeCompact)>, + /// Set to true once the cursor has been exhausted. + complete: bool, +} + +impl DepthFirstTrieIterator { + /// Create a new depth-first iterator from a trie cursor. + pub fn new(cursor: C) -> Self { + Self { + cursor, + initialized: false, + stack: Default::default(), + next: Default::default(), + complete: false, + } + } + + fn push(&mut self, path: Nibbles, node: BranchNodeCompact) { + loop { + match self.stack.last() { + None => { + // If the stack is empty then we push this node onto it, as it may have child + // nodes which need to be yielded first. + self.stack.push((path, node)); + break + } + Some((top_path, _)) if path.starts_with(top_path) => { + // If the top of the stack is a prefix of this node, it means this node is a + // child of the top of the stack (and all other nodes on the stack). Push this + // node onto the stack, as future nodes may be children of it. + self.stack.push((path, node)); + break + } + Some((_, _)) => { + // The top of the stack is not a prefix of this node, therefore it is not a + // parent of this node. Yield the top of the stack, and loop back to see if this + // node is a child of the new top-of-stack. + self.next.push(self.stack.pop().expect("stack is not empty")); + } + } + } + + // We will have popped off the top of the stack in the order we want to yield nodes, but + // `next` is itself popped off so it needs to be reversed. + self.next.reverse(); + } + + fn fill_next(&mut self) -> Result<(), DatabaseError> { + debug_assert!(self.next.is_empty()); + + loop { + let Some((path, node)) = (if self.initialized { + self.cursor.next()? + } else { + self.initialized = true; + self.cursor.seek(Nibbles::new())? + }) else { + // Record that the cursor is empty and yield the stack. The stack is in reverse + // order of what we want to yield, but `next` is popped from, so we don't have to + // reverse it. + self.complete = true; + self.next = core::mem::take(&mut self.stack); + return Ok(()) + }; + + trace!( + target: "trie::trie_cursor::depth_first", + ?path, + "Iterated from cursor", + ); + + self.push(path, node); + if !self.next.is_empty() { + return Ok(()) + } + } + } +} + +impl Iterator for DepthFirstTrieIterator { + type Item = Result<(Nibbles, BranchNodeCompact), DatabaseError>; + + fn next(&mut self) -> Option { + loop { + if let Some(next) = self.next.pop() { + return Some(Ok(next)) + } + + if self.complete { + return None + } + + if let Err(err) = self.fill_next() { + return Some(Err(err)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::trie_cursor::{mock::MockTrieCursorFactory, TrieCursorFactory}; + use alloy_trie::TrieMask; + use std::{collections::BTreeMap, sync::Arc}; + + fn create_test_node(state_nibbles: &[u8], tree_nibbles: &[u8]) -> BranchNodeCompact { + let mut state_mask = TrieMask::default(); + for &nibble in state_nibbles { + state_mask.set_bit(nibble); + } + + let mut tree_mask = TrieMask::default(); + for &nibble in tree_nibbles { + tree_mask.set_bit(nibble); + } + + BranchNodeCompact { + state_mask, + tree_mask, + hash_mask: TrieMask::default(), + hashes: Arc::new(vec![]), + root_hash: None, + } + } + + #[test] + fn test_depth_first_cmp() { + // Test case 1: Child comes before parent + let child = Nibbles::from_nibbles([0x1, 0x1]); + let parent = Nibbles::from_nibbles([0x1]); + assert_eq!(cmp(&child, &parent), Ordering::Less); + assert_eq!(cmp(&parent, &child), Ordering::Greater); + + // Test case 2: Deeper descendant comes before ancestor + let deep = Nibbles::from_nibbles([0x1, 0x2, 0x3, 0x4]); + let ancestor = Nibbles::from_nibbles([0x1, 0x2]); + assert_eq!(cmp(&deep, &ancestor), Ordering::Less); + assert_eq!(cmp(&ancestor, &deep), Ordering::Greater); + + // Test case 3: Siblings use lexicographical ordering + let sibling1 = Nibbles::from_nibbles([0x1, 0x2]); + let sibling2 = Nibbles::from_nibbles([0x1, 0x3]); + assert_eq!(cmp(&sibling1, &sibling2), Ordering::Less); + assert_eq!(cmp(&sibling2, &sibling1), Ordering::Greater); + + // Test case 4: Different branches use lexicographical ordering + let branch1 = Nibbles::from_nibbles([0x1]); + let branch2 = Nibbles::from_nibbles([0x2]); + assert_eq!(cmp(&branch1, &branch2), Ordering::Less); + assert_eq!(cmp(&branch2, &branch1), Ordering::Greater); + + // Test case 5: Empty path comes after everything + let empty = Nibbles::new(); + let non_empty = Nibbles::from_nibbles([0x0]); + assert_eq!(cmp(&non_empty, &empty), Ordering::Less); + assert_eq!(cmp(&empty, &non_empty), Ordering::Greater); + + // Test case 6: Same paths are equal + let same1 = Nibbles::from_nibbles([0x1, 0x2, 0x3]); + let same2 = Nibbles::from_nibbles([0x1, 0x2, 0x3]); + assert_eq!(cmp(&same1, &same2), Ordering::Equal); + } + + #[test] + fn test_depth_first_ordering_complex() { + // Test the example from the conversation: 0x11, 0x12, 0x1, 0x2 + let mut paths = [ + Nibbles::from_nibbles([0x1]), // 0x1 + Nibbles::from_nibbles([0x2]), // 0x2 + Nibbles::from_nibbles([0x1, 0x1]), // 0x11 + Nibbles::from_nibbles([0x1, 0x2]), // 0x12 + ]; + + // Shuffle to ensure sorting works regardless of input order + paths.reverse(); + + // Sort using depth-first ordering + paths.sort_by(cmp); + + // Expected order: 0x11, 0x12, 0x1, 0x2 + assert_eq!(paths[0], Nibbles::from_nibbles([0x1, 0x1])); // 0x11 + assert_eq!(paths[1], Nibbles::from_nibbles([0x1, 0x2])); // 0x12 + assert_eq!(paths[2], Nibbles::from_nibbles([0x1])); // 0x1 + assert_eq!(paths[3], Nibbles::from_nibbles([0x2])); // 0x2 + } + + #[test] + fn test_depth_first_ordering_tree() { + // Test a more complex tree structure + let mut paths = vec![ + Nibbles::new(), // root (empty) + Nibbles::from_nibbles([0x1]), // 0x1 + Nibbles::from_nibbles([0x1, 0x1]), // 0x11 + Nibbles::from_nibbles([0x1, 0x1, 0x1]), // 0x111 + Nibbles::from_nibbles([0x1, 0x1, 0x2]), // 0x112 + Nibbles::from_nibbles([0x1, 0x2]), // 0x12 + Nibbles::from_nibbles([0x2]), // 0x2 + Nibbles::from_nibbles([0x2, 0x1]), // 0x21 + ]; + + // Shuffle + paths.reverse(); + + // Sort using depth-first ordering + paths.sort_by(cmp); + + // Expected depth-first order: + // All descendants come before ancestors + // Within same level, lexicographical order + assert_eq!(paths[0], Nibbles::from_nibbles([0x1, 0x1, 0x1])); // 0x111 (deepest in 0x1 branch) + assert_eq!(paths[1], Nibbles::from_nibbles([0x1, 0x1, 0x2])); // 0x112 (sibling of 0x111) + assert_eq!(paths[2], Nibbles::from_nibbles([0x1, 0x1])); // 0x11 (parent of 0x111, 0x112) + assert_eq!(paths[3], Nibbles::from_nibbles([0x1, 0x2])); // 0x12 (sibling of 0x11) + assert_eq!(paths[4], Nibbles::from_nibbles([0x1])); // 0x1 (parent of 0x11, 0x12) + assert_eq!(paths[5], Nibbles::from_nibbles([0x2, 0x1])); // 0x21 (child of 0x2) + assert_eq!(paths[6], Nibbles::from_nibbles([0x2])); // 0x2 (parent of 0x21) + assert_eq!(paths[7], Nibbles::new()); // root (empty, parent of all) + } + + #[test] + fn test_empty_trie() { + let factory = MockTrieCursorFactory::new(BTreeMap::new(), Default::default()); + let cursor = factory.account_trie_cursor().unwrap(); + let mut iter = DepthFirstTrieIterator::new(cursor); + assert!(iter.next().is_none()); + } + + #[test] + fn test_single_node() { + let path = Nibbles::from_nibbles([0x1, 0x2, 0x3]); + let node = create_test_node(&[0x4], &[0x5]); + + let mut nodes = BTreeMap::new(); + nodes.insert(path, node.clone()); + let factory = MockTrieCursorFactory::new(nodes, Default::default()); + let cursor = factory.account_trie_cursor().unwrap(); + let mut iter = DepthFirstTrieIterator::new(cursor); + + let result = iter.next().unwrap().unwrap(); + assert_eq!(result.0, path); + assert_eq!(result.1, node); + assert!(iter.next().is_none()); + } + + #[test] + fn test_depth_first_order() { + // Create a simple trie structure: + // root + // ├── 0x1 (has children 0x2 and 0x3) + // │ ├── 0x12 + // │ └── 0x13 + // └── 0x2 (has child 0x4) + // └── 0x24 + + let nodes = vec![ + // Root node with children at nibbles 1 and 2 + (Nibbles::default(), create_test_node(&[], &[0x1, 0x2])), + // Node at path 0x1 with children at nibbles 2 and 3 + (Nibbles::from_nibbles([0x1]), create_test_node(&[], &[0x2, 0x3])), + // Leaf nodes + (Nibbles::from_nibbles([0x1, 0x2]), create_test_node(&[0xF], &[])), + (Nibbles::from_nibbles([0x1, 0x3]), create_test_node(&[0xF], &[])), + // Node at path 0x2 with child at nibble 4 + (Nibbles::from_nibbles([0x2]), create_test_node(&[], &[0x4])), + // Leaf node + (Nibbles::from_nibbles([0x2, 0x4]), create_test_node(&[0xF], &[])), + ]; + + let nodes_map: BTreeMap<_, _> = nodes.into_iter().collect(); + let factory = MockTrieCursorFactory::new(nodes_map, Default::default()); + let cursor = factory.account_trie_cursor().unwrap(); + let iter = DepthFirstTrieIterator::new(cursor); + + // Expected post-order (depth-first with children before parents): + // 1. 0x12 (leaf, child of 0x1) + // 2. 0x13 (leaf, child of 0x1) + // 3. 0x1 (parent of 0x12 and 0x13) + // 4. 0x24 (leaf, child of 0x2) + // 5. 0x2 (parent of 0x24) + // 6. Root (parent of 0x1 and 0x2) + + let expected_order = vec![ + Nibbles::from_nibbles([0x1, 0x2]), + Nibbles::from_nibbles([0x1, 0x3]), + Nibbles::from_nibbles([0x1]), + Nibbles::from_nibbles([0x2, 0x4]), + Nibbles::from_nibbles([0x2]), + Nibbles::default(), + ]; + + let mut actual_order = Vec::new(); + for result in iter { + let (path, _) = result.unwrap(); + actual_order.push(path); + } + + assert_eq!(actual_order, expected_order); + } + + #[test] + fn test_complex_tree() { + // Create a more complex tree structure with multiple levels + let nodes = vec![ + // Root with multiple children + (Nibbles::default(), create_test_node(&[], &[0x0, 0x5, 0xA, 0xF])), + // Branch at 0x0 with children + (Nibbles::from_nibbles([0x0]), create_test_node(&[], &[0x1, 0x2])), + (Nibbles::from_nibbles([0x0, 0x1]), create_test_node(&[0x3], &[])), + (Nibbles::from_nibbles([0x0, 0x2]), create_test_node(&[0x4], &[])), + // Branch at 0x5 with no children (leaf) + (Nibbles::from_nibbles([0x5]), create_test_node(&[0xB], &[])), + // Branch at 0xA with deep nesting + (Nibbles::from_nibbles([0xA]), create_test_node(&[], &[0xB])), + (Nibbles::from_nibbles([0xA, 0xB]), create_test_node(&[], &[0xC])), + (Nibbles::from_nibbles([0xA, 0xB, 0xC]), create_test_node(&[0xD], &[])), + // Branch at 0xF (leaf) + (Nibbles::from_nibbles([0xF]), create_test_node(&[0xE], &[])), + ]; + + let nodes_map: BTreeMap<_, _> = nodes.into_iter().collect(); + let factory = MockTrieCursorFactory::new(nodes_map, Default::default()); + let cursor = factory.account_trie_cursor().unwrap(); + let iter = DepthFirstTrieIterator::new(cursor); + + // Verify post-order traversal (children before parents) + let expected_order = vec![ + Nibbles::from_nibbles([0x0, 0x1]), // leaf child of 0x0 + Nibbles::from_nibbles([0x0, 0x2]), // leaf child of 0x0 + Nibbles::from_nibbles([0x0]), // parent of 0x01 and 0x02 + Nibbles::from_nibbles([0x5]), // leaf + Nibbles::from_nibbles([0xA, 0xB, 0xC]), // deepest leaf + Nibbles::from_nibbles([0xA, 0xB]), // parent of 0xABC + Nibbles::from_nibbles([0xA]), // parent of 0xAB + Nibbles::from_nibbles([0xF]), // leaf + Nibbles::default(), // root (last) + ]; + + let mut actual_order = Vec::new(); + for result in iter { + let (path, _node) = result.unwrap(); + actual_order.push(path); + } + + assert_eq!(actual_order, expected_order); + } +} diff --git a/crates/trie/trie/src/trie_cursor/in_memory.rs b/crates/trie/trie/src/trie_cursor/in_memory.rs index 4925dc8a666..7f6e1f10525 100644 --- a/crates/trie/trie/src/trie_cursor/in_memory.rs +++ b/crates/trie/trie/src/trie_cursor/in_memory.rs @@ -1,9 +1,6 @@ use super::{TrieCursor, TrieCursorFactory}; -use crate::{ - forward_cursor::ForwardInMemoryCursor, - updates::{StorageTrieUpdatesSorted, TrieUpdatesSorted}, -}; -use alloy_primitives::{map::HashSet, B256}; +use crate::{forward_cursor::ForwardInMemoryCursor, updates::TrieUpdatesSorted}; +use alloy_primitives::B256; use reth_storage_errors::db::DatabaseError; use reth_trie_common::{BranchNodeCompact, Nibbles}; @@ -24,52 +21,57 @@ impl<'a, CF> InMemoryTrieCursorFactory<'a, CF> { } impl<'a, CF: TrieCursorFactory> TrieCursorFactory for InMemoryTrieCursorFactory<'a, CF> { - type AccountTrieCursor = InMemoryAccountTrieCursor<'a, CF::AccountTrieCursor>; - type StorageTrieCursor = InMemoryStorageTrieCursor<'a, CF::StorageTrieCursor>; + type AccountTrieCursor = InMemoryTrieCursor<'a, CF::AccountTrieCursor>; + type StorageTrieCursor = InMemoryTrieCursor<'a, CF::StorageTrieCursor>; fn account_trie_cursor(&self) -> Result { let cursor = self.cursor_factory.account_trie_cursor()?; - Ok(InMemoryAccountTrieCursor::new(cursor, self.trie_updates)) + Ok(InMemoryTrieCursor::new(Some(cursor), self.trie_updates.account_nodes_ref())) } fn storage_trie_cursor( &self, hashed_address: B256, ) -> Result { - let cursor = self.cursor_factory.storage_trie_cursor(hashed_address)?; - Ok(InMemoryStorageTrieCursor::new( - hashed_address, - cursor, - self.trie_updates.storage_tries.get(&hashed_address), - )) + // if the storage trie has no updates then we use this as the in-memory overlay. + static EMPTY_UPDATES: Vec<(Nibbles, Option)> = Vec::new(); + + let storage_trie_updates = self.trie_updates.storage_tries.get(&hashed_address); + let (storage_nodes, cleared) = storage_trie_updates + .map(|u| (u.storage_nodes_ref(), u.is_deleted())) + .unwrap_or((&EMPTY_UPDATES, false)); + + let cursor = if cleared { + None + } else { + Some(self.cursor_factory.storage_trie_cursor(hashed_address)?) + }; + + Ok(InMemoryTrieCursor::new(cursor, storage_nodes)) } } -/// The cursor to iterate over account trie updates and corresponding database entries. +/// A cursor to iterate over trie updates and corresponding database entries. /// It will always give precedence to the data from the trie updates. #[derive(Debug)] -pub struct InMemoryAccountTrieCursor<'a, C> { - /// The underlying cursor. - cursor: C, +pub struct InMemoryTrieCursor<'a, C> { + /// The underlying cursor. If None then it is assumed there is no DB data. + cursor: Option, /// Forward-only in-memory cursor over storage trie nodes. - in_memory_cursor: ForwardInMemoryCursor<'a, Nibbles, BranchNodeCompact>, - /// Collection of removed trie nodes. - removed_nodes: &'a HashSet, + in_memory_cursor: ForwardInMemoryCursor<'a, Nibbles, Option>, /// Last key returned by the cursor. last_key: Option, } -impl<'a, C: TrieCursor> InMemoryAccountTrieCursor<'a, C> { - /// Create new account trie cursor from underlying cursor and reference to - /// [`TrieUpdatesSorted`]. - pub fn new(cursor: C, trie_updates: &'a TrieUpdatesSorted) -> Self { - let in_memory_cursor = ForwardInMemoryCursor::new(&trie_updates.account_nodes); - Self { - cursor, - in_memory_cursor, - removed_nodes: &trie_updates.removed_nodes, - last_key: None, - } +impl<'a, C: TrieCursor> InMemoryTrieCursor<'a, C> { + /// Create new trie cursor which combines a DB cursor (None to assume empty DB) and a set of + /// in-memory trie nodes. + pub fn new( + cursor: Option, + trie_updates: &'a [(Nibbles, Option)], + ) -> Self { + let in_memory_cursor = ForwardInMemoryCursor::new(trie_updates); + Self { cursor, in_memory_cursor, last_key: None } } fn seek_inner( @@ -77,44 +79,63 @@ impl<'a, C: TrieCursor> InMemoryAccountTrieCursor<'a, C> { key: Nibbles, exact: bool, ) -> Result, DatabaseError> { - let in_memory = self.in_memory_cursor.seek(&key); - if in_memory.as_ref().is_some_and(|entry| entry.0 == key) { - return Ok(in_memory) - } + let mut mem_entry = self.in_memory_cursor.seek(&key); + let mut db_entry = self.cursor.as_mut().map(|c| c.seek(key)).transpose()?.flatten(); - // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(key)?; - while db_entry.as_ref().is_some_and(|entry| self.removed_nodes.contains(&entry.0)) { - db_entry = self.cursor.next()?; + // exact matching is easy, if overlay has a value then return that (updated or removed), or + // if db has a value then return that. + if exact { + return Ok(match (mem_entry, db_entry) { + (Some((mem_key, entry_inner)), _) if mem_key == key => { + entry_inner.map(|node| (key, node)) + } + (_, Some((db_key, node))) if db_key == key => Some((key, node)), + _ => None, + }) } - // Compare two entries and return the lowest. - // If seek is exact, filter the entry for exact key match. - Ok(compare_trie_node_entries(in_memory, db_entry) - .filter(|(nibbles, _)| !exact || nibbles == &key)) + loop { + match (mem_entry, &db_entry) { + (Some((mem_key, None)), _) + if db_entry.as_ref().is_none_or(|(db_key, _)| &mem_key < db_key) => + { + // If overlay has a removed node but DB cursor is exhausted or ahead of the + // in-memory cursor then move ahead in-memory, as there might be further + // non-removed overlay nodes. + mem_entry = self.in_memory_cursor.first_after(&mem_key); + } + (Some((mem_key, None)), Some((db_key, _))) if &mem_key == db_key => { + // If overlay has a removed node which is returned from DB then move both + // cursors ahead to the next key. + mem_entry = self.in_memory_cursor.first_after(&mem_key); + db_entry = self.cursor.as_mut().map(|c| c.next()).transpose()?.flatten(); + } + (Some((mem_key, Some(node))), _) + if db_entry.as_ref().is_none_or(|(db_key, _)| &mem_key <= db_key) => + { + // If overlay returns a node prior to the DB's node, or the DB is exhausted, + // then we return the overlay's node. + return Ok(Some((mem_key, node))) + } + // All other cases: + // - mem_key > db_key + // - overlay is exhausted + // Return the db_entry. If DB is also exhausted then this returns None. + _ => return Ok(db_entry), + } + } } fn next_inner( &mut self, last: Nibbles, ) -> Result, DatabaseError> { - let in_memory = self.in_memory_cursor.first_after(&last); - - // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(last)?; - while db_entry - .as_ref() - .is_some_and(|entry| entry.0 < last || self.removed_nodes.contains(&entry.0)) - { - db_entry = self.cursor.next()?; - } - - // Compare two entries and return the lowest. - Ok(compare_trie_node_entries(in_memory, db_entry)) + let Some(key) = last.increment() else { return Ok(None) }; + self.seek_inner(key, false) } } -impl TrieCursor for InMemoryAccountTrieCursor<'_, C> { +impl TrieCursor for InMemoryTrieCursor<'_, C> { fn seek_exact( &mut self, key: Nibbles, @@ -149,158 +170,323 @@ impl TrieCursor for InMemoryAccountTrieCursor<'_, C> { fn current(&mut self) -> Result, DatabaseError> { match &self.last_key { Some(key) => Ok(Some(*key)), - None => self.cursor.current(), + None => Ok(self.cursor.as_mut().map(|c| c.current()).transpose()?.flatten()), } } } -/// The cursor to iterate over storage trie updates and corresponding database entries. -/// It will always give precedence to the data from the trie updates. -#[derive(Debug)] -#[expect(dead_code)] -pub struct InMemoryStorageTrieCursor<'a, C> { - /// The hashed address of the account that trie belongs to. - hashed_address: B256, - /// The underlying cursor. - cursor: C, - /// Forward-only in-memory cursor over storage trie nodes. - in_memory_cursor: Option>, - /// Reference to the set of removed storage node keys. - removed_nodes: Option<&'a HashSet>, - /// The flag indicating whether the storage trie was cleared. - storage_trie_cleared: bool, - /// Last key returned by the cursor. - last_key: Option, -} +#[cfg(test)] +mod tests { + use super::*; + use crate::trie_cursor::mock::MockTrieCursor; + use parking_lot::Mutex; + use std::{collections::BTreeMap, sync::Arc}; -impl<'a, C> InMemoryStorageTrieCursor<'a, C> { - /// Create new storage trie cursor from underlying cursor and reference to - /// [`StorageTrieUpdatesSorted`]. - pub fn new( - hashed_address: B256, - cursor: C, - updates: Option<&'a StorageTrieUpdatesSorted>, - ) -> Self { - let in_memory_cursor = updates.map(|u| ForwardInMemoryCursor::new(&u.storage_nodes)); - let removed_nodes = updates.map(|u| &u.removed_nodes); - let storage_trie_cleared = updates.is_some_and(|u| u.is_deleted); - Self { - hashed_address, - cursor, - in_memory_cursor, - removed_nodes, - storage_trie_cleared, - last_key: None, - } + #[derive(Debug)] + struct InMemoryTrieCursorTestCase { + db_nodes: Vec<(Nibbles, BranchNodeCompact)>, + in_memory_nodes: Vec<(Nibbles, Option)>, + expected_results: Vec<(Nibbles, BranchNodeCompact)>, } -} -impl InMemoryStorageTrieCursor<'_, C> { - fn seek_inner( - &mut self, - key: Nibbles, - exact: bool, - ) -> Result, DatabaseError> { - let in_memory = self.in_memory_cursor.as_mut().and_then(|c| c.seek(&key)); - if self.storage_trie_cleared || in_memory.as_ref().is_some_and(|entry| entry.0 == key) { - return Ok(in_memory.filter(|(nibbles, _)| !exact || nibbles == &key)) - } + fn execute_test(test_case: InMemoryTrieCursorTestCase) { + let db_nodes_map: BTreeMap = + test_case.db_nodes.into_iter().collect(); + let db_nodes_arc = Arc::new(db_nodes_map); + let visited_keys = Arc::new(Mutex::new(Vec::new())); + let mock_cursor = MockTrieCursor::new(db_nodes_arc, visited_keys); + + let mut cursor = InMemoryTrieCursor::new(Some(mock_cursor), &test_case.in_memory_nodes); - // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(key)?; - while db_entry - .as_ref() - .is_some_and(|entry| self.removed_nodes.as_ref().is_some_and(|r| r.contains(&entry.0))) + let mut results = Vec::new(); + + if let Some(first_expected) = test_case.expected_results.first() && + let Ok(Some(entry)) = cursor.seek(first_expected.0) { - db_entry = self.cursor.next()?; + results.push(entry); + } + + while let Ok(Some(entry)) = cursor.next() { + results.push(entry); } - // Compare two entries and return the lowest. - // If seek is exact, filter the entry for exact key match. - Ok(compare_trie_node_entries(in_memory, db_entry) - .filter(|(nibbles, _)| !exact || nibbles == &key)) + assert_eq!( + results, test_case.expected_results, + "Results mismatch.\nGot: {:?}\nExpected: {:?}", + results, test_case.expected_results + ); } - fn next_inner( - &mut self, - last: Nibbles, - ) -> Result, DatabaseError> { - let in_memory = self.in_memory_cursor.as_mut().and_then(|c| c.first_after(&last)); - if self.storage_trie_cleared { - return Ok(in_memory) - } + #[test] + fn test_empty_db_and_memory() { + let test_case = InMemoryTrieCursorTestCase { + db_nodes: vec![], + in_memory_nodes: vec![], + expected_results: vec![], + }; + execute_test(test_case); + } - // Reposition the cursor to the first greater or equal node that wasn't removed. - let mut db_entry = self.cursor.seek(last)?; - while db_entry.as_ref().is_some_and(|entry| { - entry.0 < last || self.removed_nodes.as_ref().is_some_and(|r| r.contains(&entry.0)) - }) { - db_entry = self.cursor.next()?; - } + #[test] + fn test_only_db_nodes() { + let db_nodes = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b0011, 0b0001, 0, vec![], None)), + (Nibbles::from_nibbles([0x2]), BranchNodeCompact::new(0b0011, 0b0010, 0, vec![], None)), + (Nibbles::from_nibbles([0x3]), BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ]; - // Compare two entries and return the lowest. - Ok(compare_trie_node_entries(in_memory, db_entry)) + let test_case = InMemoryTrieCursorTestCase { + db_nodes: db_nodes.clone(), + in_memory_nodes: vec![], + expected_results: db_nodes, + }; + execute_test(test_case); } -} -impl TrieCursor for InMemoryStorageTrieCursor<'_, C> { - fn seek_exact( - &mut self, - key: Nibbles, - ) -> Result, DatabaseError> { - let entry = self.seek_inner(key, true)?; - self.last_key = entry.as_ref().map(|(nibbles, _)| *nibbles); - Ok(entry) + #[test] + fn test_only_in_memory_nodes() { + let in_memory_nodes = vec![ + ( + Nibbles::from_nibbles([0x1]), + Some(BranchNodeCompact::new(0b0011, 0b0001, 0, vec![], None)), + ), + ( + Nibbles::from_nibbles([0x2]), + Some(BranchNodeCompact::new(0b0011, 0b0010, 0, vec![], None)), + ), + ( + Nibbles::from_nibbles([0x3]), + Some(BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ), + ]; + + let expected_results: Vec<(Nibbles, BranchNodeCompact)> = in_memory_nodes + .iter() + .filter_map(|(k, v)| v.as_ref().map(|node| (*k, node.clone()))) + .collect(); + + let test_case = + InMemoryTrieCursorTestCase { db_nodes: vec![], in_memory_nodes, expected_results }; + execute_test(test_case); } - fn seek( - &mut self, - key: Nibbles, - ) -> Result, DatabaseError> { - let entry = self.seek_inner(key, false)?; - self.last_key = entry.as_ref().map(|(nibbles, _)| *nibbles); - Ok(entry) + #[test] + fn test_in_memory_overwrites_db() { + let db_nodes = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b0011, 0b0001, 0, vec![], None)), + (Nibbles::from_nibbles([0x2]), BranchNodeCompact::new(0b0011, 0b0010, 0, vec![], None)), + ]; + + let in_memory_nodes = vec![ + ( + Nibbles::from_nibbles([0x1]), + Some(BranchNodeCompact::new(0b1111, 0b1111, 0, vec![], None)), + ), + ( + Nibbles::from_nibbles([0x3]), + Some(BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ), + ]; + + let expected_results = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b1111, 0b1111, 0, vec![], None)), + (Nibbles::from_nibbles([0x2]), BranchNodeCompact::new(0b0011, 0b0010, 0, vec![], None)), + (Nibbles::from_nibbles([0x3]), BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ]; + + let test_case = InMemoryTrieCursorTestCase { db_nodes, in_memory_nodes, expected_results }; + execute_test(test_case); } - fn next(&mut self) -> Result, DatabaseError> { - let next = match &self.last_key { - Some(last) => { - let entry = self.next_inner(*last)?; - self.last_key = entry.as_ref().map(|entry| entry.0); - entry - } - // no previous entry was found - None => None, - }; - Ok(next) + #[test] + fn test_in_memory_deletes_db_nodes() { + let db_nodes = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b0011, 0b0001, 0, vec![], None)), + (Nibbles::from_nibbles([0x2]), BranchNodeCompact::new(0b0011, 0b0010, 0, vec![], None)), + (Nibbles::from_nibbles([0x3]), BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ]; + + let in_memory_nodes = vec![(Nibbles::from_nibbles([0x2]), None)]; + + let expected_results = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b0011, 0b0001, 0, vec![], None)), + (Nibbles::from_nibbles([0x3]), BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ]; + + let test_case = InMemoryTrieCursorTestCase { db_nodes, in_memory_nodes, expected_results }; + execute_test(test_case); } - fn current(&mut self) -> Result, DatabaseError> { - match &self.last_key { - Some(key) => Ok(Some(*key)), - None => self.cursor.current(), - } + #[test] + fn test_complex_interleaving() { + let db_nodes = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b0001, 0b0001, 0, vec![], None)), + (Nibbles::from_nibbles([0x3]), BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + (Nibbles::from_nibbles([0x5]), BranchNodeCompact::new(0b0101, 0b0101, 0, vec![], None)), + (Nibbles::from_nibbles([0x7]), BranchNodeCompact::new(0b0111, 0b0111, 0, vec![], None)), + ]; + + let in_memory_nodes = vec![ + ( + Nibbles::from_nibbles([0x2]), + Some(BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None)), + ), + (Nibbles::from_nibbles([0x3]), None), + ( + Nibbles::from_nibbles([0x4]), + Some(BranchNodeCompact::new(0b0100, 0b0100, 0, vec![], None)), + ), + ( + Nibbles::from_nibbles([0x6]), + Some(BranchNodeCompact::new(0b0110, 0b0110, 0, vec![], None)), + ), + (Nibbles::from_nibbles([0x7]), None), + ( + Nibbles::from_nibbles([0x8]), + Some(BranchNodeCompact::new(0b1000, 0b1000, 0, vec![], None)), + ), + ]; + + let expected_results = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b0001, 0b0001, 0, vec![], None)), + (Nibbles::from_nibbles([0x2]), BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None)), + (Nibbles::from_nibbles([0x4]), BranchNodeCompact::new(0b0100, 0b0100, 0, vec![], None)), + (Nibbles::from_nibbles([0x5]), BranchNodeCompact::new(0b0101, 0b0101, 0, vec![], None)), + (Nibbles::from_nibbles([0x6]), BranchNodeCompact::new(0b0110, 0b0110, 0, vec![], None)), + (Nibbles::from_nibbles([0x8]), BranchNodeCompact::new(0b1000, 0b1000, 0, vec![], None)), + ]; + + let test_case = InMemoryTrieCursorTestCase { db_nodes, in_memory_nodes, expected_results }; + execute_test(test_case); } -} -/// Return the node with the lowest nibbles. -/// -/// Given the next in-memory and database entries, return the smallest of the two. -/// If the node keys are the same, the in-memory entry is given precedence. -fn compare_trie_node_entries( - mut in_memory_item: Option<(Nibbles, BranchNodeCompact)>, - mut db_item: Option<(Nibbles, BranchNodeCompact)>, -) -> Option<(Nibbles, BranchNodeCompact)> { - if let Some((in_memory_entry, db_entry)) = in_memory_item.as_ref().zip(db_item.as_ref()) { - // If both are not empty, return the smallest of the two - // In-memory is given precedence if keys are equal - if in_memory_entry.0 <= db_entry.0 { - in_memory_item.take() - } else { - db_item.take() - } - } else { - // Return either non-empty entry - db_item.or(in_memory_item) + #[test] + fn test_seek_exact() { + let db_nodes = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(0b0001, 0b0001, 0, vec![], None)), + (Nibbles::from_nibbles([0x3]), BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ]; + + let in_memory_nodes = vec![( + Nibbles::from_nibbles([0x2]), + Some(BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None)), + )]; + + let db_nodes_map: BTreeMap = db_nodes.into_iter().collect(); + let db_nodes_arc = Arc::new(db_nodes_map); + let visited_keys = Arc::new(Mutex::new(Vec::new())); + let mock_cursor = MockTrieCursor::new(db_nodes_arc, visited_keys); + + let mut cursor = InMemoryTrieCursor::new(Some(mock_cursor), &in_memory_nodes); + + let result = cursor.seek_exact(Nibbles::from_nibbles([0x2])).unwrap(); + assert_eq!( + result, + Some(( + Nibbles::from_nibbles([0x2]), + BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None) + )) + ); + + let result = cursor.seek_exact(Nibbles::from_nibbles([0x3])).unwrap(); + assert_eq!( + result, + Some(( + Nibbles::from_nibbles([0x3]), + BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None) + )) + ); + + let result = cursor.seek_exact(Nibbles::from_nibbles([0x4])).unwrap(); + assert_eq!(result, None); + } + + #[test] + fn test_multiple_consecutive_deletes() { + let db_nodes: Vec<(Nibbles, BranchNodeCompact)> = (1..=10) + .map(|i| { + ( + Nibbles::from_nibbles([i]), + BranchNodeCompact::new(i as u16, i as u16, 0, vec![], None), + ) + }) + .collect(); + + let in_memory_nodes = vec![ + (Nibbles::from_nibbles([0x3]), None), + (Nibbles::from_nibbles([0x4]), None), + (Nibbles::from_nibbles([0x5]), None), + (Nibbles::from_nibbles([0x6]), None), + ]; + + let expected_results = vec![ + (Nibbles::from_nibbles([0x1]), BranchNodeCompact::new(1, 1, 0, vec![], None)), + (Nibbles::from_nibbles([0x2]), BranchNodeCompact::new(2, 2, 0, vec![], None)), + (Nibbles::from_nibbles([0x7]), BranchNodeCompact::new(7, 7, 0, vec![], None)), + (Nibbles::from_nibbles([0x8]), BranchNodeCompact::new(8, 8, 0, vec![], None)), + (Nibbles::from_nibbles([0x9]), BranchNodeCompact::new(9, 9, 0, vec![], None)), + (Nibbles::from_nibbles([0xa]), BranchNodeCompact::new(10, 10, 0, vec![], None)), + ]; + + let test_case = InMemoryTrieCursorTestCase { db_nodes, in_memory_nodes, expected_results }; + execute_test(test_case); + } + + #[test] + fn test_empty_db_with_in_memory_deletes() { + let in_memory_nodes = vec![ + (Nibbles::from_nibbles([0x1]), None), + ( + Nibbles::from_nibbles([0x2]), + Some(BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None)), + ), + (Nibbles::from_nibbles([0x3]), None), + ]; + + let expected_results = vec![( + Nibbles::from_nibbles([0x2]), + BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None), + )]; + + let test_case = + InMemoryTrieCursorTestCase { db_nodes: vec![], in_memory_nodes, expected_results }; + execute_test(test_case); + } + + #[test] + fn test_current_key_tracking() { + let db_nodes = vec![( + Nibbles::from_nibbles([0x2]), + BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None), + )]; + + let in_memory_nodes = vec![ + ( + Nibbles::from_nibbles([0x1]), + Some(BranchNodeCompact::new(0b0001, 0b0001, 0, vec![], None)), + ), + ( + Nibbles::from_nibbles([0x3]), + Some(BranchNodeCompact::new(0b0011, 0b0011, 0, vec![], None)), + ), + ]; + + let db_nodes_map: BTreeMap = db_nodes.into_iter().collect(); + let db_nodes_arc = Arc::new(db_nodes_map); + let visited_keys = Arc::new(Mutex::new(Vec::new())); + let mock_cursor = MockTrieCursor::new(db_nodes_arc, visited_keys); + + let mut cursor = InMemoryTrieCursor::new(Some(mock_cursor), &in_memory_nodes); + + assert_eq!(cursor.current().unwrap(), None); + + cursor.seek(Nibbles::from_nibbles([0x1])).unwrap(); + assert_eq!(cursor.current().unwrap(), Some(Nibbles::from_nibbles([0x1]))); + + cursor.next().unwrap(); + assert_eq!(cursor.current().unwrap(), Some(Nibbles::from_nibbles([0x2]))); + + cursor.next().unwrap(); + assert_eq!(cursor.current().unwrap(), Some(Nibbles::from_nibbles([0x3]))); } } diff --git a/crates/trie/trie/src/trie_cursor/mock.rs b/crates/trie/trie/src/trie_cursor/mock.rs index feda1c72a85..4b0b7f699dc 100644 --- a/crates/trie/trie/src/trie_cursor/mock.rs +++ b/crates/trie/trie/src/trie_cursor/mock.rs @@ -93,7 +93,8 @@ pub struct MockTrieCursor { } impl MockTrieCursor { - fn new( + /// Creates a new mock trie cursor with the given trie nodes and key tracking. + pub fn new( trie_nodes: Arc>, visited_keys: Arc>>>, ) -> Self { diff --git a/crates/trie/trie/src/trie_cursor/mod.rs b/crates/trie/trie/src/trie_cursor/mod.rs index bd4783c5713..01eea4c40e6 100644 --- a/crates/trie/trie/src/trie_cursor/mod.rs +++ b/crates/trie/trie/src/trie_cursor/mod.rs @@ -11,11 +11,14 @@ pub mod subnode; /// Noop trie cursor implementations. pub mod noop; +/// Depth-first trie iterator. +pub mod depth_first; + /// Mock trie cursor implementations. #[cfg(test)] pub mod mock; -pub use self::{in_memory::*, subnode::CursorSubNode}; +pub use self::{depth_first::DepthFirstTrieIterator, in_memory::*, subnode::CursorSubNode}; /// Factory for creating trie cursors. #[auto_impl::auto_impl(&)] @@ -35,7 +38,8 @@ pub trait TrieCursorFactory { ) -> Result; } -/// A cursor for navigating a trie that works with both Tables and `DupSort` tables. +/// A cursor for traversing stored trie nodes. The cursor must iterate over keys in +/// lexicographical order. #[auto_impl::auto_impl(&mut, Box)] pub trait TrieCursor: Send + Sync { /// Move the cursor to the key and return if it is an exact match. diff --git a/crates/trie/trie/src/trie_cursor/subnode.rs b/crates/trie/trie/src/trie_cursor/subnode.rs index 82a5d5e670a..9c9b5e03d7d 100644 --- a/crates/trie/trie/src/trie_cursor/subnode.rs +++ b/crates/trie/trie/src/trie_cursor/subnode.rs @@ -1,5 +1,6 @@ use crate::{BranchNodeCompact, Nibbles, StoredSubNode, CHILD_INDEX_RANGE}; use alloy_primitives::B256; +use alloy_trie::proof::AddedRemovedKeys; /// Cursor for iterating over a subtrie. #[derive(Clone)] @@ -87,6 +88,26 @@ impl CursorSubNode { &self.full_key } + /// Returns true if all of: + /// - Position is a child + /// - There is a branch node + /// - All children except the current are removed according to the [`AddedRemovedKeys`]. + pub fn full_key_is_only_nonremoved_child(&self, added_removed_keys: &AddedRemovedKeys) -> bool { + self.position.as_child().zip(self.node.as_ref()).is_some_and(|(nibble, node)| { + let removed_mask = added_removed_keys.get_removed_mask(&self.key); + let nonremoved_mask = !removed_mask & node.state_mask; + tracing::trace!( + target: "trie::walker", + key = ?self.key, + ?removed_mask, + ?nonremoved_mask, + ?nibble, + "Checking full_key_is_only_nonremoved_node", + ); + nonremoved_mask.count_ones() == 1 && nonremoved_mask.is_bit_set(nibble) + }) + } + /// Updates the full key by replacing or appending a child nibble based on the old subnode /// position. #[inline] diff --git a/crates/trie/trie/src/verify.rs b/crates/trie/trie/src/verify.rs new file mode 100644 index 00000000000..391852fda6f --- /dev/null +++ b/crates/trie/trie/src/verify.rs @@ -0,0 +1,1009 @@ +use crate::{ + hashed_cursor::{HashedCursor, HashedCursorFactory}, + progress::{IntermediateStateRootState, StateRootProgress}, + trie::StateRoot, + trie_cursor::{ + depth_first::{self, DepthFirstTrieIterator}, + noop::NoopTrieCursorFactory, + TrieCursor, TrieCursorFactory, + }, + Nibbles, +}; +use alloy_primitives::B256; +use alloy_trie::BranchNodeCompact; +use reth_execution_errors::StateRootError; +use reth_storage_errors::db::DatabaseError; +use std::cmp::Ordering; +use tracing::trace; + +/// Used by [`StateRootBranchNodesIter`] to iterate over branch nodes in a state root. +#[derive(Debug)] +enum BranchNode { + Account(Nibbles, BranchNodeCompact), + Storage(B256, Nibbles, BranchNodeCompact), +} + +/// Iterates over branch nodes produced by a [`StateRoot`]. The `StateRoot` will only used the +/// hashed accounts/storages tables, meaning it is recomputing the trie from scratch without the use +/// of the trie tables. +/// +/// [`BranchNode`]s are iterated over such that: +/// * Account nodes and storage nodes may be interleaved. +/// * Storage nodes for the same account will be ordered by ascending path relative to each other. +/// * Account nodes will be ordered by ascending path relative to each other. +/// * All storage nodes for one account will finish before storage nodes for another account are +/// started. In other words, if the current storage account is not equal to the previous, the +/// previous has no more nodes. +#[derive(Debug)] +struct StateRootBranchNodesIter { + hashed_cursor_factory: H, + account_nodes: Vec<(Nibbles, BranchNodeCompact)>, + storage_tries: Vec<(B256, Vec<(Nibbles, BranchNodeCompact)>)>, + curr_storage: Option<(B256, Vec<(Nibbles, BranchNodeCompact)>)>, + intermediate_state: Option>, + complete: bool, +} + +impl StateRootBranchNodesIter { + fn new(hashed_cursor_factory: H) -> Self { + Self { + hashed_cursor_factory, + account_nodes: Default::default(), + storage_tries: Default::default(), + curr_storage: None, + intermediate_state: None, + complete: false, + } + } + + /// Sorts a Vec of updates such that it is ready to be yielded from the `next` method. We yield + /// by popping off of the account/storage vecs, so we sort them in reverse order. + /// + /// Depth-first sorting is used because this is the order that the `HashBuilder` computes + /// branch nodes internally, even if it produces them as `B256Map`s. + fn sort_updates(updates: &mut [(Nibbles, BranchNodeCompact)]) { + updates.sort_unstable_by(|a, b| depth_first::cmp(&b.0, &a.0)); + } +} + +impl Iterator for StateRootBranchNodesIter { + type Item = Result; + + fn next(&mut self) -> Option { + loop { + // If we already started iterating through a storage trie's updates, continue doing + // so. + if let Some((account, storage_updates)) = self.curr_storage.as_mut() && + let Some((path, node)) = storage_updates.pop() + { + let node = BranchNode::Storage(*account, path, node); + return Some(Ok(node)) + } + + // If there's not a storage trie already being iterated over than check if there's a + // storage trie we could start iterating over. + if let Some((account, storage_updates)) = self.storage_tries.pop() { + debug_assert!(!storage_updates.is_empty()); + + self.curr_storage = Some((account, storage_updates)); + continue; + } + + // `storage_updates` is empty, check if there are account updates. + if let Some((path, node)) = self.account_nodes.pop() { + return Some(Ok(BranchNode::Account(path, node))) + } + + // All data from any previous runs of the `StateRoot` has been produced, run the next + // partial computation, unless `StateRootProgress::Complete` has been returned in which + // case iteration is over. + if self.complete { + return None + } + + let state_root = + StateRoot::new(NoopTrieCursorFactory, self.hashed_cursor_factory.clone()) + .with_intermediate_state(self.intermediate_state.take().map(|s| *s)); + + let updates = match state_root.root_with_progress() { + Err(err) => return Some(Err(err)), + Ok(StateRootProgress::Complete(_, _, updates)) => { + self.complete = true; + updates + } + Ok(StateRootProgress::Progress(intermediate_state, _, updates)) => { + self.intermediate_state = Some(intermediate_state); + updates + } + }; + + // collect account updates and sort them in descending order, so that when we pop them + // off the Vec they are popped in ascending order. + self.account_nodes.extend(updates.account_nodes); + Self::sort_updates(&mut self.account_nodes); + + self.storage_tries = updates + .storage_tries + .into_iter() + .filter_map(|(account, t)| { + (!t.storage_nodes.is_empty()).then(|| { + let mut storage_nodes = t.storage_nodes.into_iter().collect::>(); + Self::sort_updates(&mut storage_nodes); + (account, storage_nodes) + }) + }) + .collect::>(); + + // `root_with_progress` will output storage updates ordered by their account hash. If + // `root_with_progress` only returns a partial result then it will pick up with where + // it left off in the storage trie on the next run. + // + // By sorting by the account we ensure that we continue with the partially processed + // trie (the last of the previous run) first. We sort in reverse order because we pop + // off of this Vec. + self.storage_tries.sort_unstable_by(|a, b| b.0.cmp(&a.0)); + + // loop back to the top. + } + } +} + +/// Output describes an inconsistency found when comparing the hashed state tables +/// ([`HashedCursorFactory`]) with that of the trie tables ([`TrieCursorFactory`]). The hashed +/// tables are considered the source of truth; outputs are on the part of the trie tables. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Output { + /// An extra account node was found. + AccountExtra(Nibbles, BranchNodeCompact), + /// A extra storage node was found. + StorageExtra(B256, Nibbles, BranchNodeCompact), + /// An account node had the wrong value. + AccountWrong { + /// Path of the node + path: Nibbles, + /// The node's expected value. + expected: BranchNodeCompact, + /// The node's found value. + found: BranchNodeCompact, + }, + /// A storage node had the wrong value. + StorageWrong { + /// The account the storage trie belongs to. + account: B256, + /// Path of the node + path: Nibbles, + /// The node's expected value. + expected: BranchNodeCompact, + /// The node's found value. + found: BranchNodeCompact, + }, + /// An account node was missing. + AccountMissing(Nibbles, BranchNodeCompact), + /// A storage node was missing. + StorageMissing(B256, Nibbles, BranchNodeCompact), + /// Progress indicator with the last seen account path. + Progress(Nibbles), +} + +/// Verifies the contents of a trie table against some other data source which is able to produce +/// stored trie nodes. +#[derive(Debug)] +struct SingleVerifier { + account: Option, // None for accounts trie + trie_iter: I, + curr: Option<(Nibbles, BranchNodeCompact)>, +} + +impl SingleVerifier> { + fn new(account: Option, trie_cursor: C) -> Result { + let mut trie_iter = DepthFirstTrieIterator::new(trie_cursor); + let curr = trie_iter.next().transpose()?; + Ok(Self { account, trie_iter, curr }) + } + + const fn output_extra(&self, path: Nibbles, node: BranchNodeCompact) -> Output { + if let Some(account) = self.account { + Output::StorageExtra(account, path, node) + } else { + Output::AccountExtra(path, node) + } + } + + const fn output_wrong( + &self, + path: Nibbles, + expected: BranchNodeCompact, + found: BranchNodeCompact, + ) -> Output { + if let Some(account) = self.account { + Output::StorageWrong { account, path, expected, found } + } else { + Output::AccountWrong { path, expected, found } + } + } + + const fn output_missing(&self, path: Nibbles, node: BranchNodeCompact) -> Output { + if let Some(account) = self.account { + Output::StorageMissing(account, path, node) + } else { + Output::AccountMissing(path, node) + } + } + + /// Called with the next path and node in the canonical sequence of stored trie nodes. Will + /// append to the given `outputs` Vec if walking the trie cursor produces data + /// inconsistent with that given. + /// + /// `next` must be called with paths in depth-first order. + fn next( + &mut self, + outputs: &mut Vec, + path: Nibbles, + node: BranchNodeCompact, + ) -> Result<(), DatabaseError> { + loop { + // `curr` is None only if the end of the iterator has been reached. Any further nodes + // found must be considered missing. + if self.curr.is_none() { + outputs.push(self.output_missing(path, node)); + return Ok(()) + } + + let (curr_path, curr_node) = self.curr.as_ref().expect("not None"); + trace!(target: "trie::verify", account=?self.account, ?curr_path, ?path, "Current cursor node"); + + // Use depth-first ordering for comparison + match depth_first::cmp(&path, curr_path) { + Ordering::Less => { + // If the given path comes before the cursor's current path in depth-first + // order, then the given path was not produced by the cursor. + outputs.push(self.output_missing(path, node)); + return Ok(()) + } + Ordering::Equal => { + // If the the current path matches the given one (happy path) but the nodes + // aren't equal then we produce a wrong node. Either way we want to move the + // iterator forward. + if *curr_node != node { + outputs.push(self.output_wrong(path, node, curr_node.clone())) + } + self.curr = self.trie_iter.next().transpose()?; + return Ok(()) + } + Ordering::Greater => { + // If the given path comes after the current path in depth-first order, + // it means the cursor's path was not found by the caller (otherwise it would + // have hit the equal case) and so is extraneous. + outputs.push(self.output_extra(*curr_path, curr_node.clone())); + self.curr = self.trie_iter.next().transpose()?; + // back to the top of the loop to check the latest `self.curr` value against the + // given path/node. + } + } + } + } + + /// Must be called once there are no more calls to `next` to made. All further nodes produced + /// by the iterator will be considered extraneous. + fn finalize(&mut self, outputs: &mut Vec) -> Result<(), DatabaseError> { + loop { + if let Some((curr_path, curr_node)) = self.curr.take() { + outputs.push(self.output_extra(curr_path, curr_node)); + self.curr = self.trie_iter.next().transpose()?; + } else { + return Ok(()) + } + } + } +} + +/// Checks that data stored in the trie database is consistent, using hashed accounts/storages +/// database tables as the source of truth. This will iteratively re-compute the entire trie based +/// on the hashed state, and produce any discovered [`Output`]s via the `next` method. +#[derive(Debug)] +pub struct Verifier { + trie_cursor_factory: T, + hashed_cursor_factory: H, + branch_node_iter: StateRootBranchNodesIter, + outputs: Vec, + account: SingleVerifier>, + storage: Option<(B256, SingleVerifier>)>, + complete: bool, +} + +impl Verifier { + /// Creates a new verifier instance. + pub fn new(trie_cursor_factory: T, hashed_cursor_factory: H) -> Result { + Ok(Self { + trie_cursor_factory: trie_cursor_factory.clone(), + hashed_cursor_factory: hashed_cursor_factory.clone(), + branch_node_iter: StateRootBranchNodesIter::new(hashed_cursor_factory), + outputs: Default::default(), + account: SingleVerifier::new(None, trie_cursor_factory.account_trie_cursor()?)?, + storage: None, + complete: false, + }) + } +} + +impl Verifier { + fn new_storage( + &mut self, + account: B256, + path: Nibbles, + node: BranchNodeCompact, + ) -> Result<(), DatabaseError> { + let trie_cursor = self.trie_cursor_factory.storage_trie_cursor(account)?; + let mut storage = SingleVerifier::new(Some(account), trie_cursor)?; + storage.next(&mut self.outputs, path, node)?; + self.storage = Some((account, storage)); + Ok(()) + } + + /// This method is called using the account hashes at the boundary of [`BranchNode::Storage`] + /// sequences, ie once the [`StateRootBranchNodesIter`] has begun yielding storage nodes for a + /// different account than it was yielding previously. All accounts between the two should have + /// empty storages. + fn verify_empty_storages( + &mut self, + last_account: B256, + next_account: B256, + start_inclusive: bool, + end_inclusive: bool, + ) -> Result<(), DatabaseError> { + let mut account_cursor = self.hashed_cursor_factory.hashed_account_cursor()?; + let mut account_seeked = false; + + if !start_inclusive { + account_seeked = true; + account_cursor.seek(last_account)?; + } + + loop { + let Some((curr_account, _)) = (if account_seeked { + account_cursor.next()? + } else { + account_seeked = true; + account_cursor.seek(last_account)? + }) else { + return Ok(()) + }; + + if curr_account < next_account || (end_inclusive && curr_account == next_account) { + trace!(target: "trie::verify", account = ?curr_account, "Verying account has empty storage"); + + let mut storage_cursor = + self.trie_cursor_factory.storage_trie_cursor(curr_account)?; + let mut seeked = false; + while let Some((path, node)) = if seeked { + storage_cursor.next()? + } else { + seeked = true; + storage_cursor.seek(Nibbles::new())? + } { + self.outputs.push(Output::StorageExtra(curr_account, path, node)); + } + } else { + return Ok(()) + } + } + } + + fn try_next(&mut self) -> Result<(), StateRootError> { + match self.branch_node_iter.next().transpose()? { + None => { + self.account.finalize(&mut self.outputs)?; + if let Some((prev_account, storage)) = self.storage.as_mut() { + storage.finalize(&mut self.outputs)?; + + // If there was a previous storage account, and it is the final one, then we + // need to validate that all accounts coming after it have empty storages. + let prev_account = *prev_account; + + // Calculate the max possible account address. + let mut max_account = B256::ZERO; + max_account.reverse(); + + self.verify_empty_storages(prev_account, max_account, false, true)?; + } + self.complete = true; + } + Some(BranchNode::Account(path, node)) => { + trace!(target: "trie::verify", ?path, "Account node from state root"); + self.account.next(&mut self.outputs, path, node)?; + // Push progress indicator + if !path.is_empty() { + self.outputs.push(Output::Progress(path)); + } + } + Some(BranchNode::Storage(account, path, node)) => { + trace!(target: "trie::verify", ?account, ?path, "Storage node from state root"); + match self.storage.as_mut() { + None => { + // First storage account - check for any empty storages before it + self.verify_empty_storages(B256::ZERO, account, true, false)?; + self.new_storage(account, path, node)?; + } + Some((prev_account, storage)) if *prev_account == account => { + storage.next(&mut self.outputs, path, node)?; + } + Some((prev_account, storage)) => { + storage.finalize(&mut self.outputs)?; + // Clear any storage entries between the previous account and the new one + let prev_account = *prev_account; + self.verify_empty_storages(prev_account, account, false, false)?; + self.new_storage(account, path, node)?; + } + } + } + } + + // If any outputs were appended we want to reverse them, so they are popped off + // in the same order they were appended. + self.outputs.reverse(); + Ok(()) + } +} + +impl Iterator for Verifier { + type Item = Result; + + fn next(&mut self) -> Option { + loop { + if let Some(output) = self.outputs.pop() { + return Some(Ok(output)) + } + + if self.complete { + return None + } + + if let Err(err) = self.try_next() { + return Some(Err(err)) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + hashed_cursor::mock::MockHashedCursorFactory, + trie_cursor::mock::{MockTrieCursor, MockTrieCursorFactory}, + }; + use alloy_primitives::{address, keccak256, map::B256Map, U256}; + use alloy_trie::TrieMask; + use assert_matches::assert_matches; + use reth_primitives_traits::Account; + use std::collections::BTreeMap; + + /// Helper function to create a simple test `BranchNodeCompact` + fn test_branch_node( + state_mask: u16, + tree_mask: u16, + hash_mask: u16, + hashes: Vec, + ) -> BranchNodeCompact { + // Ensure the number of hashes matches the number of bits set in hash_mask + let expected_hashes = hash_mask.count_ones() as usize; + let mut final_hashes = hashes; + let mut counter = 100u8; + while final_hashes.len() < expected_hashes { + final_hashes.push(B256::from([counter; 32])); + counter += 1; + } + final_hashes.truncate(expected_hashes); + + BranchNodeCompact::new( + TrieMask::new(state_mask), + TrieMask::new(tree_mask), + TrieMask::new(hash_mask), + final_hashes, + None, + ) + } + + /// Helper function to create a simple test `MockTrieCursor` + fn create_mock_cursor(trie_nodes: BTreeMap) -> MockTrieCursor { + let factory = MockTrieCursorFactory::new(trie_nodes, B256Map::default()); + factory.account_trie_cursor().unwrap() + } + + #[test] + fn test_state_root_branch_nodes_iter_empty() { + // Test with completely empty state + let factory = MockHashedCursorFactory::new(BTreeMap::new(), B256Map::default()); + let mut iter = StateRootBranchNodesIter::new(factory); + + // Collect all results - with empty state, should complete without producing nodes + let mut count = 0; + for result in iter.by_ref() { + assert!(result.is_ok(), "Unexpected error: {:?}", result.unwrap_err()); + count += 1; + // Prevent infinite loop in test + assert!(count <= 1000, "Too many iterations"); + } + + assert!(iter.complete); + } + + #[test] + fn test_state_root_branch_nodes_iter_basic() { + // Simple test with a few accounts and storage + let mut accounts = BTreeMap::new(); + let mut storage_tries = B256Map::default(); + + // Create test accounts + let addr1 = keccak256(address!("0000000000000000000000000000000000000001")); + accounts.insert( + addr1, + Account { + nonce: 1, + balance: U256::from(1000), + bytecode_hash: Some(keccak256(b"code1")), + }, + ); + + // Add storage for the account + let mut storage1 = BTreeMap::new(); + storage1.insert(keccak256(B256::from(U256::from(1))), U256::from(100)); + storage1.insert(keccak256(B256::from(U256::from(2))), U256::from(200)); + storage_tries.insert(addr1, storage1); + + let factory = MockHashedCursorFactory::new(accounts, storage_tries); + let mut iter = StateRootBranchNodesIter::new(factory); + + // Collect nodes and verify basic properties + let mut account_paths = Vec::new(); + let mut storage_paths_by_account: B256Map> = B256Map::default(); + let mut iterations = 0; + + for result in iter.by_ref() { + iterations += 1; + assert!(iterations <= 10000, "Too many iterations - possible infinite loop"); + + match result { + Ok(BranchNode::Account(path, _)) => { + account_paths.push(path); + } + Ok(BranchNode::Storage(account, path, _)) => { + storage_paths_by_account.entry(account).or_default().push(path); + } + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + // Verify account paths are in ascending order + for i in 1..account_paths.len() { + assert!( + account_paths[i - 1] < account_paths[i], + "Account paths should be in ascending order" + ); + } + + // Verify storage paths for each account are in ascending order + for (account, paths) in storage_paths_by_account { + for i in 1..paths.len() { + assert!( + paths[i - 1] < paths[i], + "Storage paths for account {:?} should be in ascending order", + account + ); + } + } + + assert!(iter.complete); + } + + #[test] + fn test_state_root_branch_nodes_iter_multiple_accounts() { + // Test with multiple accounts to verify ordering + let mut accounts = BTreeMap::new(); + let mut storage_tries = B256Map::default(); + + // Create multiple test addresses + for i in 1u8..=3 { + let addr = keccak256([i; 20]); + accounts.insert( + addr, + Account { + nonce: i as u64, + balance: U256::from(i as u64 * 1000), + bytecode_hash: (i == 2).then(|| keccak256([i])), + }, + ); + + // Add some storage for each account + let mut storage = BTreeMap::new(); + for j in 0..i { + storage.insert(keccak256(B256::from(U256::from(j))), U256::from(j as u64 * 10)); + } + if !storage.is_empty() { + storage_tries.insert(addr, storage); + } + } + + let factory = MockHashedCursorFactory::new(accounts, storage_tries); + let mut iter = StateRootBranchNodesIter::new(factory); + + // Track what we see + let mut seen_storage_accounts = Vec::new(); + let mut current_storage_account = None; + let mut iterations = 0; + + for result in iter.by_ref() { + iterations += 1; + assert!(iterations <= 10000, "Too many iterations"); + + match result { + Ok(BranchNode::Storage(account, _, _)) => { + if current_storage_account != Some(account) { + // Verify we don't revisit a storage account + assert!( + !seen_storage_accounts.contains(&account), + "Should not revisit storage account {:?}", + account + ); + seen_storage_accounts.push(account); + current_storage_account = Some(account); + } + } + Ok(BranchNode::Account(_, _)) => { + // Account nodes are fine + } + Err(e) => panic!("Unexpected error: {:?}", e), + } + } + + assert!(iter.complete); + } + + #[test] + fn test_single_verifier_new() { + // Test creating a new SingleVerifier for account trie + let trie_nodes = BTreeMap::from([( + Nibbles::from_nibbles([0x1]), + test_branch_node(0b1111, 0, 0, vec![]), + )]); + + let cursor = create_mock_cursor(trie_nodes); + let verifier = SingleVerifier::new(None, cursor).unwrap(); + + // Should have seeked to the beginning and found the first node + assert!(verifier.curr.is_some()); + } + + #[test] + fn test_single_verifier_next_exact_match() { + // Test when the expected node matches exactly + let node1 = test_branch_node(0b1111, 0, 0b1111, vec![B256::from([1u8; 32])]); + let node2 = test_branch_node(0b0101, 0b0001, 0b0100, vec![B256::from([2u8; 32])]); + + let trie_nodes = BTreeMap::from([ + (Nibbles::from_nibbles([0x1]), node1.clone()), + (Nibbles::from_nibbles([0x2]), node2), + ]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // Call next with the exact node that exists + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node1).unwrap(); + + // Should have no outputs + assert!(outputs.is_empty()); + } + + #[test] + fn test_single_verifier_next_wrong_value() { + // Test when the path matches but value is different + let node_in_trie = test_branch_node(0b1111, 0, 0b1111, vec![B256::from([1u8; 32])]); + let node_expected = test_branch_node(0b0101, 0b0001, 0b0100, vec![B256::from([2u8; 32])]); + + let trie_nodes = BTreeMap::from([(Nibbles::from_nibbles([0x1]), node_in_trie.clone())]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // Call next with different node value + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node_expected.clone()).unwrap(); + + // Should have one "wrong" output + assert_eq!(outputs.len(), 1); + assert_matches!( + &outputs[0], + Output::AccountWrong { path, expected, found } + if *path == Nibbles::from_nibbles([0x1]) && *expected == node_expected && *found == node_in_trie + ); + } + + #[test] + fn test_single_verifier_next_missing() { + // Test when expected node doesn't exist in trie + let node1 = test_branch_node(0b1111, 0, 0b1111, vec![B256::from([1u8; 32])]); + let node_missing = test_branch_node(0b0101, 0b0001, 0b0100, vec![B256::from([2u8; 32])]); + + let trie_nodes = BTreeMap::from([(Nibbles::from_nibbles([0x3]), node1)]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // Call next with a node that comes before any in the trie + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node_missing.clone()).unwrap(); + + // Should have one "missing" output + assert_eq!(outputs.len(), 1); + assert_matches!( + &outputs[0], + Output::AccountMissing(path, node) + if *path == Nibbles::from_nibbles([0x1]) && *node == node_missing + ); + } + + #[test] + fn test_single_verifier_next_extra() { + // Test when trie has extra nodes not in expected + // Create a proper trie structure with root + let node_root = test_branch_node(0b1110, 0, 0b1110, vec![]); // root has children at 1, 2, 3 + let node1 = test_branch_node(0b0001, 0, 0b0001, vec![]); + let node2 = test_branch_node(0b0010, 0, 0b0010, vec![]); + let node3 = test_branch_node(0b0100, 0, 0b0100, vec![]); + + let trie_nodes = BTreeMap::from([ + (Nibbles::new(), node_root.clone()), + (Nibbles::from_nibbles([0x1]), node1.clone()), + (Nibbles::from_nibbles([0x2]), node2.clone()), + (Nibbles::from_nibbles([0x3]), node3.clone()), + ]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // The depth-first iterator produces in post-order: 0x1, 0x2, 0x3, root + // We only provide 0x1 and 0x3, skipping 0x2 and root + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node1).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x3]), node3).unwrap(); + verifier.finalize(&mut outputs).unwrap(); + + // Should have two "extra" outputs for nodes in the trie that we skipped + if outputs.len() != 2 { + eprintln!("Expected 2 outputs, got {}:", outputs.len()); + for inc in &outputs { + eprintln!(" {:?}", inc); + } + } + assert_eq!(outputs.len(), 2); + assert_matches!( + &outputs[0], + Output::AccountExtra(path, node) + if *path == Nibbles::from_nibbles([0x2]) && *node == node2 + ); + assert_matches!( + &outputs[1], + Output::AccountExtra(path, node) + if *path == Nibbles::new() && *node == node_root + ); + } + + #[test] + fn test_single_verifier_finalize() { + // Test finalize marks all remaining nodes as extra + let node_root = test_branch_node(0b1110, 0, 0b1110, vec![]); // root has children at 1, 2, 3 + let node1 = test_branch_node(0b0001, 0, 0b0001, vec![]); + let node2 = test_branch_node(0b0010, 0, 0b0010, vec![]); + let node3 = test_branch_node(0b0100, 0, 0b0100, vec![]); + + let trie_nodes = BTreeMap::from([ + (Nibbles::new(), node_root.clone()), + (Nibbles::from_nibbles([0x1]), node1.clone()), + (Nibbles::from_nibbles([0x2]), node2.clone()), + (Nibbles::from_nibbles([0x3]), node3.clone()), + ]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // The depth-first iterator produces in post-order: 0x1, 0x2, 0x3, root + // Process first two nodes correctly + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node1).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x2]), node2).unwrap(); + assert!(outputs.is_empty()); + + // Finalize - should mark remaining nodes (0x3 and root) as extra + verifier.finalize(&mut outputs).unwrap(); + + // Should have two extra nodes + assert_eq!(outputs.len(), 2); + assert_matches!( + &outputs[0], + Output::AccountExtra(path, node) + if *path == Nibbles::from_nibbles([0x3]) && *node == node3 + ); + assert_matches!( + &outputs[1], + Output::AccountExtra(path, node) + if *path == Nibbles::new() && *node == node_root + ); + } + + #[test] + fn test_single_verifier_storage_trie() { + // Test SingleVerifier for storage trie (with account set) + let account = B256::from([42u8; 32]); + let node = test_branch_node(0b1111, 0, 0b1111, vec![B256::from([1u8; 32])]); + + let trie_nodes = BTreeMap::from([(Nibbles::from_nibbles([0x1]), node)]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(Some(account), cursor).unwrap(); + let mut outputs = Vec::new(); + + // Call next with missing node + let missing_node = test_branch_node(0b0101, 0b0001, 0b0100, vec![B256::from([2u8; 32])]); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x0]), missing_node.clone()).unwrap(); + + // Should produce StorageMissing, not AccountMissing + assert_eq!(outputs.len(), 1); + assert_matches!( + &outputs[0], + Output::StorageMissing(acc, path, node) + if *acc == account && *path == Nibbles::from_nibbles([0x0]) && *node == missing_node + ); + } + + #[test] + fn test_single_verifier_empty_trie() { + // Test with empty trie cursor + let trie_nodes = BTreeMap::new(); + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // Any node should be marked as missing + let node = test_branch_node(0b1111, 0, 0b1111, vec![B256::from([1u8; 32])]); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node.clone()).unwrap(); + + assert_eq!(outputs.len(), 1); + assert_matches!( + &outputs[0], + Output::AccountMissing(path, n) + if *path == Nibbles::from_nibbles([0x1]) && *n == node + ); + } + + #[test] + fn test_single_verifier_depth_first_ordering() { + // Test that nodes must be provided in depth-first order + // Create nodes with proper parent-child relationships + let node_root = test_branch_node(0b0110, 0, 0b0110, vec![]); // root has children at 1 and 2 + let node1 = test_branch_node(0b0110, 0, 0b0110, vec![]); // 0x1 has children at 1 and 2 + let node11 = test_branch_node(0b0001, 0, 0b0001, vec![]); // 0x11 is a leaf + let node12 = test_branch_node(0b0010, 0, 0b0010, vec![]); // 0x12 is a leaf + let node2 = test_branch_node(0b0100, 0, 0b0100, vec![]); // 0x2 is a leaf + + // The depth-first iterator will iterate from the root in this order: + // root -> 0x1 -> 0x11, 0x12 (children of 0x1), then 0x2 + // But because of depth-first, we get: root, 0x1, 0x11, 0x12, 0x2 + let trie_nodes = BTreeMap::from([ + (Nibbles::new(), node_root.clone()), // root + (Nibbles::from_nibbles([0x1]), node1.clone()), // 0x1 + (Nibbles::from_nibbles([0x1, 0x1]), node11.clone()), // 0x11 + (Nibbles::from_nibbles([0x1, 0x2]), node12.clone()), // 0x12 + (Nibbles::from_nibbles([0x2]), node2.clone()), // 0x2 + ]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // The depth-first iterator produces nodes in post-order (children before parents) + // Order: 0x11, 0x12, 0x1, 0x2, root + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1, 0x1]), node11).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1, 0x2]), node12).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node1).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x2]), node2).unwrap(); + verifier.next(&mut outputs, Nibbles::new(), node_root).unwrap(); + verifier.finalize(&mut outputs).unwrap(); + + // All should match, no outputs + if !outputs.is_empty() { + eprintln!( + "Test test_single_verifier_depth_first_ordering failed with {} outputs:", + outputs.len() + ); + for inc in &outputs { + eprintln!(" {:?}", inc); + } + } + assert!(outputs.is_empty()); + } + + #[test] + fn test_single_verifier_wrong_depth_first_order() { + // Test that providing nodes in wrong order produces outputs + // Create a trie with parent-child relationship + let node_root = test_branch_node(0b0010, 0, 0b0010, vec![]); // root has child at 1 + let node1 = test_branch_node(0b0010, 0, 0b0010, vec![]); // 0x1 has child at 1 + let node11 = test_branch_node(0b0001, 0, 0b0001, vec![]); // 0x11 is a leaf + + let trie_nodes = BTreeMap::from([ + (Nibbles::new(), node_root.clone()), + (Nibbles::from_nibbles([0x1]), node1.clone()), + (Nibbles::from_nibbles([0x1, 0x1]), node11.clone()), + ]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // Process in WRONG order (skip root, provide child before processing all nodes correctly) + // The iterator will produce: root, 0x1, 0x11 + // But we provide: 0x11, root, 0x1 (completely wrong order) + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1, 0x1]), node11).unwrap(); + verifier.next(&mut outputs, Nibbles::new(), node_root).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node1).unwrap(); + + // Should have outputs since we provided them in wrong order + assert!(!outputs.is_empty()); + } + + #[test] + fn test_single_verifier_complex_depth_first() { + // Test a complex tree structure with depth-first ordering + // Build a tree structure with proper parent-child relationships + let node_root = test_branch_node(0b0110, 0, 0b0110, vec![]); // root: children at nibbles 1 and 2 + let node1 = test_branch_node(0b0110, 0, 0b0110, vec![]); // 0x1: children at nibbles 1 and 2 + let node11 = test_branch_node(0b0110, 0, 0b0110, vec![]); // 0x11: children at nibbles 1 and 2 + let node111 = test_branch_node(0b0001, 0, 0b0001, vec![]); // 0x111: leaf + let node112 = test_branch_node(0b0010, 0, 0b0010, vec![]); // 0x112: leaf + let node12 = test_branch_node(0b0100, 0, 0b0100, vec![]); // 0x12: leaf + let node2 = test_branch_node(0b0010, 0, 0b0010, vec![]); // 0x2: child at nibble 1 + let node21 = test_branch_node(0b0001, 0, 0b0001, vec![]); // 0x21: leaf + + // Create the trie structure + let trie_nodes = BTreeMap::from([ + (Nibbles::new(), node_root.clone()), + (Nibbles::from_nibbles([0x1]), node1.clone()), + (Nibbles::from_nibbles([0x1, 0x1]), node11.clone()), + (Nibbles::from_nibbles([0x1, 0x1, 0x1]), node111.clone()), + (Nibbles::from_nibbles([0x1, 0x1, 0x2]), node112.clone()), + (Nibbles::from_nibbles([0x1, 0x2]), node12.clone()), + (Nibbles::from_nibbles([0x2]), node2.clone()), + (Nibbles::from_nibbles([0x2, 0x1]), node21.clone()), + ]); + + let cursor = create_mock_cursor(trie_nodes); + let mut verifier = SingleVerifier::new(None, cursor).unwrap(); + let mut outputs = Vec::new(); + + // The depth-first iterator produces nodes in post-order (children before parents) + // Order: 0x111, 0x112, 0x11, 0x12, 0x1, 0x21, 0x2, root + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1, 0x1, 0x1]), node111).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1, 0x1, 0x2]), node112).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1, 0x1]), node11).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1, 0x2]), node12).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x1]), node1).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x2, 0x1]), node21).unwrap(); + verifier.next(&mut outputs, Nibbles::from_nibbles([0x2]), node2).unwrap(); + verifier.next(&mut outputs, Nibbles::new(), node_root).unwrap(); + verifier.finalize(&mut outputs).unwrap(); + + // All should match, no outputs + if !outputs.is_empty() { + eprintln!( + "Test test_single_verifier_complex_depth_first failed with {} outputs:", + outputs.len() + ); + for inc in &outputs { + eprintln!(" {:?}", inc); + } + } + assert!(outputs.is_empty()); + } +} diff --git a/crates/trie/trie/src/walker.rs b/crates/trie/trie/src/walker.rs index 5bbedb23535..5be5f4f6fdb 100644 --- a/crates/trie/trie/src/walker.rs +++ b/crates/trie/trie/src/walker.rs @@ -4,17 +4,18 @@ use crate::{ BranchNodeCompact, Nibbles, }; use alloy_primitives::{map::HashSet, B256}; +use alloy_trie::proof::AddedRemovedKeys; use reth_storage_errors::db::DatabaseError; use tracing::{instrument, trace}; #[cfg(feature = "metrics")] use crate::metrics::WalkerMetrics; -/// `TrieWalker` is a structure that enables traversal of a Merkle trie. -/// It allows moving through the trie in a depth-first manner, skipping certain branches -/// if they have not changed. +/// Traverses the trie in lexicographic order. +/// +/// This iterator depends on the ordering guarantees of [`TrieCursor`]. #[derive(Debug)] -pub struct TrieWalker { +pub struct TrieWalker { /// A mutable reference to a trie cursor instance used for navigating the trie. pub cursor: C, /// A vector containing the trie nodes that have been visited. @@ -27,12 +28,16 @@ pub struct TrieWalker { pub changes: PrefixSet, /// The retained trie node keys that need to be removed. removed_keys: Option>, + /// Provided when it's necessary to not skip certain nodes during proof generation. + /// Specifically we don't skip certain branch nodes even when they are not in the `PrefixSet`, + /// when they might be required to support leaf removal. + added_removed_keys: Option, #[cfg(feature = "metrics")] /// Walker metrics. metrics: WalkerMetrics, } -impl TrieWalker { +impl> TrieWalker { /// Constructs a new `TrieWalker` for the state trie from existing stack and a cursor. pub fn state_trie_from_stack(cursor: C, stack: Vec, changes: PrefixSet) -> Self { Self::from_stack( @@ -72,6 +77,7 @@ impl TrieWalker { stack, can_skip_current_node: false, removed_keys: None, + added_removed_keys: None, #[cfg(feature = "metrics")] metrics: WalkerMetrics::new(trie_type), }; @@ -87,6 +93,21 @@ impl TrieWalker { self } + /// Configures the walker to not skip certain branch nodes, even when they are not in the + /// `PrefixSet`, when they might be needed to support leaf removal. + pub fn with_added_removed_keys(self, added_removed_keys: Option) -> TrieWalker { + TrieWalker { + cursor: self.cursor, + stack: self.stack, + can_skip_current_node: self.can_skip_current_node, + changes: self.changes, + removed_keys: self.removed_keys, + added_removed_keys, + #[cfg(feature = "metrics")] + metrics: self.metrics, + } + } + /// Split the walker into stack and trie updates. pub fn split(mut self) -> (Vec, HashSet) { let keys = self.take_removed_keys(); @@ -150,10 +171,27 @@ impl TrieWalker { /// Updates the skip node flag based on the walker's current state. fn update_skip_node(&mut self) { let old = self.can_skip_current_node; - self.can_skip_current_node = self - .stack - .last() - .is_some_and(|node| !self.changes.contains(node.full_key()) && node.hash_flag()); + self.can_skip_current_node = self.stack.last().is_some_and(|node| { + // If the current key is not removed according to the [`AddedRemovedKeys`], and all of + // its siblings are removed, then we don't want to skip it. This allows the + // `ProofRetainer` to include this node in the returned proofs. Required to support + // leaf removal. + let key_is_only_nonremoved_child = + self.added_removed_keys.as_ref().is_some_and(|added_removed_keys| { + node.full_key_is_only_nonremoved_child(added_removed_keys.as_ref()) + }); + + trace!( + target: "trie::walker", + ?key_is_only_nonremoved_child, + full_key=?node.full_key(), + "Checked for only nonremoved child", + ); + + !self.changes.contains(node.full_key()) && + node.hash_flag() && + !key_is_only_nonremoved_child + }); trace!( target: "trie::walker", old, @@ -162,9 +200,7 @@ impl TrieWalker { "updated skip node flag" ); } -} -impl TrieWalker { /// Constructs a new [`TrieWalker`] for the state trie. pub fn state_trie(cursor: C, changes: PrefixSet) -> Self { Self::new( @@ -198,6 +234,7 @@ impl TrieWalker { stack: vec![CursorSubNode::default()], can_skip_current_node: false, removed_keys: None, + added_removed_keys: Default::default(), #[cfg(feature = "metrics")] metrics: WalkerMetrics::new(trie_type), }; @@ -279,13 +316,13 @@ impl TrieWalker { // Sanity check that the newly retrieved trie node key is the child of the last item // on the stack. If not, advance to the next sibling instead of adding the node to the // stack. - if let Some(subnode) = self.stack.last() { - if !key.starts_with(subnode.full_key()) { - #[cfg(feature = "metrics")] - self.metrics.inc_out_of_order_subnode(1); - self.move_to_next_sibling(false)?; - return Ok(()) - } + if let Some(subnode) = self.stack.last() && + !key.starts_with(subnode.full_key()) + { + #[cfg(feature = "metrics")] + self.metrics.inc_out_of_order_subnode(1); + self.move_to_next_sibling(false)?; + return Ok(()) } // Create a new CursorSubNode and push it to the stack. @@ -296,10 +333,10 @@ impl TrieWalker { // Delete the current node if it's included in the prefix set or it doesn't contain the root // hash. - if !self.can_skip_current_node || position.is_child() { - if let Some((keys, key)) = self.removed_keys.as_mut().zip(self.cursor.current()?) { - keys.insert(key); - } + if (!self.can_skip_current_node || position.is_child()) && + let Some((keys, key)) = self.removed_keys.as_mut().zip(self.cursor.current()?) + { + keys.insert(key); } Ok(()) diff --git a/crates/trie/trie/src/witness.rs b/crates/trie/trie/src/witness.rs index 67da561f3d8..02ae6aa09c5 100644 --- a/crates/trie/trie/src/witness.rs +++ b/crates/trie/trie/src/witness.rs @@ -196,7 +196,11 @@ where .get(&hashed_address) .ok_or(TrieWitnessError::MissingAccount(hashed_address))? .unwrap_or_default(); - sparse_trie.update_account(hashed_address, account, &blinded_provider_factory)?; + + if !sparse_trie.update_account(hashed_address, account, &blinded_provider_factory)? { + let nibbles = Nibbles::unpack(hashed_address); + sparse_trie.remove_account_leaf(&nibbles, &blinded_provider_factory)?; + } while let Ok(node) = rx.try_recv() { self.witness.insert(keccak256(&node), node); diff --git a/docs/cli/help.rs b/docs/cli/help.rs index e6813a483a5..05e61eef740 100755 --- a/docs/cli/help.rs +++ b/docs/cli/help.rs @@ -38,7 +38,7 @@ macro_rules! regex { }}; } -/// Generate markdown files from help output of commands +/// Generate markdown files from the help output of commands #[derive(Parser, Debug)] #[command(about, long_about = None)] struct Args { @@ -116,14 +116,11 @@ fn main() -> io::Result<()> { } // Generate SUMMARY.mdx. - let summary: String = output - .iter() - .map(|(cmd, _)| cmd_summary(cmd, 0)) - .chain(once("\n".to_string())) - .collect(); + let summary: String = + output.iter().map(|(cmd, _)| cmd_summary(cmd, 0)).chain(once("\n".to_string())).collect(); println!("Writing SUMMARY.mdx to \"{}\"", out_dir.to_string_lossy()); - write_file(&out_dir.clone().join("SUMMARY.mdx"), &summary)?; + write_file(&out_dir.join("SUMMARY.mdx"), &summary)?; // Generate README.md. if args.readme { @@ -136,10 +133,8 @@ fn main() -> io::Result<()> { // Generate root SUMMARY.mdx. if args.root_summary { - let root_summary: String = output - .iter() - .map(|(cmd, _)| cmd_summary(cmd, args.root_indentation)) - .collect(); + let root_summary: String = + output.iter().map(|(cmd, _)| cmd_summary(cmd, args.root_indentation)).collect(); let path = Path::new(args.root_dir.as_str()); if args.verbose { diff --git a/docs/crates/db.md b/docs/crates/db.md index 4790d8daf4e..cee68fa1899 100644 --- a/docs/crates/db.md +++ b/docs/crates/db.md @@ -30,7 +30,7 @@ pub trait Value: Compress + Decompress + Serialize {} ``` -The `Table` trait has two generic values, `Key` and `Value`, which need to implement the `Key` and `Value` traits, respectively. The `Encode` trait is responsible for transforming data into bytes so it can be stored in the database, while the `Decode` trait transforms the bytes back into its original form. Similarly, the `Compress` and `Decompress` traits transform the data to and from a compressed format when storing or reading data from the database. +The `Table` trait has two generic values, `Key` and `Value`, which need to implement the `Key` and `Value` traits, respectively. The `Encode` trait is responsible for transforming data into bytes so it can be stored in the database, while the `Decode` trait transforms the bytes back into their original form. Similarly, the `Compress` and `Decompress` traits transform the data to and from a compressed format when storing or reading data from the database. There are many tables within the node, all used to store different types of data from `Headers` to `Transactions` and more. Below is a list of all of the tables. You can follow [this link](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/storage/db/src/tables/mod.rs#L274-L414) if you would like to see the table definitions for any of the tables below. @@ -196,7 +196,7 @@ pub trait DbTxMut: Send + Sync { + Send + Sync; - /// Put value to database + /// Put value in database fn put(&self, key: T::Key, value: T::Value) -> Result<(), DatabaseError>; /// Delete value from database fn delete(&self, key: T::Key, value: Option) @@ -267,7 +267,7 @@ let mut headers_cursor = provider.tx_ref().cursor_read::()?; let headers_walker = headers_cursor.walk_range(block_range.clone())?; ``` -Let's look at an examples of how cursors are used. The code snippet below contains the `unwind` method from the `BodyStage` defined in the `stages` crate. This function is responsible for unwinding any changes to the database if there is an error when executing the body stage within the Reth pipeline. +Let's look at an example of how cursors are used. The code snippet below contains the `unwind` method from the `BodyStage` defined in the `stages` crate. This function is responsible for unwinding any changes to the database if there is an error when executing the body stage within the Reth pipeline. [File: crates/stages/stages/src/stages/bodies.rs](https://github.com/paradigmxyz/reth/blob/bf9cac7571f018fec581fe3647862dab527aeafb/crates/stages/stages/src/stages/bodies.rs#L267-L345) @@ -306,7 +306,7 @@ fn unwind(&mut self, provider: &DatabaseProviderRW, input: UnwindInput) { requests_cursor.delete_current()?; } - // Delete all transaction to block values. + // Delete all transactions to block values. if !block_meta.is_empty() && tx_block_cursor.seek_exact(block_meta.last_tx_num())?.is_some() { diff --git a/docs/vocs/docs/pages/cli/SUMMARY.mdx b/docs/vocs/docs/pages/cli/SUMMARY.mdx index d7582ab64c5..7f7012f4c1e 100644 --- a/docs/vocs/docs/pages/cli/SUMMARY.mdx +++ b/docs/vocs/docs/pages/cli/SUMMARY.mdx @@ -18,6 +18,7 @@ - [`reth db clear`](/cli/reth/db/clear) - [`reth db clear mdbx`](/cli/reth/db/clear/mdbx) - [`reth db clear static-file`](/cli/reth/db/clear/static-file) + - [`reth db repair-trie`](/cli/reth/db/repair-trie) - [`reth db version`](/cli/reth/db/version) - [`reth db path`](/cli/reth/db/path) - [`reth download`](/cli/reth/download) @@ -39,7 +40,5 @@ - [`reth p2p rlpx ping`](/cli/reth/p2p/rlpx/ping) - [`reth p2p bootnode`](/cli/reth/p2p/bootnode) - [`reth config`](/cli/reth/config) - - [`reth recover`](/cli/reth/recover) - - [`reth recover storage-tries`](/cli/reth/recover/storage-tries) - [`reth prune`](/cli/reth/prune) - [`reth re-execute`](/cli/reth/re-execute) \ No newline at end of file diff --git a/docs/vocs/docs/pages/cli/reth.mdx b/docs/vocs/docs/pages/cli/reth.mdx index cae42444a7a..9a32d647876 100644 --- a/docs/vocs/docs/pages/cli/reth.mdx +++ b/docs/vocs/docs/pages/cli/reth.mdx @@ -12,7 +12,7 @@ Commands: node Start the node init Initialize the database from a genesis file init-state Initialize the database from a state dump file - import This syncs RLP encoded blocks from a file + import This syncs RLP encoded blocks from a file or files import-era This syncs ERA encoded blocks from a directory export-era Exports block to era1 files in a specified directory dump-genesis Dumps genesis block JSON configuration to stdout @@ -21,7 +21,6 @@ Commands: stage Manipulate individual stages p2p P2P Debugging utilities config Write config to stdout - recover Scripts for node recovery prune Prune according to the configuration without any limits re-execute Re-execute blocks in parallel to verify historical sync correctness help Print this message or the help of the given subcommand(s) @@ -69,6 +68,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/config.mdx b/docs/vocs/docs/pages/cli/reth/config.mdx index b5067952f89..b449f118168 100644 --- a/docs/vocs/docs/pages/cli/reth/config.mdx +++ b/docs/vocs/docs/pages/cli/reth/config.mdx @@ -54,6 +54,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db.mdx b/docs/vocs/docs/pages/cli/reth/db.mdx index 7c98b981f2e..2553a1480f9 100644 --- a/docs/vocs/docs/pages/cli/reth/db.mdx +++ b/docs/vocs/docs/pages/cli/reth/db.mdx @@ -9,16 +9,17 @@ $ reth db --help Usage: reth db [OPTIONS] Commands: - stats Lists all the tables, their entry count and their size - list Lists the contents of a table - checksum Calculates the content checksum of a table - diff Create a diff between two database tables or two entire databases - get Gets the content of a table for the given key - drop Deletes all database entries - clear Deletes all table entries - version Lists current and local database versions - path Returns the full database path - help Print this message or the help of the given subcommand(s) + stats Lists all the tables, their entry count and their size + list Lists the contents of a table + checksum Calculates the content checksum of a table + diff Create a diff between two database tables or two entire databases + get Gets the content of a table for the given key + drop Deletes all database entries + clear Deletes all table entries + repair-trie Verifies trie consistency and outputs any inconsistencies + version Lists current and local database versions + path Returns the full database path + help Print this message or the help of the given subcommand(s) Options: -h, --help @@ -118,6 +119,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/checksum.mdx b/docs/vocs/docs/pages/cli/reth/db/checksum.mdx index 65bd2246ded..ba12fd1b2f5 100644 --- a/docs/vocs/docs/pages/cli/reth/db/checksum.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/checksum.mdx @@ -71,6 +71,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/clear.mdx b/docs/vocs/docs/pages/cli/reth/db/clear.mdx index 809d464b517..79e324021bf 100644 --- a/docs/vocs/docs/pages/cli/reth/db/clear.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/clear.mdx @@ -63,6 +63,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/clear/mdbx.mdx b/docs/vocs/docs/pages/cli/reth/db/clear/mdbx.mdx index ddf915c18da..843f5253c9a 100644 --- a/docs/vocs/docs/pages/cli/reth/db/clear/mdbx.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/clear/mdbx.mdx @@ -62,6 +62,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/clear/static-file.mdx b/docs/vocs/docs/pages/cli/reth/db/clear/static-file.mdx index 50d054d13aa..3af272ff362 100644 --- a/docs/vocs/docs/pages/cli/reth/db/clear/static-file.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/clear/static-file.mdx @@ -65,6 +65,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/diff.mdx b/docs/vocs/docs/pages/cli/reth/db/diff.mdx index bcf7c641e68..f440545f129 100644 --- a/docs/vocs/docs/pages/cli/reth/db/diff.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/diff.mdx @@ -98,6 +98,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/drop.mdx b/docs/vocs/docs/pages/cli/reth/db/drop.mdx index db52366c4fb..64552318a21 100644 --- a/docs/vocs/docs/pages/cli/reth/db/drop.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/drop.mdx @@ -61,6 +61,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/get.mdx b/docs/vocs/docs/pages/cli/reth/db/get.mdx index 7437801a902..c7fc831b764 100644 --- a/docs/vocs/docs/pages/cli/reth/db/get.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/get.mdx @@ -63,6 +63,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/get/mdbx.mdx b/docs/vocs/docs/pages/cli/reth/db/get/mdbx.mdx index 6ba85a2d861..48fd6c889c6 100644 --- a/docs/vocs/docs/pages/cli/reth/db/get/mdbx.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/get/mdbx.mdx @@ -71,6 +71,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/get/static-file.mdx b/docs/vocs/docs/pages/cli/reth/db/get/static-file.mdx index 45209e77c9b..af21819a452 100644 --- a/docs/vocs/docs/pages/cli/reth/db/get/static-file.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/get/static-file.mdx @@ -71,6 +71,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/list.mdx b/docs/vocs/docs/pages/cli/reth/db/list.mdx index b5bbfc3ec78..cff6c7eed5e 100644 --- a/docs/vocs/docs/pages/cli/reth/db/list.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/list.mdx @@ -104,6 +104,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/path.mdx b/docs/vocs/docs/pages/cli/reth/db/path.mdx index dd1f384c5ec..1dd3279a797 100644 --- a/docs/vocs/docs/pages/cli/reth/db/path.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/path.mdx @@ -58,6 +58,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/repair-trie.mdx b/docs/vocs/docs/pages/cli/reth/db/repair-trie.mdx new file mode 100644 index 00000000000..f5058265196 --- /dev/null +++ b/docs/vocs/docs/pages/cli/reth/db/repair-trie.mdx @@ -0,0 +1,109 @@ +# reth db repair-trie + +Verifies trie consistency and outputs any inconsistencies + +```bash +$ reth db repair-trie --help +``` +```txt +Usage: reth db repair-trie [OPTIONS] + +Options: + --dry-run + Only show inconsistencies without making any repairs + + -h, --help + Print help (see a summary with '-h') + +Datadir: + --chain + The chain this node is running. + Possible values are either a built-in chain or the path to a chain specification file. + + Built-in chains: + mainnet, sepolia, holesky, hoodi, dev + + [default: mainnet] + +Logging: + --log.stdout.format + The format to use for logs written to stdout + + Possible values: + - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging + - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications + - terminal: Represents terminal-friendly formatting for logs + + [default: terminal] + + --log.stdout.filter + The filter to use for logs written to stdout + + [default: ] + + --log.file.format + The format to use for logs written to the log file + + Possible values: + - json: Represents JSON formatting for logs. This format outputs log records as JSON objects, making it suitable for structured logging + - log-fmt: Represents logfmt (key=value) formatting for logs. This format is concise and human-readable, typically used in command-line applications + - terminal: Represents terminal-friendly formatting for logs + + [default: terminal] + + --log.file.filter + The filter to use for logs written to the log file + + [default: debug] + + --log.file.directory + The path to put log files in + + [default: /logs] + + --log.file.name + The prefix name of the log files + + [default: reth.log] + + --log.file.max-size + The maximum size (in MB) of one log file + + [default: 200] + + --log.file.max-files + The maximum amount of log files that will be stored. If set to 0, background file logging is disabled + + [default: 5] + + --log.journald + Write logs to journald + + --log.journald.filter + The filter to use for logs written to journald + + [default: error] + + --color + Sets whether or not the formatter emits ANSI terminal escape codes for colors and other text formatting + + Possible values: + - always: Colors on + - auto: Colors on + - never: Colors off + + [default: always] + +Display: + -v, --verbosity... + Set the minimum log level. + + -v Errors + -vv Warnings + -vvv Info + -vvvv Debug + -vvvvv Traces (warning: very verbose!) + + -q, --quiet + Silence all log output +``` \ No newline at end of file diff --git a/docs/vocs/docs/pages/cli/reth/db/stats.mdx b/docs/vocs/docs/pages/cli/reth/db/stats.mdx index 0aa7637aa66..1f2c50908dc 100644 --- a/docs/vocs/docs/pages/cli/reth/db/stats.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/stats.mdx @@ -71,6 +71,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/db/version.mdx b/docs/vocs/docs/pages/cli/reth/db/version.mdx index 98be9145128..a683749fcdf 100644 --- a/docs/vocs/docs/pages/cli/reth/db/version.mdx +++ b/docs/vocs/docs/pages/cli/reth/db/version.mdx @@ -58,6 +58,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/download.mdx b/docs/vocs/docs/pages/cli/reth/download.mdx index b185275ffaa..973dce74a22 100644 --- a/docs/vocs/docs/pages/cli/reth/download.mdx +++ b/docs/vocs/docs/pages/cli/reth/download.mdx @@ -74,7 +74,7 @@ Database: Specify a snapshot URL or let the command propose a default one. Available snapshot sources: - - https://snapshots.merkle.io (default, mainnet archive) + - https://www.merkle.io/snapshots (default, mainnet archive) - https://publicnode.com/snapshots (full nodes & testnets) If no URL is provided, the latest mainnet archive snapshot @@ -116,6 +116,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/dump-genesis.mdx b/docs/vocs/docs/pages/cli/reth/dump-genesis.mdx index de1a401b051..6bc27381a24 100644 --- a/docs/vocs/docs/pages/cli/reth/dump-genesis.mdx +++ b/docs/vocs/docs/pages/cli/reth/dump-genesis.mdx @@ -57,6 +57,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/export-era.mdx b/docs/vocs/docs/pages/cli/reth/export-era.mdx index 9498fec19e0..896f7f34d08 100644 --- a/docs/vocs/docs/pages/cli/reth/export-era.mdx +++ b/docs/vocs/docs/pages/cli/reth/export-era.mdx @@ -122,6 +122,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/import-era.mdx b/docs/vocs/docs/pages/cli/reth/import-era.mdx index 4566fcb7af0..a783067d193 100644 --- a/docs/vocs/docs/pages/cli/reth/import-era.mdx +++ b/docs/vocs/docs/pages/cli/reth/import-era.mdx @@ -117,6 +117,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/import.mdx b/docs/vocs/docs/pages/cli/reth/import.mdx index b9ce3c54430..0914444e108 100644 --- a/docs/vocs/docs/pages/cli/reth/import.mdx +++ b/docs/vocs/docs/pages/cli/reth/import.mdx @@ -1,12 +1,12 @@ # reth import -This syncs RLP encoded blocks from a file +This syncs RLP encoded blocks from a file or files ```bash $ reth import --help ``` ```txt -Usage: reth import [OPTIONS] +Usage: reth import [OPTIONS] ... Options: -h, --help @@ -76,11 +76,11 @@ Database: --chunk-len Chunk byte length to read from file. - - The path to a block file for import. + ... + The path(s) to block file(s) for import. The online stages (headers and bodies) are replaced by a file import, after which the - remaining stages are executed. + remaining stages are executed. Multiple files will be imported sequentially. Logging: --log.stdout.format @@ -118,6 +118,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/init-state.mdx b/docs/vocs/docs/pages/cli/reth/init-state.mdx index 9c2e072680c..8c0cfa6e4d3 100644 --- a/docs/vocs/docs/pages/cli/reth/init-state.mdx +++ b/docs/vocs/docs/pages/cli/reth/init-state.mdx @@ -141,6 +141,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/init.mdx b/docs/vocs/docs/pages/cli/reth/init.mdx index 33630fa5529..b1ac27e8ba7 100644 --- a/docs/vocs/docs/pages/cli/reth/init.mdx +++ b/docs/vocs/docs/pages/cli/reth/init.mdx @@ -106,6 +106,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/node.mdx b/docs/vocs/docs/pages/cli/reth/node.mdx index 1ac83384169..b366635fcd0 100644 --- a/docs/vocs/docs/pages/cli/reth/node.mdx +++ b/docs/vocs/docs/pages/cli/reth/node.mdx @@ -238,6 +238,16 @@ Networking: Disables gossiping of transactions in the mempool to peers. This can be omitted for personal nodes, though providers should always opt to enable this flag. + --tx-propagation-mode + Sets the transaction propagation mode by determining how new pending transactions are propagated to other peers in full. + + Examples: sqrt, all, max:10 + + [default: sqrt] + + --required-block-hashes + Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out + RPC: --http Enable the HTTP-RPC server @@ -407,6 +417,9 @@ RPC: [default: full] + --rpc.forwarder + Endpoint to forward transactions to + --builder.disallow Path to file containing disallowed addresses, json-encoded list of strings. Block validation API will reject blocks containing transactions from these addresses @@ -452,6 +465,14 @@ Gas Price Oracle: [default: 60] + --gpo.default-suggested-fee + The default gas price to use if there are no blocks to use + + --rpc.send-raw-transaction-sync-timeout + Timeout for `send_raw_transaction_sync` RPC method + + [default: 30s] + TxPool: --txpool.pending-max-count Max number of transaction in the pending sub-pool @@ -623,8 +644,8 @@ Debug: --debug.etherscan [] Runs a fake consensus client that advances the chain using recent block hashes on Etherscan. If specified, requires an `ETHERSCAN_API_KEY` environment variable - --debug.rpc-consensus-ws - Runs a fake consensus client using blocks fetched from an RPC `WebSocket` endpoint + --debug.rpc-consensus-url + Runs a fake consensus client using blocks fetched from an RPC endpoint. Supports both HTTP and `WebSocket` endpoints - `WebSocket` endpoints will use subscriptions, while HTTP endpoints will poll for new blocks --debug.skip-fcu If provided, the engine will skip `n` consecutive FCUs @@ -786,7 +807,7 @@ Engine: --engine.memory-block-buffer-target Configure the target number of blocks to keep in memory - [default: 2] + [default: 0] --engine.legacy-state-root Enable legacy state root @@ -794,8 +815,8 @@ Engine: --engine.disable-caching-and-prewarming Disable cross-block caching and parallel prewarming - --engine.parallel-sparse-trie - Enable the parallel sparse trie in the engine + --engine.disable-parallel-sparse-trie + Disable the parallel sparse trie in the engine --engine.state-provider-metrics Enable state provider latency metrics. This allows the engine to collect and report stats about how long state provider calls took during execution, but this does introduce slight overhead to state provider calls @@ -832,6 +853,9 @@ Engine: Note: This is a no-op on OP Stack. + --engine.allow-unwind-canonical-header + Allow unwinding canonical header to ancestor during forkchoice updates. See `TreeConfig::unwind_canonical_header` for more details + ERA: --era.enable Enable import from ERA1 files @@ -907,6 +931,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/p2p.mdx b/docs/vocs/docs/pages/cli/reth/p2p.mdx index efd9851d1aa..6b24d9d326b 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p.mdx @@ -55,6 +55,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/p2p/body.mdx b/docs/vocs/docs/pages/cli/reth/p2p/body.mdx index c576c58c157..ecd6ccf8141 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p/body.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p/body.mdx @@ -196,6 +196,16 @@ Networking: Disables gossiping of transactions in the mempool to peers. This can be omitted for personal nodes, though providers should always opt to enable this flag. + --tx-propagation-mode + Sets the transaction propagation mode by determining how new pending transactions are propagated to other peers in full. + + Examples: sqrt, all, max:10 + + [default: sqrt] + + --required-block-hashes + Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out + Datadir: --datadir The path to the data dir for all reth files and subdirectories. @@ -262,6 +272,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/p2p/bootnode.mdx b/docs/vocs/docs/pages/cli/reth/p2p/bootnode.mdx index 5875d6f317a..859fec85973 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p/bootnode.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p/bootnode.mdx @@ -10,9 +10,9 @@ Usage: reth p2p bootnode [OPTIONS] Options: --addr - Listen address for the bootnode (default: ":30301") + Listen address for the bootnode (default: "0.0.0.0:30301") - [default: :30301] + [default: 0.0.0.0:30301] --gen-key Generate a new node key and save it to the specified file @@ -71,6 +71,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/p2p/header.mdx b/docs/vocs/docs/pages/cli/reth/p2p/header.mdx index ebdc3fcacaa..fee957e3385 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p/header.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p/header.mdx @@ -196,6 +196,16 @@ Networking: Disables gossiping of transactions in the mempool to peers. This can be omitted for personal nodes, though providers should always opt to enable this flag. + --tx-propagation-mode + Sets the transaction propagation mode by determining how new pending transactions are propagated to other peers in full. + + Examples: sqrt, all, max:10 + + [default: sqrt] + + --required-block-hashes + Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out + Datadir: --datadir The path to the data dir for all reth files and subdirectories. @@ -262,6 +272,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/p2p/rlpx.mdx b/docs/vocs/docs/pages/cli/reth/p2p/rlpx.mdx index e97fec44773..dbd7ca91b34 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p/rlpx.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p/rlpx.mdx @@ -52,6 +52,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx b/docs/vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx index 716e9038592..ac123d47285 100644 --- a/docs/vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx +++ b/docs/vocs/docs/pages/cli/reth/p2p/rlpx/ping.mdx @@ -52,6 +52,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/prune.mdx b/docs/vocs/docs/pages/cli/reth/prune.mdx index ec902167295..ce6bc399d8e 100644 --- a/docs/vocs/docs/pages/cli/reth/prune.mdx +++ b/docs/vocs/docs/pages/cli/reth/prune.mdx @@ -106,6 +106,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/re-execute.mdx b/docs/vocs/docs/pages/cli/reth/re-execute.mdx index 42bb54c0192..ec5e048b5cd 100644 --- a/docs/vocs/docs/pages/cli/reth/re-execute.mdx +++ b/docs/vocs/docs/pages/cli/reth/re-execute.mdx @@ -119,6 +119,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/recover.mdx b/docs/vocs/docs/pages/cli/reth/recover.mdx index ddf9bf77d88..880b8482d01 100644 --- a/docs/vocs/docs/pages/cli/reth/recover.mdx +++ b/docs/vocs/docs/pages/cli/reth/recover.mdx @@ -52,6 +52,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/recover/storage-tries.mdx b/docs/vocs/docs/pages/cli/reth/recover/storage-tries.mdx index c4afa9d6e37..701dd393686 100644 --- a/docs/vocs/docs/pages/cli/reth/recover/storage-tries.mdx +++ b/docs/vocs/docs/pages/cli/reth/recover/storage-tries.mdx @@ -106,6 +106,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage.mdx b/docs/vocs/docs/pages/cli/reth/stage.mdx index b35470ba9a7..bc693f7e463 100644 --- a/docs/vocs/docs/pages/cli/reth/stage.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage.mdx @@ -55,6 +55,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/drop.mdx b/docs/vocs/docs/pages/cli/reth/stage/drop.mdx index e68b1161262..a36545638ce 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/drop.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/drop.mdx @@ -120,6 +120,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/dump.mdx b/docs/vocs/docs/pages/cli/reth/stage/dump.mdx index 30116a24b0a..97211934295 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/dump.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/dump.mdx @@ -113,6 +113,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx b/docs/vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx index f35089b8201..c1459ee5498 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/dump/account-hashing.mdx @@ -70,6 +70,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/dump/execution.mdx b/docs/vocs/docs/pages/cli/reth/stage/dump/execution.mdx index 7ed155b06dd..4f39dccac12 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/dump/execution.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/dump/execution.mdx @@ -70,6 +70,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/dump/merkle.mdx b/docs/vocs/docs/pages/cli/reth/stage/dump/merkle.mdx index 0cf46118919..f5d6a07b09a 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/dump/merkle.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/dump/merkle.mdx @@ -70,6 +70,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx b/docs/vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx index 4324b8d49d5..fce03ffa753 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/dump/storage-hashing.mdx @@ -70,6 +70,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/run.mdx b/docs/vocs/docs/pages/cli/reth/stage/run.mdx index 80c0f5afa15..76ce30a2f79 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/run.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/run.mdx @@ -292,6 +292,16 @@ Networking: Disables gossiping of transactions in the mempool to peers. This can be omitted for personal nodes, though providers should always opt to enable this flag. + --tx-propagation-mode + Sets the transaction propagation mode by determining how new pending transactions are propagated to other peers in full. + + Examples: sqrt, all, max:10 + + [default: sqrt] + + --required-block-hashes + Comma separated list of required block hashes. Peers that don't have these blocks will be filtered out + Logging: --log.stdout.format The format to use for logs written to stdout @@ -328,6 +338,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx b/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx index d9a53bdb3ee..1a3fd02cae8 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/unwind.mdx @@ -114,6 +114,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx b/docs/vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx index d1407b887e4..bed98899e19 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/unwind/num-blocks.mdx @@ -62,6 +62,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx b/docs/vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx index 596cf06c115..bcfc87cf3e5 100644 --- a/docs/vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx +++ b/docs/vocs/docs/pages/cli/reth/stage/unwind/to-block.mdx @@ -62,6 +62,11 @@ Logging: [default: /logs] + --log.file.name + The prefix name of the log files + + [default: reth.log] + --log.file.max-size The maximum size (in MB) of one log file diff --git a/docs/vocs/docs/pages/guides/history-expiry.mdx b/docs/vocs/docs/pages/guides/history-expiry.mdx index 1f03b6b4aca..e4b09c1a530 100644 --- a/docs/vocs/docs/pages/guides/history-expiry.mdx +++ b/docs/vocs/docs/pages/guides/history-expiry.mdx @@ -6,7 +6,7 @@ description: Usage of tools for importing, exporting and pruning historical bloc In this chapter, we will learn how to use tools for dealing with historical data, it's import, export and removal. -We will use [reth cli](../cli/cli) to import and export historical data. +We will use [reth cli](/cli/cli) to import and export historical data. ## Enabling Pre-merge history expiry @@ -49,7 +49,7 @@ When enabled, the import from ERA1 files runs as its own separate stage before a ### Manual import -If you want to import block headers and transactions from ERA1 files without running the synchronization pipeline, you may use the [`import-era`](../cli/reth/import-era) command. +If you want to import block headers and transactions from ERA1 files without running the synchronization pipeline, you may use the [`import-era`](/cli/reth/import-era) command. ### Options @@ -68,7 +68,7 @@ Both options cannot be used at the same time. If no option is specified, the rem In this section we discuss how to export blocks data into ERA1 files. ### Manual export -You can manually export block data from your database to ERA1 files using the [`export-era`](../cli/reth/export-era) command. +You can manually export block data from your database to ERA1 files using the [`export-era`](/cli/reth/export-era) command. The CLI reads block headers, bodies, and receipts from your local database and packages them into the standardized ERA1 format with up to 8,192 blocks per file. diff --git a/docs/vocs/docs/pages/jsonrpc/trace.mdx b/docs/vocs/docs/pages/jsonrpc/trace.mdx index d1ddd3ca55c..85667bf2011 100644 --- a/docs/vocs/docs/pages/jsonrpc/trace.mdx +++ b/docs/vocs/docs/pages/jsonrpc/trace.mdx @@ -220,9 +220,9 @@ The first parameter is a list of call traces, where each call trace is of the fo The second and optional parameter is a block number, block hash, or a block tag (`latest`, `finalized`, `safe`, `earliest`, `pending`). -| Client | Method invocation | -| ------ | ------------------------------------------------------ | -| RPC | `{"method": "trace_call", "params": [trace[], block]}` | +| Client | Method invocation | +| ------ | ---------------------------------------------------------- | +| RPC | `{"method": "trace_callMany", "params": [trace[], block]}` | ### Example @@ -284,9 +284,9 @@ The second and optional parameter is a block number, block hash, or a block tag Traces a call to `eth_sendRawTransaction` without making the call, returning the traces. -| Client | Method invocation | -| ------ | ------------------------------------------------------ | -| RPC | `{"method": "trace_call", "params": [raw_tx, type[]]}` | +| Client | Method invocation | +| ------ | ---------------------------------------------------------------- | +| RPC | `{"method": "trace_rawTransaction", "params": [raw_tx, type[]]}` | ### Example diff --git a/docs/vocs/docs/pages/overview.mdx b/docs/vocs/docs/pages/overview.mdx index 33bc607bd45..5c3a8f9381c 100644 --- a/docs/vocs/docs/pages/overview.mdx +++ b/docs/vocs/docs/pages/overview.mdx @@ -88,7 +88,7 @@ We operate several public Reth nodes across different networks. You can monitor | Ethereum | 1 | Full | [View](https://reth.ithaca.xyz/public-dashboards/23ceb3bd26594e349aaaf2bcf336d0d4) | | Ethereum | 1 | Archive | [View](https://reth.ithaca.xyz/public-dashboards/a49fa110dc9149298fa6763d5c89c8c0) | | Base | 8453 | Archive | [View](https://reth.ithaca.xyz/public-dashboards/b3e9f2e668ee4b86960b7fac691b5e64) | -| OP | 10 | Archive | [View](https://reth.ithaca.xyz/public-dashboards/aa32f6c39a664f9aa371399b59622527) | +| OP | 10 | Full | [View](https://reth.ithaca.xyz/public-dashboards/aa32f6c39a664f9aa371399b59622527) | :::tip Want to set up metrics for your own Reth node? Check out our [monitoring guide](/run/monitoring) to learn how to configure Prometheus metrics and build your own dashboards. diff --git a/docs/vocs/docs/pages/run/faq/pruning.mdx b/docs/vocs/docs/pages/run/faq/pruning.mdx index 2a800b7bae8..6f646b2ee76 100644 --- a/docs/vocs/docs/pages/run/faq/pruning.mdx +++ b/docs/vocs/docs/pages/run/faq/pruning.mdx @@ -61,7 +61,8 @@ All numbers are as of April 2024 at block number 19.6M for mainnet. Archive node occupies at least 2.14TB. You can track the growth of Reth archive node size with our -[public Grafana dashboard](https://reth.paradigm.xyz/d/2k8BXz24x/reth?orgId=1&refresh=30s&viewPanel=52). +[public Ethereum Grafana dashboard](https://reth.ithaca.xyz/public-dashboards/a49fa110dc9149298fa6763d5c89c8c0). +[public Base Grafana dashboard](https://reth.ithaca.xyz/public-dashboards/b3e9f2e668ee4b86960b7fac691b5e64). ### Pruned Node diff --git a/docs/vocs/docs/pages/run/faq/sync-op-mainnet.mdx b/docs/vocs/docs/pages/run/faq/sync-op-mainnet.mdx index 58fe9a2babe..ed857da7c41 100644 --- a/docs/vocs/docs/pages/run/faq/sync-op-mainnet.mdx +++ b/docs/vocs/docs/pages/run/faq/sync-op-mainnet.mdx @@ -6,20 +6,35 @@ description: Syncing Reth with OP Mainnet and Bedrock state. To sync OP mainnet, Bedrock state needs to be imported as a starting point. There are currently two ways: -- Minimal bootstrap **(recommended)**: only state snapshot at Bedrock block is imported without any OVM historical data. -- Full bootstrap **(not recommended)**: state, blocks and receipts are imported. \*Not recommended for now: [storage consistency issue](https://github.com/paradigmxyz/reth/pull/11099) tldr: sudden crash may break the node +- Minimal bootstrap **(recommended)**: only state snapshot at Bedrock block is imported without any OVM historical data. +- Full bootstrap **(not recommended)**: state, blocks and receipts are imported. ## Minimal bootstrap (recommended) **The state snapshot at Bedrock block is required.** It can be exported from [op-geth](https://github.com/testinprod-io/op-erigon/blob/pcw109550/bedrock-db-migration/bedrock-migration.md#export-state) (**.jsonl**) or downloaded directly from [here](https://mega.nz/file/GdZ1xbAT#a9cBv3AqzsTGXYgX7nZc_3fl--tcBmOAIwIA5ND6kwc). -Import the state snapshot +### 1. Download and decompress + +After you downloaded the state file, ensure the state file is decompressed into **.jsonl** format: + +```sh +$ unzstd /path/to/world_trie_state.jsonl.zstd +``` + +### 2. Import the state + +Import the state snapshot: ```sh $ op-reth init-state --without-ovm --chain optimism --datadir op-mainnet world_trie_state.jsonl ``` -Sync the node to a recent finalized block (e.g. 125200000) to catch up close to the tip, before pairing with op-node. +### 3. Sync from Bedrock to tip + +Running the node with `--debug.tip ` syncs the node without help from CL until a fixed tip. The +block hash can be taken from the latest block on [https://optimistic.etherscan.io](https://optimistic.etherscan.io). + +Eg, sync the node to a recent finalized block (e.g. 125200000) to catch up close to the tip, before pairing with op-node. ```sh $ op-reth node --chain optimism --datadir op-mainnet --debug.tip 0x098f87b75c8b861c775984f9d5dbe7b70cbbbc30fc15adb03a5044de0144f2d0 # block #125200000 @@ -38,8 +53,8 @@ execution in reth's sync pipeline. Importing OP mainnet Bedrock datadir requires exported data: -- Blocks [and receipts] below Bedrock -- State snapshot at first Bedrock block +- Blocks [and receipts] below Bedrock +- State snapshot at first Bedrock block ### Manual Export Steps @@ -86,10 +101,7 @@ Import of >4 million OP mainnet accounts at Bedrock, completes in 10 minutes. $ op-reth init-state --chain optimism ``` -## Sync from Bedrock to tip - -Running the node with `--debug.tip `syncs the node without help from CL until a fixed tip. The -block hash can be taken from the latest block on [https://optimistic.etherscan.io](https://optimistic.etherscan.io). +### Start with op-node Use `op-node` to track the tip. Start `op-node` with `--syncmode=execution-layer` and `--l2.enginekind=reth`. If `op-node`'s RPC connection to L1 is over localhost, `--l1.trustrpc` can be set to improve performance. diff --git a/docs/vocs/docs/pages/run/faq/transactions.mdx b/docs/vocs/docs/pages/run/faq/transactions.mdx index a6d1f4c8d9a..c760c3507c6 100644 --- a/docs/vocs/docs/pages/run/faq/transactions.mdx +++ b/docs/vocs/docs/pages/run/faq/transactions.mdx @@ -4,7 +4,7 @@ description: Overview of Ethereum transaction types in Reth. # Transaction types -Over time, the Ethereum network has undergone various upgrades and improvements to enhance transaction efficiency, security, and user experience. Four significant transaction types that have evolved are: +Over time, the Ethereum network has undergone various upgrades and improvements to enhance transaction efficiency, security, and user experience. Five significant transaction types that have evolved are: - Legacy Transactions, - EIP-2930 Transactions, diff --git a/docs/vocs/docs/pages/run/overview.mdx b/docs/vocs/docs/pages/run/overview.mdx index 06b595ad482..d603a7be64b 100644 --- a/docs/vocs/docs/pages/run/overview.mdx +++ b/docs/vocs/docs/pages/run/overview.mdx @@ -40,7 +40,7 @@ Find answers to common questions and troubleshooting tips: | Ethereum | 1 | https://reth-ethereum.ithaca.xyz/rpc | | Sepolia Testnet | 11155111 | https://sepolia.drpc.org | | Base | 8453 | https://base-mainnet.rpc.ithaca.xyz | -| Base Sepolia | 84532 | https://base-sepolia.rpc.ithaca.xyz | +| Base Sepolia | 84532 | https://base-sepolia.drpc.org | :::tip Want to add more networks to this table? Feel free to [contribute](https://github.com/paradigmxyz/reth/edit/main/book/vocs/docs/pages/run/overview.mdx) by submitting a PR with additional networks that Reth supports! diff --git a/docs/vocs/docs/pages/sdk/custom-node/transactions.mdx b/docs/vocs/docs/pages/sdk/custom-node/transactions.mdx new file mode 100644 index 00000000000..52881a368fb --- /dev/null +++ b/docs/vocs/docs/pages/sdk/custom-node/transactions.mdx @@ -0,0 +1,299 @@ +# Custom transactions + +In this chapter, we'll learn how to define custom crate-local transaction envelope types and configure our node to use it. +We'll extend it with a custom variant, implement custom processing logic and configure our custom node to use it. + +All the while trying to minimize boilerplate, trivial, unnecessary or copy-pasted code. + +# Motivation + +Historically, custom node operators were forced to fork the blockchain client repository they were working with if they +wanted to introduce complex custom changes beyond the scope of the vanilla node configuration. Forking represents a huge +maintenance burden due to the complexities of keeping the code up-to-date with the custom changes intact. + +We introduced Reth SDK to address this widespread issue, where we operate in a continuous feed-back loop, continuously +shaping it to fit the needs of node operators. + +Oftentimes we may want to preserve the full capabilities of an Ethereum blockchain but introduce a special transaction +that has different processing. For example, one may introduce a transaction type that does not invoke any EVM processing, +but still produces a new state, performing a computation at no gas cost. + +# Type definition using declarative macro + +We'll showcase the macro on the `custom-node` example in the `reth` repository. +Please refer to it to see the complete implementation: https://github.com/paradigmxyz/reth/tree/main/examples/custom-node + +## Introduction + +In this example, we assume that we are building our node on top of an Optimism stack. But all the things we're doing are +analogous to the way you would build on top off of an L1 node, for example. Just use Ethereum counterparts to the Optimism +ones. + +## Dependencies + +We recommend copying out the dependencies list from the [manifest of the custom node example](https://github.com/paradigmxyz/reth/blob/main/examples/custom-node/Cargo.toml). + +The transaction envelope macro resides in the `alloy_consensus` crate since version `1.0.10`. It's being consistently improved upon so it's recommended to use the latest version. +Since we're building on top of Optimism we will also need `op-alloy` that contains Optimism specific extensions. + +Our goal is Reth compatibility, hence we need to import relevant Reth crates. Sometimes items from REVM are referenced +by Reth as its transaction execution environment, so we also need to import it. + +There may be occasionally additional dependencies needed. Refer to the [custom node example](https://github.com/paradigmxyz/reth/blob/main/examples/custom-node/Cargo.toml) for a complete list. + +## Declaration + +### Consensus + +When one thinks of a transaction, usually they mean as it is defined in the consensus layer. There are however more +interpretations depending on context. We'll start with the consensus definition. + +This definition is how the blockchain stores it, hence why a lot of the declarative properties relate to RLP encoding. +In the context of reth, the consensus definition is also adapted into the RPC API representation. Therefore, it is also +being JSON encoded. And lastly, it is being stored in database, it uses `Compact` encoding, which is a custom reth +database encoding. This one needs to be implemented manually, but can reuse a lot of existing functionality. More on +that later on in this chapter. + +Here is our top level consensus transaction envelope declaration: + +```rust +use alloy_consensus::{Signed, TransactionEnvelope}; +use op_alloy_consensus::OpTxEnvelope; + +/// Either [`OpTxEnvelope`] or [`TxPayment`]. +#[derive(Debug, Clone, TransactionEnvelope)] +#[envelope(tx_type_name = TxTypeCustom)] +pub enum CustomTransaction { + /// A regular Optimism transaction as defined by [`OpTxEnvelope`]. + #[envelope(flatten)] + Op(OpTxEnvelope), + /// A [`TxPayment`] tagged with type 0x7E. + #[envelope(ty = 42)] + Payment(Signed), +} +``` + +Few things to note here, let's start from up top. We added: +* `derive(TransactionEnvelope)` which generates a lot of derivable trait implementations for transaction envelope types. +* `derive(Debug, Clone)` which are necessary for this macro to work. +* The `envelope` attribute with parameter `tx_type_name` generates an enum with a given name, in our case `TxTypeCustom`. This enum contains a *flattened* list of transaction variants. +* The enum `CustomTransaction` is declared hence it remains a crate-local type, allowing us to implement methods and foreign traits for it, which is very useful. +* The enum has two variants: + * `Op` the base regular Optimism transaction envelope. + * `Payment` the custom extension we added. +* We can add more custom extensions if we need to by extending this enum with another variant. +* Both variants have `envelope` attribute telling the macro how to process them +* The `flatten` parameter tells the macro to adapt all the variants of the wrapped type as the variants of this transaction. +This affects the serialization of the transaction, making so that on the outside all the `OpTxEnvelope` encoded types can be deserialized into this one. +It only works with already existing transaction envelope types. +* The `ty` parameter sets the transaction numerical identifier for the serialization. It identifies the transaction +variant during deserialization. It's important that this number fits into one byte and does not collide with +identifier of any other transaction variant. + +The `TxPayment` is our custom transaction representation. In our example, it is meant only for money transfers, not for +smart contract interaction. Therefore, it needs fewer parameters and is smaller to encode. + +```rust +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Hash, + serde::Serialize, + serde::Deserialize, + reth_codecs::Compact, +)] +#[serde(rename_all = "camelCase")] +pub struct TxPayment { + /// EIP-155: Simple replay attack protection + #[serde(with = "alloy_serde::quantity")] + pub chain_id: ChainId, + /// A scalar value equal to the number of transactions sent by the sender; formally Tn. + #[serde(with = "alloy_serde::quantity")] + pub nonce: u64, + /// A scalar value equal to the maximum + /// amount of gas that should be used in executing + /// this transaction. This is paid up-front, before any + /// computation is done and may not be increased + /// later; formally Tg. + #[serde(with = "alloy_serde::quantity", rename = "gas", alias = "gasLimit")] + pub gas_limit: u64, + /// A scalar value equal to the maximum + /// amount of gas that should be used in executing + /// this transaction. This is paid up-front, before any + /// computation is done and may not be increased + /// later; formally Tg. + /// + /// As ethereum circulation is around 120mil eth as of 2022 that is around + /// 120000000000000000000000000 wei we are safe to use u128 as its max number is: + /// 340282366920938463463374607431768211455 + /// + /// This is also known as `GasFeeCap` + #[serde(with = "alloy_serde::quantity")] + pub max_fee_per_gas: u128, + /// Max Priority fee that transaction is paying + /// + /// As ethereum circulation is around 120mil eth as of 2022 that is around + /// 120000000000000000000000000 wei we are safe to use u128 as its max number is: + /// 340282366920938463463374607431768211455 + /// + /// This is also known as `GasTipCap` + #[serde(with = "alloy_serde::quantity")] + pub max_priority_fee_per_gas: u128, + /// The 160-bit address of the message call’s recipient. + pub to: Address, + /// A scalar value equal to the number of Wei to + /// be transferred to the message call’s recipient or, + /// in the case of contract creation, as an endowment + /// to the newly created account; formally Tv. + pub value: U256, +} +``` + +On top of our declaration, there are several traits derivations from the standard library. For our purposes, it is +enough to know that these are expected by Reth. + +Due to it being serialized in JSON, it needs to be `serde` compatible. The struct is annotated with +the`#[serde(rename_all = "camelCase")]` attribute that assumes JSON keys formatting in the serialized representation. + +A custom Reth derive macro is used here to generate a `Compact` implementation for us. As mentioned earlier, this +encoding is used for database storage. + +## Pooled + +Another important representation is the mempool one. This declaration should be made to contain any transaction that +users can submit into the node's mempool. + +Here is the declaration: + +```rust +use alloy_consensus::{Signed, TransactionEnvelope}; +use op_alloy_consensus::OpPooledTransaction; + +#[derive(Clone, Debug, TransactionEnvelope)] +#[envelope(tx_type_name = CustomPooledTxType)] +pub enum CustomPooledTransaction { + /// A regular Optimism transaction as defined by [`OpPooledTransaction`]. + #[envelope(flatten)] + Op(OpPooledTransaction), + /// A [`TxPayment`] tagged with type 0x7E. + #[envelope(ty = 42)] + Payment(Signed), +} +``` + +As you can see it is almost the same as the consensus one. The main difference is the use of `OpPooledTransaction` +as the base. This one does not contain the deposit transactions. In Optimism, deposits don't go into the mempool, +because they are not user-submitted, but rather received from the engine API that only the sequencer can use. + +## Manual trait implementations + +There are more traits to be implemented a lot of them are `reth` specific and due to the macro being defined in `alloy`, +it cannot provide these implementations automatically. + +We'll dissect the several kinds of trait implementations you may encounter. To see the complete list refer to these +source code sections: +* Consensus envelope: https://github.com/paradigmxyz/reth/blob/main/examples/custom-node/src/primitives/tx.rs#L29-L140 +* Pooled envelope: https://github.com/paradigmxyz/reth/blob/main/examples/custom-node/src/pool.rs#L23-L89 +* Transaction: https://github.com/paradigmxyz/reth/blob/main/examples/custom-node/src/primitives/tx_custom.rs#L71-L288 + +Most of these implementations simply match the envelope enum variant and then delegate the responsibility to each variant, for example: + +```rust +impl InMemorySize for CustomTransaction { + fn size(&self) -> usize { + match self { + CustomTransaction::Op(tx) => InMemorySize::size(tx), + CustomTransaction::Payment(tx) => InMemorySize::size(tx), + } + } +} +``` + +Some of these implementations are trivial, for example: + +```rust +impl OpTransaction for CustomTransaction { + fn is_deposit(&self) -> bool { + match self { + CustomTransaction::Op(op) => op.is_deposit(), + CustomTransaction::Payment(_) => false, + } + } + + fn as_deposit(&self) -> Option<&Sealed> { + match self { + CustomTransaction::Op(op) => op.as_deposit(), + CustomTransaction::Payment(_) => None, + } + } +} +``` + +This one is simply saying that the custom transaction variant is not an optimism deposit. + +A few of these trait implementations are a marker trait with no body like so: + +```rust +impl RlpBincode for CustomTransaction {} +``` + +Sometimes the fact that `CustomTransactionEnvelope` is a wrapper type means that it needs to reimplement some traits that +it's `inner` field already implements, like so: + +```rust +impl SignedTransaction for CustomTransaction { + fn tx_hash(&self) -> &B256 { + match self { + CustomTransaction::Op(tx) => SignedTransaction::tx_hash(tx), + CustomTransaction::Payment(tx) => tx.hash(), + } + } +} +``` + +The `Compact` support is largely derived and abstracted away with the help of a few minimal trivial implementations. One +slightly interesting case is `FromTxCompact`. The actual decoding is delegated to `TxPayment`, where it is macro generated. +Then it is put together with the `signature`, that given as one of the function arguments, via `Signed::new_unhashed` +which creates the `Signed` instance with no signature validation. + +Since the signature validation is done before encoding it, it would be redundant and infallible, but still need a +`Result` type or an `unwrap`. + +```rust +impl FromTxCompact for CustomTransaction { + type TxType = TxTypeCustom; + + fn from_tx_compact(buf: &[u8], tx_type: Self::TxType, signature: Signature) -> (Self, &[u8]) + where + Self: Sized, + { + match tx_type { + TxTypeCustom::Op(tx_type) => { + let (tx, buf) = OpTxEnvelope::from_tx_compact(buf, tx_type, signature); + (Self::Op(tx), buf) + } + TxTypeCustom::Payment => { + let (tx, buf) = TxPayment::from_compact(buf, buf.len()); + let tx = Signed::new_unhashed(tx, signature); + (Self::Payment(tx), buf) + } + } + } +} +``` + +# Conclusion + +We have declared our own transaction representation that is ready to be used with Reth! We have also declared our own +transaction envelope that contains either our custom representation or any other Optimism type. This means that it is +fully compatible with Optimism while also supporting the payment transaction, capable of being filling the role of an +execution client that belongs to an Op stack. + +# Where to go next + +Our work is not finished! What follows is to: +* Configure the node to use the custom type +* Implement components that work with transaction variants diff --git a/docs/vocs/docs/pages/sdk/examples/standalone-components.mdx b/docs/vocs/docs/pages/sdk/examples/standalone-components.mdx index 8b77913f539..9093858c6c2 100644 --- a/docs/vocs/docs/pages/sdk/examples/standalone-components.mdx +++ b/docs/vocs/docs/pages/sdk/examples/standalone-components.mdx @@ -80,9 +80,8 @@ let factory = EthereumNode::provider_factory_builder() Reth buffers new blocks in memory before persisting them to disk for performance optimization. If your external process needs immediate access to the latest blocks, configure the node to persist blocks immediately: - `--engine.persistence-threshold 0` - Persists new canonical blocks to disk immediately -- `--engine.memory-block-buffer-target 0` - Disables in-memory block buffering -Use both flags together to ensure external processes can read new blocks without delay. +Using this flag ensures external processes can read new blocks without delay. As soon as the reth process has persisted the block data, the external reader can read it from the database. diff --git a/docs/vocs/docs/snippets/sources/exex/hello-world/Cargo.toml b/docs/vocs/docs/snippets/sources/exex/hello-world/Cargo.toml index e5d32a14054..d3438032ec3 100644 --- a/docs/vocs/docs/snippets/sources/exex/hello-world/Cargo.toml +++ b/docs/vocs/docs/snippets/sources/exex/hello-world/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "my-exex" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] reth = { git = "https://github.com/paradigmxyz/reth.git" } # Reth diff --git a/docs/vocs/docs/snippets/sources/exex/remote/Cargo.toml b/docs/vocs/docs/snippets/sources/exex/remote/Cargo.toml index bbc4fe595cc..4d170be57cb 100644 --- a/docs/vocs/docs/snippets/sources/exex/remote/Cargo.toml +++ b/docs/vocs/docs/snippets/sources/exex/remote/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "remote-exex" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] # reth diff --git a/docs/vocs/docs/snippets/sources/exex/tracking-state/Cargo.toml b/docs/vocs/docs/snippets/sources/exex/tracking-state/Cargo.toml index 1fc940214c1..658608cac28 100644 --- a/docs/vocs/docs/snippets/sources/exex/tracking-state/Cargo.toml +++ b/docs/vocs/docs/snippets/sources/exex/tracking-state/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "tracking-state" version = "0.1.0" -edition = "2021" +edition = "2024" [dependencies] reth = { git = "https://github.com/paradigmxyz/reth.git" } diff --git a/docs/vocs/package.json b/docs/vocs/package.json index 035fc13b699..b3278dd0be4 100644 --- a/docs/vocs/package.json +++ b/docs/vocs/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vocs dev", - "build": "bash scripts/build-cargo-docs.sh && vocs build && bun scripts/generate-redirects.ts && bun scripts/inject-cargo-docs.ts", + "build": "bash scripts/build-cargo-docs.sh && vocs build && bun scripts/generate-redirects.ts && bun scripts/inject-cargo-docs.ts && bun scripts/fix-search-index.ts", "preview": "vocs preview", "check-links": "bun scripts/check-links.ts", "generate-redirects": "bun scripts/generate-redirects.ts", diff --git a/docs/vocs/scripts/fix-search-index.ts b/docs/vocs/scripts/fix-search-index.ts new file mode 100644 index 00000000000..99b53971f7b --- /dev/null +++ b/docs/vocs/scripts/fix-search-index.ts @@ -0,0 +1,79 @@ +#!/usr/bin/env bun +import { readdir, copyFile, readFile, writeFile } from 'fs/promises'; +import { join } from 'path'; + +async function fixSearchIndex() { + const distDir = 'docs/dist'; + const vocsDir = join(distDir, '.vocs'); + + try { + // 1. Find the search index file + const files = await readdir(vocsDir); + const searchIndexFile = files.find(f => f.startsWith('search-index-') && f.endsWith('.json')); + + if (!searchIndexFile) { + console.error('❌ No search index file found in .vocs directory'); + process.exit(1); + return; + } + + console.log(`📁 Found search index: ${searchIndexFile}`); + + // 2. Copy search index to root of dist + const sourcePath = join(vocsDir, searchIndexFile); + const destPath = join(distDir, searchIndexFile); + await copyFile(sourcePath, destPath); + console.log(`✅ Copied search index to root: ${destPath}`); + + // 3. Find and update all HTML and JS files that reference the search index + const htmlFiles = await findFiles(distDir, '.html'); + const jsFiles = await findFiles(distDir, '.js'); + console.log(`📝 Found ${htmlFiles.length} HTML files and ${jsFiles.length} JS files to update`); + + // 4. Replace references in all files + const allFiles = [...htmlFiles, ...jsFiles]; + for (const file of allFiles) { + const content = await readFile(file, 'utf-8'); + + // Replace /.vocs/search-index-*.json with /search-index-*.json + const updatedContent = content.replace( + /\/.vocs\/search-index-[a-f0-9]+\.json/g, + `/${searchIndexFile}` + ); + + if (content !== updatedContent) { + await writeFile(file, updatedContent); + console.log(` ✓ Updated ${file}`); + } + } + + console.log('✨ Search index fix complete!'); + + } catch (error) { + console.error('❌ Error fixing search index:', error); + process.exit(1); + } +} + +async function findFiles(dir: string, extension: string, files: string[] = []): Promise { + const { readdir } = await import('fs/promises'); + const entries = await readdir(dir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = join(dir, entry.name); + + // Skip .vocs, docs, and _site directories + if (entry.name === '.vocs' || entry.name === 'docs' || entry.name === '_site') continue; + + if (entry.isDirectory()) { + files = await findFiles(fullPath, extension, files); + } else if (entry.name.endsWith(extension)) { + files.push(fullPath); + } + } + + return files; +} + +// Run the fix +fixSearchIndex().catch(console.error); diff --git a/docs/vocs/vocs.config.ts b/docs/vocs/vocs.config.ts index 0df7a4ceb86..92aee418311 100644 --- a/docs/vocs/vocs.config.ts +++ b/docs/vocs/vocs.config.ts @@ -10,6 +10,9 @@ export default defineConfig({ ogImageUrl: '/reth-prod.png', sidebar, basePath, + search: { + fuzzy: true + }, topNav: [ { text: 'Run', link: '/run/ethereum' }, { text: 'SDK', link: '/sdk' }, @@ -18,7 +21,7 @@ export default defineConfig({ }, { text: 'GitHub', link: 'https://github.com/paradigmxyz/reth' }, { - text: 'v1.6.0', + text: 'v1.8.2', items: [ { text: 'Releases', diff --git a/etc/README.md b/etc/README.md index 6b6cff73e3c..0c431e8f463 100644 --- a/etc/README.md +++ b/etc/README.md @@ -45,7 +45,7 @@ To set up a new metric in Reth and its Grafana dashboard (this assumes running R 1. Save and arrange: - Click `Apply` to save the panel - - Drag the panel to desired position on the dashboard + - Drag the panel to the desired position on the dashboard 1. Export the dashboard: @@ -61,7 +61,7 @@ Your new metric is now integrated into the Reth Grafana dashboard. #### Import Grafana dashboards -If you are running Reth and Grafana outside of docker, and wish to import new Grafana dashboards or update a dashboard: +If you are running Reth and Grafana outside of Docker, and wish to import new Grafana dashboards or update a dashboard: 1. Go to `Home` > `Dashboards` @@ -74,5 +74,5 @@ If you are running Reth and Grafana outside of docker, and wish to import new Gr 1. Delete the old dashboard -If you are running Reth and Grafana using docker, after having pulled the updated dashboards from `main`, restart the +If you are running Reth and Grafana using Docker, after having pulled the updated dashboards from `main`, restart the Grafana service. This will update all dashboards. diff --git a/etc/grafana/dashboards/overview.json b/etc/grafana/dashboards/overview.json index addf3e85bbf..5b271d7ea8e 100644 --- a/etc/grafana/dashboards/overview.json +++ b/etc/grafana/dashboards/overview.json @@ -4437,14 +4437,14 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_skipped_storage_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_total_account_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, - "legendFormat": "Storage {{quantile}} percentile", + "legendFormat": "Account {{quantile}} percentile", "range": true, "refId": "Branch Nodes" } ], - "title": "Redundant multiproof storage nodes", + "title": "Total multiproof account nodes", "type": "timeseries" }, { @@ -4536,14 +4536,14 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_skipped_account_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_total_storage_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, - "legendFormat": "Account {{quantile}} percentile", + "legendFormat": "Storage {{quantile}} percentile", "range": true, "refId": "Branch Nodes" } ], - "title": "Redundant multiproof account nodes", + "title": "Total multiproof storage nodes", "type": "timeseries" }, { @@ -4635,7 +4635,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_total_account_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_skipped_account_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "hide": false, "instant": false, "legendFormat": "Account {{quantile}} percentile", @@ -4643,7 +4643,7 @@ "refId": "A" } ], - "title": "Total multiproof account nodes", + "title": "Redundant multiproof account nodes", "type": "timeseries" }, { @@ -4735,14 +4735,14 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "reth_sparse_state_trie_multiproof_total_storage_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", + "expr": "reth_sparse_state_trie_multiproof_skipped_storage_nodes{$instance_label=\"$instance\",quantile=~\"(0|0.5|0.9|0.95|1)\"}", "instant": false, "legendFormat": "Storage {{quantile}} percentile", "range": true, "refId": "Branch Nodes" } ], - "title": "Total multiproof storage nodes", + "title": "Redundant multiproof storage nodes", "type": "timeseries" }, { @@ -4851,7 +4851,7 @@ "type": "prometheus", "uid": "${datasource}" }, - "description": "Histogram for state root latency, the duration between finishing execution and receiving the state root", + "description": "Histogram for state root latency, the time spent blocked waiting for the state root.", "fieldConfig": { "defaults": { "color": { @@ -4906,32 +4906,7 @@ }, "unit": "s" }, - "overrides": [ - { - "__systemRef": "hideSeriesFrom", - "matcher": { - "id": "byNames", - "options": { - "mode": "exclude", - "names": [ - "State Root Duration p0.95" - ], - "prefix": "All except:", - "readOnly": true - } - }, - "properties": [ - { - "id": "custom.hideFrom", - "value": { - "legend": false, - "tooltip": false, - "viz": true - } - } - ] - } - ] + "overrides": [] }, "gridPos": { "h": 8, @@ -4962,7 +4937,7 @@ }, "disableTextWrap": false, "editorMode": "builder", - "expr": "reth_sync_block_validation_state_root_histogram{\"$instance_label\"=\"$instance\"}", + "expr": "reth_sync_block_validation_state_root_histogram{$instance_label=\"$instance\"}", "fullMetaSearch": false, "includeNullMetadata": true, "instant": false, @@ -11957,6 +11932,6 @@ "timezone": "", "title": "Reth", "uid": "2k8BXz24x", - "version": 2, + "version": 3, "weekStart": "" -} \ No newline at end of file +} diff --git a/examples/bsc-p2p/Cargo.toml b/examples/bsc-p2p/Cargo.toml index f6f5677dc2a..a3e2ba1d6a5 100644 --- a/examples/bsc-p2p/Cargo.toml +++ b/examples/bsc-p2p/Cargo.toml @@ -29,7 +29,6 @@ alloy-rpc-types = { workspace = true, features = ["engine"] } # misc bytes.workspace = true -derive_more.workspace = true futures.workspace = true secp256k1 = { workspace = true, features = ["global-context", "std", "recovery"] } serde = { workspace = true, features = ["derive"], optional = true } diff --git a/examples/bsc-p2p/src/block_import/parlia.rs b/examples/bsc-p2p/src/block_import/parlia.rs index ec7459ca8b9..a985895aa6c 100644 --- a/examples/bsc-p2p/src/block_import/parlia.rs +++ b/examples/bsc-p2p/src/block_import/parlia.rs @@ -32,7 +32,7 @@ where { /// Determines the head block hash according to Parlia consensus rules: /// 1. Follow the highest block number - /// 2. For same height blocks, pick the one with lower hash + /// 2. For the same height blocks, pick the one with the lower hash pub(crate) fn canonical_head( &self, hash: B256, diff --git a/examples/bsc-p2p/src/upgrade_status.rs b/examples/bsc-p2p/src/upgrade_status.rs index eadbdcd6ace..c31cf1751ac 100644 --- a/examples/bsc-p2p/src/upgrade_status.rs +++ b/examples/bsc-p2p/src/upgrade_status.rs @@ -44,7 +44,7 @@ impl UpgradeStatus { } /// The extension to define whether to enable or disable the flag. -/// This flag currently is ignored, and will be supported later. +/// This flag is currently ignored, and will be supported later. #[derive(Debug, Clone, PartialEq, Eq, RlpEncodable, RlpDecodable)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UpgradeStatusExtension { diff --git a/examples/custom-beacon-withdrawals/src/main.rs b/examples/custom-beacon-withdrawals/src/main.rs index 3aeaaa71769..ace98115cae 100644 --- a/examples/custom-beacon-withdrawals/src/main.rs +++ b/examples/custom-beacon-withdrawals/src/main.rs @@ -5,9 +5,10 @@ use alloy_eips::eip4895::Withdrawal; use alloy_evm::{ - block::{BlockExecutorFactory, BlockExecutorFor, CommitChanges, ExecutableTx}, + block::{BlockExecutorFactory, BlockExecutorFor, ExecutableTx}, eth::{EthBlockExecutionCtx, EthBlockExecutor}, precompiles::PrecompilesMap, + revm::context::result::ResultAndState, EthEvm, EthEvmFactory, }; use alloy_sol_macro::sol; @@ -22,7 +23,7 @@ use reth_ethereum::{ NextBlockEnvAttributes, OnStateHook, }, revm::{ - context::{result::ExecutionResult, TxEnv}, + context::TxEnv, db::State, primitives::{address, hardfork::SpecId, Address}, DatabaseCommit, @@ -134,7 +135,7 @@ impl ConfigureEvm for CustomEvmConfig { self.inner.block_assembler() } - fn evm_env(&self, header: &Header) -> EvmEnv { + fn evm_env(&self, header: &Header) -> Result, Self::Error> { self.inner.evm_env(header) } @@ -146,7 +147,10 @@ impl ConfigureEvm for CustomEvmConfig { self.inner.next_evm_env(parent, attributes) } - fn context_for_block<'a>(&self, block: &'a SealedBlock) -> EthBlockExecutionCtx<'a> { + fn context_for_block<'a>( + &self, + block: &'a SealedBlock, + ) -> Result, Self::Error> { self.inner.context_for_block(block) } @@ -154,7 +158,7 @@ impl ConfigureEvm for CustomEvmConfig { &self, parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, - ) -> EthBlockExecutionCtx<'_> { + ) -> Result, Self::Error> { self.inner.context_for_next_block(parent, attributes) } } @@ -191,12 +195,19 @@ where self.inner.apply_pre_execution_changes() } - fn execute_transaction_with_commit_condition( + fn execute_transaction_without_commit( + &mut self, + tx: impl ExecutableTx, + ) -> Result::HaltReason>, BlockExecutionError> { + self.inner.execute_transaction_without_commit(tx) + } + + fn commit_transaction( &mut self, + output: ResultAndState<::HaltReason>, tx: impl ExecutableTx, - f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, - ) -> Result, BlockExecutionError> { - self.inner.execute_transaction_with_commit_condition(tx, f) + ) -> Result { + self.inner.commit_transaction(output, tx) } fn finish(mut self) -> Result<(Self::Evm, BlockExecutionResult), BlockExecutionError> { diff --git a/examples/custom-evm/src/main.rs b/examples/custom-evm/src/main.rs index 93127bbc91b..b5e69670ec7 100644 --- a/examples/custom-evm/src/main.rs +++ b/examples/custom-evm/src/main.rs @@ -2,7 +2,15 @@ #![warn(unused_crate_dependencies)] -use alloy_evm::{eth::EthEvmContext, precompiles::PrecompilesMap, EvmFactory}; +use alloy_evm::{ + eth::EthEvmContext, + precompiles::PrecompilesMap, + revm::{ + handler::EthPrecompiles, + precompile::{Precompile, PrecompileId}, + }, + EvmFactory, +}; use alloy_genesis::Genesis; use alloy_primitives::{address, Bytes}; use reth_ethereum::{ @@ -12,10 +20,9 @@ use reth_ethereum::{ revm::{ context::{Context, TxEnv}, context_interface::result::{EVMError, HaltReason}, - handler::EthPrecompiles, inspector::{Inspector, NoOpInspector}, interpreter::interpreter::EthInterpreter, - precompile::{PrecompileFn, PrecompileOutput, PrecompileResult, Precompiles}, + precompile::{PrecompileOutput, PrecompileResult, Precompiles}, primitives::hardfork::SpecId, MainBuilder, MainContext, }, @@ -93,19 +100,18 @@ where } } -/// Returns precompiles for Fjor spec. +/// Returns precompiles for Prague spec. pub fn prague_custom() -> &'static Precompiles { static INSTANCE: OnceLock = OnceLock::new(); INSTANCE.get_or_init(|| { let mut precompiles = Precompiles::prague().clone(); // Custom precompile. - precompiles.extend([( + let precompile = Precompile::new( + PrecompileId::custom("custom"), address!("0x0000000000000000000000000000000000000999"), - |_, _| -> PrecompileResult { - PrecompileResult::Ok(PrecompileOutput::new(0, Bytes::new())) - } as PrecompileFn, - ) - .into()]); + |_, _| PrecompileResult::Ok(PrecompileOutput::new(0, Bytes::new())), + ); + precompiles.extend([precompile]); precompiles }) } diff --git a/examples/custom-inspector/src/main.rs b/examples/custom-inspector/src/main.rs index 739038ae6de..f7accf0e8c0 100644 --- a/examples/custom-inspector/src/main.rs +++ b/examples/custom-inspector/src/main.rs @@ -27,7 +27,7 @@ use reth_ethereum::{ interpreter::{interpreter::EthInterpreter, interpreter_types::Jumps, Interpreter}, }, }, - node::{builder::NodeHandle, EthereumNode}, + node::{builder::FullNodeFor, EthereumNode}, pool::TransactionPool, rpc::api::eth::helpers::Call, }; @@ -36,8 +36,10 @@ fn main() { Cli::::parse() .run(|builder, args| async move { // launch the node - let NodeHandle { node, node_exit_future } = - builder.node(EthereumNode::default()).launch().await?; + let handle = builder.node(EthereumNode::default()).launch().await?; + + let node: FullNodeFor = handle.node; + let node_exit_future = handle.node_exit_future; // create a new subscription to pending transactions let mut pending_transactions = node.pool.new_pending_pool_transactions_listener(); @@ -54,43 +56,43 @@ fn main() { let tx = event.transaction; println!("Transaction received: {tx:?}"); - if let Some(recipient) = tx.to() { - if args.is_match(&recipient) { - // convert the pool transaction - let call_request = - TransactionRequest::from_recovered_transaction(tx.to_consensus()); - - let evm_config = node.evm_config.clone(); - - let result = eth_api - .spawn_with_call_at( - call_request, - BlockNumberOrTag::Latest.into(), - EvmOverrides::default(), - move |db, evm_env, tx_env| { - let mut dummy_inspector = DummyInspector::default(); - let mut evm = evm_config.evm_with_env_and_inspector( - db, - evm_env, - &mut dummy_inspector, - ); - // execute the transaction on a blocking task and await - // the - // inspector result - let _ = evm.transact(tx_env)?; - Ok(dummy_inspector) - }, - ) - .await; - - if let Ok(ret_val) = result { - let hash = tx.hash(); - println!( - "Inspector result for transaction {}: \n {}", - hash, - ret_val.ret_val.join("\n") - ); - } + if let Some(recipient) = tx.to() && + args.is_match(&recipient) + { + // convert the pool transaction + let call_request = + TransactionRequest::from_recovered_transaction(tx.to_consensus()); + + let evm_config = node.evm_config.clone(); + + let result = eth_api + .spawn_with_call_at( + call_request, + BlockNumberOrTag::Latest.into(), + EvmOverrides::default(), + move |db, evm_env, tx_env| { + let mut dummy_inspector = DummyInspector::default(); + let mut evm = evm_config.evm_with_env_and_inspector( + db, + evm_env, + &mut dummy_inspector, + ); + // execute the transaction on a blocking task and await + // the + // inspector result + let _ = evm.transact(tx_env)?; + Ok(dummy_inspector) + }, + ) + .await; + + if let Ok(ret_val) = result { + let hash = tx.hash(); + println!( + "Inspector result for transaction {}: \n {}", + hash, + ret_val.ret_val.join("\n") + ); } } } diff --git a/examples/custom-node/Cargo.toml b/examples/custom-node/Cargo.toml index 5b340a0e493..a2f35687655 100644 --- a/examples/custom-node/Cargo.toml +++ b/examples/custom-node/Cargo.toml @@ -12,6 +12,7 @@ reth-codecs.workspace = true reth-network-peers.workspace = true reth-node-builder.workspace = true reth-optimism-forks.workspace = true +reth-optimism-flashblocks.workspace = true reth-db-api.workspace = true reth-op = { workspace = true, features = ["node", "pool", "rpc"] } reth-payload-builder.workspace = true diff --git a/examples/custom-node/src/chainspec.rs b/examples/custom-node/src/chainspec.rs index 5677d9fb576..4291b3549e4 100644 --- a/examples/custom-node/src/chainspec.rs +++ b/examples/custom-node/src/chainspec.rs @@ -50,15 +50,8 @@ impl Hardforks for CustomChainSpec { impl EthChainSpec for CustomChainSpec { type Header = CustomHeader; - fn base_fee_params_at_block( - &self, - block_number: u64, - ) -> reth_ethereum::chainspec::BaseFeeParams { - self.inner.base_fee_params_at_block(block_number) - } - - fn blob_params_at_timestamp(&self, timestamp: u64) -> Option { - self.inner.blob_params_at_timestamp(timestamp) + fn chain(&self) -> reth_ethereum::chainspec::Chain { + self.inner.chain() } fn base_fee_params_at_timestamp( @@ -68,38 +61,38 @@ impl EthChainSpec for CustomChainSpec { self.inner.base_fee_params_at_timestamp(timestamp) } - fn bootnodes(&self) -> Option> { - self.inner.bootnodes() - } - - fn chain(&self) -> reth_ethereum::chainspec::Chain { - self.inner.chain() + fn blob_params_at_timestamp(&self, timestamp: u64) -> Option { + self.inner.blob_params_at_timestamp(timestamp) } fn deposit_contract(&self) -> Option<&reth_ethereum::chainspec::DepositContract> { self.inner.deposit_contract() } - fn display_hardforks(&self) -> Box { - self.inner.display_hardforks() + fn genesis_hash(&self) -> revm_primitives::B256 { + self.genesis_header.hash() } fn prune_delete_limit(&self) -> usize { self.inner.prune_delete_limit() } - fn genesis(&self) -> &Genesis { - self.inner.genesis() - } - - fn genesis_hash(&self) -> revm_primitives::B256 { - self.genesis_header.hash() + fn display_hardforks(&self) -> Box { + self.inner.display_hardforks() } fn genesis_header(&self) -> &Self::Header { &self.genesis_header } + fn genesis(&self) -> &Genesis { + self.inner.genesis() + } + + fn bootnodes(&self) -> Option> { + self.inner.bootnodes() + } + fn final_paris_total_difficulty(&self) -> Option { self.inner.get_final_paris_total_difficulty() } diff --git a/examples/custom-node/src/evm/config.rs b/examples/custom-node/src/evm/config.rs index 0fbb9e893f6..2a7bb2829c0 100644 --- a/examples/custom-node/src/evm/config.rs +++ b/examples/custom-node/src/evm/config.rs @@ -23,6 +23,7 @@ use reth_op::{ node::{OpEvmConfig, OpNextBlockEnvAttributes, OpRethReceiptBuilder}, primitives::SignedTransaction, }; +use reth_optimism_flashblocks::ExecutionPayloadBaseV1; use reth_rpc_api::eth::helpers::pending_block::BuildPendingEnv; use std::sync::Arc; @@ -61,7 +62,7 @@ impl ConfigureEvm for CustomEvmConfig { &self.block_assembler } - fn evm_env(&self, header: &CustomHeader) -> EvmEnv { + fn evm_env(&self, header: &CustomHeader) -> Result, Self::Error> { self.inner.evm_env(header) } @@ -73,30 +74,33 @@ impl ConfigureEvm for CustomEvmConfig { self.inner.next_evm_env(parent, &attributes.inner) } - fn context_for_block(&self, block: &SealedBlock) -> CustomBlockExecutionCtx { - CustomBlockExecutionCtx { + fn context_for_block( + &self, + block: &SealedBlock, + ) -> Result { + Ok(CustomBlockExecutionCtx { inner: OpBlockExecutionCtx { parent_hash: block.header().parent_hash(), parent_beacon_block_root: block.header().parent_beacon_block_root(), extra_data: block.header().extra_data().clone(), }, extension: block.extension, - } + }) } fn context_for_next_block( &self, parent: &SealedHeader, attributes: Self::NextBlockEnvCtx, - ) -> CustomBlockExecutionCtx { - CustomBlockExecutionCtx { + ) -> Result { + Ok(CustomBlockExecutionCtx { inner: OpBlockExecutionCtx { parent_hash: parent.hash(), parent_beacon_block_root: attributes.inner.parent_beacon_block_root, extra_data: attributes.inner.extra_data, }, extension: attributes.extension, - } + }) } } @@ -129,13 +133,19 @@ impl ConfigureEngineEvm for CustomEvmConfig { } } -/// Additional parameters required for executing next block custom transactions. +/// Additional parameters required for executing next block of custom transactions. #[derive(Debug, Clone)] pub struct CustomNextBlockEnvAttributes { inner: OpNextBlockEnvAttributes, extension: u64, } +impl From for CustomNextBlockEnvAttributes { + fn from(value: ExecutionPayloadBaseV1) -> Self { + Self { inner: value.into(), extension: 0 } + } +} + impl BuildPendingEnv for CustomNextBlockEnvAttributes { fn build_pending_env(parent: &SealedHeader) -> Self { Self { diff --git a/examples/custom-node/src/evm/executor.rs b/examples/custom-node/src/evm/executor.rs index 61514813c2b..5288e1d67a5 100644 --- a/examples/custom-node/src/evm/executor.rs +++ b/examples/custom-node/src/evm/executor.rs @@ -9,7 +9,7 @@ use alloy_consensus::transaction::Recovered; use alloy_evm::{ block::{ BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory, - BlockExecutorFor, CommitChanges, ExecutableTx, OnStateHook, + BlockExecutorFor, ExecutableTx, OnStateHook, }, precompiles::PrecompilesMap, Database, Evm, @@ -17,7 +17,7 @@ use alloy_evm::{ use alloy_op_evm::{OpBlockExecutionCtx, OpBlockExecutor}; use reth_ethereum::evm::primitives::InspectorFor; use reth_op::{chainspec::OpChainSpec, node::OpRethReceiptBuilder, OpReceipt}; -use revm::{context::result::ExecutionResult, database::State}; +use revm::{context::result::ResultAndState, database::State}; use std::sync::Arc; pub struct CustomBlockExecutor { @@ -37,16 +37,27 @@ where self.inner.apply_pre_execution_changes() } - fn execute_transaction_with_commit_condition( + fn execute_transaction_without_commit( &mut self, tx: impl ExecutableTx, - f: impl FnOnce(&ExecutionResult<::HaltReason>) -> CommitChanges, - ) -> Result, BlockExecutionError> { + ) -> Result::HaltReason>, BlockExecutionError> { match tx.tx() { - CustomTransaction::Op(op_tx) => self.inner.execute_transaction_with_commit_condition( - Recovered::new_unchecked(op_tx, *tx.signer()), - f, - ), + CustomTransaction::Op(op_tx) => self + .inner + .execute_transaction_without_commit(Recovered::new_unchecked(op_tx, *tx.signer())), + CustomTransaction::Payment(..) => todo!(), + } + } + + fn commit_transaction( + &mut self, + output: ResultAndState<::HaltReason>, + tx: impl ExecutableTx, + ) -> Result { + match tx.tx() { + CustomTransaction::Op(op_tx) => { + self.inner.commit_transaction(output, Recovered::new_unchecked(op_tx, *tx.signer())) + } CustomTransaction::Payment(..) => todo!(), } } diff --git a/examples/custom-node/src/lib.rs b/examples/custom-node/src/lib.rs index 83a9a13c5e0..4210ac9b767 100644 --- a/examples/custom-node/src/lib.rs +++ b/examples/custom-node/src/lib.rs @@ -1,4 +1,4 @@ -//! This example shows how implement a custom node. +//! This example shows how to implement a custom node. //! //! A node consists of: //! - primitives: block,header,transactions diff --git a/examples/custom-node/src/pool.rs b/examples/custom-node/src/pool.rs index 1d57fd8c4ba..0959b3bcae0 100644 --- a/examples/custom-node/src/pool.rs +++ b/examples/custom-node/src/pool.rs @@ -1,7 +1,9 @@ use crate::primitives::{CustomTransaction, TxPayment}; use alloy_consensus::{ - crypto::RecoveryError, error::ValueError, transaction::SignerRecoverable, Signed, - TransactionEnvelope, + crypto::RecoveryError, + error::ValueError, + transaction::{SignerRecoverable, TxHashRef}, + Signed, TransactionEnvelope, }; use alloy_primitives::{Address, Sealed, B256}; use op_alloy_consensus::{OpPooledTransaction, OpTransaction, TxDeposit}; @@ -70,15 +72,17 @@ impl SignerRecoverable for CustomPooledTransaction { } } -impl SignedTransaction for CustomPooledTransaction { +impl TxHashRef for CustomPooledTransaction { fn tx_hash(&self) -> &B256 { match self { - CustomPooledTransaction::Op(tx) => SignedTransaction::tx_hash(tx), + CustomPooledTransaction::Op(tx) => tx.tx_hash(), CustomPooledTransaction::Payment(tx) => tx.hash(), } } } +impl SignedTransaction for CustomPooledTransaction {} + impl InMemorySize for CustomPooledTransaction { fn size(&self) -> usize { match self { diff --git a/examples/custom-node/src/primitives/tx.rs b/examples/custom-node/src/primitives/tx.rs index 729b2345d86..f04bcc8862f 100644 --- a/examples/custom-node/src/primitives/tx.rs +++ b/examples/custom-node/src/primitives/tx.rs @@ -1,6 +1,8 @@ use super::TxPayment; use alloy_consensus::{ - crypto::RecoveryError, transaction::SignerRecoverable, Signed, TransactionEnvelope, + crypto::RecoveryError, + transaction::{SignerRecoverable, TxHashRef}, + Signed, TransactionEnvelope, }; use alloy_eips::Encodable2718; use alloy_primitives::{Sealed, Signature, B256}; @@ -121,15 +123,17 @@ impl SignerRecoverable for CustomTransaction { } } -impl SignedTransaction for CustomTransaction { +impl TxHashRef for CustomTransaction { fn tx_hash(&self) -> &B256 { match self { - CustomTransaction::Op(tx) => SignedTransaction::tx_hash(tx), + CustomTransaction::Op(tx) => TxHashRef::tx_hash(tx), CustomTransaction::Payment(tx) => tx.hash(), } } } +impl SignedTransaction for CustomTransaction {} + impl InMemorySize for CustomTransaction { fn size(&self) -> usize { match self { diff --git a/examples/custom-payload-builder/src/job.rs b/examples/custom-payload-builder/src/job.rs index 761c890906a..abb6e89668f 100644 --- a/examples/custom-payload-builder/src/job.rs +++ b/examples/custom-payload-builder/src/job.rs @@ -1,6 +1,9 @@ use futures_util::Future; use reth_basic_payload_builder::{HeaderForPayload, PayloadBuilder, PayloadConfig}; -use reth_ethereum::{node::api::PayloadKind, tasks::TaskSpawner}; +use reth_ethereum::{ + node::api::{PayloadBuilderAttributes, PayloadKind}, + tasks::TaskSpawner, +}; use reth_payload_builder::{KeepPayloadJobAlive, PayloadBuilderError, PayloadJob}; use std::{ @@ -44,6 +47,10 @@ where Ok(self.config.attributes.clone()) } + fn payload_timestamp(&self) -> Result { + Ok(self.config.attributes.timestamp()) + } + fn resolve_kind( &mut self, _kind: PayloadKind, diff --git a/examples/custom-payload-builder/src/main.rs b/examples/custom-payload-builder/src/main.rs index 45e9d214c42..c38b46a5b9c 100644 --- a/examples/custom-payload-builder/src/main.rs +++ b/examples/custom-payload-builder/src/main.rs @@ -1,4 +1,4 @@ -//! Example for how hook into the node via the CLI extension mechanism without registering +//! Example for how to hook into the node via the CLI extension mechanism without registering //! additional arguments //! //! Run with diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs index e18a63673a0..8a6dead2cbc 100644 --- a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/handler.rs @@ -4,7 +4,7 @@ use reth_ethereum::network::{api::PeerId, protocol::ProtocolHandler}; use std::net::SocketAddr; use tokio::sync::mpsc; -/// Protocol state is an helper struct to store the protocol events. +/// Protocol state is a helper struct to store the protocol events. #[derive(Clone, Debug)] pub(crate) struct ProtocolState { pub(crate) events: mpsc::UnboundedSender, diff --git a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs index 495c4357823..19508c17035 100644 --- a/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs +++ b/examples/custom-rlpx-subprotocol/src/subprotocol/protocol/proto.rs @@ -1,4 +1,4 @@ -//! Simple RLPx Ping Pong protocol that also support sending messages, +//! Simple RLPx Ping Pong protocol that also supports sending messages, //! following [RLPx specs](https://github.com/ethereum/devp2p/blob/master/rlpx.md) use alloy_primitives::bytes::{Buf, BufMut, BytesMut}; diff --git a/examples/db-access/src/main.rs b/examples/db-access/src/main.rs index b7e92f66a71..4027beb70cb 100644 --- a/examples/db-access/src/main.rs +++ b/examples/db-access/src/main.rs @@ -123,7 +123,7 @@ fn block_provider_example>( let block = provider.block(number.into())?.ok_or(eyre::eyre!("block num not found"))?; assert_eq!(block.number, number); - // Can query a block with its senders, this is useful when you'd want to execute a block and do + // Can query a block with its senders, this is useful when you want to execute a block and do // not want to manually recover the senders for each transaction (as each transaction is // stored on disk with its v,r,s but not its `from` field.). let _recovered_block = provider @@ -145,7 +145,7 @@ fn block_provider_example>( .ok_or(eyre::eyre!("block by hash not found"))?; assert_eq!(block, block_by_hash2); - // Or you can also specify the datasource. For this provider this always return `None`, but + // Or you can also specify the datasource. For this provider this always returns `None`, but // the blockchain tree is also able to access pending state not available in the db yet. let block_by_hash3 = provider .find_block_by_hash(sealed_block.hash(), BlockSource::Any)? diff --git a/examples/engine-api-access/Cargo.toml b/examples/engine-api-access/Cargo.toml index 9f969135d8b..3e1f185077f 100644 --- a/examples/engine-api-access/Cargo.toml +++ b/examples/engine-api-access/Cargo.toml @@ -9,22 +9,7 @@ license.workspace = true # reth reth-db = { workspace = true, features = ["op", "test-utils"] } reth-node-builder.workspace = true -reth-optimism-consensus.workspace = true -reth-tasks.workspace = true -reth-node-api.workspace = true -reth-rpc-api.workspace = true -reth-tracing.workspace = true -reth-provider.workspace = true reth-optimism-node.workspace = true reth-optimism-chainspec.workspace = true -# alloy -alloy-rpc-types-engine.workspace = true - -async-trait.workspace = true -clap = { workspace = true, features = ["derive"] } -eyre.workspace = true -jsonrpsee.workspace = true -futures.workspace = true -serde_json.workspace = true tokio = { workspace = true, features = ["sync"] } diff --git a/examples/exex-hello-world/src/main.rs b/examples/exex-hello-world/src/main.rs index 4253d8185e4..2c89fb72627 100644 --- a/examples/exex-hello-world/src/main.rs +++ b/examples/exex-hello-world/src/main.rs @@ -58,7 +58,7 @@ async fn my_exex(mut ctx: ExExContext) -> eyre:: /// This function supports both Opstack Eth API and ethereum Eth API. /// /// The received handle gives access to the `EthApi` has full access to all eth api functionality -/// [`FullEthApi`]. And also gives access to additional eth related rpc method handlers, such as eth +/// [`FullEthApi`]. And also gives access to additional eth-related rpc method handlers, such as eth /// filter. async fn ethapi_exex( mut ctx: ExExContext, diff --git a/examples/exex-subscription/src/main.rs b/examples/exex-subscription/src/main.rs index b234c1c71f9..eb7ffaaf754 100644 --- a/examples/exex-subscription/src/main.rs +++ b/examples/exex-subscription/src/main.rs @@ -1,6 +1,6 @@ #![allow(dead_code)] -//! An ExEx example that installs a new RPC subscription endpoint that emit storage changes for a +//! An ExEx example that installs a new RPC subscription endpoint that emits storage changes for a //! requested address. #[allow(dead_code)] use alloy_primitives::{Address, U256}; @@ -12,7 +12,7 @@ use jsonrpsee::{ }; use reth_ethereum::{ exex::{ExExContext, ExExEvent, ExExNotification}, - node::{api::FullNodeComponents, EthereumNode}, + node::{api::FullNodeComponents, builder::NodeHandleFor, EthereumNode}, }; use std::collections::HashMap; use tokio::sync::{mpsc, oneshot}; @@ -178,7 +178,7 @@ fn main() -> eyre::Result<()> { let rpc = StorageWatcherRpc::new(subscriptions_tx.clone()); - let handle = builder + let handle: NodeHandleFor = builder .node(EthereumNode::default()) .extend_rpc_modules(move |ctx| { ctx.modules.merge_configured(StorageWatcherApiServer::into_rpc(rpc))?; diff --git a/examples/manual-p2p/src/main.rs b/examples/manual-p2p/src/main.rs index edd5ade245f..41fb846c940 100644 --- a/examples/manual-p2p/src/main.rs +++ b/examples/manual-p2p/src/main.rs @@ -124,7 +124,7 @@ async fn handshake_eth( } // Snoop by greedily capturing all broadcasts that the peer emits -// note: this node cannot handle request so will be disconnected by peer when challenged +// note: this node cannot handle request so it will be disconnected by peer when challenged async fn snoop(peer: NodeRecord, mut eth_stream: AuthedEthStream) { while let Some(Ok(update)) = eth_stream.next().await { match update { diff --git a/examples/node-builder-api/Cargo.toml b/examples/node-builder-api/Cargo.toml new file mode 100644 index 00000000000..287456ec04e --- /dev/null +++ b/examples/node-builder-api/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "example-node-builder-api" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth-ethereum = { workspace = true, features = ["node", "pool", "node-api", "cli", "test-utils"] } diff --git a/examples/node-builder-api/src/main.rs b/examples/node-builder-api/src/main.rs new file mode 100644 index 00000000000..f0d937a2d97 --- /dev/null +++ b/examples/node-builder-api/src/main.rs @@ -0,0 +1,29 @@ +//! This example showcases various Nodebuilder use cases + +use reth_ethereum::{ + cli::interface::Cli, + node::{builder::components::NoopNetworkBuilder, node::EthereumAddOns, EthereumNode}, +}; + +/// Maps the ethereum node's network component to the noop implementation. +/// +/// This installs the [`NoopNetworkBuilder`] that does not launch a real network. +pub fn noop_network() { + Cli::parse_args() + .run(|builder, _| async move { + let handle = builder + // use the default ethereum node types + .with_types::() + // Configure the components of the node + // use default ethereum components but use the Noop network that does nothing but + .with_components(EthereumNode::components().network(NoopNetworkBuilder::eth())) + .with_add_ons(EthereumAddOns::default()) + .launch() + .await?; + + handle.wait_for_node_exit().await + }) + .unwrap(); +} + +fn main() {} diff --git a/examples/node-custom-rpc/src/main.rs b/examples/node-custom-rpc/src/main.rs index 8504949d9d9..3c7c9269f58 100644 --- a/examples/node-custom-rpc/src/main.rs +++ b/examples/node-custom-rpc/src/main.rs @@ -79,6 +79,10 @@ pub trait TxpoolExtApi { #[method(name = "transactionCount")] fn transaction_count(&self) -> RpcResult; + /// Clears the transaction pool. + #[method(name = "clearTxpool")] + fn clear_txpool(&self) -> RpcResult<()>; + /// Creates a subscription that returns the number of transactions in the pool every 10s. #[subscription(name = "subscribeTransactionCount", item = usize)] fn subscribe_transaction_count( @@ -101,6 +105,12 @@ where Ok(self.pool.pool_size().total) } + fn clear_txpool(&self) -> RpcResult<()> { + let all_tx_hashes = self.pool.all_transaction_hashes(); + self.pool.remove_transactions(all_tx_hashes); + Ok(()) + } + fn subscribe_transaction_count( &self, pending_subscription_sink: PendingSubscriptionSink, @@ -148,6 +158,12 @@ mod tests { Ok(self.pool.pool_size().total) } + fn clear_txpool(&self) -> RpcResult<()> { + let all_tx_hashes = self.pool.all_transaction_hashes(); + self.pool.remove_transactions(all_tx_hashes); + Ok(()) + } + fn subscribe_transaction_count( &self, pending: PendingSubscriptionSink, @@ -190,6 +206,16 @@ mod tests { assert_eq!(count, 0); } + #[tokio::test(flavor = "multi_thread")] + async fn test_call_clear_txpool_http() { + let server_addr = start_server().await; + let uri = format!("http://{server_addr}"); + let client = HttpClientBuilder::default().build(&uri).unwrap(); + TxpoolExtApiClient::clear_txpool(&client).await.unwrap(); + let count = TxpoolExtApiClient::transaction_count(&client).await.unwrap(); + assert_eq!(count, 0); + } + #[tokio::test(flavor = "multi_thread")] async fn test_subscribe_transaction_count_ws() { let server_addr = start_server().await; diff --git a/examples/node-event-hooks/src/main.rs b/examples/node-event-hooks/src/main.rs index 60bc8c13250..fc72b936f5f 100644 --- a/examples/node-event-hooks/src/main.rs +++ b/examples/node-event-hooks/src/main.rs @@ -1,4 +1,4 @@ -//! Example for how hook into the node via the CLI extension mechanism without registering +//! Example for how to hook into the node via the CLI extension mechanism without registering //! additional arguments //! //! Run with diff --git a/examples/op-db-access/src/main.rs b/examples/op-db-access/src/main.rs index 7a44a62174e..6afe8d25b35 100644 --- a/examples/op-db-access/src/main.rs +++ b/examples/op-db-access/src/main.rs @@ -1,8 +1,8 @@ -//! Shows how manually access the database +//! Shows how to manually access the database use reth_op::{chainspec::BASE_MAINNET, node::OpNode, provider::providers::ReadOnlyConfig}; -// Providers are zero cost abstractions on top of an opened MDBX Transaction +// Providers are zero-cost abstractions on top of an opened MDBX Transaction // exposing a familiar API to query the chain's information without requiring knowledge // of the inner tables. // diff --git a/examples/polygon-p2p/src/main.rs b/examples/polygon-p2p/src/main.rs index 8882a9f6c80..d4301ec0124 100644 --- a/examples/polygon-p2p/src/main.rs +++ b/examples/polygon-p2p/src/main.rs @@ -1,4 +1,4 @@ -//! Example for how hook into the polygon p2p network +//! Example for how to hook into the polygon p2p network //! //! Run with //! @@ -67,13 +67,13 @@ async fn main() { let net_handle = net_manager.handle(); let mut events = net_handle.event_listener(); - // NetworkManager is a long running task, let's spawn it + // NetworkManager is a long-running task, let's spawn it tokio::spawn(net_manager); info!("Looking for Polygon peers..."); while let Some(evt) = events.next().await { // For the sake of the example we only print the session established event - // with the chain specific details + // with the chain-specific details if let NetworkEvent::ActivePeerSession { info, .. } = evt { let SessionInfo { status, client_version, .. } = info; let chain = status.chain; @@ -81,5 +81,5 @@ async fn main() { } // More events here } - // We will be disconnected from peers since we are not able to answer to network requests + // We will be disconnected from peers since we are not able to respond to network requests } diff --git a/examples/precompile-cache/src/main.rs b/examples/precompile-cache/src/main.rs index e72fee598cc..dcaa886d736 100644 --- a/examples/precompile-cache/src/main.rs +++ b/examples/precompile-cache/src/main.rs @@ -5,6 +5,7 @@ use alloy_evm::{ eth::EthEvmContext, precompiles::{DynPrecompile, Precompile, PrecompileInput, PrecompilesMap}, + revm::{handler::EthPrecompiles, precompile::PrecompileId}, Evm, EvmFactory, }; use alloy_genesis::Genesis; @@ -17,7 +18,6 @@ use reth_ethereum::{ revm::{ context::{Context, TxEnv}, context_interface::result::{EVMError, HaltReason}, - handler::EthPrecompiles, inspector::{Inspector, NoOpInspector}, interpreter::interpreter::EthInterpreter, precompile::PrecompileResult, @@ -117,12 +117,20 @@ impl WrappedPrecompile { /// Given a [`DynPrecompile`] and cache for a specific precompiles, create a /// wrapper that can be used inside Evm. fn wrap(precompile: DynPrecompile, cache: Arc>) -> DynPrecompile { + let precompile_id = precompile.precompile_id().clone(); let wrapped = Self::new(precompile, cache); - move |input: PrecompileInput<'_>| -> PrecompileResult { wrapped.call(input) }.into() + (precompile_id, move |input: PrecompileInput<'_>| -> PrecompileResult { + wrapped.call(input) + }) + .into() } } impl Precompile for WrappedPrecompile { + fn precompile_id(&self) -> &PrecompileId { + self.precompile.precompile_id() + } + fn call(&self, input: PrecompileInput<'_>) -> PrecompileResult { let mut cache = self.cache.write(); let key = (Bytes::copy_from_slice(input.data), input.gas); diff --git a/examples/rpc-db/src/main.rs b/examples/rpc-db/src/main.rs index b0e4b59a1a3..97bd1debdcc 100644 --- a/examples/rpc-db/src/main.rs +++ b/examples/rpc-db/src/main.rs @@ -1,4 +1,4 @@ -//! Example illustrating how to run the ETH JSON RPC API as standalone over a DB file. +//! Example illustrating how to run the ETH JSON RPC API as a standalone over a DB file. //! //! Run with //! @@ -41,7 +41,7 @@ pub mod myrpc_ext; #[tokio::main] async fn main() -> eyre::Result<()> { - // 1. Setup the DB + // 1. Set up the DB let db_path = std::env::var("RETH_DB_PATH")?; let db_path = Path::new(&db_path); let db = Arc::new(open_db_read_only( @@ -55,7 +55,7 @@ async fn main() -> eyre::Result<()> { StaticFileProvider::read_only(db_path.join("static_files"), true)?, ); - // 2. Setup the blockchain provider using only the database provider and a noop for the tree to + // 2. Set up the blockchain provider using only the database provider and a noop for the tree to // satisfy trait bounds. Tree is not used in this example since we are only operating on the // disk and don't handle new blocks/live sync etc, which is done by the blockchain tree. let provider = BlockchainProvider::new(factory)?; diff --git a/examples/txpool-tracing/src/main.rs b/examples/txpool-tracing/src/main.rs index a1b61422cb9..f510a3f68b8 100644 --- a/examples/txpool-tracing/src/main.rs +++ b/examples/txpool-tracing/src/main.rs @@ -44,17 +44,17 @@ fn main() { let tx = event.transaction; println!("Transaction received: {tx:?}"); - if let Some(recipient) = tx.to() { - if args.is_match(&recipient) { - // trace the transaction with `trace_call` - let callrequest = - TransactionRequest::from_recovered_transaction(tx.to_consensus()); - let tracerequest = TraceCallRequest::new(callrequest) - .with_trace_type(TraceType::Trace); - if let Ok(trace_result) = traceapi.trace_call(tracerequest).await { - let hash = tx.hash(); - println!("trace result for transaction {hash}: {trace_result:?}"); - } + if let Some(recipient) = tx.to() && + args.is_match(&recipient) + { + // trace the transaction with `trace_call` + let callrequest = + TransactionRequest::from_recovered_transaction(tx.to_consensus()); + let tracerequest = + TraceCallRequest::new(callrequest).with_trace_type(TraceType::Trace); + if let Ok(trace_result) = traceapi.trace_call(tracerequest).await { + let hash = tx.hash(); + println!("trace result for transaction {hash}: {trace_result:?}"); } } } @@ -68,7 +68,7 @@ fn main() { /// Our custom cli args extension that adds one flag to reth default CLI. #[derive(Debug, Clone, Default, clap::Args)] struct RethCliTxpoolExt { - /// recipients addresses that we want to trace + /// recipients' addresses that we want to trace #[arg(long, value_delimiter = ',')] pub recipients: Vec

, } diff --git a/examples/txpool-tracing/src/submit.rs b/examples/txpool-tracing/src/submit.rs index b59cefe2f21..f3e0de16edb 100644 --- a/examples/txpool-tracing/src/submit.rs +++ b/examples/txpool-tracing/src/submit.rs @@ -32,7 +32,7 @@ pub async fn submit_transaction( max_fee_per_gas: u128, ) -> eyre::Result where - // This enforces `EthPrimitives` types for this node, this unlocks the proper conversions when + // This enforces `EthPrimitives` types for this node, which unlocks the proper conversions when FC: FullNodeComponents>, { // Create the transaction request diff --git a/flake.nix b/flake.nix index 54351e56d46..7550edc31e3 100644 --- a/flake.nix +++ b/flake.nix @@ -118,6 +118,7 @@ packages = nativeBuildInputs ++ [ rustNightly.rust-analyzer rustNightly.rustfmt + pkgs.cargo-nextest ]; } overrides); } diff --git a/rustfmt.toml b/rustfmt.toml index 68c3c93033d..bf86a535083 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,4 @@ +style_edition = "2021" reorder_imports = true imports_granularity = "Crate" use_small_heuristics = "Max" diff --git a/testing/ef-tests/.gitignore b/testing/ef-tests/.gitignore index eae5bd973fc..0bf9998816a 100644 --- a/testing/ef-tests/.gitignore +++ b/testing/ef-tests/.gitignore @@ -1 +1,2 @@ -ethereum-tests \ No newline at end of file +ethereum-tests +execution-spec-tests \ No newline at end of file diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml index b79ecccbbb7..6b11e29c707 100644 --- a/testing/ef-tests/Cargo.toml +++ b/testing/ef-tests/Cargo.toml @@ -45,4 +45,3 @@ serde.workspace = true serde_json.workspace = true thiserror.workspace = true rayon.workspace = true -tracing.workspace = true diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs index a420296e917..75ff73101d2 100644 --- a/testing/ef-tests/src/cases/blockchain_test.rs +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -23,26 +23,31 @@ use reth_revm::{database::StateProviderDatabase, witness::ExecutionWitnessRecord use reth_stateless::{validation::stateless_validation, ExecutionWitness}; use reth_trie::{HashedPostState, KeccakKeyHasher, StateRoot}; use reth_trie_db::DatabaseStateRoot; -use std::{collections::BTreeMap, fs, path::Path, sync::Arc}; +use std::{ + collections::BTreeMap, + fs, + path::{Path, PathBuf}, + sync::Arc, +}; /// A handler for the blockchain test suite. #[derive(Debug)] pub struct BlockchainTests { - suite: String, + suite_path: PathBuf, } impl BlockchainTests { - /// Create a new handler for a subset of the blockchain test suite. - pub const fn new(suite: String) -> Self { - Self { suite } + /// Create a new suite for tests with blockchain tests format. + pub const fn new(suite_path: PathBuf) -> Self { + Self { suite_path } } } impl Suite for BlockchainTests { type Case = BlockchainTestCase; - fn suite_name(&self) -> String { - format!("BlockchainTests/{}", self.suite) + fn suite_path(&self) -> &Path { + &self.suite_path } } @@ -131,7 +136,7 @@ impl BlockchainTestCase { // Since it is unexpected, we treat it as a test failure. // // One reason for this happening is when one forgets to wrap the error from `run_case` - // so that it produces a `Error::BlockProcessingFailed` + // so that it produces an `Error::BlockProcessingFailed` Err(other) => Err(other), } } @@ -157,7 +162,7 @@ impl Case for BlockchainTestCase { fn run(&self) -> Result<(), Error> { // If the test is marked for skipping, return a Skipped error immediately. if self.skip { - return Err(Error::Skipped) + return Err(Error::Skipped); } // Iterate through test cases, filtering by the network type to exclude specific forks. @@ -306,18 +311,25 @@ fn run_case(case: &BlockchainTest) -> Result<(), Error> { parent = block.clone() } - // Validate the post-state for the test case. - // - // If we get here then it means that the post-state root checks - // made after we execute each block was successful. - // - // If an error occurs here, then it is: - // - Either an issue with the test setup - // - Possibly an error in the test case where the post-state root in the last block does not - // match the post-state values. - let expected_post_state = case.post_state.as_ref().ok_or(Error::MissingPostState)?; - for (&address, account) in expected_post_state { - account.assert_db(address, provider.tx_ref())?; + match &case.post_state { + Some(expected_post_state) => { + // Validate the post-state for the test case. + // + // If we get here then it means that the post-state root checks + // made after we execute each block was successful. + // + // If an error occurs here, then it is: + // - Either an issue with the test setup + // - Possibly an error in the test case where the post-state root in the last block does + // not match the post-state values. + for (address, account) in expected_post_state { + account.assert_db(*address, provider.tx_ref())?; + } + } + None => { + // Some tests may not have post-state (e.g., state-heavy benchmark tests). + // In this case, we can skip the post-state validation. + } } // Now validate using the stateless client if everything else passes @@ -396,7 +408,7 @@ pub fn should_skip(path: &Path) -> bool { | "typeTwoBerlin.json" // Test checks if nonce overflows. We are handling this correctly but we are not parsing - // exception in testsuite There are more nonce overflow tests that are internal + // exception in testsuite. There are more nonce overflow tests that are internal // call/create, and those tests are passing and are enabled. | "CreateTransactionHighNonce.json" diff --git a/testing/ef-tests/src/models.rs b/testing/ef-tests/src/models.rs index 6cad5331e59..49c49bf1936 100644 --- a/testing/ef-tests/src/models.rs +++ b/testing/ef-tests/src/models.rs @@ -5,7 +5,7 @@ use alloy_consensus::Header as RethHeader; use alloy_eips::eip4895::Withdrawals; use alloy_genesis::GenesisAccount; use alloy_primitives::{keccak256, Address, Bloom, Bytes, B256, B64, U256}; -use reth_chainspec::{ChainSpec, ChainSpecBuilder}; +use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, ForkCondition}; use reth_db_api::{cursor::DbDupCursorRO, tables, transaction::DbTx}; use reth_primitives_traits::SealedHeader; use serde::Deserialize; @@ -294,9 +294,14 @@ pub enum ForkSpec { /// London London, /// Paris aka The Merge + #[serde(alias = "Paris")] Merge, + /// Paris to Shanghai at time 15k + ParisToShanghaiAtTime15k, /// Shanghai Shanghai, + /// Shanghai to Cancun at time 15k + ShanghaiToCancunAtTime15k, /// Merge EOF test #[serde(alias = "Merge+3540+3670")] MergeEOF, @@ -308,39 +313,63 @@ pub enum ForkSpec { MergePush0, /// Cancun Cancun, + /// Cancun to Prague at time 15k + CancunToPragueAtTime15k, /// Prague Prague, } impl From for ChainSpec { fn from(fork_spec: ForkSpec) -> Self { - let spec_builder = ChainSpecBuilder::mainnet(); + let spec_builder = ChainSpecBuilder::mainnet().reset(); match fork_spec { ForkSpec::Frontier => spec_builder.frontier_activated(), - ForkSpec::Homestead | ForkSpec::FrontierToHomesteadAt5 => { - spec_builder.homestead_activated() - } - ForkSpec::EIP150 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => { - spec_builder.tangerine_whistle_activated() - } + ForkSpec::FrontierToHomesteadAt5 => spec_builder + .frontier_activated() + .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)), + ForkSpec::Homestead => spec_builder.homestead_activated(), + ForkSpec::HomesteadToDaoAt5 => spec_builder + .homestead_activated() + .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)), + ForkSpec::HomesteadToEIP150At5 => spec_builder + .homestead_activated() + .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)), + ForkSpec::EIP150 => spec_builder.tangerine_whistle_activated(), ForkSpec::EIP158 => spec_builder.spurious_dragon_activated(), - ForkSpec::Byzantium | - ForkSpec::EIP158ToByzantiumAt5 | - ForkSpec::ConstantinopleFix | - ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder.byzantium_activated(), + ForkSpec::EIP158ToByzantiumAt5 => spec_builder + .spurious_dragon_activated() + .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)), + ForkSpec::Byzantium => spec_builder.byzantium_activated(), + ForkSpec::ByzantiumToConstantinopleAt5 => spec_builder + .byzantium_activated() + .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)), + ForkSpec::ByzantiumToConstantinopleFixAt5 => spec_builder + .byzantium_activated() + .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)), + ForkSpec::Constantinople => spec_builder.constantinople_activated(), + ForkSpec::ConstantinopleFix => spec_builder.petersburg_activated(), ForkSpec::Istanbul => spec_builder.istanbul_activated(), ForkSpec::Berlin => spec_builder.berlin_activated(), - ForkSpec::London | ForkSpec::BerlinToLondonAt5 => spec_builder.london_activated(), + ForkSpec::BerlinToLondonAt5 => spec_builder + .berlin_activated() + .with_fork(EthereumHardfork::London, ForkCondition::Block(5)), + ForkSpec::London => spec_builder.london_activated(), ForkSpec::Merge | ForkSpec::MergeEOF | ForkSpec::MergeMeterInitCode | ForkSpec::MergePush0 => spec_builder.paris_activated(), + ForkSpec::ParisToShanghaiAtTime15k => spec_builder + .paris_activated() + .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)), ForkSpec::Shanghai => spec_builder.shanghai_activated(), + ForkSpec::ShanghaiToCancunAtTime15k => spec_builder + .shanghai_activated() + .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)), ForkSpec::Cancun => spec_builder.cancun_activated(), - ForkSpec::ByzantiumToConstantinopleAt5 | ForkSpec::Constantinople => { - panic!("Overridden with PETERSBURG") - } + ForkSpec::CancunToPragueAtTime15k => spec_builder + .cancun_activated() + .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)), ForkSpec::Prague => spec_builder.prague_activated(), } .build() diff --git a/testing/ef-tests/src/result.rs b/testing/ef-tests/src/result.rs index f53a4fab256..0284e06da02 100644 --- a/testing/ef-tests/src/result.rs +++ b/testing/ef-tests/src/result.rs @@ -17,9 +17,6 @@ pub enum Error { /// The test was skipped #[error("test was skipped")] Skipped, - /// No post state found in test - #[error("no post state found for validation")] - MissingPostState, /// Block processing failed /// Note: This includes but is not limited to execution. /// For example, the header number could be incorrect. diff --git a/testing/ef-tests/src/suite.rs b/testing/ef-tests/src/suite.rs index 237ca935baf..0b3ed447a24 100644 --- a/testing/ef-tests/src/suite.rs +++ b/testing/ef-tests/src/suite.rs @@ -12,25 +12,28 @@ pub trait Suite { /// The type of test cases in this suite. type Case: Case; - /// The name of the test suite used to locate the individual test cases. - /// - /// # Example - /// - /// - `GeneralStateTests` - /// - `BlockchainTests/InvalidBlocks` - /// - `BlockchainTests/TransitionTests` - fn suite_name(&self) -> String; + /// The path to the test suite directory. + fn suite_path(&self) -> &Path; + + /// Run all test cases in the suite. + fn run(&self) { + let suite_path = self.suite_path(); + for entry in WalkDir::new(suite_path).min_depth(1).max_depth(1) { + let entry = entry.expect("Failed to read directory"); + if entry.file_type().is_dir() { + self.run_only(entry.file_name().to_string_lossy().as_ref()); + } + } + } - /// Load and run each contained test case. + /// Load and run each contained test case for the provided sub-folder. /// /// # Note /// /// This recursively finds every test description in the resulting path. - fn run(&self) { + fn run_only(&self, name: &str) { // Build the path to the test suite directory - let suite_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("ethereum-tests") - .join(self.suite_name()); + let suite_path = self.suite_path().join(name); // Verify that the path exists assert!(suite_path.exists(), "Test suite path does not exist: {suite_path:?}"); @@ -48,7 +51,7 @@ pub trait Suite { let results = Cases { test_cases }.run(); // Assert that all tests in the suite pass - assert_tests_pass(&self.suite_name(), &suite_path, &results); + assert_tests_pass(name, &suite_path, &results); } } diff --git a/testing/ef-tests/tests/tests.rs b/testing/ef-tests/tests/tests.rs index a1838d43e51..0961817e901 100644 --- a/testing/ef-tests/tests/tests.rs +++ b/testing/ef-tests/tests/tests.rs @@ -2,13 +2,19 @@ #![cfg(feature = "ef-tests")] use ef_tests::{cases::blockchain_test::BlockchainTests, suite::Suite}; +use std::path::PathBuf; macro_rules! general_state_test { ($test_name:ident, $dir:ident) => { #[test] fn $test_name() { reth_tracing::init_test_tracing(); - BlockchainTests::new(format!("GeneralStateTests/{}", stringify!($dir))).run(); + let suite_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("ethereum-tests") + .join("BlockchainTests"); + + BlockchainTests::new(suite_path) + .run_only(&format!("GeneralStateTests/{}", stringify!($dir))); } }; } @@ -83,10 +89,24 @@ macro_rules! blockchain_test { #[test] fn $test_name() { reth_tracing::init_test_tracing(); - BlockchainTests::new(format!("{}", stringify!($dir))).run(); + let suite_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("ethereum-tests") + .join("BlockchainTests"); + + BlockchainTests::new(suite_path).run_only(&format!("{}", stringify!($dir))); } }; } blockchain_test!(valid_blocks, ValidBlocks); blockchain_test!(invalid_blocks, InvalidBlocks); + +#[test] +fn eest_fixtures() { + reth_tracing::init_test_tracing(); + let suite_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("execution-spec-tests") + .join("blockchain_tests"); + + BlockchainTests::new(suite_path).run(); +} diff --git a/testing/runner/Cargo.toml b/testing/runner/Cargo.toml new file mode 100644 index 00000000000..0b6893fd8b9 --- /dev/null +++ b/testing/runner/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "ef-test-runner" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[dependencies] +clap = { workspace = true, features = ["derive"] } +ef-tests.path = "../ef-tests" + +[lints] +workspace = true diff --git a/testing/runner/src/main.rs b/testing/runner/src/main.rs new file mode 100644 index 00000000000..a36c443850c --- /dev/null +++ b/testing/runner/src/main.rs @@ -0,0 +1,17 @@ +//! Command-line interface for running tests. +use std::path::PathBuf; + +use clap::Parser; +use ef_tests::{cases::blockchain_test::BlockchainTests, Suite}; + +/// Command-line arguments for the test runner. +#[derive(Debug, Parser)] +pub struct TestRunnerCommand { + /// Path to the test suite + suite_path: PathBuf, +} + +fn main() { + let cmd = TestRunnerCommand::parse(); + BlockchainTests::new(cmd.suite_path.join("blockchain_tests")).run(); +} diff --git a/testing/testing-utils/src/generators.rs b/testing/testing-utils/src/generators.rs index b35ae13a819..52aa8eab665 100644 --- a/testing/testing-utils/src/generators.rs +++ b/testing/testing-utils/src/generators.rs @@ -87,7 +87,7 @@ pub fn rng_with_seed(seed: &[u8]) -> StdRng { /// The parent hash of the first header /// in the result will be equal to `head`. /// -/// The headers are assumed to not be correct if validated. +/// The headers are assumed not to be correct if validated. pub fn random_header_range( rng: &mut R, range: Range, @@ -118,7 +118,7 @@ pub fn random_block_with_parent( /// Generate a random [`SealedHeader`]. /// -/// The header is assumed to not be correct if validated. +/// The header is assumed not to be correct if validated. pub fn random_header(rng: &mut R, number: u64, parent: Option) -> SealedHeader { let header = alloy_consensus::Header { number,