mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-04-30 03:01:58 -04:00
Compare commits
31 Commits
mediocrego
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcfa8287f6 | ||
|
|
d25de30050 | ||
|
|
4ffde69d94 | ||
|
|
077e5eecfe | ||
|
|
709485dcb7 | ||
|
|
88505c7fcb | ||
|
|
c14bc59236 | ||
|
|
347c1325cc | ||
|
|
5f85eb7ac8 | ||
|
|
a12454d2e6 | ||
|
|
c194c17a27 | ||
|
|
43a7452b0e | ||
|
|
73ec2c9d56 | ||
|
|
76e886578b | ||
|
|
ad08829288 | ||
|
|
b89288582b | ||
|
|
87d878a979 | ||
|
|
473f85c558 | ||
|
|
a8eee6028f | ||
|
|
671da55884 | ||
|
|
a8fc13dc25 | ||
|
|
4c1f6b6507 | ||
|
|
b850f2a81d | ||
|
|
79144cb430 | ||
|
|
b04346ffe5 | ||
|
|
674623f14e | ||
|
|
97b1b56b2d | ||
|
|
af6d20b5ea | ||
|
|
64cf412aaf | ||
|
|
5b10e03c5c | ||
|
|
91d248e6fa |
4
.github/dependabot.yml
vendored
4
.github/dependabot.yml
vendored
@@ -4,10 +4,14 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
- package-ecosystem: "cargo"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
labels:
|
||||
- "A-dependencies"
|
||||
commit-message:
|
||||
|
||||
19
.github/scripts/bench-reth-run.sh
vendored
19
.github/scripts/bench-reth-run.sh
vendored
@@ -323,13 +323,18 @@ if [ "$BIG_BLOCKS" = "true" ]; then
|
||||
--output "$OUTPUT_DIR" 2>&1 | sed -u "s/^/[bench] /"
|
||||
else
|
||||
# Standard mode: warmup + new-payload-fcu
|
||||
# Warmup
|
||||
$BENCH_NICE "$RETH_BENCH" new-payload-fcu \
|
||||
--rpc-url "$BENCH_RPC_URL" \
|
||||
--engine-rpc-url http://127.0.0.1:8551 \
|
||||
--jwt-secret "$DATADIR/jwt.hex" \
|
||||
--advance "${BENCH_WARMUP_BLOCKS:-50}" \
|
||||
"${EXTRA_BENCH_ARGS[@]}" 2>&1 | sed -u "s/^/[bench] /"
|
||||
WARMUP="${BENCH_WARMUP_BLOCKS:-50}"
|
||||
if [ "$WARMUP" -gt 0 ] 2>/dev/null; then
|
||||
# Warm up the node before measuring the benchmark window.
|
||||
$BENCH_NICE "$RETH_BENCH" new-payload-fcu \
|
||||
--rpc-url "$BENCH_RPC_URL" \
|
||||
--engine-rpc-url http://127.0.0.1:8551 \
|
||||
--jwt-secret "$DATADIR/jwt.hex" \
|
||||
--advance "$WARMUP" \
|
||||
"${EXTRA_BENCH_ARGS[@]}" 2>&1 | sed -u "s/^/[bench] /"
|
||||
else
|
||||
echo "Skipping warmup (0 blocks)..."
|
||||
fi
|
||||
|
||||
# Start tracy-capture after warmup so profile only covers the benchmark
|
||||
if [ "${BENCH_TRACY:-off}" != "off" ]; then
|
||||
|
||||
4
.github/scripts/hive/build_simulators.sh
vendored
4
.github/scripts/hive/build_simulators.sh
vendored
@@ -5,8 +5,8 @@ fixture_variant="${1:-osaka}"
|
||||
|
||||
case "${fixture_variant}" in
|
||||
amsterdam)
|
||||
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/bal@v5.7.0/fixtures_bal.tar.gz"
|
||||
eels_branch="devnets/bal/4"
|
||||
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/snobal-devnet-5@v8037.0.0/fixtures_snobal-devnet-5.tar.gz"
|
||||
eels_branch="devnets/snobal/5"
|
||||
;;
|
||||
osaka)
|
||||
eels_fixtures="https://github.com/ethereum/execution-spec-tests/releases/download/v5.3.0/fixtures_develop.tar.gz"
|
||||
|
||||
78
.github/scripts/hive/expected_failures.yaml
vendored
78
.github/scripts/hive/expected_failures.yaml
vendored
@@ -13,15 +13,15 @@ rpc-compat:
|
||||
# syncing mode, the test expects syncing to be false on start
|
||||
- eth_syncing/check-syncing (reth)
|
||||
|
||||
engine-withdrawals: [ ]
|
||||
engine-withdrawals: []
|
||||
|
||||
engine-api: [ ]
|
||||
engine-api: []
|
||||
|
||||
engine-cancun: [ ]
|
||||
engine-cancun: []
|
||||
|
||||
sync: [ ]
|
||||
sync: []
|
||||
|
||||
engine-auth: [ ]
|
||||
engine-auth: []
|
||||
|
||||
# EIP-7610 related tests (Revert creation in case of non-empty storage):
|
||||
#
|
||||
@@ -99,6 +99,40 @@ eels/consume-engine:
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Amsterdam-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_engine_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_engine_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Prague-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Shanghai-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Paris-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Cancun-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Osaka-blockchain_test_engine_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_engine_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_engine_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_engine_from_state_test-empty-initcode]-reth
|
||||
|
||||
# Blob limit tests:
|
||||
#
|
||||
@@ -193,3 +227,37 @@ eels/consume-rlp:
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Prague-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Shanghai-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_2-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Amsterdam-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_1-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_from_state_test-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_tx[fork_Amsterdam-tx_type_0-blockchain_test_from_state_test-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-correct-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_initcollision.py::test_init_collision_create_opcode[fork_Amsterdam-blockchain_test_from_state_test-opcode_CREATE2-non-empty-balance-revert-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Amsterdam-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Prague-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Shanghai-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Paris-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Cancun-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Shanghai-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_collision_with_create2_revert_in_initcode[fork_Osaka-blockchain_test_from_state_test]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Prague-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Cancun-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Paris-blockchain_test_from_state_test-sstore-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_from_state_test-empty-initcode]-reth
|
||||
- tests/paris/eip7610_create_collision/test_revert_in_create.py::test_create2_collision_storage[fork_Osaka-blockchain_test_from_state_test-initcode-with-deploy]-reth
|
||||
|
||||
54
.github/workflows/bench-scheduled.yml
vendored
54
.github/workflows/bench-scheduled.yml
vendored
@@ -54,9 +54,7 @@ env:
|
||||
|
||||
name: bench-scheduled
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -65,6 +63,9 @@ jobs:
|
||||
resolve-refs:
|
||||
name: resolve-refs
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
outputs:
|
||||
mode: ${{ steps.mode.outputs.mode }}
|
||||
baseline-ref: ${{ steps.refs.outputs.baseline-ref }}
|
||||
@@ -76,21 +77,26 @@ jobs:
|
||||
long-running: ${{ steps.refs.outputs.long-running }}
|
||||
release-tag: ${{ steps.refs.outputs.release-tag }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
sparse-checkout: .github/scripts
|
||||
sparse-checkout-cone-mode: true
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Detect mode
|
||||
id: mode
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
INPUT_MODE: ${{ inputs.mode }}
|
||||
SCHEDULE: ${{ github.event.schedule }}
|
||||
run: |
|
||||
# Maps cron schedules to modes (must match the schedule entries above)
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
MODE="${{ inputs.mode || 'nightly' }}"
|
||||
elif [ "${{ github.event.schedule }}" = "30 5 * * *" ]; then
|
||||
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
|
||||
MODE="${INPUT_MODE:-nightly}"
|
||||
elif [ "$SCHEDULE" = "30 5 * * *" ]; then
|
||||
MODE="nightly"
|
||||
elif [ "${{ github.event.schedule }}" = "0 9 * * *" ]; then
|
||||
elif [ "$SCHEDULE" = "0 9 * * *" ]; then
|
||||
MODE="release"
|
||||
else
|
||||
MODE="hourly"
|
||||
@@ -105,14 +111,15 @@ jobs:
|
||||
DEREK_TOKEN: ${{ secrets.DEREK_TOKEN }}
|
||||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||
INPUT_FORCE: ${{ inputs.force || 'false' }}
|
||||
run: |
|
||||
FORCE="${{ inputs.force || 'false' }}"
|
||||
FORCE="${INPUT_FORCE:-false}"
|
||||
MODE="${{ steps.mode.outputs.mode }}"
|
||||
.github/scripts/bench-scheduled-refs.sh "$FORCE" "$MODE"
|
||||
|
||||
- name: Alert on long-running hourly
|
||||
if: steps.mode.outputs.mode == 'hourly' && steps.refs.outputs.long-running == 'true' && !(github.event_name == 'workflow_dispatch' && inputs.slack == 'never')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -154,7 +161,7 @@ jobs:
|
||||
|
||||
- name: Alert on stale nightly
|
||||
if: steps.mode.outputs.mode == 'nightly' && steps.refs.outputs.is-stale == 'true' && !(github.event_name == 'workflow_dispatch' && inputs.slack == 'never')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -242,6 +249,9 @@ jobs:
|
||||
needs.resolve-refs.outputs.is-stale != 'true'
|
||||
name: bench-scheduled
|
||||
runs-on: [self-hosted, Linux, X64, available]
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
|
||||
@@ -270,15 +280,16 @@ jobs:
|
||||
- name: Clean up previous bench-work
|
||||
run: sudo rm -rf "$BENCH_WORK_DIR" 2>/dev/null || true
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
ref: ${{ needs.resolve-refs.outputs.feature-ref }}
|
||||
|
||||
- name: Resolve job URL
|
||||
id: job-url
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { data: jobs } = await github.rest.actions.listJobsForWorkflowRun({
|
||||
@@ -291,8 +302,9 @@ jobs:
|
||||
core.exportVariable('BENCH_JOB_URL', jobUrl);
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
continue-on-error: true
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
@@ -628,7 +640,7 @@ jobs:
|
||||
|
||||
- name: Upload results
|
||||
if: "!cancelled()"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: bench-scheduled-results
|
||||
path: ${{ env.BENCH_WORK_DIR }}
|
||||
@@ -636,10 +648,12 @@ jobs:
|
||||
- name: Push charts
|
||||
id: push-charts
|
||||
if: success() && env.BENCH_MODE != 'hourly'
|
||||
env:
|
||||
DEREK_TOKEN: ${{ secrets.DEREK_TOKEN }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
RUN_ID=${{ github.run_id }}
|
||||
CHART_DIR="${BENCH_MODE}/${RUN_ID}"
|
||||
CHARTS_REPO="https://x-access-token:${{ secrets.DEREK_TOKEN }}@github.com/decofe/reth-bench-charts.git"
|
||||
CHARTS_REPO="https://x-access-token:${DEREK_TOKEN}@github.com/decofe/reth-bench-charts.git"
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
if git clone --depth 1 "${CHARTS_REPO}" "${TMP_DIR}" 2>/dev/null; then
|
||||
@@ -660,7 +674,7 @@ jobs:
|
||||
|
||||
- name: Write job summary
|
||||
if: success()
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
@@ -739,7 +753,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (success)
|
||||
if: success() && (env.BENCH_SLACK == 'always' || env.BENCH_SLACK == 'on-win')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -894,7 +908,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (failure)
|
||||
if: failure() && env.BENCH_SLACK != 'never' && env.BENCH_SLACK != 'on-win'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
|
||||
130
.github/workflows/bench.yml
vendored
130
.github/workflows/bench.yml
vendored
@@ -82,7 +82,7 @@ on:
|
||||
- on-error
|
||||
- never
|
||||
abba:
|
||||
description: "Run ABBA (BFFB) interleaved order; false = single AB pass"
|
||||
description: "Run ABBA (FBBF) interleaved order; false = single FB pass"
|
||||
required: false
|
||||
default: "true"
|
||||
type: boolean
|
||||
@@ -99,9 +99,7 @@ env:
|
||||
|
||||
name: bench
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
reth-bench-ack:
|
||||
@@ -110,6 +108,9 @@ jobs:
|
||||
github.event_name == 'workflow_dispatch'
|
||||
name: reth-bench-ack
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
outputs:
|
||||
pr: ${{ steps.args.outputs.pr }}
|
||||
actor: ${{ steps.args.outputs.actor }}
|
||||
@@ -133,7 +134,7 @@ jobs:
|
||||
steps:
|
||||
- name: Check org membership
|
||||
if: github.event_name == 'issue_comment'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
@@ -152,7 +153,7 @@ jobs:
|
||||
|
||||
- name: Parse arguments
|
||||
id: args
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -359,7 +360,7 @@ jobs:
|
||||
|
||||
- name: Acknowledge request
|
||||
id: ack
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -445,7 +446,7 @@ jobs:
|
||||
|
||||
- name: Poll queue position
|
||||
if: steps.ack.outputs.comment-id && steps.ack.outputs.queue-position != '0'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -529,6 +530,9 @@ jobs:
|
||||
needs: reth-bench-ack
|
||||
name: reth-bench
|
||||
runs-on: [self-hosted, Linux, X64, available]
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
BENCH_RPC_URL: https://ethereum.reth.rs/rpc
|
||||
@@ -560,7 +564,7 @@ jobs:
|
||||
|
||||
- name: Resolve checkout ref
|
||||
id: checkout-ref
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
if (!process.env.BENCH_PR) {
|
||||
@@ -578,15 +582,16 @@ jobs:
|
||||
core.info(`PR #${process.env.BENCH_PR} (${pr.state}), using head SHA ${pr.head.sha}`);
|
||||
core.setOutput('ref', pr.head.sha);
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.checkout-ref.outputs.ref }}
|
||||
|
||||
- name: Resolve job URL and update status
|
||||
if: env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -634,8 +639,9 @@ jobs:
|
||||
});
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
continue-on-error: true
|
||||
- uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
|
||||
|
||||
- name: Install dependencies
|
||||
env:
|
||||
@@ -696,7 +702,7 @@ jobs:
|
||||
# Build binaries
|
||||
- name: Resolve PR head branch
|
||||
id: pr-info
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
if (process.env.BENCH_PR) {
|
||||
@@ -714,7 +720,7 @@ jobs:
|
||||
|
||||
- name: Resolve refs
|
||||
id: refs
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const { execSync } = require('child_process');
|
||||
@@ -937,28 +943,15 @@ jobs:
|
||||
|
||||
- name: Update status (running benchmarks)
|
||||
if: success() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
const s = require('./.github/scripts/bench-update-status.js');
|
||||
await s({github, context, status: 'Running benchmarks...'});
|
||||
|
||||
# Interleaved run order (B-F-F-B) to reduce systematic bias from
|
||||
# Interleaved run order (F-B-B-F) to reduce systematic bias from
|
||||
# thermal drift and cache warming.
|
||||
- name: "Run benchmark: baseline (1/2)"
|
||||
id: run-baseline-1
|
||||
env:
|
||||
BASELINE_REF: ${{ steps.refs.outputs.baseline-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=baseline-1,run_type=baseline,git_ref=${{ steps.refs.outputs.baseline-ref }}"
|
||||
run: |
|
||||
LAST_RUN_START=$(date +%s)
|
||||
echo "BENCH_LAST_RUN_START=${LAST_RUN_START}" >> "$GITHUB_ENV"
|
||||
cat > "$BENCH_LABELS_FILE" <<LABELS
|
||||
{"benchmark_run":"baseline-1","run_type":"baseline","git_ref":"${BASELINE_REF}","bench_sha":"${BASELINE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh baseline "../reth-baseline/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/baseline-1"
|
||||
|
||||
- name: "Run benchmark: feature (1/2)"
|
||||
id: run-feature-1
|
||||
env:
|
||||
@@ -972,19 +965,18 @@ jobs:
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh feature "../reth-feature/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/feature-1"
|
||||
|
||||
- name: "Run benchmark: feature (2/2)"
|
||||
if: env.BENCH_ABBA != 'false'
|
||||
id: run-feature-2
|
||||
- name: "Run benchmark: baseline (1/2)"
|
||||
id: run-baseline-1
|
||||
env:
|
||||
FEATURE_REF: ${{ steps.refs.outputs.feature-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=feature-2,run_type=feature,git_ref=${{ steps.refs.outputs.feature-ref }}"
|
||||
BASELINE_REF: ${{ steps.refs.outputs.baseline-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=baseline-1,run_type=baseline,git_ref=${{ steps.refs.outputs.baseline-ref }}"
|
||||
run: |
|
||||
LAST_RUN_START=$(date +%s)
|
||||
echo "BENCH_LAST_RUN_START=${LAST_RUN_START}" >> "$GITHUB_ENV"
|
||||
cat > "$BENCH_LABELS_FILE" <<LABELS
|
||||
{"benchmark_run":"feature-2","run_type":"feature","git_ref":"${FEATURE_REF}","bench_sha":"${FEATURE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
{"benchmark_run":"baseline-1","run_type":"baseline","git_ref":"${BASELINE_REF}","bench_sha":"${BASELINE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh feature "../reth-feature/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/feature-2"
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh baseline "../reth-baseline/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/baseline-1"
|
||||
|
||||
- name: "Run benchmark: baseline (2/2)"
|
||||
if: env.BENCH_ABBA != 'false'
|
||||
@@ -1000,6 +992,20 @@ jobs:
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh baseline "../reth-baseline/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/baseline-2"
|
||||
|
||||
- name: "Run benchmark: feature (2/2)"
|
||||
if: env.BENCH_ABBA != 'false'
|
||||
id: run-feature-2
|
||||
env:
|
||||
FEATURE_REF: ${{ steps.refs.outputs.feature-ref }}
|
||||
OTEL_RESOURCE_ATTRIBUTES: "benchmark_id=${{ env.BENCH_ID }},benchmark_run=feature-2,run_type=feature,git_ref=${{ steps.refs.outputs.feature-ref }}"
|
||||
run: |
|
||||
LAST_RUN_START=$(date +%s)
|
||||
echo "BENCH_LAST_RUN_START=${LAST_RUN_START}" >> "$GITHUB_ENV"
|
||||
cat > "$BENCH_LABELS_FILE" <<LABELS
|
||||
{"benchmark_run":"feature-2","run_type":"feature","git_ref":"${FEATURE_REF}","bench_sha":"${FEATURE_REF}","benchmark_id":"${BENCH_ID}","run_start_epoch":"${LAST_RUN_START}","reference_epoch":"${BENCH_REFERENCE_EPOCH}"}
|
||||
LABELS
|
||||
taskset -c 0 .github/scripts/bench-reth-run.sh feature "../reth-feature/target/profiling/${BENCH_NODE_BIN}" "$BENCH_WORK_DIR/feature-2"
|
||||
|
||||
- name: Stop metrics proxy & generate Grafana URL
|
||||
id: metrics
|
||||
if: "!cancelled()"
|
||||
@@ -1166,7 +1172,7 @@ jobs:
|
||||
|
||||
- name: Upload results
|
||||
if: "!cancelled()"
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: bench-reth-results
|
||||
path: ${{ env.BENCH_WORK_DIR }}
|
||||
@@ -1174,11 +1180,13 @@ jobs:
|
||||
- name: Push charts
|
||||
id: push-charts
|
||||
if: success()
|
||||
env:
|
||||
DEREK_TOKEN: ${{ secrets.DEREK_TOKEN }}
|
||||
RUN_ID: ${{ github.run_id }}
|
||||
run: |
|
||||
PR_NUMBER="${BENCH_PR:-0}"
|
||||
RUN_ID=${{ github.run_id }}
|
||||
CHART_DIR="pr/${PR_NUMBER}/${RUN_ID}"
|
||||
CHARTS_REPO="https://x-access-token:${{ secrets.DEREK_TOKEN }}@github.com/decofe/reth-bench-charts.git"
|
||||
CHARTS_REPO="https://x-access-token:${DEREK_TOKEN}@github.com/decofe/reth-bench-charts.git"
|
||||
|
||||
TMP_DIR=$(mktemp -d)
|
||||
if git clone --depth 1 "${CHARTS_REPO}" "${TMP_DIR}" 2>/dev/null; then
|
||||
@@ -1191,15 +1199,35 @@ jobs:
|
||||
mkdir -p "${TMP_DIR}/${CHART_DIR}"
|
||||
cp "$BENCH_WORK_DIR"/charts/*.png "${TMP_DIR}/${CHART_DIR}/"
|
||||
git -C "${TMP_DIR}" add "${CHART_DIR}"
|
||||
if git -C "${TMP_DIR}" diff --cached --quiet; then
|
||||
echo "Charts for ${CHART_DIR} are already present, skipping push"
|
||||
echo "sha=$(git -C "${TMP_DIR}" rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
rm -rf "${TMP_DIR}"
|
||||
exit 0
|
||||
fi
|
||||
git -C "${TMP_DIR}" -c user.name="github-actions" -c user.email="github-actions@github.com" \
|
||||
commit -m "bench charts for PR #${PR_NUMBER} run ${RUN_ID}"
|
||||
git -C "${TMP_DIR}" push origin HEAD:main
|
||||
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if git -C "${TMP_DIR}" push origin HEAD:main; then
|
||||
break
|
||||
fi
|
||||
if [ "$attempt" -eq 5 ]; then
|
||||
echo "::error::Failed to push charts after ${attempt} attempts"
|
||||
rm -rf "${TMP_DIR}"
|
||||
exit 1
|
||||
fi
|
||||
sleep "$attempt"
|
||||
git -C "${TMP_DIR}" fetch origin main
|
||||
git -C "${TMP_DIR}" rebase origin/main
|
||||
done
|
||||
|
||||
echo "sha=$(git -C "${TMP_DIR}" rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
rm -rf "${TMP_DIR}"
|
||||
|
||||
- name: Compare & comment
|
||||
if: success() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -1235,7 +1263,7 @@ jobs:
|
||||
// Samply profile links (URLs point directly to Firefox Profiler)
|
||||
if (process.env.BENCH_SAMPLY === 'true') {
|
||||
const abba = (process.env.BENCH_ABBA || 'true') !== 'false';
|
||||
const runs = abba ? ['baseline-1', 'feature-1', 'feature-2', 'baseline-2'] : ['baseline-1', 'feature-1'];
|
||||
const runs = abba ? ['feature-1', 'baseline-1', 'baseline-2', 'feature-2'] : ['feature-1', 'baseline-1'];
|
||||
const links = [];
|
||||
for (const run of runs) {
|
||||
try {
|
||||
@@ -1279,7 +1307,7 @@ jobs:
|
||||
|
||||
- name: Write job summary
|
||||
if: success()
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const jobSummary = require('./.github/scripts/bench-job-summary.js');
|
||||
@@ -1293,7 +1321,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (success)
|
||||
if: success() && (env.BENCH_SLACK == 'always' || env.BENCH_SLACK == 'on-win')
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -1304,7 +1332,7 @@ jobs:
|
||||
|
||||
- name: Update status (failed)
|
||||
if: failure() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
@@ -1314,10 +1342,10 @@ jobs:
|
||||
...(bigBlocks ? [['validating local big-block data', '${{ steps.big-blocks-check.outcome }}']] : []),
|
||||
['validating local snapshot', '${{ steps.snapshot-check.outcome }}'],
|
||||
['building binaries', '${{ steps.build.outcome }}'],
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'],
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
...(abba ? [['running baseline benchmark (2/2)', '${{ steps.run-baseline-2.outcome }}']] : []),
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
];
|
||||
const failed = steps_status.find(([, o]) => o === 'failure');
|
||||
const failedStep = failed ? failed[0] : 'unknown step';
|
||||
@@ -1340,7 +1368,7 @@ jobs:
|
||||
|
||||
- name: Send Slack notification (failure)
|
||||
if: failure() && env.BENCH_SLACK != 'never' && env.BENCH_SLACK != 'on-win'
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
env:
|
||||
SLACK_BENCH_BOT_TOKEN: ${{ secrets.SLACK_BENCH_BOT_TOKEN }}
|
||||
SLACK_BENCH_CHANNEL: ${{ secrets.SLACK_BENCH_CHANNEL }}
|
||||
@@ -1352,10 +1380,10 @@ jobs:
|
||||
...(bigBlocks ? [['validating local big-block data', '${{ steps.big-blocks-check.outcome }}']] : []),
|
||||
['validating local snapshot', '${{ steps.snapshot-check.outcome }}'],
|
||||
['building binaries', '${{ steps.build.outcome }}'],
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
['running feature benchmark (1/2)', '${{ steps.run-feature-1.outcome }}'],
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
['running baseline benchmark (1/2)', '${{ steps.run-baseline-1.outcome }}'],
|
||||
...(abba ? [['running baseline benchmark (2/2)', '${{ steps.run-baseline-2.outcome }}']] : []),
|
||||
...(abba ? [['running feature benchmark (2/2)', '${{ steps.run-feature-2.outcome }}']] : []),
|
||||
];
|
||||
const failed = steps_status.find(([, o]) => o === 'failure');
|
||||
const failedStep = failed ? failed[0] : 'unknown step';
|
||||
@@ -1364,7 +1392,7 @@ jobs:
|
||||
|
||||
- name: Update status (cancelled)
|
||||
if: cancelled() && env.BENCH_COMMENT_ID
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
github-token: ${{ secrets.DEREK_PAT }}
|
||||
script: |
|
||||
|
||||
19
.github/workflows/book.yml
vendored
19
.github/workflows/book.yml
vendored
@@ -10,19 +10,22 @@ on:
|
||||
types: [opened, reopened, synchronize, closed]
|
||||
merge_group:
|
||||
|
||||
env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
|
||||
with:
|
||||
bun-version: v1.2.23
|
||||
|
||||
@@ -36,8 +39,6 @@ jobs:
|
||||
- name: Install Rust nightly
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: Build docs
|
||||
run: cd docs/vocs && bash scripts/build-cargo-docs.sh
|
||||
|
||||
@@ -47,10 +48,10 @@ jobs:
|
||||
echo "Vocs Build Complete"
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v6
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
path: "./docs/vocs/docs/dist"
|
||||
|
||||
@@ -74,4 +75,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
||||
|
||||
26
.github/workflows/check-alloy.yml
vendored
26
.github/workflows/check-alloy.yml
vendored
@@ -22,31 +22,41 @@ on:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check compilation with patched alloy
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-16' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
|
||||
- name: Apply alloy patches
|
||||
env:
|
||||
ALLOY_BRANCH: ${{ inputs.alloy_branch }}
|
||||
ALLOY_EVM_BRANCH: ${{ inputs.alloy_evm_branch }}
|
||||
OP_ALLOY_BRANCH: ${{ inputs.op_alloy_branch }}
|
||||
run: |
|
||||
ARGS=""
|
||||
if [ -n "${{ inputs.alloy_branch }}" ]; then
|
||||
ARGS="$ARGS --alloy ${{ inputs.alloy_branch }}"
|
||||
if [ -n "$ALLOY_BRANCH" ]; then
|
||||
ARGS="$ARGS --alloy $ALLOY_BRANCH"
|
||||
fi
|
||||
if [ -n "${{ inputs.alloy_evm_branch }}" ]; then
|
||||
ARGS="$ARGS --evm ${{ inputs.alloy_evm_branch }}"
|
||||
if [ -n "$ALLOY_EVM_BRANCH" ]; then
|
||||
ARGS="$ARGS --evm $ALLOY_EVM_BRANCH"
|
||||
fi
|
||||
if [ -n "${{ inputs.op_alloy_branch }}" ]; then
|
||||
ARGS="$ARGS --op ${{ inputs.op_alloy_branch }}"
|
||||
if [ -n "$OP_ALLOY_BRANCH" ]; then
|
||||
ARGS="$ARGS --op $OP_ALLOY_BRANCH"
|
||||
fi
|
||||
|
||||
if [ -z "$ARGS" ]; then
|
||||
|
||||
16
.github/workflows/compact.yml
vendored
16
.github/workflows/compact.yml
vendored
@@ -16,32 +16,38 @@ env:
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
name: compact-codec
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
compact-codec:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
bin:
|
||||
- cargo run --bin reth --features "dev"
|
||||
steps:
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Checkout base
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ github.base_ref || 'main' }}
|
||||
persist-credentials: false
|
||||
# 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@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
clean: false
|
||||
persist-credentials: false
|
||||
# On incoming merge try to read and decode previously generated vectors with `Compact`
|
||||
- name: Read vectors
|
||||
run: ${{ matrix.bin }} -- test-vectors compact --read
|
||||
|
||||
9
.github/workflows/dependencies.yml
vendored
9
.github/workflows/dependencies.yml
vendored
@@ -9,13 +9,14 @@ on:
|
||||
workflow_dispatch:
|
||||
# Needed so we can run it manually
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
update:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
uses: tempoxyz/ci/.github/workflows/cargo-update-pr.yml@main
|
||||
secrets:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
14
.github/workflows/docker-tag-latest.yml
vendored
14
.github/workflows/docker-tag-latest.yml
vendored
@@ -17,6 +17,8 @@ on:
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ github.actor }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
tag-reth-latest:
|
||||
name: Tag reth as latest
|
||||
@@ -27,16 +29,22 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Log in to Docker
|
||||
env:
|
||||
DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io --username ${DOCKER_USERNAME} --password-stdin
|
||||
echo "$DOCKER_PASSWORD" | docker login ghcr.io --username "${DOCKER_USERNAME}" --password-stdin
|
||||
|
||||
- name: Pull reth release image
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }}
|
||||
docker pull ghcr.io/${{ github.repository_owner }}/reth:${VERSION}
|
||||
|
||||
- name: Tag reth as latest
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
docker tag ghcr.io/${{ github.repository_owner }}/reth:${{ inputs.version }} ghcr.io/${{ github.repository_owner }}/reth:latest
|
||||
docker tag ghcr.io/${{ github.repository_owner }}/reth:${VERSION} ghcr.io/${{ github.repository_owner }}/reth:latest
|
||||
|
||||
- name: Push reth latest tag
|
||||
run: |
|
||||
|
||||
22
.github/workflows/docker-test.yml
vendored
22
.github/workflows/docker-test.yml
vendored
@@ -13,6 +13,8 @@ on:
|
||||
default: "artifacts"
|
||||
description: "Name for the uploaded artifact"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
timeout-minutes: 45
|
||||
@@ -21,7 +23,9 @@ jobs:
|
||||
id-token: write
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- run: mkdir -p artifacts
|
||||
|
||||
- name: Get git info
|
||||
@@ -32,8 +36,12 @@ jobs:
|
||||
|
||||
- name: Detect fork
|
||||
id: fork
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
REPO: ${{ github.repository }}
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" = "pull_request" ] && [ "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]; then
|
||||
if [ "$EVENT_NAME" = "pull_request" ] && [ "$HEAD_REPO" != "$REPO" ]; then
|
||||
echo "is_fork=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "is_fork=false" >> "$GITHUB_OUTPUT"
|
||||
@@ -42,11 +50,11 @@ jobs:
|
||||
# Depot build (upstream only)
|
||||
- name: Set up Depot CLI
|
||||
if: steps.fork.outputs.is_fork == 'false'
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: Build reth image (Depot)
|
||||
if: steps.fork.outputs.is_fork == 'false'
|
||||
uses: depot/bake-action@v1
|
||||
uses: depot/bake-action@1d58c2668346981089b088b7ef36755b206b20e9 # v1.13.0
|
||||
env:
|
||||
DEPOT_TOKEN: ${{ secrets.DEPOT_TOKEN }}
|
||||
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
|
||||
@@ -60,11 +68,11 @@ jobs:
|
||||
# Docker build (forks)
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.fork.outputs.is_fork == 'true'
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build reth image (Docker)
|
||||
if: steps.fork.outputs.is_fork == 'true'
|
||||
uses: docker/bake-action@v6
|
||||
uses: docker/bake-action@82490499d2e5613fcead7e128237ef0b0ea210f7 # v7.0.0
|
||||
env:
|
||||
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
|
||||
VERGEN_GIT_DESCRIBE: ${{ steps.git.outputs.describe }}
|
||||
@@ -76,7 +84,7 @@ jobs:
|
||||
*.dockerfile=Dockerfile
|
||||
|
||||
- name: Upload reth image
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: ./artifacts
|
||||
|
||||
23
.github/workflows/docker.yml
vendored
23
.github/workflows/docker.yml
vendored
@@ -29,6 +29,8 @@ on:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
@@ -39,13 +41,15 @@ jobs:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@v1
|
||||
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -60,10 +64,13 @@ jobs:
|
||||
|
||||
- name: Determine build parameters
|
||||
id: params
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BUILD_TYPE: ${{ inputs.build_type }}
|
||||
run: |
|
||||
REGISTRY="ghcr.io/${{ github.repository_owner }}"
|
||||
|
||||
if [[ "${{ github.event_name }}" == "push" ]]; then
|
||||
if [[ "${EVENT_NAME}" == "push" ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "targets=ethereum" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -81,7 +88,7 @@ jobs:
|
||||
echo "ethereum_set=ethereum.tags=${REGISTRY}/reth:${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
elif [[ "${{ github.event_name }}" == "schedule" ]] || [[ "${{ inputs.build_type }}" == "nightly" ]]; then
|
||||
elif [[ "${EVENT_NAME}" == "schedule" ]] || [[ "${BUILD_TYPE}" == "nightly" ]]; then
|
||||
echo "targets=nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "ethereum_tags=${REGISTRY}/reth:nightly" >> "$GITHUB_OUTPUT"
|
||||
echo "ethereum_set=ethereum.tags=${REGISTRY}/reth:nightly" >> "$GITHUB_OUTPUT"
|
||||
@@ -94,7 +101,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Build and push images
|
||||
uses: depot/bake-action@v1
|
||||
uses: depot/bake-action@1d58c2668346981089b088b7ef36755b206b20e9 # v1.13.0
|
||||
env:
|
||||
VERGEN_GIT_SHA: ${{ steps.git.outputs.sha }}
|
||||
VERGEN_GIT_DESCRIBE: ${{ steps.git.outputs.describe }}
|
||||
@@ -105,6 +112,8 @@ jobs:
|
||||
files: docker-bake.hcl
|
||||
targets: ${{ steps.params.outputs.targets }}
|
||||
push: ${{ !(github.event_name == 'workflow_dispatch' && inputs.dry_run) }}
|
||||
save: false
|
||||
load: false
|
||||
set: |
|
||||
${{ steps.params.outputs.ethereum_set }}
|
||||
|
||||
@@ -124,7 +133,7 @@ jobs:
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||
env:
|
||||
SLACK_COLOR: danger
|
||||
SLACK_ICON_EMOJI: ":rotating_light:"
|
||||
|
||||
30
.github/workflows/e2e.yml
vendored
30
.github/workflows/e2e.yml
vendored
@@ -17,19 +17,27 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: e2e-testsuite
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run e2e tests
|
||||
@@ -48,15 +56,21 @@ jobs:
|
||||
rocksdb:
|
||||
name: e2e-rocksdb
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run RocksDB e2e tests
|
||||
|
||||
24
.github/workflows/fetch-grafana-dashboard.yml
vendored
24
.github/workflows/fetch-grafana-dashboard.yml
vendored
@@ -12,6 +12,8 @@ on:
|
||||
required: true
|
||||
default: "etc/grafana/dashboards/overview.json"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
fetch:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -19,9 +21,11 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -29,14 +33,18 @@ jobs:
|
||||
env:
|
||||
FETCH_GRAFANA_DASHBOARD_URL: ${{ secrets.FETCH_GRAFANA_DASHBOARD_URL }}
|
||||
FETCH_GRAFANA_DASHBOARD_TOKEN: ${{ secrets.FETCH_GRAFANA_DASHBOARD_TOKEN }}
|
||||
DASHBOARD_UID: ${{ inputs.dashboard_uid }}
|
||||
TARGET_PATH: ${{ inputs.target_path }}
|
||||
run: |
|
||||
python3 .github/scripts/fetch-grafana-dashboard.py "${{ inputs.dashboard_uid }}" \
|
||||
> "${{ inputs.target_path }}"
|
||||
python3 .github/scripts/fetch-grafana-dashboard.py "${DASHBOARD_UID}" \
|
||||
> "${TARGET_PATH}"
|
||||
|
||||
- name: Check for changes
|
||||
id: diff
|
||||
env:
|
||||
TARGET_PATH: ${{ inputs.target_path }}
|
||||
run: |
|
||||
if git diff --quiet "${{ inputs.target_path }}"; then
|
||||
if git diff --quiet "${TARGET_PATH}"; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No changes detected."
|
||||
else
|
||||
@@ -47,8 +55,10 @@ jobs:
|
||||
if: steps.diff.outputs.changed == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DASHBOARD_UID: ${{ inputs.dashboard_uid }}
|
||||
TARGET_PATH: ${{ inputs.target_path }}
|
||||
run: |
|
||||
TARGET="${{ inputs.target_path }}"
|
||||
TARGET="${TARGET_PATH}"
|
||||
FILENAME="$(basename "$TARGET")"
|
||||
BRANCH="chore/sync-grafana-${FILENAME%.*}-$(date +%Y%m%d-%H%M%S)"
|
||||
git config user.name "github-actions[bot]"
|
||||
@@ -59,4 +69,4 @@ jobs:
|
||||
git push origin "$BRANCH"
|
||||
gh pr create \
|
||||
--title "chore: update Grafana dashboard ${FILENAME}" \
|
||||
--body "Automated export from Grafana (dashboard UID: \`${{ inputs.dashboard_uid }}\`, target: \`${TARGET}\`)."
|
||||
--body "Automated export from Grafana (dashboard UID: \`${DASHBOARD_UID}\`, target: \`${TARGET}\`)."
|
||||
|
||||
8
.github/workflows/grafana.yml
vendored
8
.github/workflows/grafana.yml
vendored
@@ -6,11 +6,17 @@ on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-dashboard:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Validate dashboard format
|
||||
run: |
|
||||
python3 -c "
|
||||
|
||||
64
.github/workflows/hive.yml
vendored
64
.github/workflows/hive.yml
vendored
@@ -14,8 +14,13 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-reth:
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: ./.github/workflows/docker-test.yml
|
||||
with:
|
||||
hive_target: hive
|
||||
@@ -26,6 +31,8 @@ jobs:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-16' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -34,25 +41,28 @@ jobs:
|
||||
- osaka
|
||||
name: Prepare Hive - ${{ matrix.variant == 'amsterdam' && 'Amsterdam' || 'Osaka' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
path: hivetests
|
||||
persist-credentials: false
|
||||
|
||||
- name: Get hive commit hash
|
||||
id: hive-commit
|
||||
run: echo "hash=$(cd hivetests && git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
with:
|
||||
go-version: "^1.13.1"
|
||||
- run: go version
|
||||
|
||||
- name: Restore hive assets cache
|
||||
id: cache-hive
|
||||
uses: actions/cache@v5
|
||||
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: ./hive_assets
|
||||
key: hive-assets-${{ matrix.variant }}-${{ steps.hive-commit.outputs.hash }}-${{ hashFiles('.github/scripts/hive/build_simulators.sh') }}
|
||||
@@ -75,7 +85,7 @@ jobs:
|
||||
chmod +x hive
|
||||
|
||||
- name: Upload hive assets
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: hive_assets_${{ matrix.variant }}
|
||||
path: ./hive_assets
|
||||
@@ -193,20 +203,22 @@ jobs:
|
||||
# Use larger runners for eels tests to avoid OOM runner crashes
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && (contains(matrix.scenario.sim, 'eels') && 'depot-ubuntu-latest-8' || 'depot-ubuntu-latest-4') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download hive assets
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: hive_assets_amsterdam
|
||||
path: /tmp
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: reth
|
||||
path: /tmp
|
||||
@@ -220,16 +232,21 @@ jobs:
|
||||
chmod +x /usr/local/bin/hive
|
||||
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run simulator
|
||||
env:
|
||||
SCENARIO_SIM: ${{ matrix.scenario.sim }}
|
||||
SCENARIO_LIMIT: ${{ matrix.scenario.limit }}
|
||||
SCENARIO_TESTS: ${{ join(matrix.scenario.include, '|') }}
|
||||
run: |
|
||||
LIMIT="${{ matrix.scenario.limit }}"
|
||||
TESTS="${{ join(matrix.scenario.include, '|') }}"
|
||||
LIMIT="$SCENARIO_LIMIT"
|
||||
TESTS="$SCENARIO_TESTS"
|
||||
if [ -n "$LIMIT" ] && [ -n "$TESTS" ]; then
|
||||
FILTER="$LIMIT/$TESTS"
|
||||
elif [ -n "$LIMIT" ]; then
|
||||
@@ -240,7 +257,7 @@ jobs:
|
||||
FILTER="/"
|
||||
fi
|
||||
echo "filter: $FILTER"
|
||||
.github/scripts/hive/run_simulator.sh "${{ matrix.scenario.sim }}" "$FILTER" "amsterdam"
|
||||
.github/scripts/hive/run_simulator.sh "$SCENARIO_SIM" "$FILTER" "amsterdam"
|
||||
|
||||
- name: Parse hive output
|
||||
run: |
|
||||
@@ -366,20 +383,22 @@ jobs:
|
||||
# Use larger runners for eels tests to avoid OOM runner crashes
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && (contains(matrix.scenario.sim, 'eels') && 'depot-ubuntu-latest-8' || 'depot-ubuntu-latest-4') || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download hive assets
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: hive_assets_osaka
|
||||
path: /tmp
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: reth
|
||||
path: /tmp
|
||||
@@ -393,16 +412,21 @@ jobs:
|
||||
chmod +x /usr/local/bin/hive
|
||||
|
||||
- name: Checkout hive tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/hive
|
||||
ref: master
|
||||
path: hivetests
|
||||
persist-credentials: false
|
||||
|
||||
- name: Run simulator
|
||||
env:
|
||||
SCENARIO_SIM: ${{ matrix.scenario.sim }}
|
||||
SCENARIO_LIMIT: ${{ matrix.scenario.limit }}
|
||||
SCENARIO_TESTS: ${{ join(matrix.scenario.include, '|') }}
|
||||
run: |
|
||||
LIMIT="${{ matrix.scenario.limit }}"
|
||||
TESTS="${{ join(matrix.scenario.include, '|') }}"
|
||||
LIMIT="$SCENARIO_LIMIT"
|
||||
TESTS="$SCENARIO_TESTS"
|
||||
if [ -n "$LIMIT" ] && [ -n "$TESTS" ]; then
|
||||
FILTER="$LIMIT/$TESTS"
|
||||
elif [ -n "$LIMIT" ]; then
|
||||
@@ -413,7 +437,7 @@ jobs:
|
||||
FILTER="/"
|
||||
fi
|
||||
echo "filter: $FILTER"
|
||||
.github/scripts/hive/run_simulator.sh "${{ matrix.scenario.sim }}" "$FILTER" "osaka"
|
||||
.github/scripts/hive/run_simulator.sh "$SCENARIO_SIM" "$FILTER" "osaka"
|
||||
|
||||
- name: Parse hive output
|
||||
run: |
|
||||
@@ -436,7 +460,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}"
|
||||
|
||||
36
.github/workflows/integration.yml
vendored
36
.github/workflows/integration.yml
vendored
@@ -20,11 +20,15 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.network }}
|
||||
if: github.event_name != 'schedule'
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
@@ -32,14 +36,18 @@ jobs:
|
||||
network: ["ethereum"]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install Geth
|
||||
run: .github/scripts/install_geth.sh
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run tests
|
||||
@@ -58,7 +66,7 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
@@ -66,13 +74,19 @@ jobs:
|
||||
name: era1 file integration tests once a day
|
||||
if: github.event_name == 'schedule' && github.repository == 'paradigmxyz/reth'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: run era1 files integration tests
|
||||
|
||||
16
.github/workflows/kurtosis.yml
vendored
16
.github/workflows/kurtosis.yml
vendored
@@ -18,9 +18,14 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build-reth:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: ./.github/workflows/docker-test.yml
|
||||
with:
|
||||
hive_target: kurtosis
|
||||
@@ -32,15 +37,18 @@ jobs:
|
||||
fail-fast: false
|
||||
name: run kurtosis
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
needs:
|
||||
- build-reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download reth image
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: artifacts
|
||||
path: /tmp
|
||||
@@ -52,7 +60,7 @@ jobs:
|
||||
docker image ls -a
|
||||
|
||||
- name: Run kurtosis
|
||||
uses: ethpandaops/kurtosis-assertoor-github-action@v1
|
||||
uses: ethpandaops/kurtosis-assertoor-github-action@f64942cbc780df731a731ea9f45765b161d2c8df # v1.0.1
|
||||
with:
|
||||
ethereum_package_args: ".github/assets/kurtosis_network_params.yaml"
|
||||
|
||||
@@ -62,7 +70,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Slack Webhook Action
|
||||
uses: rtCamp/action-slack-notify@v2
|
||||
uses: rtCamp/action-slack-notify@e31e87e03dd19038e411e38ae27cbad084a90661 # v2.3.3
|
||||
env:
|
||||
SLACK_COLOR: ${{ job.status }}
|
||||
SLACK_MESSAGE: "Failed run: https://github.com/paradigmxyz/reth/actions/runs/${{ github.run_id }}"
|
||||
|
||||
8
.github/workflows/label-pr.yml
vendored
8
.github/workflows/label-pr.yml
vendored
@@ -4,19 +4,23 @@ on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
label_prs:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Label PRs
|
||||
uses: actions/github-script@v9
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const label_pr = require('./.github/scripts/label_pr.js')
|
||||
|
||||
8
.github/workflows/lint-actions.yml
vendored
8
.github/workflows/lint-actions.yml
vendored
@@ -8,11 +8,17 @@ on:
|
||||
paths:
|
||||
- '.github/**'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
actionlint:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download actionlint
|
||||
id: get_actionlint
|
||||
run: bash <(curl https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
|
||||
|
||||
190
.github/workflows/lint.yml
vendored
190
.github/workflows/lint.yml
vendored
@@ -10,10 +10,14 @@ env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
clippy-binaries:
|
||||
name: clippy binaries / ${{ matrix.type }}
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -22,17 +26,19 @@ jobs:
|
||||
args: --workspace --lib --examples --tests --benches --locked
|
||||
features: "ethereum asm-keccak jemalloc jemalloc-prof min-error-logs min-warn-logs min-info-logs min-debug-logs min-trace-logs"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@clippy
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- if: "${{ matrix.type == 'book' }}"
|
||||
uses: arduino/setup-protoc@v3
|
||||
uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run clippy on binaries
|
||||
@@ -43,15 +49,19 @@ jobs:
|
||||
clippy:
|
||||
name: clippy
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: clippy
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo clippy --workspace --lib --examples --tests --benches --all-features --locked
|
||||
@@ -60,19 +70,25 @@ jobs:
|
||||
|
||||
wasm:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: wasm32-wasip1
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: dcarbone/install-jq-action@v3
|
||||
- uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0
|
||||
- name: Run Wasm checks
|
||||
run: |
|
||||
sudo apt update && sudo apt install gcc-multilib
|
||||
@@ -80,37 +96,49 @@ jobs:
|
||||
|
||||
riscv:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: riscv32imac-unknown-none-elf
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: dcarbone/install-jq-action@v3
|
||||
- uses: dcarbone/install-jq-action@b7ef57d46ece78760b4019dbc4080a1ba2a40b45 # v3.2.0
|
||||
- name: Run RISC-V checks
|
||||
run: .github/scripts/check_rv32imac.sh
|
||||
|
||||
crate-checks:
|
||||
name: crate-checks (${{ matrix.partition }}/${{ matrix.total_partitions }})
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
partition: [1, 2, 3]
|
||||
total_partitions: [3]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-hack
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo hack check --workspace --partition ${{ matrix.partition }}/${{ matrix.total_partitions }}
|
||||
@@ -118,15 +146,19 @@ jobs:
|
||||
msrv:
|
||||
name: MSRV
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: "1.93" # MSRV
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo build --bin reth --workspace
|
||||
@@ -136,13 +168,17 @@ jobs:
|
||||
docs:
|
||||
name: docs
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo docs --document-private-items
|
||||
@@ -154,42 +190,56 @@ jobs:
|
||||
fmt:
|
||||
name: fmt
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rustfmt
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- name: Run fmt
|
||||
run: cargo fmt --all --check
|
||||
|
||||
udeps:
|
||||
name: udeps
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: taiki-e/install-action@cargo-udeps
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: cargo-udeps
|
||||
- run: cargo udeps --workspace --lib --examples --tests --benches --all-features --locked
|
||||
|
||||
book:
|
||||
name: book
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo build --bin reth --workspace
|
||||
@@ -201,38 +251,54 @@ jobs:
|
||||
|
||||
typos:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: crate-ci/typos@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: crate-ci/typos@02ea592e44b3a53c302f697cddca7641cd051c3d # v1.45.0
|
||||
|
||||
check-toml:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run dprint
|
||||
uses: dprint/check@v2.3
|
||||
uses: dprint/check@9cb3a2b17a8e606d37aae341e49df3654933fc23 # v2.3
|
||||
with:
|
||||
config-path: dprint.json
|
||||
|
||||
grafana:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Check dashboard JSON with jq
|
||||
uses: sergeysova/jq-action@v2
|
||||
uses: sergeysova/jq-action@a3f0d4ff59cc1dddf023fc0b325dd75b10deec58 # v2.3.0
|
||||
with:
|
||||
cmd: jq empty etc/grafana/dashboards/overview.json
|
||||
|
||||
no-test-deps:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # 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
|
||||
@@ -240,13 +306,17 @@ jobs:
|
||||
# Check crates correctly propagate features
|
||||
feature-propagation:
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: taiki-e/cache-cargo-install-action@v3
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: taiki-e/cache-cargo-install-action@a8b9ecf8e0c0ea09d7481cfc583a5203ecd585b5 # v3.0.5
|
||||
with:
|
||||
tool: zepter
|
||||
- name: Eagerly pull dependencies
|
||||
@@ -254,6 +324,8 @@ jobs:
|
||||
- run: zepter run check
|
||||
|
||||
deny:
|
||||
permissions:
|
||||
contents: read
|
||||
uses: tempoxyz/ci/.github/workflows/deny.yml@main
|
||||
|
||||
lint-success:
|
||||
@@ -277,6 +349,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
23
.github/workflows/pr-audit.yml
vendored
23
.github/workflows/pr-audit.yml
vendored
@@ -4,27 +4,36 @@ on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'cyclops'
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Publish event
|
||||
env:
|
||||
EVENTS_KEY: ${{ secrets.EVENTS_KEY }}
|
||||
EVENTS_CERT: ${{ secrets.EVENTS_CERT }}
|
||||
EVENTS_ARGS: ${{ secrets.EVENTS_ARGS }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
PR_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "${{ secrets.EVENTS_KEY }}" > ${{ runner.temp }}/key
|
||||
echo "${{ secrets.EVENTS_CERT }}" > ${{ runner.temp }}/cert
|
||||
echo "$EVENTS_KEY" > "${{ runner.temp }}/key"
|
||||
echo "$EVENTS_CERT" > "${{ runner.temp }}/cert"
|
||||
|
||||
curl -sf -o /dev/null -X POST ${{ secrets.EVENTS_ARGS }} \
|
||||
curl -sf -o /dev/null -X POST $EVENTS_ARGS \
|
||||
-H "Content-Type: application/json" \
|
||||
--key ${{ runner.temp }}/key \
|
||||
--cert ${{ runner.temp }}/cert \
|
||||
--key "${{ runner.temp }}/key" \
|
||||
--cert "${{ runner.temp }}/cert" \
|
||||
-d '{
|
||||
"repository": "${{ github.repository }}",
|
||||
"event": "pr_audit",
|
||||
"data": {
|
||||
"pr_number": ${{ github.event.pull_request.number }},
|
||||
"sha": "${{ github.event.pull_request.head.sha }}"
|
||||
"pr_number": '"$PR_NUMBER"',
|
||||
"sha": "'"$PR_SHA"'"
|
||||
}
|
||||
}'
|
||||
|
||||
11
.github/workflows/pr-title.yml
vendored
11
.github/workflows/pr-title.yml
vendored
@@ -8,20 +8,19 @@ on:
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
pull-requests: read
|
||||
contents: read
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
conventional-title:
|
||||
name: Validate PR title is Conventional Commit
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check title
|
||||
id: lint_pr_title
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -40,7 +39,7 @@ jobs:
|
||||
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
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
message: |
|
||||
@@ -76,7 +75,7 @@ jobs:
|
||||
|
||||
- name: Remove Comment for Valid Title
|
||||
if: steps.lint_pr_title.outcome == 'success'
|
||||
uses: marocchino/sticky-pull-request-comment@v2
|
||||
uses: marocchino/sticky-pull-request-comment@d4d6b0936434b21bc8345ad45a440c5f7d2c40ff # v3.0.3
|
||||
with:
|
||||
header: pr-title-lint-error
|
||||
delete: true
|
||||
|
||||
5
.github/workflows/release-dist.yml
vendored
5
.github/workflows/release-dist.yml
vendored
@@ -7,12 +7,15 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
release-homebrew:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Update Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@v7
|
||||
uses: dawidd6/action-homebrew-bump-formula@1446dca236b0440c6f02723a3f14f13be2c04ab0 # v7
|
||||
with:
|
||||
token: ${{ secrets.HOMEBREW }}
|
||||
no_fork: true
|
||||
|
||||
24
.github/workflows/release-reproducible.yml
vendored
24
.github/workflows/release-reproducible.yml
vendored
@@ -2,6 +2,8 @@
|
||||
|
||||
name: release-reproducible
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [release]
|
||||
@@ -15,20 +17,23 @@ jobs:
|
||||
name: extract version
|
||||
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Extract version from triggering tag
|
||||
id: extract_version
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
|
||||
run: |
|
||||
# Get the tag that points to the head SHA of the triggering workflow
|
||||
TAG=$(gh api /repos/${{ github.repository }}/git/refs/tags \
|
||||
--jq '.[] | select(.object.sha == "${{ github.event.workflow_run.head_sha }}") | .ref' \
|
||||
--jq ".[] | select(.object.sha == \"${HEAD_SHA}\") | .ref" \
|
||||
| head -1 \
|
||||
| sed 's|refs/tags/||')
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
echo "No tag found for SHA ${{ github.event.workflow_run.head_sha }}"
|
||||
echo "No tag found for SHA ${HEAD_SHA}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -44,15 +49,16 @@ jobs:
|
||||
packages: write
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ needs.extract-version.outputs.VERSION }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v4
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -65,7 +71,7 @@ jobs:
|
||||
echo "RUST_TOOLCHAIN=$RUST_TOOLCHAIN" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build reproducible artifacts
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
id: docker_build
|
||||
with:
|
||||
context: .
|
||||
@@ -75,13 +81,11 @@ jobs:
|
||||
VERSION=${{ needs.extract-version.outputs.VERSION }}
|
||||
target: artifacts
|
||||
outputs: type=local,dest=./docker-artifacts
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
env:
|
||||
DOCKER_BUILD_RECORD_UPLOAD: false
|
||||
|
||||
- name: Build and push final image
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile.reproducible
|
||||
@@ -92,8 +96,6 @@ jobs:
|
||||
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
|
||||
|
||||
48
.github/workflows/release.yml
vendored
48
.github/workflows/release.yml
vendored
@@ -3,6 +3,8 @@
|
||||
|
||||
name: release
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
@@ -20,21 +22,24 @@ env:
|
||||
REPRODUCIBLE_IMAGE_NAME: ${{ github.repository_owner }}/reth-reproducible
|
||||
CARGO_TERM_COLOR: always
|
||||
DOCKER_IMAGE_NAME_URL: https://ghcr.io/${{ github.repository_owner }}/reth
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
|
||||
jobs:
|
||||
dry-run:
|
||||
name: check dry run
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
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'}}"
|
||||
- env:
|
||||
DRY_RUN: ${{ github.event.inputs.dry_run }}
|
||||
run: |
|
||||
echo "Dry run value: ${DRY_RUN}"
|
||||
echo "Dry run enabled: $( [ "${DRY_RUN}" = 'true' ] && echo true || echo false )"
|
||||
echo "Dry run disabled: $( [ "${DRY_RUN}" != 'true' ] && echo true || echo false )"
|
||||
|
||||
extract-version:
|
||||
name: extract version
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Extract version
|
||||
run: echo "VERSION=${GITHUB_REF_NAME//\//-}" >> $GITHUB_OUTPUT
|
||||
@@ -45,12 +50,15 @@ jobs:
|
||||
check-version:
|
||||
name: check version
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
needs: extract-version
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Verify crate version matches tag
|
||||
# Check that the Cargo version starts with the tag,
|
||||
# so that Cargo version 1.4.8 can be matched against both v1.4.8 and v1.4.8-rc.1
|
||||
@@ -63,6 +71,8 @@ jobs:
|
||||
build:
|
||||
name: build release
|
||||
runs-on: ${{ matrix.configs.os }}
|
||||
permissions:
|
||||
contents: read
|
||||
needs: extract-version
|
||||
continue-on-error: ${{ matrix.configs.allow_fail }}
|
||||
strategy:
|
||||
@@ -95,20 +105,20 @@ jobs:
|
||||
- command: build
|
||||
binary: reth
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: ${{ matrix.configs.target }}
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- name: Install cross main
|
||||
if: ${{ !matrix.configs.native }}
|
||||
id: cross_main
|
||||
run: |
|
||||
cargo install cross --locked --git https://github.com/cross-rs/cross
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
cache-on-failure: true
|
||||
cargo install cross --locked \
|
||||
--git https://github.com/cross-rs/cross \
|
||||
--rev 65fe72b0cdb1e7e0cc0652517498d4389cc8f5cf
|
||||
|
||||
- name: Apple M1 setup
|
||||
if: matrix.configs.target == 'aarch64-apple-darwin'
|
||||
@@ -145,14 +155,14 @@ jobs:
|
||||
|
||||
- name: Upload artifact
|
||||
if: ${{ github.event.inputs.dry_run != 'true' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
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@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
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
|
||||
@@ -171,11 +181,12 @@ jobs:
|
||||
steps:
|
||||
# This is necessary for generating the changelog.
|
||||
# It has to come before "Download Artifacts" or else it deletes the artifacts.
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
- name: Generate full changelog
|
||||
id: changelog
|
||||
run: |
|
||||
@@ -261,6 +272,7 @@ jobs:
|
||||
dry-run-summary:
|
||||
name: dry run summary
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
needs: [build, extract-version]
|
||||
if: ${{ github.event.inputs.dry_run == 'true' }}
|
||||
env:
|
||||
|
||||
16
.github/workflows/reproducible-build.yml
vendored
16
.github/workflows/reproducible-build.yml
vendored
@@ -5,11 +5,15 @@ on:
|
||||
schedule:
|
||||
- cron: "0 1 */2 * *"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
name: build reproducible binaries
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
@@ -18,14 +22,16 @@ jobs:
|
||||
- runner: ubuntu-22.04
|
||||
machine: machine-2
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
target: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||
|
||||
- name: Build reproducible binary with Docker
|
||||
run: |
|
||||
@@ -43,7 +49,7 @@ jobs:
|
||||
echo "Binaries SHA256 on ${{ matrix.machine }}: $(cat checksum.sha256)"
|
||||
|
||||
- name: Upload the hash
|
||||
uses: actions/upload-artifact@v7
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: checksum-${{ matrix.machine }}
|
||||
path: |
|
||||
@@ -56,12 +62,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download artifacts from machine-1
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: checksum-machine-1
|
||||
path: machine-1/
|
||||
- name: Download artifacts from machine-2
|
||||
uses: actions/download-artifact@v8
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: checksum-machine-2
|
||||
path: machine-2/
|
||||
|
||||
14
.github/workflows/stage.yml
vendored
14
.github/workflows/stage.yml
vendored
@@ -18,22 +18,28 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
stage:
|
||||
name: stage-run-test
|
||||
# Only run stage commands test in merge groups
|
||||
if: github.event_name == 'merge_group'
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Build reth
|
||||
|
||||
4
.github/workflows/stale.yml
vendored
4
.github/workflows/stale.yml
vendored
@@ -7,6 +7,8 @@ on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
@@ -15,7 +17,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v10
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
with:
|
||||
days-before-stale: 21
|
||||
days-before-close: 7
|
||||
|
||||
14
.github/workflows/sync-era.yml
vendored
14
.github/workflows/sync-era.yml
vendored
@@ -15,11 +15,15 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -34,11 +38,13 @@ jobs:
|
||||
block: 100000
|
||||
unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Build ${{ matrix.chain.bin }}
|
||||
|
||||
14
.github/workflows/sync.yml
vendored
14
.github/workflows/sync.yml
vendored
@@ -15,11 +15,15 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
if: github.repository == 'paradigmxyz/reth'
|
||||
name: sync (${{ matrix.chain.bin }})
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
@@ -34,11 +38,13 @@ jobs:
|
||||
block: 100000
|
||||
unwind-target: "0x52e0509d33a988ef807058e2980099ee3070187f7333aae12b64d4d675f34c5a"
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Build ${{ matrix.chain.bin }}
|
||||
|
||||
53
.github/workflows/unit.yml
vendored
53
.github/workflows/unit.yml
vendored
@@ -17,10 +17,14 @@ concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: test / ${{ matrix.type }}
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-4' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
@@ -32,16 +36,20 @@ jobs:
|
||||
exclude_args: ""
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- if: "${{ matrix.type == 'book' }}"
|
||||
uses: arduino/setup-protoc@v3
|
||||
uses: arduino/setup-protoc@c65c819552d16ad3c9b72d9dfd5ba5237b9c906b # v3.0.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Run tests
|
||||
@@ -56,20 +64,25 @@ jobs:
|
||||
state:
|
||||
name: Ethereum state tests
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest-8' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_LOG: info,sync=error
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Checkout ethereum/tests
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
repository: ethereum/tests
|
||||
ref: 81862e4848585a438d64f911a19b3825f0f4cd95
|
||||
path: testing/ef-tests/ethereum-tests
|
||||
submodules: recursive
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
- name: Download & extract EEST fixtures (public)
|
||||
shell: bash
|
||||
env:
|
||||
@@ -79,11 +92,13 @@ jobs:
|
||||
mkdir -p testing/ef-tests/execution-spec-tests
|
||||
URL="https://github.com/ethereum/execution-spec-tests/releases/download/${EEST_TESTS_TAG}/fixtures_stable.tar.gz"
|
||||
curl -L "$URL" | tar -xz --strip-components=1 -C testing/ef-tests/execution-spec-tests
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@nextest
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: taiki-e/install-action@1f2425cdb59f8fffb99ee16a5968edf6f57a2b93 # v2.75.24
|
||||
with:
|
||||
tool: nextest
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- run: cargo nextest run --no-fail-fast --cargo-profile hivetests -p ef-tests --features "asm-keccak ef-tests"
|
||||
@@ -91,15 +106,19 @@ jobs:
|
||||
doc:
|
||||
name: doc tests
|
||||
runs-on: ${{ github.repository == 'paradigmxyz/reth' && 'depot-ubuntu-latest' || 'ubuntu-latest' }}
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@v1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: rui314/setup-mold@725a8794d15fc7563f59595bd9556495c0564878 # v1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: mozilla-actions/sccache-action@v0.0.9
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- uses: mozilla-actions/sccache-action@7d986dd989559c6ecdb630a3fd2557667be217ad # v0.0.9
|
||||
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
||||
with:
|
||||
cache-on-failure: true
|
||||
- name: Run doctests
|
||||
@@ -113,6 +132,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
544
Cargo.lock
generated
544
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
60
Cargo.toml
60
Cargo.toml
@@ -1,5 +1,5 @@
|
||||
[workspace.package]
|
||||
version = "2.1.0"
|
||||
version = "2.2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.93"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -450,39 +450,39 @@ alloy-sol-types = { version = "1.5.6", default-features = false }
|
||||
alloy-chains = { version = "0.2.33", default-features = false }
|
||||
alloy-eip2124 = { version = "0.2.0", default-features = false }
|
||||
alloy-eip7928 = { version = "0.3.4", default-features = false }
|
||||
alloy-evm = { version = "0.33.0", default-features = false }
|
||||
alloy-evm = { version = "0.34.0", default-features = false }
|
||||
alloy-rlp = { version = "0.3.13", default-features = false, features = ["core-net"] }
|
||||
alloy-trie = { version = "0.9.4", default-features = false }
|
||||
|
||||
alloy-hardforks = "0.4.7"
|
||||
|
||||
alloy-consensus = { version = "2.0.1", default-features = false }
|
||||
alloy-contract = { version = "2.0.1", default-features = false }
|
||||
alloy-eips = { version = "2.0.1", default-features = false }
|
||||
alloy-genesis = { version = "2.0.1", default-features = false }
|
||||
alloy-json-rpc = { version = "2.0.1", default-features = false }
|
||||
alloy-network = { version = "2.0.1", default-features = false }
|
||||
alloy-network-primitives = { version = "2.0.1", default-features = false }
|
||||
alloy-provider = { version = "2.0.1", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-client = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types = { version = "2.0.1", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "2.0.1", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "2.0.1", default-features = false }
|
||||
alloy-serde = { version = "2.0.1", default-features = false }
|
||||
alloy-signer = { version = "2.0.1", default-features = false }
|
||||
alloy-signer-local = { version = "2.0.1", default-features = false }
|
||||
alloy-transport = { version = "2.0.1" }
|
||||
alloy-transport-http = { version = "2.0.1", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "2.0.1", default-features = false }
|
||||
alloy-transport-ws = { version = "2.0.1", default-features = false }
|
||||
alloy-consensus = { version = "2.0.4", default-features = false }
|
||||
alloy-contract = { version = "2.0.4", default-features = false }
|
||||
alloy-eips = { version = "2.0.4", default-features = false }
|
||||
alloy-genesis = { version = "2.0.4", default-features = false }
|
||||
alloy-json-rpc = { version = "2.0.4", default-features = false }
|
||||
alloy-network = { version = "2.0.4", default-features = false }
|
||||
alloy-network-primitives = { version = "2.0.4", default-features = false }
|
||||
alloy-provider = { version = "2.0.4", features = ["reqwest", "debug-api"], default-features = false }
|
||||
alloy-pubsub = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-client = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types = { version = "2.0.4", features = ["eth"], default-features = false }
|
||||
alloy-rpc-types-admin = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-anvil = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-beacon = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-debug = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-engine = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-eth = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-mev = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-trace = { version = "2.0.4", default-features = false }
|
||||
alloy-rpc-types-txpool = { version = "2.0.4", default-features = false }
|
||||
alloy-serde = { version = "2.0.4", default-features = false }
|
||||
alloy-signer = { version = "2.0.4", default-features = false }
|
||||
alloy-signer-local = { version = "2.0.4", default-features = false }
|
||||
alloy-transport = { version = "2.0.4" }
|
||||
alloy-transport-http = { version = "2.0.4", features = ["reqwest-rustls-tls"], default-features = false }
|
||||
alloy-transport-ipc = { version = "2.0.4", default-features = false }
|
||||
alloy-transport-ws = { version = "2.0.4", default-features = false }
|
||||
|
||||
# misc
|
||||
either = { version = "1.15.0", default-features = false }
|
||||
@@ -581,7 +581,7 @@ tower = "0.5"
|
||||
tower-http = "0.6"
|
||||
|
||||
# p2p
|
||||
discv5 = "0.10"
|
||||
discv5 = { git = "https://github.com/sigp/discv5", rev = "7663c00" }
|
||||
if-addrs = "0.14"
|
||||
|
||||
# rpc
|
||||
|
||||
@@ -50,7 +50,7 @@ RUN if [ -n "$RUSTFLAGS" ]; then \
|
||||
RUN cp /app/target/$BUILD_PROFILE/reth /app/reth
|
||||
|
||||
# Use Ubuntu as the release image
|
||||
FROM ubuntu AS runtime
|
||||
FROM ubuntu:24.04 AS runtime
|
||||
WORKDIR /app
|
||||
|
||||
# Copy reth over from the build stage
|
||||
|
||||
@@ -7,15 +7,16 @@
|
||||
//! `execute_transaction` to apply segment-boundary changes.
|
||||
|
||||
use crate::evm_config::BigBlockSegment;
|
||||
use alloy_consensus::TransactionEnvelope;
|
||||
use alloy_eips::eip7685::Requests;
|
||||
use alloy_evm::{
|
||||
block::{
|
||||
BlockExecutionError, BlockExecutionResult, BlockExecutor, BlockExecutorFactory,
|
||||
BlockExecutorFor, ExecutableTx, GasOutput, OnStateHook, StateChangeSource, StateDB,
|
||||
ExecutableTx, GasOutput, OnStateHook, StateChangeSource, StateDB,
|
||||
},
|
||||
eth::{EthBlockExecutionCtx, EthBlockExecutor, EthEvmContext, EthTxResult},
|
||||
precompiles::PrecompilesMap,
|
||||
Database, EthEvm, EthEvmFactory, Evm, FromRecoveredTx, FromTxWithEncoded,
|
||||
Database, EthEvm, EthEvmFactory, Evm, EvmFactory, FromRecoveredTx, FromTxWithEncoded,
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use reth_ethereum_primitives::{Receipt, TransactionSigned};
|
||||
@@ -36,6 +37,7 @@ use tracing::{debug, trace};
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Runtime state for segment boundary tracking.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BbEvmPlan {
|
||||
/// The segment boundaries and environments.
|
||||
pub(crate) segments: Vec<BigBlockSegment>,
|
||||
@@ -73,6 +75,10 @@ impl BbEvmPlan {
|
||||
.filter(|(n, _)| *n >= min && *n < block_number)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn segment_index_for_tx(&self, tx_index: usize) -> usize {
|
||||
self.segments.partition_point(|segment| segment.start_tx <= tx_index).saturating_sub(1)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BbEvmPlan {
|
||||
@@ -97,6 +103,9 @@ impl std::fmt::Debug for BbEvmPlan {
|
||||
/// segment boundaries without requiring additional trait bounds on `DB`.
|
||||
pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
|
||||
|
||||
/// Function pointer that reads the BAL index from the DB.
|
||||
pub(crate) type BalIndexReader<DB> = fn(&DB) -> u64;
|
||||
|
||||
/// Block executor that wraps [`EthBlockExecutor`] and handles segment-boundary
|
||||
/// changes for big-block execution.
|
||||
///
|
||||
@@ -108,7 +117,8 @@ pub(crate) type BlockHashSeeder<DB> = fn(&mut DB, &[(u64, B256)]);
|
||||
/// Gas counters reset at each boundary so that each segment's real gas limit
|
||||
/// is used (preserving correct GASLIMIT opcode behavior). Accumulated offsets
|
||||
/// are applied to receipts and totals in `finish()`.
|
||||
pub(crate) struct BbBlockExecutor<'a, DB, I, P, Spec>
|
||||
#[expect(missing_debug_implementations)]
|
||||
pub struct BbBlockExecutor<'a, DB, I, P, Spec>
|
||||
where
|
||||
DB: Database,
|
||||
{
|
||||
@@ -131,6 +141,10 @@ where
|
||||
/// Callback to reseed block hashes into the DB's cache at segment
|
||||
/// boundaries. See [`BlockHashSeeder`].
|
||||
block_hash_seeder: Option<BlockHashSeeder<DB>>,
|
||||
/// Callback to read the BAL index from the DB.
|
||||
bal_index_reader: Option<BalIndexReader<DB>>,
|
||||
/// Whether the executor has selected its starting segment.
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl<'a, DB, I, P, Spec> BbBlockExecutor<'a, DB, I, P, Spec>
|
||||
@@ -156,6 +170,7 @@ where
|
||||
receipt_builder: RethReceiptBuilder,
|
||||
plan: Option<BbEvmPlan>,
|
||||
block_hash_seeder: Option<BlockHashSeeder<DB>>,
|
||||
bal_index_reader: Option<BalIndexReader<DB>>,
|
||||
) -> Self {
|
||||
let inner = EthBlockExecutor::new(evm, ctx, spec, receipt_builder);
|
||||
Self {
|
||||
@@ -166,9 +181,63 @@ where
|
||||
blob_gas_used_offset: 0,
|
||||
shared_hook: Arc::new(Mutex::new(None)),
|
||||
block_hash_seeder,
|
||||
bal_index_reader,
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&mut self) -> Result<(), BlockExecutionError> {
|
||||
if self.initialized {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let plan = match &self.plan {
|
||||
Some(plan) => plan,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
self.initialized = true;
|
||||
|
||||
let bal_index =
|
||||
self.bal_index_reader.map(|reader| reader(self.inner().evm().db())).unwrap_or(0);
|
||||
let segment_idx =
|
||||
if bal_index == 0 { 0 } else { plan.segment_index_for_tx((bal_index - 1) as usize) };
|
||||
let segment = &plan.segments[segment_idx];
|
||||
|
||||
// Swap the EVM's block_env and executor ctx to the selected segment's
|
||||
// values so that EIP-2935/EIP-4788 system calls use the correct block
|
||||
// number and parent hash. Without this, the outer big block header's
|
||||
// block_number (which is synthetic) would be used, writing to wrong
|
||||
// EIP-2935 slots and corrupting state.
|
||||
let block_env = segment.evm_env.block_env.clone();
|
||||
let block_number = block_env.number.saturating_to::<u64>();
|
||||
let mut cfg_env = segment.evm_env.cfg_env.clone();
|
||||
cfg_env.disable_base_fee = true;
|
||||
let ctx = EthBlockExecutionCtx {
|
||||
parent_hash: segment.ctx.parent_hash,
|
||||
parent_beacon_block_root: segment.ctx.parent_beacon_block_root,
|
||||
ommers: segment.ctx.ommers,
|
||||
withdrawals: segment.ctx.withdrawals.clone(),
|
||||
extra_data: segment.ctx.extra_data.clone(),
|
||||
tx_count_hint: segment.ctx.tx_count_hint,
|
||||
slot_number: segment.ctx.slot_number,
|
||||
};
|
||||
|
||||
let inner = self.inner_mut();
|
||||
let evm_ctx = inner.evm.ctx_mut();
|
||||
evm_ctx.block = block_env;
|
||||
evm_ctx.cfg = cfg_env;
|
||||
inner.ctx = ctx;
|
||||
|
||||
self.reseed_block_hashes_for(block_number);
|
||||
|
||||
if bal_index > 0 {
|
||||
self.plan = None;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a forwarding `OnStateHook` that delegates to the shared hook.
|
||||
fn forwarding_hook(&self) -> Option<Box<dyn OnStateHook>> {
|
||||
let shared = self.shared_hook.clone();
|
||||
@@ -349,35 +418,9 @@ where
|
||||
type Result = EthTxResult<HaltReason, alloy_consensus::TxType>;
|
||||
|
||||
fn apply_pre_execution_changes(&mut self) -> Result<(), BlockExecutionError> {
|
||||
// Swap the EVM's block_env and executor ctx to the first segment's
|
||||
// values so that the initial EIP-2935/EIP-4788 system calls use the
|
||||
// correct block number and parent hash. Without this, the outer big
|
||||
// block header's block_number (which is synthetic) would be used,
|
||||
// writing to wrong EIP-2935 slots and corrupting state.
|
||||
if let Some(seg0) = self.plan.as_ref().map(|p| &p.segments[0]) {
|
||||
let block_env = seg0.evm_env.block_env.clone();
|
||||
let block_number = block_env.number.saturating_to::<u64>();
|
||||
let mut cfg_env = seg0.evm_env.cfg_env.clone();
|
||||
cfg_env.disable_base_fee = true;
|
||||
let seg0_ctx = EthBlockExecutionCtx {
|
||||
parent_hash: seg0.ctx.parent_hash,
|
||||
parent_beacon_block_root: seg0.ctx.parent_beacon_block_root,
|
||||
ommers: seg0.ctx.ommers,
|
||||
withdrawals: seg0.ctx.withdrawals.clone(),
|
||||
extra_data: seg0.ctx.extra_data.clone(),
|
||||
tx_count_hint: seg0.ctx.tx_count_hint,
|
||||
slot_number: seg0.ctx.slot_number,
|
||||
};
|
||||
|
||||
let inner = self.inner_mut();
|
||||
let evm_ctx = inner.evm.ctx_mut();
|
||||
evm_ctx.block = block_env;
|
||||
evm_ctx.cfg = cfg_env;
|
||||
inner.ctx = seg0_ctx;
|
||||
|
||||
self.reseed_block_hashes_for(block_number);
|
||||
}
|
||||
|
||||
// The outer big-block header uses a synthetic block number, so start
|
||||
// system calls must run against the selected real segment env.
|
||||
self.initialize()?;
|
||||
self.inner_mut().apply_pre_execution_changes()
|
||||
}
|
||||
|
||||
@@ -385,15 +428,16 @@ where
|
||||
&mut self,
|
||||
tx: impl ExecutableTx<Self>,
|
||||
) -> Result<Self::Result, BlockExecutionError> {
|
||||
self.initialize()?;
|
||||
self.maybe_apply_boundary()?;
|
||||
self.inner_mut().execute_transaction_without_commit(tx)
|
||||
}
|
||||
|
||||
fn commit_transaction(
|
||||
&mut self,
|
||||
output: Self::Result,
|
||||
) -> Result<GasOutput, BlockExecutionError> {
|
||||
let gas_used = self.inner_mut().commit_transaction(output)?;
|
||||
fn commit_transaction(&mut self, output: Self::Result) -> GasOutput {
|
||||
self.maybe_apply_boundary()
|
||||
.expect("segment boundary application must succeed before committing transaction");
|
||||
|
||||
let gas_used = self.inner_mut().commit_transaction(output);
|
||||
|
||||
// Fix up cumulative_gas_used on the just-committed receipt so that
|
||||
// the receipt root task (which reads receipts incrementally) sees
|
||||
@@ -408,7 +452,7 @@ where
|
||||
if let Some(plan) = &mut self.plan {
|
||||
plan.tx_counter += 1;
|
||||
}
|
||||
Ok(gas_used)
|
||||
gas_used
|
||||
}
|
||||
|
||||
fn finish(
|
||||
@@ -499,7 +543,7 @@ pub struct BbBlockExecutorFactory<Spec> {
|
||||
receipt_builder: RethReceiptBuilder,
|
||||
spec: Spec,
|
||||
evm_factory: EthEvmFactory,
|
||||
/// Staged plan consumed by the next [`BbBlockExecutor`].
|
||||
/// Staged plan cloned into each [`BbBlockExecutor`].
|
||||
pub(crate) staged_plan: Arc<Mutex<Option<BbEvmPlan>>>,
|
||||
}
|
||||
|
||||
@@ -528,8 +572,12 @@ impl<Spec> BbBlockExecutorFactory<Spec> {
|
||||
*self.staged_plan.lock().unwrap() = Some(plan);
|
||||
}
|
||||
|
||||
fn take_plan(&self) -> Option<BbEvmPlan> {
|
||||
self.staged_plan.lock().unwrap().take()
|
||||
pub(crate) fn clear_staged_plan(&self) {
|
||||
*self.staged_plan.lock().unwrap() = None;
|
||||
}
|
||||
|
||||
fn peek_plan(&self) -> Option<BbEvmPlan> {
|
||||
self.staged_plan.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn create_executor_with_seeder<'a, DB, I>(
|
||||
@@ -537,14 +585,23 @@ impl<Spec> BbBlockExecutorFactory<Spec> {
|
||||
evm: EthEvm<DB, I, PrecompilesMap>,
|
||||
ctx: EthBlockExecutionCtx<'a>,
|
||||
block_hash_seeder: Option<BlockHashSeeder<DB>>,
|
||||
bal_index_reader: Option<BalIndexReader<DB>>,
|
||||
) -> BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>
|
||||
where
|
||||
Spec: alloy_evm::eth::spec::EthExecutorSpec,
|
||||
DB: StateDB + 'a,
|
||||
I: Inspector<EthEvmContext<DB>> + 'a,
|
||||
{
|
||||
let plan = self.take_plan();
|
||||
BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, block_hash_seeder)
|
||||
let plan = self.peek_plan();
|
||||
BbBlockExecutor::new(
|
||||
evm,
|
||||
ctx,
|
||||
&self.spec,
|
||||
self.receipt_builder,
|
||||
plan,
|
||||
block_hash_seeder,
|
||||
bal_index_reader,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -557,6 +614,12 @@ where
|
||||
type ExecutionCtx<'a> = EthBlockExecutionCtx<'a>;
|
||||
type Transaction = TransactionSigned;
|
||||
type Receipt = Receipt;
|
||||
type TxExecutionResult = EthTxResult<
|
||||
<EthEvmFactory as EvmFactory>::HaltReason,
|
||||
<TransactionSigned as TransactionEnvelope>::TxType,
|
||||
>;
|
||||
type Executor<'a, DB: StateDB, I: Inspector<EthEvmContext<DB>>> =
|
||||
BbBlockExecutor<'a, DB, I, PrecompilesMap, &'a Spec>;
|
||||
|
||||
fn evm_factory(&self) -> &Self::EvmFactory {
|
||||
&self.evm_factory
|
||||
@@ -566,12 +629,12 @@ where
|
||||
&'a self,
|
||||
evm: EthEvm<DB, I, PrecompilesMap>,
|
||||
ctx: EthBlockExecutionCtx<'a>,
|
||||
) -> impl BlockExecutorFor<'a, Self, DB, I>
|
||||
) -> Self::Executor<'a, DB, I>
|
||||
where
|
||||
DB: StateDB + 'a,
|
||||
I: Inspector<EthEvmContext<DB>> + 'a,
|
||||
DB: StateDB,
|
||||
I: Inspector<EthEvmContext<DB>>,
|
||||
{
|
||||
let plan = self.take_plan();
|
||||
BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, None)
|
||||
let plan = self.peek_plan();
|
||||
BbBlockExecutor::new(evm, ctx, &self.spec, self.receipt_builder, plan, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@
|
||||
pub(crate) use reth_engine_primitives::BigBlockData;
|
||||
|
||||
use crate::{
|
||||
evm::{BbBlockExecutorFactory, BbEvmPlan},
|
||||
evm::{BalIndexReader, BbBlockExecutorFactory, BbEvmPlan},
|
||||
BigBlockMap,
|
||||
};
|
||||
use alloy_consensus::Header;
|
||||
use alloy_evm::eth::EthBlockExecutionCtx;
|
||||
use alloy_evm::{
|
||||
eth::{spec::EthExecutorSpec, EthBlockExecutionCtx},
|
||||
EthEvmFactory,
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rpc_types::engine::ExecutionData;
|
||||
use core::convert::Infallible;
|
||||
@@ -20,8 +23,8 @@ use reth_chainspec::{ChainSpec, EthChainSpec};
|
||||
use reth_ethereum_forks::Hardforks;
|
||||
use reth_ethereum_primitives::EthPrimitives;
|
||||
use reth_evm::{
|
||||
ConfigureEngineEvm, ConfigureEvm, Database, EvmEnv, ExecutableTxIterator,
|
||||
NextBlockEnvAttributes,
|
||||
ConfigureEngineEvm, ConfigureEvm, Database, EvmEnv, EvmEnvFor, ExecutableTxIterator,
|
||||
ExecutionCtxFor, NextBlockEnvAttributes,
|
||||
};
|
||||
use reth_evm_ethereum::{EthBlockAssembler, EthEvmConfig, RethReceiptBuilder};
|
||||
use reth_primitives_traits::{SealedBlock, SealedHeader};
|
||||
@@ -29,9 +32,6 @@ use revm::primitives::hardfork::SpecId;
|
||||
use std::sync::Arc;
|
||||
use tracing::debug;
|
||||
|
||||
use alloy_evm::{eth::spec::EthExecutorSpec, EthEvmFactory};
|
||||
use reth_evm::{EvmEnvFor, ExecutionCtxFor};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Execution plan types
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -55,7 +55,7 @@ pub(crate) struct BigBlockSegment {
|
||||
///
|
||||
/// Wraps [`EthEvmConfig`] and a shared [`BigBlockMap`]. When a big-block
|
||||
/// payload is received, the plan is staged on the [`BbBlockExecutorFactory`]
|
||||
/// and consumed when the executor is created. Block hashes for inter-segment
|
||||
/// and cloned when executors are created. Block hashes for inter-segment
|
||||
/// BLOCKHASH resolution are reseeded into `State::block_hashes` at each
|
||||
/// segment boundary via a [`BlockHashSeeder`](crate::evm::BlockHashSeeder)
|
||||
/// callback injected in [`ConfigureEvm::create_executor`].
|
||||
@@ -106,6 +106,10 @@ fn seed_state_block_hashes<DB>(state: &mut &mut revm::database::State<DB>, hashe
|
||||
}
|
||||
}
|
||||
|
||||
fn read_bal_index<DB>(state: &&mut revm::database::State<DB>) -> u64 {
|
||||
state.bal_state.bal_index()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ConfigureEvm
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -144,6 +148,12 @@ where
|
||||
&self,
|
||||
block: &'a SealedBlock<reth_ethereum_primitives::Block>,
|
||||
) -> Result<EthBlockExecutionCtx<'a>, Self::Error> {
|
||||
if let Some(plan) = self.plan_for_payload_hash(&block.hash()) {
|
||||
self.executor_factory.stage_plan(plan);
|
||||
} else {
|
||||
self.executor_factory.clear_staged_plan();
|
||||
}
|
||||
|
||||
self.inner.context_for_block(block)
|
||||
}
|
||||
|
||||
@@ -159,7 +169,7 @@ where
|
||||
&'a self,
|
||||
evm: reth_evm::EvmFor<Self, &'a mut revm::database::State<DB>, I>,
|
||||
ctx: EthBlockExecutionCtx<'a>,
|
||||
) -> impl alloy_evm::block::BlockExecutorFor<
|
||||
) -> alloy_evm::block::BlockExecutorFor<
|
||||
'a,
|
||||
Self::BlockExecutorFactory,
|
||||
&'a mut revm::database::State<DB>,
|
||||
@@ -169,15 +179,16 @@ where
|
||||
DB: Database,
|
||||
I: reth_evm::InspectorFor<Self, &'a mut revm::database::State<DB>> + 'a,
|
||||
{
|
||||
// Use create_executor_with_seeder to inject a concrete seeder that
|
||||
// can reseed State::block_hashes at segment boundaries. The seeder
|
||||
// is a function pointer that knows the concrete State<DB> type,
|
||||
// allowing the generic BbBlockExecutor to reseed without additional
|
||||
// trait bounds on DB.
|
||||
let bal_index_reader: Option<BalIndexReader<&'a mut revm::database::State<DB>>> =
|
||||
Some(read_bal_index::<DB>);
|
||||
|
||||
// Inject concrete function pointers that know the `State<DB>` type so
|
||||
// the generic executor can reseed block hashes and read `bal_index`.
|
||||
self.executor_factory.create_executor_with_seeder(
|
||||
evm,
|
||||
ctx,
|
||||
Some(seed_state_block_hashes::<DB>),
|
||||
bal_index_reader,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -214,6 +225,7 @@ where
|
||||
|
||||
Ok(env)
|
||||
} else {
|
||||
self.executor_factory.clear_staged_plan();
|
||||
self.inner.evm_env_for_payload(payload)
|
||||
}
|
||||
}
|
||||
@@ -248,10 +260,12 @@ where
|
||||
/// In practice, this is called from `evm_env_for_payload` in the
|
||||
/// engine pipeline.
|
||||
pub fn stage_plan_for_payload(&self, payload_hash: &B256) {
|
||||
let bb = match self.pending.lock().unwrap().remove(payload_hash) {
|
||||
Some(bb) => bb,
|
||||
None => return,
|
||||
};
|
||||
let Some(plan) = self.plan_for_payload_hash(payload_hash) else { return };
|
||||
self.executor_factory.stage_plan(plan);
|
||||
}
|
||||
|
||||
fn plan_for_payload_hash(&self, payload_hash: &B256) -> Option<BbEvmPlan> {
|
||||
let bb = self.pending.lock().unwrap().remove(payload_hash)?;
|
||||
|
||||
let segments: Vec<_> = bb
|
||||
.env_switches
|
||||
@@ -287,6 +301,6 @@ where
|
||||
|
||||
plan.block_hashes_to_seed.sort_unstable_by_key(|(n, _)| *n);
|
||||
|
||||
self.executor_factory.stage_plan(plan);
|
||||
Some(plan)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use alloy_rpc_types_engine::{
|
||||
};
|
||||
use clap::Parser;
|
||||
use eyre::Context;
|
||||
use futures::{stream, StreamExt};
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_cli::chainspec::ChainSpecParser;
|
||||
use reth_cli_runner::CliContext;
|
||||
@@ -29,7 +30,10 @@ use reth_ethereum_cli::chainspec::EthereumChainSpecParser;
|
||||
use reth_ethereum_primitives::Receipt;
|
||||
use reth_primitives_traits::proofs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, future::Future};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
future::Future,
|
||||
};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::bench::helpers::fetch_block_access_list;
|
||||
@@ -267,6 +271,15 @@ pub struct Command {
|
||||
/// the flattened BAL on the stored payload.
|
||||
#[arg(long, default_value_t = false)]
|
||||
bal: bool,
|
||||
|
||||
/// Maximum number of in-flight RPC fetches to keep buffered ahead of the merger.
|
||||
///
|
||||
/// Each entry is one full per-block fetch (block + receipts, plus BAL when `--bal` is
|
||||
/// set). Larger values absorb RPC latency at the cost of more concurrent connections
|
||||
/// and memory; the buffer persists across `--num-big-blocks` so prefetching continues
|
||||
/// across big-block boundaries.
|
||||
#[arg(long, value_name = "PREFETCH_BUFFER", default_value_t = 32)]
|
||||
prefetch_buffer: usize,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
@@ -319,13 +332,27 @@ impl Command {
|
||||
}
|
||||
let mut prev_big_block_header: Option<PrevBigBlockHeader> = None;
|
||||
|
||||
// Track the next block to fetch across big blocks so they don't overlap.
|
||||
// Persistent prefetch stream: keeps `prefetch_buffer` per-block fetches in flight
|
||||
// ahead of the merger across all big blocks. Each item is a fully materialized
|
||||
// `FetchedBlock` (or `None` once the chain tip is reached on this fetch).
|
||||
let prefetch_buffer = self.prefetch_buffer.max(1);
|
||||
let bal_enabled = self.bal;
|
||||
let block_stream = stream::iter(self.from_block..)
|
||||
.map(|block_number| {
|
||||
let provider = provider.clone();
|
||||
async move { fetch_one_block(provider, block_number, bal_enabled).await }
|
||||
})
|
||||
.buffered(prefetch_buffer);
|
||||
let mut block_stream = Box::pin(block_stream);
|
||||
|
||||
// Track the next block number we expect from the stream (purely for logging /
|
||||
// big-block range bookkeeping; the stream produces blocks in `from_block..` order).
|
||||
let mut next_block = self.from_block;
|
||||
|
||||
for big_block_idx in 0..self.num_big_blocks {
|
||||
let range_start = next_block;
|
||||
|
||||
// Fetch consecutive blocks until the gas target is reached.
|
||||
// Drain the prefetch stream until the gas target is reached for this big block.
|
||||
let mut blocks = Vec::new();
|
||||
let mut block_receipts: Vec<Vec<Receipt>> = Vec::new();
|
||||
let mut block_access_lists: Vec<Option<BlockAccessList>> = Vec::new();
|
||||
@@ -334,16 +361,11 @@ impl Command {
|
||||
let mut reached_chain_tip = false;
|
||||
while accumulated_block_gas < self.target_gas {
|
||||
let block_number = next_block;
|
||||
info!(target: "reth-bench", block_number, big_block = big_block_idx, "Fetching block");
|
||||
info!(target: "reth-bench", block_number, big_block = big_block_idx, "Awaiting prefetched block");
|
||||
|
||||
let fetch_result = tokio::try_join!(
|
||||
provider.get_block_by_number(block_number.into()).full(),
|
||||
provider.get_block_receipts(block_number.into()),
|
||||
);
|
||||
|
||||
let (rpc_block, receipts) = match fetch_result {
|
||||
Ok((Some(block), Some(receipts))) => (block, receipts),
|
||||
Ok((None, _) | (_, None)) => {
|
||||
let fetched = match block_stream.next().await {
|
||||
Some(Ok(Some(fetched))) => fetched,
|
||||
Some(Ok(None)) => {
|
||||
warn!(
|
||||
target: "reth-bench",
|
||||
block_number,
|
||||
@@ -352,52 +374,16 @@ impl Command {
|
||||
reached_chain_tip = true;
|
||||
break;
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
Some(Err(e)) => return Err(e),
|
||||
// The block-number stream is open-ended; this only fires if the
|
||||
// upstream `iter(from..)` is somehow exhausted.
|
||||
None => {
|
||||
reached_chain_tip = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let block_access_list = if self.bal {
|
||||
Some(fetch_block_access_list(&provider, block_number).await.wrap_err_with(
|
||||
|| format!("Failed to fetch BAL for block {block_number}"),
|
||||
)?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Convert RPC receipts to consensus receipts
|
||||
let consensus_receipts: Vec<Receipt> = receipts
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let inner = &r.inner.inner.inner;
|
||||
let tx_type = r.inner.inner.r#type.try_into().unwrap_or_default();
|
||||
Receipt {
|
||||
tx_type,
|
||||
success: inner.receipt.status.coerce_status(),
|
||||
cumulative_gas_used: inner.receipt.cumulative_gas_used,
|
||||
logs: inner
|
||||
.receipt
|
||||
.logs
|
||||
.iter()
|
||||
.map(|log| alloy_primitives::Log {
|
||||
address: log.inner.address,
|
||||
data: log.inner.data.clone(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Convert to consensus block
|
||||
let block = rpc_block
|
||||
.into_inner()
|
||||
.map_header(|header| header.map(|h| h.into_header_with_defaults()))
|
||||
.try_map_transactions(|tx| -> eyre::Result<TxEnvelope> {
|
||||
tx.try_into().map_err(|_| eyre::eyre!("unsupported tx type"))
|
||||
})?
|
||||
.into_consensus();
|
||||
|
||||
// Convert to ExecutionData
|
||||
let (payload, sidecar) = ExecutionPayload::from_block_slow(&block);
|
||||
let execution_data = ExecutionData { payload, sidecar };
|
||||
let FetchedBlock { execution_data, consensus_receipts, block_access_list } =
|
||||
fetched;
|
||||
|
||||
let block_gas = execution_data.payload.as_v1().gas_used;
|
||||
let block_blob_gas =
|
||||
@@ -671,6 +657,79 @@ impl Command {
|
||||
}
|
||||
}
|
||||
|
||||
/// One fully-materialized block fetched by the prefetcher.
|
||||
struct FetchedBlock {
|
||||
/// Execution payload with sidecar derived from the RPC block.
|
||||
execution_data: ExecutionData,
|
||||
/// Consensus-format receipts (`cumulative_gas_used` is still per-block, callers offset
|
||||
/// it when merging).
|
||||
consensus_receipts: Vec<Receipt>,
|
||||
/// `eth_getBlockAccessListByBlockNumber` result when `--bal` is enabled.
|
||||
block_access_list: Option<BlockAccessList>,
|
||||
}
|
||||
|
||||
/// Fetches one block + receipts (and optionally its BAL) from the RPC. Returns `Ok(None)`
|
||||
/// when the block doesn't exist yet (chain-tip reached).
|
||||
async fn fetch_one_block(
|
||||
provider: RootProvider<AnyNetwork>,
|
||||
block_number: u64,
|
||||
bal_enabled: bool,
|
||||
) -> eyre::Result<Option<FetchedBlock>> {
|
||||
let (rpc_block, receipts) = tokio::try_join!(
|
||||
provider.get_block_by_number(block_number.into()).full(),
|
||||
provider.get_block_receipts(block_number.into()),
|
||||
)?;
|
||||
let (rpc_block, receipts) = match (rpc_block, receipts) {
|
||||
(Some(b), Some(r)) => (b, r),
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
let block_access_list = if bal_enabled {
|
||||
Some(
|
||||
fetch_block_access_list(&provider, block_number)
|
||||
.await
|
||||
.wrap_err_with(|| format!("Failed to fetch BAL for block {block_number}"))?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let consensus_receipts: Vec<Receipt> = receipts
|
||||
.iter()
|
||||
.map(|r| {
|
||||
let inner = &r.inner.inner.inner;
|
||||
let tx_type = r.inner.inner.r#type.try_into().unwrap_or_default();
|
||||
Receipt {
|
||||
tx_type,
|
||||
success: inner.receipt.status.coerce_status(),
|
||||
cumulative_gas_used: inner.receipt.cumulative_gas_used,
|
||||
logs: inner
|
||||
.receipt
|
||||
.logs
|
||||
.iter()
|
||||
.map(|log| alloy_primitives::Log {
|
||||
address: log.inner.address,
|
||||
data: log.inner.data.clone(),
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let block = rpc_block
|
||||
.into_inner()
|
||||
.map_header(|header| header.map(|h| h.into_header_with_defaults()))
|
||||
.try_map_transactions(|tx| -> eyre::Result<TxEnvelope> {
|
||||
tx.try_into().map_err(|_| eyre::eyre!("unsupported tx type"))
|
||||
})?
|
||||
.into_consensus();
|
||||
|
||||
let (payload, sidecar) = ExecutionPayload::from_block_slow(&block);
|
||||
let execution_data = ExecutionData { payload, sidecar };
|
||||
|
||||
Ok(Some(FetchedBlock { execution_data, consensus_receipts, block_access_list }))
|
||||
}
|
||||
|
||||
fn merge_block_access_list(
|
||||
merged: &mut BlockAccessList,
|
||||
incoming: BlockAccessList,
|
||||
@@ -717,6 +776,17 @@ fn merge_account_changes(existing: &mut AccountChanges, incoming: AccountChanges
|
||||
existing.balance_changes.extend(incoming.balance_changes);
|
||||
existing.nonce_changes.extend(incoming.nonce_changes);
|
||||
existing.code_changes.extend(incoming.code_changes);
|
||||
|
||||
// EIP-7928 invariant: a slot must appear in either storage_changes or storage_reads,
|
||||
// not both. Per-block BALs respect this, but merging blocks can produce a slot
|
||||
// that is read in one block and changed in another. Without this normalization,
|
||||
// an empty read entry can shadow the real writes during BAL deserialization,
|
||||
// making reads of that slot fall through to stale snapshot state.
|
||||
let written: HashSet<_> =
|
||||
existing.storage_changes.iter().map(|slot_changes| slot_changes.slot).collect();
|
||||
existing.storage_reads.retain(|slot| !written.contains(slot));
|
||||
let mut seen = HashSet::with_capacity(existing.storage_reads.len());
|
||||
existing.storage_reads.retain(|slot| seen.insert(*slot));
|
||||
}
|
||||
|
||||
fn merge_slot_changes(existing: &mut Vec<SlotChanges>, incoming: Vec<SlotChanges>) {
|
||||
@@ -836,4 +906,54 @@ mod tests {
|
||||
assert_eq!(other.address, Address::repeat_byte(0x22));
|
||||
assert_eq!(other.storage_changes[0].changes[0].block_access_index, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_account_changes_normalizes_storage_reads_after_cross_block_merge() {
|
||||
let address = Address::repeat_byte(0x33);
|
||||
const A: U256 = U256::from_limbs([1, 0, 0, 0]);
|
||||
const B: U256 = U256::from_limbs([2, 0, 0, 0]);
|
||||
const C: U256 = U256::from_limbs([3, 0, 0, 0]);
|
||||
const D: U256 = U256::from_limbs([4, 0, 0, 0]);
|
||||
|
||||
// Each AccountChanges value is valid on its own: storage slots only appear in
|
||||
// either reads or changes. The invalid read/change overlap is introduced when
|
||||
// these per-block BAL entries are merged for a standalone big block.
|
||||
let mut existing = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![SlotChanges::new(A, vec![StorageChange::new(0, U256::from(10))])],
|
||||
storage_reads: vec![B, C],
|
||||
balance_changes: vec![],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
// B is read before it is written by the incoming block, and A is written before
|
||||
// it appears as a read in the incoming block. C is read in both blocks, so the
|
||||
// merge should also dedupe it. D remains read-only.
|
||||
let incoming = AccountChanges {
|
||||
address,
|
||||
storage_changes: vec![SlotChanges::new(B, vec![StorageChange::new(1, U256::from(20))])],
|
||||
storage_reads: vec![A, C, D],
|
||||
balance_changes: vec![],
|
||||
nonce_changes: vec![],
|
||||
code_changes: vec![],
|
||||
};
|
||||
|
||||
merge_account_changes(&mut existing, incoming);
|
||||
|
||||
// Written slots remain represented by storage_changes, while storage_reads only
|
||||
// keeps unique read-only slots in first-seen order.
|
||||
assert_eq!(
|
||||
existing
|
||||
.storage_changes
|
||||
.iter()
|
||||
.map(|slot_changes| slot_changes.slot)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![A, B]
|
||||
);
|
||||
assert_eq!(existing.storage_reads, vec![C, D]);
|
||||
assert!(existing.storage_reads.iter().all(|read_slot| {
|
||||
!existing.storage_changes.iter().any(|slot_changes| slot_changes.slot == *read_slot)
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,10 @@ use reth_provider::{
|
||||
};
|
||||
use reth_revm::{
|
||||
database::StateProviderDatabase,
|
||||
db::{states::reverts::AccountInfoRevert, BundleState},
|
||||
db::{
|
||||
states::reverts::{AccountInfoRevert, RevertToSlot},
|
||||
BundleState,
|
||||
},
|
||||
};
|
||||
use reth_stages::stages::calculate_gas_used_from_headers;
|
||||
use reth_storage_api::{ChangeSetReader, DBProvider, StorageChangeSetReader};
|
||||
@@ -425,14 +428,19 @@ where
|
||||
let mut cs_slots = cs_storage.get_mut(addr);
|
||||
for (slot_key, revert_slot) in &revert.storage {
|
||||
let b256_key = B256::from(*slot_key);
|
||||
match cs_slots.as_mut().and_then(|s| s.remove(&b256_key)) {
|
||||
Some(cs_value) => eyre::ensure!(
|
||||
revert_slot.to_previous_value() == cs_value,
|
||||
let cs_value = cs_slots.as_mut().and_then(|s| s.remove(&b256_key));
|
||||
match (revert_slot, cs_value) {
|
||||
// When a contract is selfdestructed and re-created at the same address
|
||||
// within the same block, revm marks slots touched by the new contract
|
||||
// as `Destroyed` and never reads the original DB value, so
|
||||
// `to_previous_value()` would resolve to zero, which might be wrong.
|
||||
(RevertToSlot::Destroyed, _) => {}
|
||||
(RevertToSlot::Some(prev), Some(cs_value)) => eyre::ensure!(
|
||||
*prev == cs_value,
|
||||
"Block {block_number}: {addr} slot {b256_key} mismatch: \
|
||||
revert={} cs={cs_value}",
|
||||
revert_slot.to_previous_value(),
|
||||
revert={prev} cs={cs_value}",
|
||||
),
|
||||
None => eyre::ensure!(
|
||||
(RevertToSlot::Some(_), None) => eyre::ensure!(
|
||||
revert.wipe_storage,
|
||||
"Block {block_number}: {addr} slot {b256_key} in reverts but not in changeset",
|
||||
),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use futures_util::StreamExt;
|
||||
use reth_node_api::{BlockBody, PayloadAttributes, PayloadKind};
|
||||
use reth_node_api::{PayloadAttributes, PayloadKind};
|
||||
use reth_payload_builder::{PayloadBuilderHandle, PayloadId};
|
||||
use reth_payload_builder_primitives::Events;
|
||||
use reth_payload_primitives::{BuiltPayload, PayloadTypes};
|
||||
use reth_payload_primitives::PayloadTypes;
|
||||
use tokio_stream::wrappers::BroadcastStream;
|
||||
|
||||
/// Helper for payload operations
|
||||
@@ -53,27 +53,11 @@ impl<T: PayloadTypes> PayloadTestContext<T> {
|
||||
///
|
||||
/// Panics if the payload builder does not produce a non-empty payload within 30 seconds.
|
||||
pub async fn wait_for_built_payload(&self, payload_id: PayloadId) {
|
||||
let start = std::time::Instant::now();
|
||||
loop {
|
||||
let payload =
|
||||
self.payload_builder.best_payload(payload_id).await.transpose().ok().flatten();
|
||||
if payload.is_none_or(|p| p.block().body().transactions().is_empty()) {
|
||||
assert!(
|
||||
start.elapsed() < std::time::Duration::from_secs(30),
|
||||
"timed out waiting for a non-empty payload for {payload_id} — \
|
||||
check that the chain spec supports all generated tx types"
|
||||
);
|
||||
tokio::time::sleep(std::time::Duration::from_millis(20)).await;
|
||||
continue
|
||||
}
|
||||
// Resolve payload once its built
|
||||
self.payload_builder
|
||||
.resolve_kind(payload_id, PayloadKind::Earliest)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
break;
|
||||
}
|
||||
self.payload_builder
|
||||
.resolve_kind(payload_id, PayloadKind::WaitForPending)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Expects the next event to be a built payload event or panics
|
||||
|
||||
@@ -191,9 +191,9 @@ pub struct TreeConfig {
|
||||
/// When disabled, the BAL hashed post state is not sent to the multiproof task for
|
||||
/// early parallel state root computation.
|
||||
disable_bal_parallel_state_root: bool,
|
||||
/// Whether to disable BAL (Block Access List) batched IO during prewarming.
|
||||
/// When disabled, falls back to individual per-slot storage reads instead of
|
||||
/// batched cursor reads via `storage_range`.
|
||||
/// Whether to disable BAL (Block Access List) storage prefetch IO during prewarming.
|
||||
/// When set, BAL storage slots are not read into the execution cache. BAL hashed-state
|
||||
/// streaming for parallel state-root computation is controlled separately.
|
||||
disable_bal_batch_io: bool,
|
||||
/// Maximum random jitter applied before each proof computation (trie-debug only).
|
||||
/// When set, each proof worker sleeps for a random duration up to this value
|
||||
@@ -239,7 +239,7 @@ impl Default for TreeConfig {
|
||||
share_execution_cache_with_payload_builder: false,
|
||||
share_sparse_trie_with_payload_builder: false,
|
||||
suppress_persistence_during_build: false,
|
||||
disable_bal_parallel_execution: false,
|
||||
disable_bal_parallel_execution: true,
|
||||
disable_bal_parallel_state_root: false,
|
||||
disable_bal_batch_io: false,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
@@ -316,7 +316,7 @@ impl TreeConfig {
|
||||
share_execution_cache_with_payload_builder,
|
||||
share_sparse_trie_with_payload_builder,
|
||||
suppress_persistence_during_build: false,
|
||||
disable_bal_parallel_execution: false,
|
||||
disable_bal_parallel_execution: true,
|
||||
disable_bal_parallel_state_root: false,
|
||||
disable_bal_batch_io: false,
|
||||
#[cfg(feature = "trie-debug")]
|
||||
|
||||
@@ -21,7 +21,8 @@ impl ForkchoiceStateTracker {
|
||||
/// `sync_target` to `None`, since we're now fully synced.
|
||||
pub const fn set_latest(&mut self, state: ForkchoiceState, status: ForkchoiceStatus) {
|
||||
if status.is_valid() {
|
||||
self.set_valid(state);
|
||||
self.last_syncing = None;
|
||||
self.last_valid = Some(state);
|
||||
} else if status.is_syncing() {
|
||||
self.last_syncing = Some(state);
|
||||
}
|
||||
@@ -30,11 +31,24 @@ impl ForkchoiceStateTracker {
|
||||
self.latest = Some(received);
|
||||
}
|
||||
|
||||
const fn set_valid(&mut self, state: ForkchoiceState) {
|
||||
// we no longer need to sync to this state.
|
||||
/// Promotes a previously tracked syncing forkchoice state to valid, without overwriting a
|
||||
/// newer `latest` state.
|
||||
///
|
||||
/// This is used when a `Syncing` FCU's head finally becomes canonical via the downloaded-block
|
||||
/// flow, so the safe/finalized anchors of that FCU can be applied. Unlike
|
||||
/// [`Self::set_latest`], this preserves a newer `latest` (e.g. an `Invalid` FCU received
|
||||
/// after the syncing one) and only flips `latest` to `Valid` when it still refers to the same
|
||||
/// syncing FCU being promoted.
|
||||
pub fn promote_sync_target_to_valid(&mut self, state: ForkchoiceState) {
|
||||
self.last_syncing = None;
|
||||
|
||||
self.last_valid = Some(state);
|
||||
|
||||
if let Some(received) = self.latest.as_mut() &&
|
||||
received.state == state &&
|
||||
received.status.is_syncing()
|
||||
{
|
||||
received.status = ForkchoiceStatus::Valid;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`ForkchoiceStatus`] of the latest received FCU.
|
||||
|
||||
@@ -1917,9 +1917,37 @@ where
|
||||
self.on_canonical_chain_update(chain_update);
|
||||
}
|
||||
|
||||
self.on_canonicalized_sync_target(target);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Applies the tracked forkchoice state once its sync target head becomes canonical.
|
||||
fn on_canonicalized_sync_target(&mut self, target: B256) {
|
||||
let Some(sync_target_state) = self
|
||||
.state
|
||||
.forkchoice_state_tracker
|
||||
.sync_target_state()
|
||||
.filter(|state| state.head_block_hash == target)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(outcome) = self.ensure_consistent_forkchoice_state(sync_target_state) {
|
||||
debug!(
|
||||
target: "engine::tree",
|
||||
head = %sync_target_state.head_block_hash,
|
||||
safe = %sync_target_state.safe_block_hash,
|
||||
finalized = %sync_target_state.finalized_block_hash,
|
||||
?outcome,
|
||||
"Canonicalized sync target head before safe/finalized could be applied"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
self.state.forkchoice_state_tracker.promote_sync_target_to_valid(sync_target_state);
|
||||
}
|
||||
|
||||
/// Convenience function to handle an optional tree event.
|
||||
fn on_maybe_tree_event(&mut self, event: Option<TreeEvent>) -> ProviderResult<()> {
|
||||
if let Some(event) = event {
|
||||
|
||||
@@ -361,8 +361,44 @@ where
|
||||
let (prefetch_tx, prefetch_rx) = oneshot::channel();
|
||||
let (stream_tx, stream_rx) = oneshot::channel();
|
||||
|
||||
if let Some(to_sparse_trie_task) = to_sparse_trie_task {
|
||||
let stream_ctx = ctx.clone();
|
||||
executor.bal_streaming_pool().spawn(move || {
|
||||
let branch_span = debug_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
parent: &stream_parent_span,
|
||||
"bal_hashed_state_stream",
|
||||
bal_accounts = stream_bal.as_bal().len(),
|
||||
);
|
||||
let provider_parent_span = branch_span.clone();
|
||||
let _span = branch_span.entered();
|
||||
|
||||
stream_bal.as_bal().par_iter().for_each_init(
|
||||
|| {
|
||||
(
|
||||
stream_ctx.clone(),
|
||||
None::<Box<dyn AccountReader>>,
|
||||
provider_parent_span.clone(),
|
||||
)
|
||||
},
|
||||
|(ctx, provider, parent_span), account_changes| {
|
||||
ctx.send_bal_hashed_state(
|
||||
parent_span,
|
||||
provider,
|
||||
account_changes,
|
||||
&to_sparse_trie_task,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
let _ = to_sparse_trie_task.send(StateRootMessage::FinishedStateUpdates);
|
||||
let _ = stream_tx.send(());
|
||||
});
|
||||
} else {
|
||||
let _ = stream_tx.send(());
|
||||
}
|
||||
|
||||
if ctx.saved_cache.is_some() {
|
||||
let prefetch_ctx = ctx.clone();
|
||||
executor.prewarming_pool().spawn(move || {
|
||||
let branch_span = debug_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
@@ -376,7 +412,7 @@ where
|
||||
prefetch_bal.as_bal().par_iter().for_each_init(
|
||||
|| {
|
||||
(
|
||||
prefetch_ctx.clone(),
|
||||
ctx.clone(),
|
||||
None::<CachedStateProvider<reth_provider::StateProviderBox, true>>,
|
||||
provider_parent_span.clone(),
|
||||
)
|
||||
@@ -395,36 +431,6 @@ where
|
||||
let _ = prefetch_tx.send(());
|
||||
}
|
||||
|
||||
if let Some(to_sparse_trie_task) = to_sparse_trie_task {
|
||||
executor.bal_streaming_pool().spawn(move || {
|
||||
let branch_span = debug_span!(
|
||||
target: "engine::tree::payload_processor::prewarm",
|
||||
parent: &stream_parent_span,
|
||||
"bal_hashed_state_stream",
|
||||
bal_accounts = stream_bal.as_bal().len(),
|
||||
);
|
||||
let provider_parent_span = branch_span.clone();
|
||||
let _span = branch_span.entered();
|
||||
|
||||
stream_bal.as_bal().par_iter().for_each_init(
|
||||
|| (ctx.clone(), None::<Box<dyn AccountReader>>, provider_parent_span.clone()),
|
||||
|(ctx, provider, parent_span), account_changes| {
|
||||
ctx.send_bal_hashed_state(
|
||||
parent_span,
|
||||
provider,
|
||||
account_changes,
|
||||
&to_sparse_trie_task,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
let _ = to_sparse_trie_task.send(StateRootMessage::FinishedStateUpdates);
|
||||
let _ = stream_tx.send(());
|
||||
});
|
||||
} else {
|
||||
let _ = stream_tx.send(());
|
||||
}
|
||||
|
||||
prefetch_rx
|
||||
.blocking_recv()
|
||||
.expect("BAL prefetch task dropped without signaling completion");
|
||||
@@ -751,7 +757,9 @@ where
|
||||
provider: &mut Option<CachedStateProvider<reth_provider::StateProviderBox, true>>,
|
||||
account: &alloy_eip7928::AccountChanges,
|
||||
) {
|
||||
if account.storage_changes.is_empty() && account.storage_reads.is_empty() {
|
||||
if self.disable_bal_batch_io ||
|
||||
(account.storage_changes.is_empty() && account.storage_reads.is_empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::{hash::Hash, sync::Arc};
|
||||
use tracing::error;
|
||||
|
||||
/// Default max cache size for [`PrecompileCache`]
|
||||
const MAX_CACHE_SIZE: u32 = 10_000;
|
||||
const MAX_CACHE_SIZE: u32 = 1024 * 1024;
|
||||
|
||||
/// Stores caches for each precompile.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
@@ -54,6 +54,9 @@ where
|
||||
moka::sync::CacheBuilder::new(MAX_CACHE_SIZE as u64)
|
||||
.initial_capacity(MAX_CACHE_SIZE as usize)
|
||||
.eviction_policy(EvictionPolicy::lru())
|
||||
.weigher(|key: &Bytes, value: &CacheEntry<S>| {
|
||||
(key.len() + value.output.bytes.len()) as u32
|
||||
})
|
||||
.build_with_hasher(Default::default()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2254,3 +2254,65 @@ fn test_on_valid_downloaded_head_sync_target_returns_make_canonical() {
|
||||
other => panic!("Expected MakeCanonical for head block, got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that canonicalizing a downloaded sync target head also applies the tracked finalized
|
||||
/// block from the original `SYNCING` forkchoice state.
|
||||
#[test]
|
||||
fn test_canonicalizing_downloaded_sync_target_head_updates_finalized() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let chain_spec = MAINNET.clone();
|
||||
let mut test_harness = TestHarness::new(chain_spec);
|
||||
|
||||
let blocks: Vec<_> = test_harness.block_builder.get_executed_blocks(0..3).collect();
|
||||
let genesis = &blocks[0];
|
||||
let finalized_block = &blocks[1];
|
||||
let head_block = &blocks[2];
|
||||
|
||||
test_harness = test_harness.with_blocks(vec![
|
||||
genesis.clone(),
|
||||
finalized_block.clone(),
|
||||
head_block.clone(),
|
||||
]);
|
||||
|
||||
let finalized_num_hash = finalized_block.recovered_block().num_hash();
|
||||
let head_num_hash = head_block.recovered_block().num_hash();
|
||||
|
||||
test_harness.tree.state.tree_state.set_canonical_head(genesis.recovered_block().num_hash());
|
||||
|
||||
let fcu_state = ForkchoiceState {
|
||||
head_block_hash: head_num_hash.hash,
|
||||
safe_block_hash: head_num_hash.hash,
|
||||
finalized_block_hash: finalized_num_hash.hash,
|
||||
};
|
||||
test_harness
|
||||
.tree
|
||||
.state
|
||||
.forkchoice_state_tracker
|
||||
.set_latest(fcu_state, ForkchoiceStatus::Syncing);
|
||||
|
||||
let event = test_harness
|
||||
.tree
|
||||
.on_valid_downloaded_block(head_num_hash)
|
||||
.unwrap()
|
||||
.expect("expected canonicalization event for sync target head");
|
||||
|
||||
test_harness.tree.on_tree_event(event).unwrap();
|
||||
|
||||
assert_eq!(test_harness.tree.state.tree_state.canonical_block_hash(), head_num_hash.hash);
|
||||
assert_eq!(
|
||||
test_harness.tree.canonical_in_memory_state.get_finalized_num_hash(),
|
||||
Some(finalized_num_hash),
|
||||
"Finalized block from the syncing FCU should be applied once the head becomes canonical"
|
||||
);
|
||||
assert_eq!(
|
||||
test_harness.tree.canonical_in_memory_state.get_safe_num_hash(),
|
||||
Some(head_num_hash),
|
||||
"Safe block from the syncing FCU should be applied once the head becomes canonical"
|
||||
);
|
||||
assert_eq!(
|
||||
test_harness.tree.state.forkchoice_state_tracker.last_valid_state(),
|
||||
Some(fcu_state)
|
||||
);
|
||||
assert!(test_harness.tree.state.forkchoice_state_tracker.sync_target_state().is_none());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use alloy_rpc_types_eth::TransactionRequest;
|
||||
use rand::{rngs::StdRng, Rng, SeedableRng};
|
||||
use reth_chainspec::{ChainSpecBuilder, EthChainSpec, MAINNET};
|
||||
use reth_e2e_test_utils::setup_engine;
|
||||
use reth_network::types::NatResolver;
|
||||
use reth_network::{types::NatResolver, PeersInfo};
|
||||
use reth_node_builder::{NodeBuilder, NodeHandle};
|
||||
use reth_node_core::{
|
||||
args::{NetworkArgs, RpcServerArgs},
|
||||
@@ -375,3 +375,47 @@ async fn test_admin_external_ip() -> eyre::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_admin_node_info_uses_discv5_port_when_discv4_is_disabled() -> eyre::Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let runtime = Runtime::test();
|
||||
|
||||
let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap();
|
||||
let chain_spec =
|
||||
Arc::new(ChainSpecBuilder::default().chain(MAINNET.chain).genesis(genesis).build());
|
||||
|
||||
let mut network = NetworkArgs::default().with_unused_ports();
|
||||
network.bootnodes = Some(Vec::new());
|
||||
network.discovery.disable_dns_discovery = true;
|
||||
network.discovery.disable_discv4_discovery = true;
|
||||
network = network.with_nat_resolver(NatResolver::ExternalIp("127.0.0.1".parse().unwrap()));
|
||||
|
||||
let node_config = NodeConfig::test()
|
||||
.with_chain(chain_spec)
|
||||
.with_network(network)
|
||||
.with_rpc(RpcServerArgs::default().with_unused_ports().with_http());
|
||||
|
||||
let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config)
|
||||
.testing_node(runtime)
|
||||
.node(EthereumNode::default())
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
assert!(node.network.discv4().is_none());
|
||||
let discv5_port = node.network.discv5().expect("discv5 should be enabled").local_port();
|
||||
|
||||
let local_record = node.network.local_node_record();
|
||||
let local_enr = node.network.local_enr();
|
||||
let info = node.add_ons_handle.admin_api().node_info().await.unwrap();
|
||||
|
||||
assert_eq!(local_record.udp_port, discv5_port);
|
||||
assert_eq!(local_enr.udp4(), Some(discv5_port));
|
||||
assert_eq!(info.ports.discovery, discv5_port);
|
||||
assert_eq!(info.ports.listener, local_record.tcp_port);
|
||||
assert_eq!(info.enode, local_record.to_string());
|
||||
assert!(info.enode.contains(&format!("?discport={discv5_port}")));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ use reth_consensus_common::validation::MAX_RLP_BLOCK_SIZE;
|
||||
use reth_errors::{BlockExecutionError, BlockValidationError, ConsensusError};
|
||||
use reth_ethereum_primitives::{EthPrimitives, TransactionSigned};
|
||||
use reth_evm::{
|
||||
block::TxResult,
|
||||
execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutor},
|
||||
ConfigureEvm, Evm, NextBlockEnvAttributes,
|
||||
};
|
||||
@@ -37,7 +38,7 @@ use reth_transaction_pool::{
|
||||
BestTransactions, BestTransactionsAttributes, PoolTransaction, TransactionPool,
|
||||
ValidPoolTransaction,
|
||||
};
|
||||
use revm::context_interface::Block as _;
|
||||
use revm::context_interface::{Block as _, Cfg as _};
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
@@ -204,8 +205,11 @@ where
|
||||
.map_err(PayloadBuilderError::other)?;
|
||||
|
||||
debug!(target: "payload_builder", id=%payload_id, parent_header = ?parent_header.hash(), parent_number = parent_header.number, "building new payload");
|
||||
let mut cumulative_gas_used = 0;
|
||||
let mut cumulative_tx_gas_used = 0;
|
||||
let mut block_regular_gas_used = 0;
|
||||
let mut block_state_gas_used = 0;
|
||||
let block_gas_limit: u64 = builder.evm_mut().block().gas_limit();
|
||||
let tx_gas_limit_cap = builder.evm_mut().cfg_env().tx_gas_limit_cap();
|
||||
let base_fee = builder.evm_mut().block().basefee();
|
||||
|
||||
let mut best_txs = best_txs(BestTransactionsAttributes::new(
|
||||
@@ -250,13 +254,34 @@ where
|
||||
|
||||
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 {
|
||||
let exceeds_gas_limit = if is_amsterdam {
|
||||
let regular_available_gas = block_gas_limit.saturating_sub(block_regular_gas_used);
|
||||
let state_available_gas = block_gas_limit.saturating_sub(block_state_gas_used);
|
||||
let regular_tx_gas_limit = pool_tx.gas_limit().min(tx_gas_limit_cap);
|
||||
|
||||
if regular_tx_gas_limit > regular_available_gas {
|
||||
Some((regular_tx_gas_limit, regular_available_gas))
|
||||
} else if pool_tx.gas_limit() > state_available_gas {
|
||||
Some((pool_tx.gas_limit(), state_available_gas))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let block_available_gas = block_gas_limit.saturating_sub(cumulative_tx_gas_used);
|
||||
(pool_tx.gas_limit() > block_available_gas)
|
||||
.then_some((pool_tx.gas_limit(), block_available_gas))
|
||||
};
|
||||
|
||||
if let Some((transaction_gas_limit, block_available_gas)) = exceeds_gas_limit {
|
||||
// we can't fit this transaction into the block, so we need to mark it as invalid
|
||||
// which also removes all dependent transaction from the iterator before we can
|
||||
// continue
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(pool_tx.gas_limit(), block_gas_limit),
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
}
|
||||
@@ -341,8 +366,11 @@ where
|
||||
let miner_fee = tx.effective_tip_per_gas(base_fee);
|
||||
let tx_hash = *tx.tx_hash();
|
||||
|
||||
let gas_used = match builder.execute_transaction(tx) {
|
||||
Ok(gas_used) => gas_used.tx_gas_used(),
|
||||
let mut tx_regular_gas_used = 0;
|
||||
let gas_output = match builder.execute_transaction_with_result_closure(tx, |result| {
|
||||
tx_regular_gas_used = result.result().result.gas().block_regular_gas_used();
|
||||
}) {
|
||||
Ok(gas_output) => gas_output,
|
||||
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
|
||||
error, ..
|
||||
})) => {
|
||||
@@ -362,9 +390,8 @@ where
|
||||
}
|
||||
continue
|
||||
}
|
||||
// EIP-7778: the executor tracks gas_before_refund while the payload builder's
|
||||
// pre-check uses gas_after_refund. Near-full blocks can pass the pre-check but
|
||||
// fail the executor's check. Skip the tx and continue building.
|
||||
// The executor is the source of truth for block gas availability. Keep this
|
||||
// non-fatal in case local builder accounting diverges from executor rules.
|
||||
Err(BlockExecutionError::Validation(
|
||||
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
|
||||
transaction_gas_limit,
|
||||
@@ -398,9 +425,12 @@ where
|
||||
block_transactions_rlp_length += tx_rlp_len;
|
||||
|
||||
// update and add to total fees
|
||||
let gas_used = gas_output.tx_gas_used();
|
||||
let miner_fee = miner_fee.expect("fee is always valid; execution succeeded");
|
||||
total_fees += U256::from(miner_fee) * U256::from(gas_used);
|
||||
cumulative_gas_used += gas_used;
|
||||
cumulative_tx_gas_used += gas_used;
|
||||
block_regular_gas_used += tx_regular_gas_used;
|
||||
block_state_gas_used += gas_output.state_gas_used();
|
||||
|
||||
// Add blob tx sidecar to the payload.
|
||||
if let Some(sidecar) = blob_tx_sidecar {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
//! Helper aliases when working with [`ConfigureEvm`] and the traits in this crate.
|
||||
|
||||
use crate::ConfigureEvm;
|
||||
use alloy_evm::{block::BlockExecutorFactory, Database, EvmEnv, EvmFactory};
|
||||
use revm::{inspector::NoOpInspector, Inspector};
|
||||
use alloy_evm::{
|
||||
block::{BlockExecutorFactory, BlockExecutorFor},
|
||||
Database, EvmEnv, EvmFactory,
|
||||
};
|
||||
use revm::{database::State, inspector::NoOpInspector, Inspector};
|
||||
|
||||
/// Helper to access [`EvmFactory`] for a given [`ConfigureEvm`].
|
||||
pub type EvmFactoryFor<Evm> =
|
||||
@@ -33,6 +36,10 @@ pub type TxEnvFor<Evm> = <EvmFactoryFor<Evm> as EvmFactory>::Tx;
|
||||
pub type ExecutionCtxFor<'a, Evm> =
|
||||
<<Evm as ConfigureEvm>::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>;
|
||||
|
||||
/// Helper to access [`alloy_evm::block::BlockExecutor`] for a given [`ConfigureEvm`].
|
||||
pub type BlockExecutorForEvm<'a, Evm, DB, I = NoOpInspector> =
|
||||
BlockExecutorFor<'a, <Evm as ConfigureEvm>::BlockExecutorFactory, &'a mut State<DB>, I>;
|
||||
|
||||
/// Type alias for [`EvmEnv`] for a given [`ConfigureEvm`].
|
||||
pub type EvmEnvFor<Evm> = EvmEnv<SpecFor<Evm>, BlockEnvFor<Evm>>;
|
||||
|
||||
|
||||
@@ -20,10 +20,7 @@ extern crate alloc;
|
||||
use crate::execute::{BasicBlockBuilder, Executor};
|
||||
use alloc::vec::Vec;
|
||||
use alloy_eips::eip4895::Withdrawals;
|
||||
use alloy_evm::{
|
||||
block::{BlockExecutorFactory, BlockExecutorFor},
|
||||
precompiles::PrecompilesMap,
|
||||
};
|
||||
use alloy_evm::{block::BlockExecutorFactory, precompiles::PrecompilesMap};
|
||||
use alloy_primitives::{Address, Bytes, B256};
|
||||
use core::{error::Error, fmt::Debug};
|
||||
use execute::{BasicBlockExecutor, BlockAssembler, BlockBuilder};
|
||||
@@ -312,7 +309,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
&'a self,
|
||||
evm: EvmFor<Self, &'a mut State<DB>, I>,
|
||||
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
|
||||
) -> impl BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>, I>
|
||||
) -> BlockExecutorForEvm<'a, Self, DB, I>
|
||||
where
|
||||
DB: Database,
|
||||
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
|
||||
@@ -325,8 +322,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
&'a self,
|
||||
db: &'a mut State<DB>,
|
||||
block: &'a SealedBlock<<Self::Primitives as NodePrimitives>::Block>,
|
||||
) -> Result<impl BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>>, Self::Error>
|
||||
{
|
||||
) -> Result<BlockExecutorForEvm<'a, Self, DB>, Self::Error> {
|
||||
let evm = self.evm_for_block(db, block.header())?;
|
||||
let ctx = self.context_for_block(block)?;
|
||||
Ok(self.create_executor(evm, ctx))
|
||||
@@ -352,10 +348,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
evm: EvmFor<Self, &'a mut State<DB>, I>,
|
||||
parent: &'a SealedHeader<HeaderTy<Self::Primitives>>,
|
||||
ctx: <Self::BlockExecutorFactory as BlockExecutorFactory>::ExecutionCtx<'a>,
|
||||
) -> impl BlockBuilder<
|
||||
Primitives = Self::Primitives,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>, I>,
|
||||
>
|
||||
) -> impl BlockBuilder<Primitives = Self::Primitives, Executor = BlockExecutorForEvm<'a, Self, DB, I>>
|
||||
where
|
||||
DB: Database,
|
||||
I: InspectorFor<Self, &'a mut State<DB>> + 'a,
|
||||
@@ -404,10 +397,7 @@ pub trait ConfigureEvm: Clone + Debug + Send + Sync + Unpin {
|
||||
parent: &'a SealedHeader<<Self::Primitives as NodePrimitives>::BlockHeader>,
|
||||
attributes: Self::NextBlockEnvCtx,
|
||||
) -> Result<
|
||||
impl BlockBuilder<
|
||||
Primitives = Self::Primitives,
|
||||
Executor: BlockExecutorFor<'a, Self::BlockExecutorFactory, &'a mut State<DB>>,
|
||||
>,
|
||||
impl BlockBuilder<Primitives = Self::Primitives, Executor = BlockExecutorForEvm<'a, Self, DB>>,
|
||||
Self::Error,
|
||||
> {
|
||||
let evm_env = self.next_evm_env(parent, &attributes)?;
|
||||
|
||||
@@ -16,10 +16,13 @@ workspace = true
|
||||
metrics.workspace = true
|
||||
metrics-derive.workspace = true
|
||||
|
||||
# reth
|
||||
reth-primitives-traits = { workspace = true, optional = true }
|
||||
|
||||
# async
|
||||
tokio = { workspace = true, features = ["full"], optional = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
tokio-util = { workspace = true, optional = true }
|
||||
|
||||
[features]
|
||||
common = ["tokio", "futures", "tokio-util"]
|
||||
common = ["tokio", "futures", "tokio-util", "reth-primitives-traits"]
|
||||
|
||||
@@ -4,8 +4,13 @@
|
||||
use crate::Metrics;
|
||||
use futures::Stream;
|
||||
use metrics::Counter;
|
||||
use reth_primitives_traits::InMemorySize;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
use tokio::sync::mpsc::{
|
||||
@@ -399,3 +404,147 @@ struct MeteredPollSenderMetrics {
|
||||
/// Number of delayed message deliveries caused by a full channel
|
||||
back_pressure_total: Counter,
|
||||
}
|
||||
|
||||
/// Shared state for tracking memory budget across sender and receiver.
|
||||
///
|
||||
/// `used` is a pure accounting counter — it does not gate access to any other
|
||||
/// shared memory, so all operations on it use [`Ordering::Relaxed`]. Cross-thread
|
||||
/// publication of message contents is handled by the underlying mpsc channel.
|
||||
#[derive(Debug)]
|
||||
struct MemoryBudget {
|
||||
/// Current number of bytes used by buffered messages.
|
||||
used: AtomicUsize,
|
||||
/// Maximum allowed bytes.
|
||||
max_bytes: usize,
|
||||
}
|
||||
|
||||
/// Guard that releases memory budget when dropped.
|
||||
///
|
||||
/// Holds the size of the message and a reference to the shared budget counter.
|
||||
/// When dropped, it atomically decreases the used counter.
|
||||
#[derive(Debug)]
|
||||
struct BudgetGuard {
|
||||
size: usize,
|
||||
budget: Arc<MemoryBudget>,
|
||||
}
|
||||
|
||||
impl Drop for BudgetGuard {
|
||||
fn drop(&mut self) {
|
||||
self.budget.used.fetch_sub(self.size, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Message envelope that holds the memory budget while the message sits in the channel.
|
||||
///
|
||||
/// The guard is dropped (releasing the budget) as soon as the receiver dequeues
|
||||
/// the message via [`MemoryBoundedReceiver::recv`] / [`MemoryBoundedReceiver::poll_recv`],
|
||||
/// so the budget tracks bytes *currently in the channel queue*, not bytes in flight
|
||||
/// downstream of the receiver.
|
||||
#[derive(Debug)]
|
||||
struct Budgeted<T> {
|
||||
msg: T,
|
||||
_guard: BudgetGuard,
|
||||
}
|
||||
|
||||
/// A sender that enforces a byte budget before enqueueing messages.
|
||||
///
|
||||
/// Uses a shared atomic counter to track memory usage. Each message's size is added
|
||||
/// to the counter on send and subtracted when the message is dequeued by the receiver.
|
||||
///
|
||||
/// The current call sites (specifically [`crate::common::mpsc::MemoryBoundedSender`] used
|
||||
/// for the `NetworkManager → TransactionsManager` channel) have a single producer driven
|
||||
/// from a single `poll`, so the `fetch_add → check → fetch_sub-on-overflow` reservation
|
||||
/// pattern can never race with itself. The atomic is still used so the receiver can
|
||||
/// release budget from a different task.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemoryBoundedSender<T: InMemorySize> {
|
||||
/// The underlying unbounded metered sender
|
||||
inner: UnboundedMeteredSender<Budgeted<T>>,
|
||||
/// Shared memory budget tracker
|
||||
budget: Arc<MemoryBudget>,
|
||||
}
|
||||
|
||||
impl<T: InMemorySize> MemoryBoundedSender<T> {
|
||||
/// Tries to send a message if there is sufficient budget.
|
||||
///
|
||||
/// Returns `TrySendError::Full` if insufficient budget is available.
|
||||
pub fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
|
||||
let size = msg.size();
|
||||
|
||||
// Reserve budget: add first, check after
|
||||
let prev = self.budget.used.fetch_add(size, Ordering::Relaxed);
|
||||
if prev.saturating_add(size) > self.budget.max_bytes {
|
||||
// Over budget, undo
|
||||
self.budget.used.fetch_sub(size, Ordering::Relaxed);
|
||||
return Err(TrySendError::Full(msg));
|
||||
}
|
||||
|
||||
let guard = BudgetGuard { size, budget: Arc::clone(&self.budget) };
|
||||
let budgeted = Budgeted { msg, _guard: guard };
|
||||
|
||||
self.inner.send(budgeted).map_err(|e| {
|
||||
// Guard will be dropped here, releasing the budget
|
||||
TrySendError::Closed(e.0.msg)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A receiver for memory-bounded messages.
|
||||
///
|
||||
/// On receive, the budget reserved for the message is released immediately and the
|
||||
/// inner `T` is yielded — callers do not need to opt into any wrapper type.
|
||||
#[derive(Debug)]
|
||||
pub struct MemoryBoundedReceiver<T> {
|
||||
/// The underlying unbounded metered receiver
|
||||
inner: UnboundedMeteredReceiver<Budgeted<T>>,
|
||||
}
|
||||
|
||||
impl<T> MemoryBoundedReceiver<T> {
|
||||
/// Receives the next message, returning `None` if the channel is closed.
|
||||
///
|
||||
/// Releases the message's reserved budget before returning.
|
||||
pub async fn recv(&mut self) -> Option<T> {
|
||||
self.inner.recv().await.map(unwrap_budgeted)
|
||||
}
|
||||
|
||||
/// Polls to receive the next message on this channel.
|
||||
///
|
||||
/// Releases the message's reserved budget before returning.
|
||||
pub fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll<Option<T>> {
|
||||
self.inner.poll_recv(cx).map(|opt| opt.map(unwrap_budgeted))
|
||||
}
|
||||
}
|
||||
|
||||
/// Releases the budget guard and returns the inner message.
|
||||
fn unwrap_budgeted<T>(b: Budgeted<T>) -> T {
|
||||
// Destructuring binds `_guard` so it is dropped when this function returns,
|
||||
// which runs `BudgetGuard::drop` and releases the reserved bytes.
|
||||
let Budgeted { msg, _guard } = b;
|
||||
msg
|
||||
}
|
||||
|
||||
impl<T> Stream for MemoryBoundedReceiver<T> {
|
||||
type Item = T;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
self.poll_recv(cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new memory-bounded channel with the given byte budget.
|
||||
///
|
||||
/// The budget tracks bytes currently buffered in the channel; it is reserved on
|
||||
/// [`MemoryBoundedSender::try_send`] and released as soon as the receiver dequeues
|
||||
/// the message.
|
||||
pub fn memory_bounded_channel<T: InMemorySize>(
|
||||
max_bytes: usize,
|
||||
scope: &'static str,
|
||||
) -> (MemoryBoundedSender<T>, MemoryBoundedReceiver<T>) {
|
||||
let (tx, rx) = metered_unbounded_channel(scope);
|
||||
let budget = Arc::new(MemoryBudget { used: AtomicUsize::new(0), max_bytes });
|
||||
|
||||
let sender = MemoryBoundedSender { inner: tx, budget };
|
||||
let receiver = MemoryBoundedReceiver { inner: rx };
|
||||
|
||||
(sender, receiver)
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ use secp256k1::SecretKey;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
collections::{btree_map, hash_map::Entry, BTreeMap, HashMap, VecDeque},
|
||||
fmt,
|
||||
future::poll_fn,
|
||||
io,
|
||||
fmt, io,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
@@ -243,17 +241,56 @@ impl Discv4 {
|
||||
/// ```
|
||||
pub async fn bind(
|
||||
local_address: SocketAddr,
|
||||
local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> io::Result<(Self, Discv4Service)> {
|
||||
let socket = Arc::new(UdpSocket::bind(local_address).await?);
|
||||
trace!(target: "discv4", local_addr=?socket.local_addr(), "opened UDP socket");
|
||||
let (tx, rx) = mpsc::channel(config.udp_ingress_message_buffer);
|
||||
|
||||
Self::bind_with_socket(socket, Some(tx), rx, local_node_record, secret_key, config)
|
||||
}
|
||||
|
||||
/// Creates a new `Discv4` instance using a pre-bound shared socket. No receive loop is
|
||||
/// spawned; instead returns an [`IngressHandler`] that should be used to forward raw packets
|
||||
/// received by the socket owner (e.g. discv5 unrecognized frames).
|
||||
pub fn bind_shared(
|
||||
socket: Arc<UdpSocket>,
|
||||
local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> io::Result<(Self, Discv4Service, IngressHandler)> {
|
||||
let (tx, rx) = mpsc::channel(config.udp_ingress_message_buffer);
|
||||
let local_id = local_node_record.id;
|
||||
let (discv4, service) =
|
||||
Self::bind_with_socket(socket, None, rx, local_node_record, secret_key, config)?;
|
||||
|
||||
let handler = IngressHandler::new(tx, local_id);
|
||||
|
||||
Ok((discv4, service, handler))
|
||||
}
|
||||
|
||||
fn bind_with_socket(
|
||||
socket: Arc<UdpSocket>,
|
||||
ingress_tx: Option<IngressSender>,
|
||||
ingress_rx: IngressReceiver,
|
||||
mut local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> io::Result<(Self, Discv4Service)> {
|
||||
let socket = UdpSocket::bind(local_address).await?;
|
||||
let local_addr = socket.local_addr()?;
|
||||
local_node_record.udp_port = local_addr.port();
|
||||
trace!(target: "discv4", ?local_addr,"opened UDP socket");
|
||||
|
||||
let mut service =
|
||||
Discv4Service::new(socket, local_addr, local_node_record, secret_key, config);
|
||||
let mut service = Discv4Service::new(
|
||||
socket,
|
||||
ingress_tx,
|
||||
ingress_rx,
|
||||
local_addr,
|
||||
local_node_record,
|
||||
secret_key,
|
||||
config,
|
||||
);
|
||||
|
||||
// resolve the external address immediately
|
||||
service.resolve_external_ip();
|
||||
@@ -520,20 +557,25 @@ pub struct Discv4Service {
|
||||
|
||||
impl Discv4Service {
|
||||
/// Create a new instance for a bound [`UdpSocket`].
|
||||
///
|
||||
/// If `ingress_tx` is `Some`, the receive loop is spawned to read from the socket. If `None`,
|
||||
/// the caller feeds packets into `ingress_rx` externally (shared socket mode).
|
||||
pub(crate) fn new(
|
||||
socket: UdpSocket,
|
||||
socket: Arc<UdpSocket>,
|
||||
ingress_tx: Option<IngressSender>,
|
||||
ingress_rx: IngressReceiver,
|
||||
local_address: SocketAddr,
|
||||
local_node_record: NodeRecord,
|
||||
secret_key: SecretKey,
|
||||
config: Discv4Config,
|
||||
) -> Self {
|
||||
let socket = Arc::new(socket);
|
||||
let (ingress_tx, ingress_rx) = mpsc::channel(config.udp_ingress_message_buffer);
|
||||
let (egress_tx, egress_rx) = mpsc::channel(config.udp_egress_message_buffer);
|
||||
let mut tasks = JoinSet::<()>::new();
|
||||
|
||||
let udp = Arc::clone(&socket);
|
||||
tasks.spawn(receive_loop(udp, ingress_tx, local_node_record.id));
|
||||
if let Some(ingress_tx) = ingress_tx {
|
||||
let udp = Arc::clone(&socket);
|
||||
tasks.spawn(receive_loop(udp, ingress_tx, local_node_record.id));
|
||||
}
|
||||
|
||||
let udp = Arc::clone(&socket);
|
||||
tasks.spawn(send_loop(udp, egress_rx));
|
||||
@@ -947,7 +989,7 @@ impl Discv4Service {
|
||||
let key = kad_key(peer_id);
|
||||
match self.kbuckets.entry(&key) {
|
||||
BucketEntry::Present(entry, _) => Some(f(entry.value())),
|
||||
BucketEntry::Pending(mut entry, _) => Some(f(entry.value())),
|
||||
BucketEntry::Pending(entry, _) => Some(f(entry.value())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -973,7 +1015,9 @@ impl Discv4Service {
|
||||
kbucket::Entry::Present(mut entry, _) => {
|
||||
entry.value_mut().update_with_enr(last_enr_seq)
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, _) => entry.value().update_with_enr(last_enr_seq),
|
||||
kbucket::Entry::Pending(mut entry, _) => {
|
||||
entry.value_mut().update_with_enr(last_enr_seq)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
@@ -1025,8 +1069,8 @@ impl Discv4Service {
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, mut status) => {
|
||||
// endpoint is now proven
|
||||
entry.value().establish_proof();
|
||||
entry.value().update_with_enr(last_enr_seq);
|
||||
entry.value_mut().establish_proof();
|
||||
entry.value_mut().update_with_enr(last_enr_seq);
|
||||
|
||||
if !status.is_connected() {
|
||||
status.state = ConnectionState::Connected;
|
||||
@@ -1158,7 +1202,7 @@ impl Discv4Service {
|
||||
} else {
|
||||
is_proven = entry.value().has_endpoint_proof;
|
||||
}
|
||||
entry.value().update_with_enr(ping.enr_sq)
|
||||
entry.value_mut().update_with_enr(ping.enr_sq)
|
||||
}
|
||||
kbucket::Entry::Absent(entry) => {
|
||||
let mut node = NodeEntry::new(record);
|
||||
@@ -1388,7 +1432,7 @@ impl Discv4Service {
|
||||
(entry.value().record, id)
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, _) => {
|
||||
let id = entry.value().update_with_fork_id(fork_id);
|
||||
let id = entry.value_mut().update_with_fork_id(fork_id);
|
||||
(entry.value().record, id)
|
||||
}
|
||||
_ => return,
|
||||
@@ -1538,7 +1582,7 @@ impl Discv4Service {
|
||||
}
|
||||
}
|
||||
}
|
||||
BucketEntry::Pending(mut entry, _) => {
|
||||
BucketEntry::Pending(entry, _) => {
|
||||
if entry.value().has_endpoint_proof {
|
||||
if entry
|
||||
.value()
|
||||
@@ -1642,7 +1686,7 @@ impl Discv4Service {
|
||||
entry.value().find_node_failures
|
||||
}
|
||||
kbucket::Entry::Pending(mut entry, _) => {
|
||||
entry.value().inc_failed_request();
|
||||
entry.value_mut().inc_failed_request();
|
||||
entry.value().find_node_failures
|
||||
}
|
||||
_ => continue,
|
||||
@@ -1962,80 +2006,100 @@ const MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP: usize = 60usize;
|
||||
|
||||
/// Continuously awaits new incoming messages and sends them back through the channel.
|
||||
///
|
||||
/// The receive loop enforce primitive rate limiting for ips to prevent message spams from
|
||||
/// individual IPs
|
||||
/// The receive loop enforces primitive rate limiting for IPs to prevent message spams from
|
||||
/// individual IPs.
|
||||
pub(crate) async fn receive_loop(udp: Arc<UdpSocket>, tx: IngressSender, local_id: PeerId) {
|
||||
let send = |event: IngressEvent| async {
|
||||
let _ = tx.send(event).await.map_err(|err| {
|
||||
debug!(
|
||||
target: "discv4",
|
||||
%err,
|
||||
"failed send incoming packet",
|
||||
)
|
||||
});
|
||||
};
|
||||
|
||||
let mut cache = ReceiveCache::default();
|
||||
|
||||
// tick at half the rate of the limit
|
||||
let tick = MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP / 2;
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(tick as u64));
|
||||
|
||||
let mut handler = IngressHandler::new(tx, local_id);
|
||||
let mut buf = [0; MAX_PACKET_SIZE];
|
||||
loop {
|
||||
let res = udp.recv_from(&mut buf).await;
|
||||
match res {
|
||||
Err(err) => {
|
||||
debug!(target: "discv4", %err, "Failed to read datagram.");
|
||||
send(IngressEvent::RecvError(err)).await;
|
||||
handler.send(IngressEvent::RecvError(err)).await;
|
||||
}
|
||||
Ok((read, remote_addr)) => {
|
||||
// rate limit incoming packets by IP
|
||||
if cache.inc_ip(remote_addr.ip()) > MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP {
|
||||
trace!(target: "discv4", ?remote_addr, "Too many incoming packets from IP.");
|
||||
continue
|
||||
}
|
||||
|
||||
let packet = &buf[..read];
|
||||
match Message::decode(packet) {
|
||||
Ok(packet) => {
|
||||
if packet.node_id == local_id {
|
||||
// received our own message
|
||||
debug!(target: "discv4", ?remote_addr, "Received own packet.");
|
||||
continue
|
||||
}
|
||||
|
||||
// skip if we've already received the same packet
|
||||
if cache.contains_packet(packet.hash) {
|
||||
debug!(target: "discv4", ?remote_addr, "Received duplicate packet.");
|
||||
continue
|
||||
}
|
||||
|
||||
send(IngressEvent::Packet(remote_addr, packet)).await;
|
||||
}
|
||||
Err(err) => {
|
||||
trace!(target: "discv4", %err,"Failed to decode packet");
|
||||
send(IngressEvent::BadPacket(remote_addr, err, packet.to_vec())).await
|
||||
}
|
||||
}
|
||||
handler.handle_packet(&buf[..read], remote_addr).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reset the tracked ips if the interval has passed
|
||||
if poll_fn(|cx| match interval.poll_tick(cx) {
|
||||
Poll::Ready(_) => Poll::Ready(true),
|
||||
Poll::Pending => Poll::Ready(false),
|
||||
})
|
||||
.await
|
||||
{
|
||||
cache.tick_ips(tick);
|
||||
/// Handles decoding, rate-limiting, and deduplication of incoming discv4 packets.
|
||||
///
|
||||
/// Used by both the standalone receive loop and the shared-port mode via
|
||||
/// [`Discv4::bind_shared`].
|
||||
#[derive(Debug)]
|
||||
pub struct IngressHandler {
|
||||
tx: IngressSender,
|
||||
local_id: PeerId,
|
||||
tick: usize,
|
||||
tick_interval: Duration,
|
||||
cache: ReceiveCache,
|
||||
last_tick: Instant,
|
||||
}
|
||||
|
||||
impl IngressHandler {
|
||||
fn new(tx: IngressSender, local_id: PeerId) -> Self {
|
||||
let tick = MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP / 2;
|
||||
Self {
|
||||
tx,
|
||||
local_id,
|
||||
tick,
|
||||
tick_interval: Duration::from_secs(tick as u64),
|
||||
cache: ReceiveCache::default(),
|
||||
last_tick: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn send(&self, event: IngressEvent) {
|
||||
let _ = self.tx.send(event).await.map_err(|err| {
|
||||
debug!(target: "discv4", %err, "failed send incoming packet");
|
||||
});
|
||||
}
|
||||
|
||||
/// Handles an incoming raw packet: decodes, rate-limits, deduplicates, and forwards to the
|
||||
/// discv4 service. Used in shared-port mode to process unrecognized frames from discv5.
|
||||
pub async fn handle_packet(&mut self, data: &[u8], src: SocketAddr) {
|
||||
if self.last_tick.elapsed() >= self.tick_interval {
|
||||
self.cache.tick_ips(self.tick);
|
||||
self.last_tick = Instant::now();
|
||||
}
|
||||
|
||||
// rate limit incoming packets by IP
|
||||
if self.cache.inc_ip(src.ip()) > MAX_INCOMING_PACKETS_PER_MINUTE_BY_IP {
|
||||
trace!(target: "discv4", ?src, "Too many incoming packets from IP.");
|
||||
return
|
||||
}
|
||||
|
||||
let event = match Message::decode(data) {
|
||||
Ok(packet) => {
|
||||
if packet.node_id == self.local_id {
|
||||
debug!(target: "discv4", ?src, "Received own packet.");
|
||||
return
|
||||
}
|
||||
|
||||
if self.cache.contains_packet(packet.hash) {
|
||||
debug!(target: "discv4", ?src, "Received duplicate packet.");
|
||||
return
|
||||
}
|
||||
|
||||
IngressEvent::Packet(src, packet)
|
||||
}
|
||||
Err(err) => {
|
||||
trace!(target: "discv4", %err, "Failed to decode packet");
|
||||
IngressEvent::BadPacket(src, err, data.to_vec())
|
||||
}
|
||||
};
|
||||
|
||||
self.send(event).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// A cache for received packets and their source address.
|
||||
///
|
||||
/// This is used to discard duplicated packets and rate limit messages from the same source.
|
||||
#[derive(Debug)]
|
||||
struct ReceiveCache {
|
||||
/// keeps track of how many messages we've received from a given IP address since the last
|
||||
/// tick.
|
||||
|
||||
@@ -308,6 +308,18 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner [`discv5::Config`]. This allows overriding
|
||||
/// the listen config after the config has been built.
|
||||
pub const fn discv5_config_mut(&mut self) -> &mut discv5::Config {
|
||||
&mut self.discv5_config
|
||||
}
|
||||
|
||||
/// Returns `true` if any socket in the discv5 listen config matches the given address.
|
||||
pub fn has_matching_socket(&self, addr: SocketAddr) -> bool {
|
||||
ipv4(&self.discv5_config.listen_config).is_some_and(|v4| SocketAddr::V4(v4) == addr) ||
|
||||
ipv6(&self.discv5_config.listen_config).is_some_and(|v6| SocketAddr::V6(v6) == addr)
|
||||
}
|
||||
|
||||
/// Inserts a new boot node to the list of boot nodes.
|
||||
pub fn insert_boot_node(&mut self, boot_node: BootNode) {
|
||||
self.bootstrap_nodes.insert(boot_node);
|
||||
@@ -333,11 +345,11 @@ impl Config {
|
||||
/// socket, if both IPv4 and v6 are configured. This socket will be advertised to peers in the
|
||||
/// local [`Enr`](discv5::enr::Enr).
|
||||
pub fn discovery_socket(&self) -> SocketAddr {
|
||||
match self.discv5_config.listen_config {
|
||||
ListenConfig::Ipv4 { ip, port } => (ip, port).into(),
|
||||
ListenConfig::Ipv6 { ip, port } => (ip, port).into(),
|
||||
ListenConfig::DualStack { ipv6, ipv6_port, .. } => (ipv6, ipv6_port).into(),
|
||||
}
|
||||
// Prefer v6 when both are configured (matches original `DualStack` behavior).
|
||||
ipv6(&self.discv5_config.listen_config)
|
||||
.map(SocketAddr::V6)
|
||||
.or_else(|| ipv4(&self.discv5_config.listen_config).map(SocketAddr::V4))
|
||||
.unwrap_or_else(|| SocketAddr::from((std::net::Ipv4Addr::UNSPECIFIED, 0)))
|
||||
}
|
||||
|
||||
/// Returns the `RLPx` (TCP) socket contained in the [`discv5::Config`]. This socket will be
|
||||
@@ -348,24 +360,32 @@ impl Config {
|
||||
}
|
||||
|
||||
/// Returns the IPv4 discovery socket if one is configured.
|
||||
pub const fn ipv4(listen_config: &ListenConfig) -> Option<SocketAddrV4> {
|
||||
pub fn ipv4(listen_config: &ListenConfig) -> Option<SocketAddrV4> {
|
||||
match listen_config {
|
||||
ListenConfig::Ipv4 { ip, port } |
|
||||
ListenConfig::DualStack { ipv4: ip, ipv4_port: port, .. } => {
|
||||
Some(SocketAddrV4::new(*ip, *port))
|
||||
}
|
||||
ListenConfig::Ipv6 { .. } => None,
|
||||
ListenConfig::FromSockets { ipv4: Some(s), .. } => match s.local_addr().ok()? {
|
||||
SocketAddr::V4(addr) => Some(addr),
|
||||
SocketAddr::V6(_) => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the IPv6 discovery socket if one is configured.
|
||||
pub const fn ipv6(listen_config: &ListenConfig) -> Option<SocketAddrV6> {
|
||||
pub fn ipv6(listen_config: &ListenConfig) -> Option<SocketAddrV6> {
|
||||
match listen_config {
|
||||
ListenConfig::Ipv4 { .. } => None,
|
||||
ListenConfig::Ipv6 { ip, port } |
|
||||
ListenConfig::DualStack { ipv6: ip, ipv6_port: port, .. } => {
|
||||
Some(SocketAddrV6::new(*ip, *port, 0, 0))
|
||||
}
|
||||
ListenConfig::FromSockets { ipv6: Some(s), .. } => match s.local_addr().ok()? {
|
||||
SocketAddr::V6(addr) => Some(addr),
|
||||
SocketAddr::V4(_) => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ use std::{
|
||||
|
||||
use ::enr::Enr;
|
||||
use alloy_primitives::bytes::Bytes;
|
||||
use discv5::ListenConfig;
|
||||
use enr::{discv4_id_to_discv5_id, EnrCombinedKeyWrapper};
|
||||
use futures::future::join_all;
|
||||
use itertools::Itertools;
|
||||
@@ -247,7 +246,9 @@ impl Discv5 {
|
||||
match update {
|
||||
discv5::Event::SocketUpdated(_) | discv5::Event::TalkRequest(_) |
|
||||
// `Discovered` not unique discovered peers
|
||||
discv5::Event::Discovered(_) => None,
|
||||
discv5::Event::Discovered(_) |
|
||||
// Unrecognized frames are handled separately by the discovery layer
|
||||
discv5::Event::UnrecognizedFrame(_) => None,
|
||||
discv5::Event::NodeInserted { .. } => {
|
||||
|
||||
// node has been inserted into kbuckets
|
||||
@@ -472,39 +473,33 @@ pub fn build_local_enr(
|
||||
|
||||
let Config { discv5_config, fork, tcp_socket, other_enr_kv_pairs, .. } = config;
|
||||
|
||||
let socket = match discv5_config.listen_config {
|
||||
ListenConfig::Ipv4 { ip, port } => {
|
||||
if ip != Ipv4Addr::UNSPECIFIED {
|
||||
builder.ip4(ip);
|
||||
}
|
||||
builder.udp4(port);
|
||||
builder.tcp4(tcp_socket.port());
|
||||
let socket = {
|
||||
let v4 = crate::config::ipv4(&discv5_config.listen_config);
|
||||
let v6 = crate::config::ipv6(&discv5_config.listen_config);
|
||||
|
||||
(ip, port).into()
|
||||
}
|
||||
ListenConfig::Ipv6 { ip, port } => {
|
||||
if ip != Ipv6Addr::UNSPECIFIED {
|
||||
builder.ip6(ip);
|
||||
if let Some(addr) = v4 {
|
||||
if *addr.ip() != Ipv4Addr::UNSPECIFIED {
|
||||
builder.ip4(*addr.ip());
|
||||
}
|
||||
builder.udp6(port);
|
||||
builder.udp4(addr.port());
|
||||
}
|
||||
if let Some(addr) = v6 {
|
||||
if *addr.ip() != Ipv6Addr::UNSPECIFIED {
|
||||
builder.ip6(*addr.ip());
|
||||
}
|
||||
builder.udp6(addr.port());
|
||||
}
|
||||
// Advertise tcp4 when v4 is configured, else tcp6.
|
||||
if v4.is_some() {
|
||||
builder.tcp4(tcp_socket.port());
|
||||
} else if v6.is_some() {
|
||||
builder.tcp6(tcp_socket.port());
|
||||
|
||||
(ip, port).into()
|
||||
}
|
||||
ListenConfig::DualStack { ipv4, ipv4_port, ipv6, ipv6_port } => {
|
||||
if ipv4 != Ipv4Addr::UNSPECIFIED {
|
||||
builder.ip4(ipv4);
|
||||
}
|
||||
builder.udp4(ipv4_port);
|
||||
builder.tcp4(tcp_socket.port());
|
||||
|
||||
if ipv6 != Ipv6Addr::UNSPECIFIED {
|
||||
builder.ip6(ipv6);
|
||||
}
|
||||
builder.udp6(ipv6_port);
|
||||
|
||||
(ipv6, ipv6_port).into()
|
||||
}
|
||||
// Prefer v6 when both are configured
|
||||
v6.map(SocketAddr::V6)
|
||||
.or_else(|| v4.map(SocketAddr::V4))
|
||||
.unwrap_or_else(|| SocketAddr::from((Ipv4Addr::UNSPECIFIED, 0)))
|
||||
};
|
||||
|
||||
let rlpx_ip_mode = if tcp_socket.is_ipv4() { IpMode::Ip4 } else { IpMode::Ip6 };
|
||||
@@ -711,6 +706,7 @@ mod test {
|
||||
#![allow(deprecated)]
|
||||
use super::*;
|
||||
use ::enr::{CombinedKey, EnrKey};
|
||||
use discv5::ListenConfig;
|
||||
use rand_08::thread_rng;
|
||||
use reth_chainspec::MAINNET;
|
||||
use std::{
|
||||
|
||||
@@ -7,13 +7,14 @@ use alloy_primitives::{
|
||||
Bytes, TxHash, B256, U128,
|
||||
};
|
||||
use alloy_rlp::{
|
||||
Decodable, Encodable, RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper,
|
||||
Decodable, Encodable, Header, RlpDecodable, RlpDecodableWrapper, RlpEncodable,
|
||||
RlpEncodableWrapper,
|
||||
};
|
||||
use core::{fmt::Debug, mem};
|
||||
use derive_more::{Constructor, Deref, DerefMut, From, IntoIterator};
|
||||
use reth_codecs_derive::{add_arbitrary_tests, generate_tests};
|
||||
use reth_ethereum_primitives::TransactionSigned;
|
||||
use reth_primitives_traits::{Block, SignedTransaction};
|
||||
use reth_primitives_traits::{Block, InMemorySize, SignedTransaction};
|
||||
|
||||
/// This informs peers of new blocks that have appeared on the network.
|
||||
#[derive(
|
||||
@@ -143,6 +144,53 @@ impl<T> From<Transactions<T>> for Vec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Decodable + InMemorySize> Transactions<T> {
|
||||
/// Decodes the RLP list of transactions, stopping once the cumulative
|
||||
/// [`InMemorySize`] of decoded transactions exceeds `memory_budget` bytes.
|
||||
/// Any remaining transactions in the payload are skipped.
|
||||
pub fn decode_with_memory_budget(
|
||||
buf: &mut &[u8],
|
||||
memory_budget: usize,
|
||||
) -> alloy_rlp::Result<Self> {
|
||||
decode_list_with_memory_budget(buf, memory_budget).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Decodes an RLP list, stopping once the cumulative [`InMemorySize`] of decoded items exceeds
|
||||
/// `memory_budget` bytes. Any remaining items in the payload are skipped.
|
||||
pub fn decode_list_with_memory_budget<T: Decodable + InMemorySize>(
|
||||
buf: &mut &[u8],
|
||||
memory_budget: usize,
|
||||
) -> alloy_rlp::Result<Vec<T>> {
|
||||
let header = Header::decode(buf)?;
|
||||
if !header.list {
|
||||
return Err(alloy_rlp::Error::UnexpectedString);
|
||||
}
|
||||
if buf.len() < header.payload_length {
|
||||
return Err(alloy_rlp::Error::InputTooShort);
|
||||
}
|
||||
|
||||
let (payload, rest) = buf.split_at(header.payload_length);
|
||||
let mut payload = payload;
|
||||
|
||||
let mut txs = Vec::new();
|
||||
let mut total_size = 0usize;
|
||||
|
||||
while !payload.is_empty() {
|
||||
let item = T::decode(&mut payload)?;
|
||||
total_size = total_size.saturating_add(item.size());
|
||||
|
||||
if total_size > memory_budget {
|
||||
break;
|
||||
}
|
||||
|
||||
txs.push(item);
|
||||
}
|
||||
|
||||
*buf = rest;
|
||||
Ok(txs)
|
||||
}
|
||||
|
||||
/// Same as [`Transactions`] but this is intended as egress message send from local to _many_ peers.
|
||||
///
|
||||
/// The list of transactions is constructed on per-peers basis, but the underlying transaction
|
||||
@@ -837,6 +885,19 @@ pub struct BlockRangeUpdate {
|
||||
pub latest_hash: B256,
|
||||
}
|
||||
|
||||
impl InMemorySize for NewPooledTransactionHashes {
|
||||
fn size(&self) -> usize {
|
||||
match self {
|
||||
Self::Eth66(msg) => msg.0.len() * core::mem::size_of::<B256>(),
|
||||
Self::Eth68(msg) => {
|
||||
msg.types.len() * core::mem::size_of::<u8>() +
|
||||
msg.sizes.len() * core::mem::size_of::<usize>() +
|
||||
msg.hashes.len() * core::mem::size_of::<B256>()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -28,6 +28,15 @@ use core::fmt::Debug;
|
||||
// https://github.com/ethereum/go-ethereum/blob/30602163d5d8321fbc68afdcbbaf2362b2641bde/eth/protocols/eth/protocol.go#L50
|
||||
pub const MAX_MESSAGE_SIZE: usize = 10 * 1024 * 1024;
|
||||
|
||||
/// Multiplier applied to `max_message_size` to derive the in-memory budget for decoding
|
||||
/// `Transactions` and `PooledTransactions` messages.
|
||||
///
|
||||
/// Decoded transactions expand relative to their RLP encoding due to struct overhead and heap
|
||||
/// allocations. With many peers in flight this can cause significant memory pressure, so we
|
||||
/// stop decoding once the cumulative in-memory size of decoded transactions exceeds
|
||||
/// `max_message_size * TX_MEMORY_BUDGET_MULTIPLIER`. Remaining transactions are silently dropped.
|
||||
pub const TX_MEMORY_BUDGET_MULTIPLIER: usize = 2;
|
||||
|
||||
/// Error when sending/receiving a message
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum MessageError {
|
||||
@@ -87,6 +96,19 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
///
|
||||
/// This will enforce decoding according to the given [`EthVersion`] of the connection.
|
||||
pub fn decode_message(version: EthVersion, buf: &mut &[u8]) -> Result<Self, MessageError> {
|
||||
Self::decode_message_with_tx_memory_budget(version, buf, usize::MAX)
|
||||
}
|
||||
|
||||
/// Like [`Self::decode_message`], but caps the cumulative in-memory size of decoded
|
||||
/// transactions in `Transactions` and `PooledTransactions` messages. Once exceeded,
|
||||
/// remaining transactions are silently dropped.
|
||||
///
|
||||
/// Use [`TX_MEMORY_BUDGET_MULTIPLIER`] to derive a reasonable default.
|
||||
pub fn decode_message_with_tx_memory_budget(
|
||||
version: EthVersion,
|
||||
buf: &mut &[u8],
|
||||
tx_memory_budget: usize,
|
||||
) -> Result<Self, MessageError> {
|
||||
let message_type = EthMessageID::decode(buf)?;
|
||||
|
||||
// For EIP-7642 (https://github.com/ethereum/EIPs/blob/master/EIPS/eip-7642.md):
|
||||
@@ -103,7 +125,9 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
EthMessageID::NewBlock => {
|
||||
EthMessage::NewBlock(Box::new(N::NewBlockPayload::decode(buf)?))
|
||||
}
|
||||
EthMessageID::Transactions => EthMessage::Transactions(Transactions::decode(buf)?),
|
||||
EthMessageID::Transactions => EthMessage::Transactions(
|
||||
Transactions::decode_with_memory_budget(buf, tx_memory_budget)?,
|
||||
),
|
||||
EthMessageID::NewPooledTransactionHashes => {
|
||||
if version >= EthVersion::Eth68 {
|
||||
EthMessage::NewPooledTransactionHashes68(NewPooledTransactionHashes68::decode(
|
||||
@@ -123,7 +147,9 @@ impl<N: NetworkPrimitives> ProtocolMessage<N> {
|
||||
EthMessage::GetPooledTransactions(RequestPair::decode(buf)?)
|
||||
}
|
||||
EthMessageID::PooledTransactions => {
|
||||
EthMessage::PooledTransactions(RequestPair::decode(buf)?)
|
||||
EthMessage::PooledTransactions(RequestPair::decode_with(buf, |buf| {
|
||||
PooledTransactions::decode_with_memory_budget(buf, tx_memory_budget)
|
||||
})?)
|
||||
}
|
||||
EthMessageID::GetNodeData => {
|
||||
if version >= EthVersion::Eth67 {
|
||||
@@ -732,6 +758,25 @@ impl<T> RequestPair<T> {
|
||||
let Self { request_id, message } = self;
|
||||
RequestPair { request_id, message: f(message) }
|
||||
}
|
||||
|
||||
/// Decodes the request id and then decodes the message payload using `decode_msg`.
|
||||
pub fn decode_with<F>(buf: &mut &[u8], decode_msg: F) -> alloy_rlp::Result<Self>
|
||||
where
|
||||
F: FnOnce(&mut &[u8]) -> alloy_rlp::Result<T>,
|
||||
{
|
||||
let header = Header::decode(buf)?;
|
||||
|
||||
let initial_length = buf.len();
|
||||
let request_id = u64::decode(buf)?;
|
||||
let message = decode_msg(buf)?;
|
||||
|
||||
let consumed_len = initial_length - buf.len();
|
||||
if consumed_len != header.payload_length {
|
||||
return Err(alloy_rlp::Error::UnexpectedLength)
|
||||
}
|
||||
|
||||
Ok(Self { request_id, message })
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows messages with request ids to be serialized into RLP bytes.
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
//! Implements the `GetPooledTransactions` and `PooledTransactions` message types.
|
||||
|
||||
use crate::broadcast::decode_list_with_memory_budget;
|
||||
use alloc::vec::Vec;
|
||||
use alloy_consensus::transaction::PooledTransaction;
|
||||
use alloy_eips::eip2718::Encodable2718;
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rlp::{RlpDecodableWrapper, RlpEncodableWrapper};
|
||||
use alloy_rlp::{Decodable, RlpDecodableWrapper, RlpEncodableWrapper};
|
||||
use derive_more::{Constructor, Deref, IntoIterator};
|
||||
use reth_codecs_derive::add_arbitrary_tests;
|
||||
use reth_primitives_traits::InMemorySize;
|
||||
|
||||
/// A list of transaction hashes that the peer would like transaction bodies for.
|
||||
#[derive(
|
||||
@@ -37,6 +39,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl InMemorySize for GetPooledTransactions {
|
||||
fn size(&self) -> usize {
|
||||
self.0.len() * core::mem::size_of::<B256>()
|
||||
}
|
||||
}
|
||||
|
||||
/// The response to [`GetPooledTransactions`], containing the transaction bodies associated with
|
||||
/// the requested hashes.
|
||||
///
|
||||
@@ -62,6 +70,18 @@ pub struct PooledTransactions<T = PooledTransaction>(
|
||||
pub Vec<T>,
|
||||
);
|
||||
|
||||
impl<T: Decodable + InMemorySize> PooledTransactions<T> {
|
||||
/// Decodes the RLP list of transactions, stopping once the cumulative
|
||||
/// [`InMemorySize`] of decoded transactions exceeds `memory_budget` bytes.
|
||||
/// Any remaining transactions in the payload are skipped.
|
||||
pub fn decode_with_memory_budget(
|
||||
buf: &mut &[u8],
|
||||
memory_budget: usize,
|
||||
) -> alloy_rlp::Result<Self> {
|
||||
decode_list_with_memory_budget(buf, memory_budget).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Encodable2718> PooledTransactions<T> {
|
||||
/// Returns an iterator over the transaction hashes in this response.
|
||||
pub fn hashes(&self) -> impl Iterator<Item = B256> + '_ {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
use super::message::MAX_MESSAGE_SIZE;
|
||||
use crate::{
|
||||
message::{EthBroadcastMessage, ProtocolBroadcastMessage},
|
||||
message::{EthBroadcastMessage, ProtocolBroadcastMessage, TX_MEMORY_BUDGET_MULTIPLIER},
|
||||
EthMessage, EthMessageID, EthNetworkPrimitives, EthVersion, NetworkPrimitives, ProtocolMessage,
|
||||
RawCapabilityMessage, SnapProtocolMessage, SnapVersion,
|
||||
};
|
||||
@@ -298,7 +298,11 @@ where
|
||||
// See also <https://github.com/paradigmxyz/reth/blob/main/crates/net/eth-wire/src/capability.rs#L272-L283>.
|
||||
if message_id <= EthMessageID::max(self.eth_version) {
|
||||
let mut buf = bytes.as_ref();
|
||||
match ProtocolMessage::decode_message(self.eth_version, &mut buf) {
|
||||
match ProtocolMessage::decode_message_with_tx_memory_budget(
|
||||
self.eth_version,
|
||||
&mut buf,
|
||||
self.max_message_size * TX_MEMORY_BUDGET_MULTIPLIER,
|
||||
) {
|
||||
Ok(protocol_msg) => {
|
||||
if matches!(protocol_msg.message, EthMessage::Status(_)) {
|
||||
return Err(EthSnapStreamError::StatusNotInHandshake);
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
use crate::{
|
||||
errors::{EthHandshakeError, EthStreamError},
|
||||
handshake::EthereumEthHandshake,
|
||||
message::{EthBroadcastMessage, EthMessageID, ProtocolBroadcastMessage, MAX_MESSAGE_SIZE},
|
||||
message::{
|
||||
EthBroadcastMessage, ProtocolBroadcastMessage, MAX_MESSAGE_SIZE,
|
||||
TX_MEMORY_BUDGET_MULTIPLIER,
|
||||
},
|
||||
p2pstream::HANDSHAKE_TIMEOUT,
|
||||
CanDisconnect, DisconnectReason, EthMessage, EthNetworkPrimitives, EthVersion, ProtocolMessage,
|
||||
UnifiedStatus,
|
||||
@@ -16,7 +19,7 @@ use alloy_primitives::bytes::{Bytes, BytesMut};
|
||||
use alloy_rlp::Encodable;
|
||||
use futures::{ready, Sink, SinkExt};
|
||||
use pin_project::pin_project;
|
||||
use reth_eth_wire_types::{NetworkPrimitives, RawCapabilityMessage};
|
||||
use reth_eth_wire_types::{EthMessageID, NetworkPrimitives, RawCapabilityMessage};
|
||||
use reth_ethereum_forks::ForkFilter;
|
||||
use std::{
|
||||
future::Future,
|
||||
@@ -158,7 +161,11 @@ where
|
||||
return Err(EthStreamError::UnsupportedMessage { message_id: id });
|
||||
}
|
||||
|
||||
let msg = match ProtocolMessage::decode_message(self.version, &mut bytes.as_ref()) {
|
||||
let msg = match ProtocolMessage::decode_message_with_tx_memory_budget(
|
||||
self.version,
|
||||
&mut bytes.as_ref(),
|
||||
self.max_message_size * TX_MEMORY_BUDGET_MULTIPLIER,
|
||||
) {
|
||||
Ok(m) => m,
|
||||
Err(err) => {
|
||||
let msg = if bytes.len() > 50 {
|
||||
|
||||
@@ -50,6 +50,7 @@ reth-ethereum-primitives.workspace = true
|
||||
futures.workspace = true
|
||||
pin-project.workspace = true
|
||||
tokio = { workspace = true, features = ["io-util", "net", "macros", "rt-multi-thread", "time"] }
|
||||
socket2 = { workspace = true, features = ["all"] }
|
||||
tokio-stream.workspace = true
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use crate::{
|
||||
eth_requests::EthRequestHandler,
|
||||
metrics::NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
transactions::{
|
||||
config::{
|
||||
AnnouncementFilteringPolicy, StrictEthAnnouncementFilter, TransactionPropagationKind,
|
||||
@@ -12,6 +13,7 @@ use crate::{
|
||||
NetworkHandle, NetworkManager,
|
||||
};
|
||||
use reth_eth_wire::{EthNetworkPrimitives, NetworkPrimitives};
|
||||
use reth_metrics::common::mpsc::memory_bounded_channel;
|
||||
use reth_network_api::test_utils::PeersHandleProvider;
|
||||
use reth_storage_api::BalProvider;
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
@@ -122,7 +124,10 @@ impl<Tx, Eth, N: NetworkPrimitives> NetworkBuilder<Tx, Eth, N> {
|
||||
announcement_policy: A,
|
||||
) -> NetworkBuilder<TransactionsManager<Pool, N>, Eth, N> {
|
||||
let Self { mut network, request_handler, .. } = self;
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
transactions_manager_config.tx_channel_memory_limit_bytes,
|
||||
NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
);
|
||||
network.set_transactions(tx);
|
||||
let handle = network.handle().clone();
|
||||
let policies = NetworkPolicies::new(propagation_policy, announcement_policy);
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
sync::Arc,
|
||||
task::{ready, Context, Poll},
|
||||
};
|
||||
use tokio::{sync::mpsc, task::JoinHandle};
|
||||
use tokio::{net::UdpSocket, sync::mpsc, task::JoinHandle};
|
||||
use tokio_stream::{wrappers::ReceiverStream, Stream};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
@@ -54,6 +54,9 @@ pub struct Discovery {
|
||||
discv5: Option<Discv5>,
|
||||
/// All KAD table updates from the discv5 service.
|
||||
discv5_updates: Option<ReceiverStream<discv5::Event>>,
|
||||
/// Background task that, in shared-port mode, drains `UnrecognizedFrame`s from discv5 and
|
||||
/// feeds them into the discv4 ingress so packets advance without polling `Discovery`.
|
||||
_discv5_forwarder: Option<JoinHandle<()>>,
|
||||
/// Handler to interact with the DNS discovery service
|
||||
_dns_discovery: Option<DnsDiscoveryHandle>,
|
||||
/// Updates from the DNS discovery service.
|
||||
@@ -76,39 +79,138 @@ impl Discovery {
|
||||
discovery_v4_addr: SocketAddr,
|
||||
sk: SecretKey,
|
||||
discv4_config: Option<Discv4Config>,
|
||||
discv5_config: Option<reth_discv5::Config>, // contains discv5 listen address
|
||||
mut discv5_config: Option<reth_discv5::Config>, // contains discv5 listen address
|
||||
dns_discovery_config: Option<DnsDiscoveryConfig>,
|
||||
) -> Result<Self, NetworkError> {
|
||||
// setup discv4 with the discovery address and tcp port
|
||||
let local_enr =
|
||||
NodeRecord::from_secret_key(discovery_v4_addr, &sk).with_tcp_port(tcp_addr.port());
|
||||
|
||||
let discv4_future = async {
|
||||
let Some(disc_config) = discv4_config else { return Ok((None, None, None)) };
|
||||
let (discv4, mut discv4_service) =
|
||||
Discv4::bind(discovery_v4_addr, local_enr, sk, disc_config).await.map_err(
|
||||
|err| {
|
||||
// For IPv6 we set IPV6_V6ONLY=true so an IPv4 sibling socket on the same port doesn't
|
||||
// clash with the IPv6 one (Linux's default of V6ONLY=0 has IPv6 also claim the IPv4
|
||||
// port via mapped addresses), matching how discv5 binds its `DualStack` sockets.
|
||||
let bind_socket = async |addr: SocketAddr| {
|
||||
let result = match addr {
|
||||
SocketAddr::V4(_) => UdpSocket::bind(addr).await,
|
||||
SocketAddr::V6(_) => {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
(|| {
|
||||
let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))?;
|
||||
socket.set_only_v6(true)?;
|
||||
socket.set_nonblocking(true)?;
|
||||
socket.bind(&addr.into())?;
|
||||
UdpSocket::from_std(socket.into())
|
||||
})()
|
||||
}
|
||||
};
|
||||
result
|
||||
.map(Arc::new)
|
||||
.map_err(|err| NetworkError::from_io_error(err, ServiceKind::Discovery(addr)))
|
||||
};
|
||||
|
||||
// In shared-port mode, bind the shared socket and start discv4 without its own receive
|
||||
// loop. Unrecognized frames from discv5 will be forwarded to the ingress handler.
|
||||
let (discv4, discv4_updates, _discv4_service, discv4_ingress, shared_socket) =
|
||||
if let Some(config) = discv4_config {
|
||||
if let Some(discv5_config) = &mut discv5_config &&
|
||||
discv5_config.has_matching_socket(discovery_v4_addr)
|
||||
{
|
||||
let socket = bind_socket(discovery_v4_addr).await?;
|
||||
|
||||
let (discv4, mut discv4_service, ingress) = Discv4::bind_shared(
|
||||
socket.clone(),
|
||||
local_enr,
|
||||
sk,
|
||||
config,
|
||||
)
|
||||
.map_err(|err| {
|
||||
NetworkError::from_io_error(err, ServiceKind::Discovery(discovery_v4_addr))
|
||||
},
|
||||
)?;
|
||||
let discv4_updates = discv4_service.update_stream();
|
||||
// spawn the service
|
||||
let discv4_service = discv4_service.spawn();
|
||||
})?;
|
||||
|
||||
debug!(target:"net", ?discovery_v4_addr, "started discovery v4");
|
||||
let discv4_updates = discv4_service.update_stream();
|
||||
let discv4_service = discv4_service.spawn();
|
||||
debug!(target:"net", ?discovery_v4_addr, "started discovery v4 (shared port)");
|
||||
(
|
||||
Some(discv4),
|
||||
Some(discv4_updates),
|
||||
Some(discv4_service),
|
||||
Some(ingress),
|
||||
Some(socket),
|
||||
)
|
||||
} else {
|
||||
let (discv4, mut discv4_service) =
|
||||
Discv4::bind(discovery_v4_addr, local_enr, sk, config).await.map_err(
|
||||
|err| {
|
||||
NetworkError::from_io_error(
|
||||
err,
|
||||
ServiceKind::Discovery(discovery_v4_addr),
|
||||
)
|
||||
},
|
||||
)?;
|
||||
let discv4_updates = discv4_service.update_stream();
|
||||
// spawn the service
|
||||
let discv4_service = discv4_service.spawn();
|
||||
|
||||
Ok((Some(discv4), Some(discv4_updates), Some(discv4_service)))
|
||||
};
|
||||
debug!(target:"net", ?discovery_v4_addr, "started discovery v4");
|
||||
|
||||
(Some(discv4), Some(discv4_updates), Some(discv4_service), None, None)
|
||||
}
|
||||
} else {
|
||||
(None, None, None, None, None)
|
||||
};
|
||||
|
||||
// Start discv5, wiring in the shared socket if in shared-port mode.
|
||||
let (discv5, discv5_updates) = if let Some(mut config) = discv5_config {
|
||||
if let Some(socket) = shared_socket {
|
||||
let discv5_cfg = config.discv5_config_mut();
|
||||
|
||||
// The shared socket covers discv4's address family; bind the opposite family
|
||||
// only if discv5 was configured for dual-stack.
|
||||
let (mut ipv4, mut ipv6) = (None, None);
|
||||
if discovery_v4_addr.is_ipv4() {
|
||||
ipv4 = Some(socket);
|
||||
if let Some(addr) = reth_discv5::config::ipv6(&discv5_cfg.listen_config) {
|
||||
ipv6 = Some(bind_socket(SocketAddr::V6(addr)).await?);
|
||||
}
|
||||
} else {
|
||||
ipv6 = Some(socket);
|
||||
if let Some(addr) = reth_discv5::config::ipv4(&discv5_cfg.listen_config) {
|
||||
ipv4 = Some(bind_socket(SocketAddr::V4(addr)).await?);
|
||||
}
|
||||
}
|
||||
|
||||
discv5_cfg.listen_config = discv5::ListenConfig::FromSockets { ipv4, ipv6 };
|
||||
}
|
||||
|
||||
let discv5_future = async {
|
||||
let Some(config) = discv5_config else { return Ok::<_, NetworkError>((None, None)) };
|
||||
let (discv5, discv5_updates) = Discv5::start(&sk, config).await?;
|
||||
debug!(target:"net", discovery_v5_enr=? discv5.local_enr(), "started discovery v5");
|
||||
Ok((Some(discv5), Some(discv5_updates.into())))
|
||||
debug!(target:"net", discovery_v5_enr=?discv5.local_enr(), "started discovery v5");
|
||||
(Some(discv5), Some(discv5_updates))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
let ((discv4, discv4_updates, _discv4_service), (discv5, discv5_updates)) =
|
||||
tokio::try_join!(discv4_future, discv5_future)?;
|
||||
// In shared-port mode, spawn a task that peels `UnrecognizedFrame` events off the discv5
|
||||
// update stream and feeds them into discv4's ingress. Other events are forwarded through
|
||||
// a new channel that `Discovery::poll` reads. This keeps both protocols moving without
|
||||
// requiring the main `Discovery::poll` loop to be driven for packets to be routed.
|
||||
let (discv5_updates, _discv5_forwarder) = match (discv4_ingress, discv5_updates) {
|
||||
(Some(mut ingress), Some(mut updates)) => {
|
||||
let (tx, rx) = mpsc::channel(updates.max_capacity());
|
||||
let handle = tokio::spawn(async move {
|
||||
while let Some(event) = updates.recv().await {
|
||||
if let discv5::Event::UnrecognizedFrame(frame) = &event {
|
||||
ingress.handle_packet(&frame.packet, frame.src_address).await;
|
||||
continue;
|
||||
}
|
||||
if tx.send(event).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
(Some(ReceiverStream::new(rx)), Some(handle))
|
||||
}
|
||||
(_, updates) => (updates.map(ReceiverStream::new), None),
|
||||
};
|
||||
|
||||
// setup DNS discovery
|
||||
let (_dns_discovery, dns_discovery_updates, _dns_disc_service) =
|
||||
@@ -132,6 +234,7 @@ impl Discovery {
|
||||
_discv4_service,
|
||||
discv5,
|
||||
discv5_updates,
|
||||
_discv5_forwarder,
|
||||
discovered_nodes: LruMap::new(DEFAULT_MAX_CAPACITY_DISCOVERED_PEERS_CACHE),
|
||||
queued_events: Default::default(),
|
||||
_dns_disc_service,
|
||||
@@ -309,6 +412,9 @@ impl Drop for Discovery {
|
||||
if let Some(handle) = self._discv4_service.take() {
|
||||
handle.abort();
|
||||
}
|
||||
if let Some(handle) = self._discv5_forwarder.take() {
|
||||
handle.abort();
|
||||
}
|
||||
if let Some(handle) = self._dns_disc_service.take() {
|
||||
handle.abort();
|
||||
}
|
||||
@@ -342,10 +448,11 @@ impl Discovery {
|
||||
},
|
||||
discv4: Default::default(),
|
||||
discv4_updates: Default::default(),
|
||||
_discv4_service: Default::default(),
|
||||
_discv5_forwarder: None,
|
||||
discv5: None,
|
||||
discv5_updates: None,
|
||||
queued_events: Default::default(),
|
||||
_discv4_service: Default::default(),
|
||||
_dns_discovery: None,
|
||||
dns_discovery_updates: None,
|
||||
_dns_disc_service: None,
|
||||
@@ -487,4 +594,179 @@ mod tests {
|
||||
assert_eq!(1, node_1.discovered_nodes.len());
|
||||
assert_eq!(1, node_2.discovered_nodes.len());
|
||||
}
|
||||
|
||||
/// Starts a discovery node with discv4 and discv5 sharing the same UDP port.
|
||||
async fn start_shared_port_node(port: u16) -> Discovery {
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let disc_addr: SocketAddr = format!("127.0.0.1:{port}").parse().unwrap();
|
||||
// Use a non-zero TCP port so the node record isn't filtered out by
|
||||
// `on_node_record_update` (which drops peers with tcp port == 0).
|
||||
let tcp_addr: SocketAddr = "127.0.0.1:30303".parse().unwrap();
|
||||
|
||||
let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
|
||||
|
||||
let discv5_listen_config = discv5::ListenConfig::from(disc_addr);
|
||||
let discv5_config = reth_discv5::Config::builder(tcp_addr)
|
||||
.discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
|
||||
.build();
|
||||
|
||||
// Both protocols use the same address, triggering shared-port mode
|
||||
Discovery::new(
|
||||
tcp_addr,
|
||||
disc_addr,
|
||||
secret_key,
|
||||
Some(discv4_config),
|
||||
Some(discv5_config),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("should start with shared port")
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_setup() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
// Use port 0 so the OS picks a free port
|
||||
let node = start_shared_port_node(0).await;
|
||||
|
||||
// Both protocols should be active
|
||||
assert!(node.discv4.is_some(), "discv4 should be running");
|
||||
assert!(node.discv5.is_some(), "discv5 should be running");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_discv5_discovery() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let mut node_1 = start_shared_port_node(0).await;
|
||||
let mut node_2 = start_shared_port_node(0).await;
|
||||
|
||||
let discv5_enr_1 = node_1.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
|
||||
let discv5_enr_2 = node_2.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
|
||||
|
||||
let peer_id_1 = enr_to_discv4_id(&discv5_enr_1).unwrap();
|
||||
let peer_id_2 = enr_to_discv4_id(&discv5_enr_2).unwrap();
|
||||
|
||||
// Add node_2's ENR to node_1's discv5 kbuckets and trigger a ping to establish a session.
|
||||
// send_ping awaits the PONG, so the handshake completes before we poll the Discovery
|
||||
// stream. The discv5 service runs its own background task.
|
||||
node_1.add_discv5_node(EnrCombinedKeyWrapper(discv5_enr_2.clone()).into()).unwrap();
|
||||
node_1
|
||||
.discv5
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.with_discv5(|discv5| discv5.send_ping(discv5_enr_2))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Both SessionEstablished events should now be buffered in the update channels.
|
||||
// Drive both nodes concurrently to collect them.
|
||||
let mut event_1 = None;
|
||||
let mut event_2 = None;
|
||||
let timeout = tokio::time::sleep(std::time::Duration::from_secs(5));
|
||||
tokio::pin!(timeout);
|
||||
loop {
|
||||
tokio::select! {
|
||||
ev = node_1.next(), if event_1.is_none() => {
|
||||
event_1 = ev;
|
||||
}
|
||||
ev = node_2.next(), if event_2.is_none() => {
|
||||
event_2 = ev;
|
||||
}
|
||||
_ = &mut timeout => {
|
||||
panic!("timed out waiting for discv5 discovery events");
|
||||
}
|
||||
}
|
||||
if event_1.is_some() && event_2.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(matches!(
|
||||
event_1.unwrap(),
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, .. })
|
||||
if peer_id == peer_id_2
|
||||
));
|
||||
assert!(matches!(
|
||||
event_2.unwrap(),
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, .. })
|
||||
if peer_id == peer_id_1
|
||||
));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_discv4_discovery() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let mut node_1 = start_shared_port_node(0).await;
|
||||
let mut node_2 = start_shared_port_node(0).await;
|
||||
|
||||
let enr_1 = node_1.discv4.as_ref().unwrap().node_record();
|
||||
let enr_2 = node_2.discv4.as_ref().unwrap().node_record();
|
||||
|
||||
// Introduce node_2 to node_1 via discv4
|
||||
node_1.add_discv4_node(enr_2);
|
||||
|
||||
// Both nodes should discover each other via discv4 ping/pong
|
||||
let event_1 = node_1.next().await.unwrap();
|
||||
let event_2 = node_2.next().await.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
|
||||
peer_id: enr_2.id,
|
||||
addr: PeerAddr::new(enr_2.tcp_addr(), Some(enr_2.udp_addr())),
|
||||
fork_id: None
|
||||
}),
|
||||
event_1
|
||||
);
|
||||
assert_eq!(
|
||||
DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
|
||||
peer_id: enr_1.id,
|
||||
addr: PeerAddr::new(enr_1.tcp_addr(), Some(enr_1.udp_addr())),
|
||||
fork_id: None
|
||||
}),
|
||||
event_2
|
||||
);
|
||||
}
|
||||
|
||||
/// Verifies that shared-port mode binds correctly when discv5 is configured for dual-stack.
|
||||
/// On Linux this exercises the IPv6 V6ONLY path: without it, the IPv4 sibling would clash
|
||||
/// with the IPv6 socket bound to the same port.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_shared_port_dual_stack() {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
// Find a port that's free on the v4 wildcard so we can use it for both v4 and v6.
|
||||
let probe = UdpSocket::bind("0.0.0.0:0").await.expect("probe bind");
|
||||
let port = probe.local_addr().unwrap().port();
|
||||
drop(probe);
|
||||
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let v4_addr: SocketAddr = format!("0.0.0.0:{port}").parse().unwrap();
|
||||
let tcp_addr: SocketAddr = "0.0.0.0:30303".parse().unwrap();
|
||||
|
||||
let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
|
||||
|
||||
let discv5_listen_config = discv5::ListenConfig::DualStack {
|
||||
ipv4: std::net::Ipv4Addr::UNSPECIFIED,
|
||||
ipv4_port: port,
|
||||
ipv6: std::net::Ipv6Addr::UNSPECIFIED,
|
||||
ipv6_port: port,
|
||||
};
|
||||
let discv5_config = reth_discv5::Config::builder(tcp_addr)
|
||||
.discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
|
||||
.build();
|
||||
|
||||
Discovery::new(
|
||||
tcp_addr,
|
||||
v4_addr,
|
||||
secret_key,
|
||||
Some(discv4_config),
|
||||
Some(discv5_config),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("discovery should start with shared port + dual-stack");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,11 @@ pub const MAX_HEADERS_SERVE: usize = 1024;
|
||||
/// `SOFT_RESPONSE_LIMIT`.
|
||||
pub const MAX_BODIES_SERVE: usize = 1024;
|
||||
|
||||
/// Maximum number of block access lists to serve.
|
||||
///
|
||||
/// Used to limit lookups.
|
||||
pub const MAX_BLOCK_ACCESS_LISTS_SERVE: usize = 1024;
|
||||
|
||||
/// Maximum size of replies to data retrievals: 2MB
|
||||
pub const SOFT_RESPONSE_LIMIT: usize = 2 * 1024 * 1024;
|
||||
|
||||
@@ -323,9 +328,11 @@ where
|
||||
fn on_block_access_lists_request(
|
||||
&self,
|
||||
_peer_id: PeerId,
|
||||
request: GetBlockAccessLists,
|
||||
mut request: GetBlockAccessLists,
|
||||
response: oneshot::Sender<RequestResult<BlockAccessLists>>,
|
||||
) {
|
||||
request.0.truncate(MAX_BLOCK_ACCESS_LISTS_SERVE);
|
||||
|
||||
let limit = GetBlockAccessListLimit::ResponseSizeSoftLimit(SOFT_RESPONSE_LIMIT);
|
||||
let access_lists = self
|
||||
.client
|
||||
|
||||
@@ -26,7 +26,7 @@ use crate::{
|
||||
message::{NewBlockMessage, PeerMessage},
|
||||
metrics::{
|
||||
BackedOffPeersMetrics, ClosedSessionsMetrics, DirectionalDisconnectMetrics, NetworkMetrics,
|
||||
PendingSessionFailureMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
PendingSessionFailureMetrics,
|
||||
},
|
||||
network::{NetworkHandle, NetworkHandleMessage},
|
||||
peers::{BackoffReason, PeersManager},
|
||||
@@ -44,7 +44,7 @@ use parking_lot::Mutex;
|
||||
use reth_chainspec::EnrForkIdEntry;
|
||||
use reth_eth_wire::{DisconnectReason, EthNetworkPrimitives, NetworkPrimitives};
|
||||
use reth_fs_util::{self as fs, FsPathError};
|
||||
use reth_metrics::common::mpsc::UnboundedMeteredSender;
|
||||
use reth_metrics::common::mpsc::MemoryBoundedSender;
|
||||
use reth_network_api::{
|
||||
events::{PeerEvent, SessionInfo},
|
||||
test_utils::PeersHandle,
|
||||
@@ -118,7 +118,7 @@ pub struct NetworkManager<N: NetworkPrimitives = EthNetworkPrimitives> {
|
||||
event_sender: EventSender<NetworkEvent<PeerRequest<N>>>,
|
||||
/// Sender half to send events to the
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager) task, if configured.
|
||||
to_transactions_manager: Option<UnboundedMeteredSender<NetworkTransactionEvent<N>>>,
|
||||
to_transactions_manager: Option<MemoryBoundedSender<NetworkTransactionEvent<N>>>,
|
||||
/// Sender half to send events to the
|
||||
/// [`EthRequestHandler`](crate::eth_requests::EthRequestHandler) task, if configured.
|
||||
///
|
||||
@@ -175,7 +175,7 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager).
|
||||
pub fn with_transactions(
|
||||
mut self,
|
||||
tx: mpsc::UnboundedSender<NetworkTransactionEvent<N>>,
|
||||
tx: MemoryBoundedSender<NetworkTransactionEvent<N>>,
|
||||
) -> Self {
|
||||
self.set_transactions(tx);
|
||||
self
|
||||
@@ -183,9 +183,8 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
|
||||
/// Sets the dedicated channel for events intended for the
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager).
|
||||
pub fn set_transactions(&mut self, tx: mpsc::UnboundedSender<NetworkTransactionEvent<N>>) {
|
||||
self.to_transactions_manager =
|
||||
Some(UnboundedMeteredSender::new(tx, NETWORK_POOL_TRANSACTIONS_SCOPE));
|
||||
pub fn set_transactions(&mut self, tx: MemoryBoundedSender<NetworkTransactionEvent<N>>) {
|
||||
self.to_transactions_manager = Some(tx);
|
||||
}
|
||||
|
||||
/// Sets the dedicated channel for events intended for the
|
||||
@@ -496,8 +495,16 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
/// Sends an event to the [`TransactionsManager`](crate::transactions::TransactionsManager) if
|
||||
/// configured.
|
||||
fn notify_tx_manager(&self, event: NetworkTransactionEvent<N>) {
|
||||
if let Some(ref tx) = self.to_transactions_manager {
|
||||
let _ = tx.send(event);
|
||||
if let Some(ref tx) = self.to_transactions_manager &&
|
||||
let Err(e) = tx.try_send(event)
|
||||
{
|
||||
match e {
|
||||
TrySendError::Full(_) => {
|
||||
trace!(target: "net", "Transaction events channel at capacity, dropping event");
|
||||
self.metrics.total_dropped_tx_events_at_full_capacity.increment(1);
|
||||
}
|
||||
TrySendError::Closed(_) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,7 +772,7 @@ impl<N: NetworkPrimitives> NetworkManager<N> {
|
||||
NetworkHandleMessage::AddRlpxSubProtocol(proto) => self.add_rlpx_sub_protocol(proto),
|
||||
NetworkHandleMessage::GetTransactionsHandle(tx) => {
|
||||
if let Some(ref tx_inner) = self.to_transactions_manager {
|
||||
let _ = tx_inner.send(NetworkTransactionEvent::GetTransactionsHandle(tx));
|
||||
let _ = tx_inner.try_send(NetworkTransactionEvent::GetTransactionsHandle(tx));
|
||||
} else {
|
||||
let _ = tx.send(None);
|
||||
}
|
||||
|
||||
@@ -46,6 +46,9 @@ pub struct NetworkMetrics {
|
||||
/// Number of Eth Requests dropped due to channel being at full capacity
|
||||
pub(crate) total_dropped_eth_requests_at_full_capacity: Counter,
|
||||
|
||||
/// Number of transaction events dropped due to the tx manager channel being at full capacity
|
||||
pub(crate) total_dropped_tx_events_at_full_capacity: Counter,
|
||||
|
||||
/* ================ POLL DURATION ================ */
|
||||
|
||||
/* -- Total poll duration of `NetworksManager` future -- */
|
||||
|
||||
@@ -20,6 +20,7 @@ use reth_eth_wire::{
|
||||
};
|
||||
use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
|
||||
use reth_evm_ethereum::EthEvmConfig;
|
||||
use reth_metrics::common::mpsc::memory_bounded_channel;
|
||||
use reth_network_api::{
|
||||
events::{PeerEvent, SessionInfo},
|
||||
test_utils::{PeersHandle, PeersHandleProvider},
|
||||
@@ -46,13 +47,12 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc::{channel, unbounded_channel},
|
||||
oneshot,
|
||||
},
|
||||
sync::{mpsc::channel, oneshot},
|
||||
task::JoinHandle,
|
||||
};
|
||||
|
||||
use crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES;
|
||||
|
||||
/// A test network consisting of multiple peers.
|
||||
pub struct Testnet<C, Pool> {
|
||||
/// All running peers in the network.
|
||||
@@ -478,7 +478,10 @@ where
|
||||
|
||||
/// Set a new transactions manager that's connected to the peer's network
|
||||
pub fn install_transactions_manager(&mut self, pool: Pool) {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
self.network.set_transactions(tx);
|
||||
let transactions_manager = TransactionsManager::new(
|
||||
self.handle(),
|
||||
@@ -496,7 +499,10 @@ where
|
||||
P: TransactionPool,
|
||||
{
|
||||
let Self { mut network, request_handler, client, secret_key, .. } = self;
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
network.set_transactions(tx);
|
||||
let transactions_manager = TransactionsManager::new(
|
||||
network.handle().clone(),
|
||||
@@ -537,7 +543,10 @@ where
|
||||
P: TransactionPool,
|
||||
{
|
||||
let Self { mut network, request_handler, client, secret_key, .. } = self;
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let (tx, rx) = memory_bounded_channel(
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
network.set_transactions(tx);
|
||||
|
||||
let announcement_policy = StrictEthAnnouncementFilter::default();
|
||||
|
||||
@@ -6,9 +6,12 @@ use super::{
|
||||
DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
|
||||
SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESPONSE,
|
||||
};
|
||||
use crate::transactions::constants::tx_fetcher::{
|
||||
DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
|
||||
DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
|
||||
use crate::transactions::constants::{
|
||||
tx_fetcher::{
|
||||
DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH, DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS,
|
||||
DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
|
||||
},
|
||||
tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
};
|
||||
use alloy_eips::eip2718::IsTyped2718;
|
||||
use alloy_primitives::B256;
|
||||
@@ -30,6 +33,17 @@ pub struct TransactionsManagerConfig {
|
||||
/// Which peers we accept incoming transactions or announcements from.
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub ingress_policy: TransactionIngressPolicy,
|
||||
/// Memory limit (in bytes) for the channel that carries
|
||||
/// `NetworkTransactionEvent`s from the `NetworkManager` to the `TransactionsManager`.
|
||||
///
|
||||
/// When the budget is exhausted, new events are dropped.
|
||||
#[cfg_attr(feature = "serde", serde(default = "default_tx_channel_memory_limit_bytes"))]
|
||||
pub tx_channel_memory_limit_bytes: usize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
const fn default_tx_channel_memory_limit_bytes() -> usize {
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES
|
||||
}
|
||||
|
||||
impl Default for TransactionsManagerConfig {
|
||||
@@ -39,6 +53,7 @@ impl Default for TransactionsManagerConfig {
|
||||
max_transactions_seen_by_peer_history: DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
|
||||
propagation_mode: TransactionPropagationMode::default(),
|
||||
ingress_policy: TransactionIngressPolicy::default(),
|
||||
tx_channel_memory_limit_bytes: DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,15 @@ pub mod tx_manager {
|
||||
///
|
||||
/// Default is 100 KiB, i.e. 3 200 transaction hashes.
|
||||
pub const DEFAULT_MAX_COUNT_BAD_IMPORTS: u32 = 100 * 1024 / 32;
|
||||
|
||||
/// Default memory limit (in bytes) for the channel between
|
||||
/// [`NetworkManager`](crate::NetworkManager) and
|
||||
/// [`TransactionsManager`](crate::transactions::TransactionsManager).
|
||||
///
|
||||
/// Caps the total in-flight bytes of `NetworkTransactionEvent`s buffered between the two
|
||||
/// tasks. When the budget is exhausted, new events are dropped (see metric
|
||||
/// `total_dropped_tx_events_at_full_capacity`).
|
||||
pub const DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES: usize = 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
/// Constants used by [`TransactionFetcher`](super::TransactionFetcher).
|
||||
|
||||
@@ -33,9 +33,7 @@ use crate::{
|
||||
},
|
||||
cache::LruCache,
|
||||
duration_metered_exec, metered_poll_nested_stream_with_budget,
|
||||
metrics::{
|
||||
AnnouncedTxTypesMetrics, TransactionsManagerMetrics, NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
},
|
||||
metrics::{AnnouncedTxTypesMetrics, TransactionsManagerMetrics},
|
||||
transactions::config::{StrictEthAnnouncementFilter, TransactionPropagationKind},
|
||||
NetworkHandle, TxTypesCounter,
|
||||
};
|
||||
@@ -49,7 +47,7 @@ use reth_eth_wire::{
|
||||
RequestTxHashes, Transactions, ValidAnnouncementData,
|
||||
};
|
||||
use reth_ethereum_primitives::{TransactionSigned, TxType};
|
||||
use reth_metrics::common::mpsc::UnboundedMeteredReceiver;
|
||||
use reth_metrics::common::mpsc::MemoryBoundedReceiver;
|
||||
use reth_network_api::{
|
||||
events::{PeerEvent, SessionInfo},
|
||||
NetworkEvent, NetworkEventListenerProvider, PeerKind, PeerRequest, PeerRequestSender, Peers,
|
||||
@@ -60,7 +58,7 @@ use reth_network_p2p::{
|
||||
};
|
||||
use reth_network_peers::PeerId;
|
||||
use reth_network_types::ReputationChangeKind;
|
||||
use reth_primitives_traits::SignedTransaction;
|
||||
use reth_primitives_traits::{InMemorySize, SignedTransaction};
|
||||
use reth_tokio_util::EventStream;
|
||||
use reth_transaction_pool::{
|
||||
error::{PoolError, PoolResult},
|
||||
@@ -333,7 +331,7 @@ pub struct TransactionsManager<Pool, N: NetworkPrimitives = EthNetworkPrimitives
|
||||
/// - account has enough balance to cover the transaction's gas
|
||||
pending_transactions: mpsc::Receiver<TxHash>,
|
||||
/// Incoming events from the [`NetworkManager`](crate::NetworkManager).
|
||||
transaction_events: UnboundedMeteredReceiver<NetworkTransactionEvent<N>>,
|
||||
transaction_events: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
|
||||
/// How the `TransactionsManager` is configured.
|
||||
config: TransactionsManagerConfig,
|
||||
/// Network Policies
|
||||
@@ -351,7 +349,7 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
|
||||
pub fn new(
|
||||
network: NetworkHandle<N>,
|
||||
pool: Pool,
|
||||
from_network: mpsc::UnboundedReceiver<NetworkTransactionEvent<N>>,
|
||||
from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
|
||||
transactions_manager_config: TransactionsManagerConfig,
|
||||
) -> Self {
|
||||
Self::with_policy(
|
||||
@@ -374,7 +372,7 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
|
||||
pub fn with_policy(
|
||||
network: NetworkHandle<N>,
|
||||
pool: Pool,
|
||||
from_network: mpsc::UnboundedReceiver<NetworkTransactionEvent<N>>,
|
||||
from_network: MemoryBoundedReceiver<NetworkTransactionEvent<N>>,
|
||||
transactions_manager_config: TransactionsManagerConfig,
|
||||
policies: NetworkPolicies<N>,
|
||||
) -> Self {
|
||||
@@ -409,10 +407,7 @@ impl<Pool: TransactionPool, N: NetworkPrimitives> TransactionsManager<Pool, N> {
|
||||
command_tx,
|
||||
command_rx: UnboundedReceiverStream::new(command_rx),
|
||||
pending_transactions: pending,
|
||||
transaction_events: UnboundedMeteredReceiver::new(
|
||||
from_network,
|
||||
NETWORK_POOL_TRANSACTIONS_SCOPE,
|
||||
),
|
||||
transaction_events: from_network,
|
||||
config: transactions_manager_config,
|
||||
policies,
|
||||
metrics,
|
||||
@@ -1626,7 +1621,7 @@ where
|
||||
"Network transaction events stream",
|
||||
DEFAULT_BUDGET_TRY_DRAIN_NETWORK_TRANSACTION_EVENTS,
|
||||
this.transaction_events.poll_next_unpin(cx),
|
||||
|event| this.on_network_tx_event(event),
|
||||
|event: NetworkTransactionEvent<N>| this.on_network_tx_event(event),
|
||||
);
|
||||
|
||||
// Advance inflight fetch requests (flush transaction fetcher and queue for
|
||||
@@ -2174,6 +2169,28 @@ struct TxManagerPollDurations {
|
||||
acc_cmds: Duration,
|
||||
}
|
||||
|
||||
impl<N: NetworkPrimitives> InMemorySize for NetworkTransactionEvent<N> {
|
||||
// `N::BroadcastedTransaction` and `N::PooledTransaction` already implement
|
||||
// `InMemorySize` via `SignedTransaction: InMemorySize`, so no extra bound is needed.
|
||||
fn size(&self) -> usize {
|
||||
match self {
|
||||
Self::IncomingTransactions { peer_id, msg } => {
|
||||
core::mem::size_of_val(peer_id) +
|
||||
msg.0.iter().map(InMemorySize::size).sum::<usize>()
|
||||
}
|
||||
Self::IncomingPooledTransactionHashes { peer_id, msg } => {
|
||||
core::mem::size_of_val(peer_id) + msg.size()
|
||||
}
|
||||
Self::GetPooledTransactions { peer_id, request, response } => {
|
||||
core::mem::size_of_val(peer_id) +
|
||||
request.0.len() * core::mem::size_of::<TxHash>() +
|
||||
core::mem::size_of_val(response)
|
||||
}
|
||||
Self::GetTransactionsHandle(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -3106,7 +3123,12 @@ mod tests {
|
||||
|
||||
let mut network_manager = NetworkManager::new(network_config).await.unwrap();
|
||||
let (to_tx_manager_tx, from_network_rx) =
|
||||
mpsc::unbounded_channel::<NetworkTransactionEvent<EthNetworkPrimitives>>();
|
||||
reth_metrics::common::mpsc::memory_bounded_channel::<
|
||||
NetworkTransactionEvent<EthNetworkPrimitives>,
|
||||
>(
|
||||
crate::transactions::constants::tx_manager::DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
"test_tx_channel",
|
||||
);
|
||||
network_manager.set_transactions(to_tx_manager_tx);
|
||||
let network_handle = network_manager.handle().clone();
|
||||
let network_service_handle = tokio::spawn(network_manager);
|
||||
|
||||
@@ -7,7 +7,7 @@ use rand::Rng;
|
||||
use reth_eth_wire::{BlockAccessLists, EthVersion, GetBlockAccessLists, HeadersDirection};
|
||||
use reth_ethereum_primitives::Block;
|
||||
use reth_network::{
|
||||
eth_requests::SOFT_RESPONSE_LIMIT,
|
||||
eth_requests::{MAX_BLOCK_ACCESS_LISTS_SERVE, SOFT_RESPONSE_LIMIT},
|
||||
test_utils::{NetworkEventStream, PeerConfig, Testnet, TestnetHandle},
|
||||
BlockDownloaderProvider, NetworkEventListenerProvider,
|
||||
};
|
||||
@@ -607,6 +607,26 @@ async fn test_eth71_get_block_access_lists_empty_request() {
|
||||
assert_eq!(response, BlockAccessLists(Vec::new()));
|
||||
}
|
||||
|
||||
// Ensures BAL responses are capped at MAX_BLOCK_ACCESS_LISTS_SERVE entries.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_eth71_get_block_access_lists_caps_count() {
|
||||
reth_tracing::init_test_tracing();
|
||||
let (net, bal_store) = spawn_eth71_bal_testnet().await;
|
||||
|
||||
// Request more hashes than the count cap.
|
||||
let request_count = MAX_BLOCK_ACCESS_LISTS_SERVE + 100;
|
||||
let hashes: Vec<B256> = (0..request_count).map(|_| B256::random()).collect();
|
||||
|
||||
// Insert one BAL so the store isn't entirely empty (not strictly needed,
|
||||
// but keeps the test path closer to real usage).
|
||||
let bal = Bytes::from_static(&[0xc1, 0x01]);
|
||||
bal_store.insert(hashes[0], 1, bal).unwrap();
|
||||
|
||||
let response = request_block_access_lists(&net, hashes).await;
|
||||
|
||||
assert_eq!(response.0.len(), MAX_BLOCK_ACCESS_LISTS_SERVE);
|
||||
}
|
||||
|
||||
// Ensures the fetch client can request BALs through an eth/71 peer.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_eth71_fetch_client_get_block_access_lists() {
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use reth_chainspec::MAINNET;
|
||||
use reth_discv4::{Discv4Config, NatResolver, DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT};
|
||||
use reth_discv4::{Discv4Config, NatResolver, DEFAULT_DISCOVERY_ADDR};
|
||||
use reth_network::{
|
||||
error::{NetworkError, ServiceKind},
|
||||
Discovery, NetworkConfigBuilder, NetworkManager,
|
||||
@@ -73,27 +73,31 @@ async fn test_discovery_addr_in_use() {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_discv5_and_discv4_same_socket_fails() {
|
||||
async fn test_discv5_and_discv4_same_socket_ok() {
|
||||
// Pick a free port for the shared UDP discovery socket and TCP RLPx listener.
|
||||
let test_port: u16 = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.expect("Failed to bind to a port")
|
||||
.local_addr()
|
||||
.unwrap()
|
||||
.port();
|
||||
|
||||
let secret_key = SecretKey::new(&mut rand_08::thread_rng());
|
||||
let config = NetworkConfigBuilder::eth(secret_key, Runtime::test())
|
||||
.listener_port(DEFAULT_DISCOVERY_PORT)
|
||||
.listener_port(test_port)
|
||||
.discovery_port(test_port)
|
||||
.discovery_v5(
|
||||
reth_discv5::Config::builder((DEFAULT_DISCOVERY_ADDR, DEFAULT_DISCOVERY_PORT).into())
|
||||
.discv5_config(
|
||||
discv5::ConfigBuilder::new(discv5::ListenConfig::from_ip(
|
||||
DEFAULT_DISCOVERY_ADDR,
|
||||
DEFAULT_DISCOVERY_PORT,
|
||||
))
|
||||
.build(),
|
||||
),
|
||||
reth_discv5::Config::builder((DEFAULT_DISCOVERY_ADDR, test_port).into()).discv5_config(
|
||||
discv5::ConfigBuilder::new(discv5::ListenConfig::from_ip(
|
||||
DEFAULT_DISCOVERY_ADDR,
|
||||
test_port,
|
||||
))
|
||||
.build(),
|
||||
),
|
||||
)
|
||||
.disable_dns_discovery()
|
||||
.build(NoopProvider::default());
|
||||
let addr = config.listener_addr;
|
||||
let result = NetworkManager::new(config).await;
|
||||
let err = result.err().unwrap();
|
||||
|
||||
assert!(is_addr_in_use_kind(&err, ServiceKind::Listener(addr)), "{err:?}")
|
||||
let _network = NetworkManager::new(config).await.expect("shared port discovery should start");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
||||
@@ -289,7 +289,7 @@ impl Default for DefaultEngineValues {
|
||||
share_execution_cache_with_payload_builder: false,
|
||||
share_sparse_trie_with_payload_builder: false,
|
||||
suppress_persistence_during_build: false,
|
||||
bal_parallel_execution_disabled: false,
|
||||
bal_parallel_execution_disabled: true,
|
||||
bal_parallel_state_root_disabled: false,
|
||||
}
|
||||
}
|
||||
@@ -511,8 +511,8 @@ pub struct EngineArgs {
|
||||
)]
|
||||
pub suppress_persistence_during_build: bool,
|
||||
|
||||
/// Disable BAL (Block Access List, EIP-7928) based parallel execution. When set, falls back
|
||||
/// to transaction-based prewarming even when a BAL is available.
|
||||
/// Disable BAL (Block Access List, EIP-7928) based parallel execution. Defaults to disabled,
|
||||
/// falling back to transaction-based prewarming even when a BAL is available.
|
||||
#[arg(long = "engine.disable-bal-parallel-execution", default_value_t = DefaultEngineValues::get_global().bal_parallel_execution_disabled)]
|
||||
pub bal_parallel_execution_disabled: bool,
|
||||
|
||||
@@ -521,8 +521,8 @@ pub struct EngineArgs {
|
||||
#[arg(long = "engine.disable-bal-parallel-state-root", default_value_t = DefaultEngineValues::get_global().bal_parallel_state_root_disabled)]
|
||||
pub bal_parallel_state_root_disabled: bool,
|
||||
|
||||
/// Disable BAL (Block Access List) batched IO during prewarming. When set, falls back
|
||||
/// to individual per-slot storage reads instead of batched cursor reads.
|
||||
/// Disable BAL (Block Access List) storage prefetch IO during prewarming. When set, BAL
|
||||
/// storage slots are not read into the execution cache.
|
||||
#[arg(long = "engine.disable-bal-batch-io", default_value_t = false)]
|
||||
pub disable_bal_batch_io: bool,
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
/// NetworkArg struct for configuring the network
|
||||
mod network;
|
||||
pub use network::{DefaultNetworkArgs, DiscoveryArgs, NetworkArgs};
|
||||
pub use network::{DefaultDiscoveryArgs, DefaultNetworkArgs, DiscoveryArgs, NetworkArgs};
|
||||
|
||||
/// RpcServerArg struct for configuring the RPC
|
||||
mod rpc_server;
|
||||
|
||||
@@ -11,7 +11,10 @@ use std::{
|
||||
};
|
||||
|
||||
use crate::version::version_metadata;
|
||||
use clap::Args;
|
||||
use clap::{
|
||||
builder::{OsStr, Resettable},
|
||||
Args,
|
||||
};
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_cli_util::{get_secret_key, load_secret_key::SecretKeyError};
|
||||
use reth_config::Config;
|
||||
@@ -31,7 +34,9 @@ use reth_network::{
|
||||
DEFAULT_MAX_COUNT_CONCURRENT_REQUESTS_PER_PEER,
|
||||
},
|
||||
tx_manager::{
|
||||
DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS, DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
|
||||
DEFAULT_MAX_COUNT_PENDING_POOL_IMPORTS,
|
||||
DEFAULT_MAX_COUNT_TRANSACTIONS_SEEN_BY_PEER,
|
||||
DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
},
|
||||
},
|
||||
TransactionFetcherConfig, TransactionPropagationMode, TransactionsManagerConfig,
|
||||
@@ -76,6 +81,8 @@ pub struct DefaultNetworkArgs {
|
||||
pub soft_limit_byte_size_pooled_transactions_response_on_pack_request: usize,
|
||||
/// Default max capacity of cache of hashes for transactions pending fetch.
|
||||
pub max_capacity_cache_txns_pending_fetch: u32,
|
||||
/// Default memory limit (in bytes) for the network manager → transactions manager channel.
|
||||
pub tx_channel_memory_limit_bytes: usize,
|
||||
/// Default transaction propagation policy.
|
||||
pub tx_propagation_policy: TransactionPropagationKind,
|
||||
/// Default transaction ingress policy.
|
||||
@@ -169,6 +176,13 @@ impl DefaultNetworkArgs {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default memory limit (in bytes) for the network manager → transactions
|
||||
/// manager channel.
|
||||
pub const fn with_tx_channel_memory_limit_bytes(mut self, v: usize) -> Self {
|
||||
self.tx_channel_memory_limit_bytes = v;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default transaction propagation policy.
|
||||
pub const fn with_tx_propagation_policy(mut self, v: TransactionPropagationKind) -> Self {
|
||||
self.tx_propagation_policy = v;
|
||||
@@ -210,6 +224,7 @@ impl Default for DefaultNetworkArgs {
|
||||
soft_limit_byte_size_pooled_transactions_response_on_pack_request:
|
||||
DEFAULT_SOFT_LIMIT_BYTE_SIZE_POOLED_TRANSACTIONS_RESP_ON_PACK_GET_POOLED_TRANSACTIONS_REQ,
|
||||
max_capacity_cache_txns_pending_fetch: DEFAULT_MAX_CAPACITY_CACHE_PENDING_FETCH,
|
||||
tx_channel_memory_limit_bytes: DEFAULT_TX_MANAGER_CHANNEL_MEMORY_LIMIT_BYTES,
|
||||
tx_propagation_policy: TransactionPropagationKind::default(),
|
||||
tx_ingress_policy: TransactionIngressPolicy::default(),
|
||||
propagation_mode: TransactionPropagationMode::Sqrt,
|
||||
@@ -348,6 +363,15 @@ pub struct NetworkArgs {
|
||||
#[arg(long = "max-tx-pending-fetch", value_name = "COUNT", default_value_t = DefaultNetworkArgs::get_global().max_capacity_cache_txns_pending_fetch, verbatim_doc_comment)]
|
||||
pub max_capacity_cache_txns_pending_fetch: u32,
|
||||
|
||||
/// Memory limit (in bytes) for the channel that buffers transaction events flowing
|
||||
/// from the network manager to the transactions manager.
|
||||
///
|
||||
/// When the budget is exhausted, new events are dropped (see metric
|
||||
/// `total_dropped_tx_events_at_full_capacity`). Acts as a backstop against unbounded
|
||||
/// memory growth under sustained P2P transaction flooding.
|
||||
#[arg(long = "tx-channel-memory-limit", value_name = "BYTES", default_value_t = DefaultNetworkArgs::get_global().tx_channel_memory_limit_bytes, verbatim_doc_comment)]
|
||||
pub tx_channel_memory_limit_bytes: usize,
|
||||
|
||||
/// Name of network interface used to communicate with peers.
|
||||
///
|
||||
/// If flag is set, but no value is passed, the default interface for docker `eth0` is tried.
|
||||
@@ -485,6 +509,7 @@ impl NetworkArgs {
|
||||
max_transactions_seen_by_peer_history: self.max_seen_tx_history,
|
||||
propagation_mode: self.propagation_mode,
|
||||
ingress_policy: self.tx_ingress_policy,
|
||||
tx_channel_memory_limit_bytes: self.tx_channel_memory_limit_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,15 +572,8 @@ impl NetworkArgs {
|
||||
let rlpx_socket = (addr, self.port).into();
|
||||
self.discovery.apply_to_builder(builder, rlpx_socket, chain_bootnodes)
|
||||
})
|
||||
.listener_addr(SocketAddr::new(
|
||||
addr, // set discovery port based on instance number
|
||||
self.port,
|
||||
))
|
||||
.discovery_addr(SocketAddr::new(
|
||||
self.discovery.addr,
|
||||
// set discovery port based on instance number
|
||||
self.discovery.port,
|
||||
))
|
||||
.listener_addr(SocketAddr::new(addr, self.port))
|
||||
.discovery_addr(SocketAddr::new(self.discovery.addr, self.discovery.port))
|
||||
.disable_tx_gossip(self.disable_tx_gossip)
|
||||
.required_block_hashes(self.required_block_hashes.clone())
|
||||
.eth_max_message_size_opt(self.eth_max_message_size.map(NonZeroUsize::get))
|
||||
@@ -660,6 +678,7 @@ impl Default for NetworkArgs {
|
||||
soft_limit_byte_size_pooled_transactions_response,
|
||||
soft_limit_byte_size_pooled_transactions_response_on_pack_request,
|
||||
max_capacity_cache_txns_pending_fetch,
|
||||
tx_channel_memory_limit_bytes,
|
||||
tx_propagation_policy,
|
||||
tx_ingress_policy,
|
||||
propagation_mode,
|
||||
@@ -689,6 +708,7 @@ impl Default for NetworkArgs {
|
||||
max_pending_pool_imports,
|
||||
max_seen_tx_history,
|
||||
max_capacity_cache_txns_pending_fetch,
|
||||
tx_channel_memory_limit_bytes,
|
||||
net_if: None,
|
||||
tx_propagation_policy,
|
||||
tx_ingress_policy,
|
||||
@@ -703,19 +723,172 @@ impl Default for NetworkArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// Global static discovery defaults
|
||||
static DISCOVERY_DEFAULTS: OnceLock<DefaultDiscoveryArgs> = OnceLock::new();
|
||||
|
||||
/// Default values for discovery CLI arguments that can be customized.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DefaultDiscoveryArgs {
|
||||
/// Default for `--disable-discovery`.
|
||||
pub disable_discovery: bool,
|
||||
/// Default for `--disable-dns-discovery`.
|
||||
pub disable_dns_discovery: bool,
|
||||
/// Default for `--disable-discv4-discovery`.
|
||||
pub disable_discv4_discovery: bool,
|
||||
/// Default for `--disable-discv5-discovery`.
|
||||
pub disable_discv5_discovery: bool,
|
||||
/// Default for `--disable-nat`.
|
||||
pub disable_nat: bool,
|
||||
/// Default UDP address for devp2p discovery v4.
|
||||
pub addr: IpAddr,
|
||||
/// Default UDP port for devp2p discovery v4.
|
||||
pub port: u16,
|
||||
/// Default UDP IPv4 address for devp2p discovery v5.
|
||||
pub discv5_addr: Option<Ipv4Addr>,
|
||||
/// Default UDP IPv6 address for devp2p discovery v5.
|
||||
pub discv5_addr_ipv6: Option<Ipv6Addr>,
|
||||
/// Default UDP IPv4 port for devp2p discovery v5.
|
||||
pub discv5_port: Option<u16>,
|
||||
/// Default UDP IPv6 port for devp2p discovery v5.
|
||||
pub discv5_port_ipv6: Option<u16>,
|
||||
/// Default discv5 periodic lookup interval (seconds).
|
||||
pub discv5_lookup_interval: u64,
|
||||
/// Default discv5 bootstrap lookup interval (seconds).
|
||||
pub discv5_bootstrap_lookup_interval: u64,
|
||||
/// Default discv5 bootstrap lookup countdown.
|
||||
pub discv5_bootstrap_lookup_countdown: u64,
|
||||
}
|
||||
|
||||
impl DefaultDiscoveryArgs {
|
||||
/// Initialize the global discovery defaults with this configuration.
|
||||
pub fn try_init(self) -> Result<(), Self> {
|
||||
DISCOVERY_DEFAULTS.set(self)
|
||||
}
|
||||
|
||||
/// Get a reference to the global discovery defaults.
|
||||
pub fn get_global() -> &'static Self {
|
||||
DISCOVERY_DEFAULTS.get_or_init(Self::default)
|
||||
}
|
||||
|
||||
/// Set the default for `--disable-discovery`.
|
||||
pub const fn with_disable_discovery(mut self, disable: bool) -> Self {
|
||||
self.disable_discovery = disable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default for `--disable-dns-discovery`.
|
||||
pub const fn with_disable_dns_discovery(mut self, disable: bool) -> Self {
|
||||
self.disable_dns_discovery = disable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default for `--disable-discv4-discovery`.
|
||||
pub const fn with_disable_discv4_discovery(mut self, disable: bool) -> Self {
|
||||
self.disable_discv4_discovery = disable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default for `--disable-discv5-discovery`.
|
||||
pub const fn with_disable_discv5_discovery(mut self, disable: bool) -> Self {
|
||||
self.disable_discv5_discovery = disable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default for `--disable-nat`.
|
||||
pub const fn with_disable_nat(mut self, disable: bool) -> Self {
|
||||
self.disable_nat = disable;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discovery v4 address.
|
||||
pub const fn with_addr(mut self, addr: IpAddr) -> Self {
|
||||
self.addr = addr;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discovery v4 port.
|
||||
pub const fn with_port(mut self, port: u16) -> Self {
|
||||
self.port = port;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discovery v5 IPv4 address.
|
||||
pub fn with_discv5_addr(mut self, addr: impl Into<Option<Ipv4Addr>>) -> Self {
|
||||
self.discv5_addr = addr.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discovery v5 IPv6 address.
|
||||
pub fn with_discv5_addr_ipv6(mut self, addr: impl Into<Option<Ipv6Addr>>) -> Self {
|
||||
self.discv5_addr_ipv6 = addr.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discovery V5 port.
|
||||
pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
|
||||
self.discv5_port = port.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discovery v5 IPv6 port.
|
||||
pub fn with_discv5_port_ipv6(mut self, port: impl Into<Option<u16>>) -> Self {
|
||||
self.discv5_port_ipv6 = port.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discv5 periodic lookup interval (seconds).
|
||||
pub const fn with_discv5_lookup_interval(mut self, interval: u64) -> Self {
|
||||
self.discv5_lookup_interval = interval;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discv5 bootstrap lookup interval (seconds).
|
||||
pub const fn with_discv5_bootstrap_lookup_interval(mut self, interval: u64) -> Self {
|
||||
self.discv5_bootstrap_lookup_interval = interval;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default discv5 bootstrap lookup countdown.
|
||||
pub const fn with_discv5_bootstrap_lookup_countdown(mut self, countdown: u64) -> Self {
|
||||
self.discv5_bootstrap_lookup_countdown = countdown;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DefaultDiscoveryArgs {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
disable_discovery: false,
|
||||
disable_dns_discovery: false,
|
||||
disable_discv4_discovery: false,
|
||||
disable_discv5_discovery: false,
|
||||
disable_nat: false,
|
||||
addr: DEFAULT_DISCOVERY_ADDR,
|
||||
port: DEFAULT_DISCOVERY_PORT,
|
||||
discv5_addr: None,
|
||||
discv5_addr_ipv6: None,
|
||||
discv5_port: Some(DEFAULT_DISCOVERY_V5_PORT),
|
||||
discv5_port_ipv6: Some(DEFAULT_DISCOVERY_V5_PORT),
|
||||
discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
|
||||
discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
|
||||
discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Arguments to setup discovery
|
||||
#[derive(Debug, Clone, Args, PartialEq, Eq)]
|
||||
pub struct DiscoveryArgs {
|
||||
/// Disable the discovery service.
|
||||
#[arg(short, long, default_value_if("dev", "true", "true"))]
|
||||
#[arg(short, long, default_value_if("dev", "true", "true"), default_value_t = DefaultDiscoveryArgs::get_global().disable_discovery)]
|
||||
pub disable_discovery: bool,
|
||||
|
||||
/// Disable the DNS discovery.
|
||||
#[arg(long, conflicts_with = "disable_discovery")]
|
||||
#[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_dns_discovery)]
|
||||
pub disable_dns_discovery: bool,
|
||||
|
||||
/// Disable Discv4 discovery.
|
||||
#[arg(long, conflicts_with = "disable_discovery")]
|
||||
#[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv4_discovery)]
|
||||
pub disable_discv4_discovery: bool,
|
||||
|
||||
/// Enable Discv5 discovery.
|
||||
@@ -726,57 +899,57 @@ pub struct DiscoveryArgs {
|
||||
pub enable_discv5_discovery: bool,
|
||||
|
||||
/// Disable Discv5 discovery.
|
||||
#[arg(long, conflicts_with = "disable_discovery")]
|
||||
#[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_discv5_discovery)]
|
||||
pub disable_discv5_discovery: bool,
|
||||
|
||||
/// Disable Nat discovery.
|
||||
#[arg(long, conflicts_with = "disable_discovery")]
|
||||
#[arg(long, conflicts_with = "disable_discovery", default_value_t = DefaultDiscoveryArgs::get_global().disable_nat)]
|
||||
pub disable_nat: bool,
|
||||
|
||||
/// The UDP address to use for devp2p peer discovery version 4.
|
||||
#[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DEFAULT_DISCOVERY_ADDR)]
|
||||
#[arg(id = "discovery.addr", long = "discovery.addr", value_name = "DISCOVERY_ADDR", default_value_t = DefaultDiscoveryArgs::get_global().addr)]
|
||||
pub addr: IpAddr,
|
||||
|
||||
/// The UDP port to use for devp2p peer discovery version 4.
|
||||
#[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DEFAULT_DISCOVERY_PORT)]
|
||||
#[arg(id = "discovery.port", long = "discovery.port", value_name = "DISCOVERY_PORT", default_value_t = DefaultDiscoveryArgs::get_global().port)]
|
||||
pub port: u16,
|
||||
|
||||
/// The UDP IPv4 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
|
||||
/// address, if it's also IPv4.
|
||||
#[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = None)]
|
||||
#[arg(id = "discovery.v5.addr", long = "discovery.v5.addr", value_name = "DISCOVERY_V5_ADDR", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_addr.map(|a| OsStr::from(a.to_string()))))]
|
||||
pub discv5_addr: Option<Ipv4Addr>,
|
||||
|
||||
/// The UDP IPv6 address to use for devp2p peer discovery version 5. Overwritten by `RLPx`
|
||||
/// address, if it's also IPv6.
|
||||
#[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = None)]
|
||||
#[arg(id = "discovery.v5.addr.ipv6", long = "discovery.v5.addr.ipv6", value_name = "DISCOVERY_V5_ADDR_IPV6", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_addr_ipv6.map(|a| OsStr::from(a.to_string()))))]
|
||||
pub discv5_addr_ipv6: Option<Ipv6Addr>,
|
||||
|
||||
/// The UDP IPv4 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
|
||||
/// IPv4, or `--discovery.v5.addr` is set.
|
||||
#[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT",
|
||||
default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
|
||||
pub discv5_port: u16,
|
||||
#[arg(id = "discovery.v5.port", long = "discovery.v5.port", value_name = "DISCOVERY_V5_PORT", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_port.map(|p| OsStr::from(p.to_string()))))]
|
||||
pub discv5_port: Option<u16>,
|
||||
|
||||
/// The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is
|
||||
/// IPv6, or `--discovery.addr.ipv6` is set.
|
||||
#[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6",
|
||||
default_value_t = DEFAULT_DISCOVERY_V5_PORT)]
|
||||
pub discv5_port_ipv6: u16,
|
||||
///
|
||||
/// If not provided, discovery V5 defaults to same port as discovery V4 (--discovery.port).
|
||||
#[arg(id = "discovery.v5.port.ipv6", long = "discovery.v5.port.ipv6", value_name = "DISCOVERY_V5_PORT_IPV6", default_value = Resettable::from(DefaultDiscoveryArgs::get_global().discv5_port_ipv6.map(|p| OsStr::from(p.to_string()))))]
|
||||
pub discv5_port_ipv6: Option<u16>,
|
||||
|
||||
/// The interval in seconds at which to carry out periodic lookup queries, for the whole
|
||||
/// run of the program.
|
||||
#[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DEFAULT_SECONDS_LOOKUP_INTERVAL)]
|
||||
#[arg(id = "discovery.v5.lookup-interval", long = "discovery.v5.lookup-interval", value_name = "DISCOVERY_V5_LOOKUP_INTERVAL", default_value_t = DefaultDiscoveryArgs::get_global().discv5_lookup_interval)]
|
||||
pub discv5_lookup_interval: u64,
|
||||
|
||||
/// The interval in seconds at which to carry out boost lookup queries, for a fixed number of
|
||||
/// times, at bootstrap.
|
||||
#[arg(id = "discovery.v5.bootstrap.lookup-interval", long = "discovery.v5.bootstrap.lookup-interval", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_INTERVAL",
|
||||
default_value_t = DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL)]
|
||||
default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_interval)]
|
||||
pub discv5_bootstrap_lookup_interval: u64,
|
||||
|
||||
/// The number of times to carry out boost lookup queries at bootstrap.
|
||||
#[arg(id = "discovery.v5.bootstrap.lookup-countdown", long = "discovery.v5.bootstrap.lookup-countdown", value_name = "DISCOVERY_V5_BOOTSTRAP_LOOKUP_COUNTDOWN",
|
||||
default_value_t = DEFAULT_COUNT_BOOTSTRAP_LOOKUPS)]
|
||||
default_value_t = DefaultDiscoveryArgs::get_global().discv5_bootstrap_lookup_countdown)]
|
||||
pub discv5_bootstrap_lookup_countdown: u64,
|
||||
}
|
||||
|
||||
@@ -826,6 +999,7 @@ impl DiscoveryArgs {
|
||||
discv5_lookup_interval,
|
||||
discv5_bootstrap_lookup_interval,
|
||||
discv5_bootstrap_lookup_countdown,
|
||||
port,
|
||||
..
|
||||
} = self;
|
||||
|
||||
@@ -843,8 +1017,9 @@ impl DiscoveryArgs {
|
||||
|
||||
let mut discv5_config_builder =
|
||||
reth_discv5::discv5::ConfigBuilder::new(ListenConfig::from_two_sockets(
|
||||
discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, *discv5_port)),
|
||||
discv5_addr_ipv6.map(|addr| SocketAddrV6::new(addr, *discv5_port_ipv6, 0, 0)),
|
||||
discv5_addr_ipv4.map(|addr| SocketAddrV4::new(addr, discv5_port.unwrap_or(*port))),
|
||||
discv5_addr_ipv6
|
||||
.map(|addr| SocketAddrV6::new(addr, discv5_port_ipv6.unwrap_or(*port), 0, 0)),
|
||||
));
|
||||
|
||||
if has_discv5_addr_args || self.disable_nat {
|
||||
@@ -874,14 +1049,14 @@ impl DiscoveryArgs {
|
||||
/// discovery binds to the sockets.
|
||||
pub const fn with_unused_discovery_port(mut self) -> Self {
|
||||
self.port = 0;
|
||||
self.discv5_port = 0;
|
||||
self.discv5_port_ipv6 = 0;
|
||||
self.discv5_port = Some(0);
|
||||
self.discv5_port_ipv6 = Some(0);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the discovery V5 port
|
||||
pub const fn with_discv5_port(mut self, port: u16) -> Self {
|
||||
self.discv5_port = port;
|
||||
pub fn with_discv5_port(mut self, port: impl Into<Option<u16>>) -> Self {
|
||||
self.discv5_port = port.into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -893,29 +1068,45 @@ impl DiscoveryArgs {
|
||||
pub fn adjust_instance_ports(&mut self, instance: u16) {
|
||||
debug_assert_ne!(instance, 0, "instance must be non-zero");
|
||||
self.port += instance - 1;
|
||||
self.discv5_port += instance - 1;
|
||||
self.discv5_port_ipv6 += instance - 1;
|
||||
self.discv5_port = self.discv5_port.map(|port| port + instance - 1);
|
||||
self.discv5_port_ipv6 = self.discv5_port_ipv6.map(|port| port + instance - 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DiscoveryArgs {
|
||||
fn default() -> Self {
|
||||
let DefaultDiscoveryArgs {
|
||||
disable_discovery,
|
||||
disable_dns_discovery,
|
||||
disable_discv4_discovery,
|
||||
disable_discv5_discovery,
|
||||
disable_nat,
|
||||
addr,
|
||||
port,
|
||||
discv5_addr,
|
||||
discv5_addr_ipv6,
|
||||
discv5_port,
|
||||
discv5_port_ipv6,
|
||||
discv5_lookup_interval,
|
||||
discv5_bootstrap_lookup_interval,
|
||||
discv5_bootstrap_lookup_countdown,
|
||||
} = *DefaultDiscoveryArgs::get_global();
|
||||
Self {
|
||||
disable_discovery: false,
|
||||
disable_dns_discovery: false,
|
||||
disable_discv4_discovery: false,
|
||||
disable_discovery,
|
||||
disable_dns_discovery,
|
||||
disable_discv4_discovery,
|
||||
enable_discv5_discovery: false,
|
||||
disable_discv5_discovery: false,
|
||||
disable_nat: false,
|
||||
addr: DEFAULT_DISCOVERY_ADDR,
|
||||
port: DEFAULT_DISCOVERY_PORT,
|
||||
discv5_addr: None,
|
||||
discv5_addr_ipv6: None,
|
||||
discv5_port: DEFAULT_DISCOVERY_V5_PORT,
|
||||
discv5_port_ipv6: DEFAULT_DISCOVERY_V5_PORT,
|
||||
discv5_lookup_interval: DEFAULT_SECONDS_LOOKUP_INTERVAL,
|
||||
discv5_bootstrap_lookup_interval: DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL,
|
||||
discv5_bootstrap_lookup_countdown: DEFAULT_COUNT_BOOTSTRAP_LOOKUPS,
|
||||
disable_discv5_discovery,
|
||||
disable_nat,
|
||||
addr,
|
||||
port,
|
||||
discv5_addr,
|
||||
discv5_addr_ipv6,
|
||||
discv5_port,
|
||||
discv5_port_ipv6,
|
||||
discv5_lookup_interval,
|
||||
discv5_bootstrap_lookup_interval,
|
||||
discv5_bootstrap_lookup_countdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
//! the consensus client.
|
||||
|
||||
use alloy_eips::{
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip7685::RequestsOrHash,
|
||||
BlockId, BlockNumberOrTag,
|
||||
};
|
||||
use alloy_json_rpc::RpcObject;
|
||||
use alloy_primitives::{Address, BlockHash, Bytes, B256, U256, U64};
|
||||
use alloy_primitives::{Address, BlockHash, Bytes, B128, B256, U256, U64};
|
||||
use alloy_rpc_types_engine::{
|
||||
ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadBodiesV2, ExecutionPayloadInputV2,
|
||||
ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated,
|
||||
@@ -324,6 +324,20 @@ pub trait EngineApi<Engine: EngineTypes> {
|
||||
&self,
|
||||
versioned_hashes: Vec<B256>,
|
||||
) -> RpcResult<Option<Vec<Option<BlobAndProofV2>>>>;
|
||||
|
||||
/// Fetch blob cells for the consensus layer from the blob store.
|
||||
///
|
||||
/// Returns a response of the same length as the request. Missing blobs are returned as `null`
|
||||
/// elements; missing requested cells within an available blob are returned as `null` cell and
|
||||
/// proof entries.
|
||||
///
|
||||
/// Returns `null` if syncing.
|
||||
#[method(name = "getBlobsV4")]
|
||||
async fn get_blobs_v4(
|
||||
&self,
|
||||
versioned_hashes: Vec<B256>,
|
||||
indices_bitarray: B128,
|
||||
) -> RpcResult<Option<Vec<Option<BlobCellsAndProofsV1>>>>;
|
||||
}
|
||||
|
||||
/// A subset of the ETH rpc interface: <https://ethereum.github.io/execution-apis/api-documentation>
|
||||
|
||||
@@ -37,6 +37,7 @@ pub const CAPABILITIES: &[&str] = &[
|
||||
"engine_getBlobsV1",
|
||||
"engine_getBlobsV2",
|
||||
"engine_getBlobsV3",
|
||||
"engine_getBlobsV4",
|
||||
];
|
||||
|
||||
/// Engine API capabilities set.
|
||||
@@ -218,6 +219,7 @@ mod tests {
|
||||
|
||||
assert!(!is_critical_method("engine_getBlobsV1"));
|
||||
assert!(!is_critical_method("engine_getBlobsV3"));
|
||||
assert!(!is_critical_method("engine_getBlobsV4"));
|
||||
assert!(!is_critical_method("engine_getPayloadBodiesByHashV1"));
|
||||
assert!(!is_critical_method("engine_getPayloadBodiesByRangeV1"));
|
||||
assert!(!is_critical_method("engine_getClientVersionV1"));
|
||||
|
||||
@@ -3,11 +3,11 @@ use crate::{
|
||||
};
|
||||
use alloy_eips::{
|
||||
eip1898::BlockHashOrNumber,
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip4895::Withdrawals,
|
||||
eip7685::RequestsOrHash,
|
||||
};
|
||||
use alloy_primitives::{BlockHash, BlockNumber, B256, U64};
|
||||
use alloy_primitives::{BlockHash, BlockNumber, B128, B256, U64};
|
||||
use alloy_rpc_types_engine::{
|
||||
CancunPayloadFields, ClientVersionV1, ExecutionData, ExecutionPayloadBodiesV1,
|
||||
ExecutionPayloadBodiesV2, ExecutionPayloadBodyV1, ExecutionPayloadBodyV2,
|
||||
@@ -953,6 +953,35 @@ where
|
||||
.map_err(|err| EngineApiError::Internal(Box::new(err)))
|
||||
}
|
||||
|
||||
fn get_blobs_v4(
|
||||
&self,
|
||||
versioned_hashes: Vec<B256>,
|
||||
indices_bitarray: B128,
|
||||
) -> EngineApiResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
|
||||
let current_timestamp =
|
||||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default().as_secs();
|
||||
if !self.inner.chain_spec.is_amsterdam_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() })
|
||||
}
|
||||
|
||||
// Spec requires returning `null` if syncing.
|
||||
if (*self.inner.is_syncing)() {
|
||||
return Ok(None)
|
||||
}
|
||||
|
||||
self.inner
|
||||
.tx_pool
|
||||
.get_blobs_for_versioned_hashes_v4(&versioned_hashes, indices_bitarray)
|
||||
.map(Some)
|
||||
.map_err(|err| EngineApiError::Internal(Box::new(err)))
|
||||
}
|
||||
|
||||
/// Metered version of `get_blobs_v2`.
|
||||
pub fn get_blobs_v2_metered(
|
||||
&self,
|
||||
@@ -1009,6 +1038,28 @@ where
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// Metered version of `get_blobs_v4`.
|
||||
pub fn get_blobs_v4_metered(
|
||||
&self,
|
||||
versioned_hashes: Vec<B256>,
|
||||
indices_bitarray: B128,
|
||||
) -> EngineApiResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
|
||||
let hashes_len = versioned_hashes.len();
|
||||
let start = Instant::now();
|
||||
let res = Self::get_blobs_v4(self, versioned_hashes, indices_bitarray);
|
||||
self.inner.metrics.latency.get_blobs_v4.record(start.elapsed());
|
||||
|
||||
if let Ok(Some(blobs)) = &res {
|
||||
let blobs_found = blobs.iter().flatten().count();
|
||||
let blobs_missed = hashes_len - blobs_found;
|
||||
|
||||
self.inner.metrics.blob_metrics.blob_count.increment(blobs_found as u64);
|
||||
self.inner.metrics.blob_metrics.blob_misses.increment(blobs_missed as u64);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
// This is the concrete ethereum engine API implementation.
|
||||
@@ -1375,6 +1426,15 @@ where
|
||||
trace!(target: "rpc::engine", "Serving engine_getBlobsV3");
|
||||
Ok(self.get_blobs_v3_metered(versioned_hashes)?)
|
||||
}
|
||||
|
||||
async fn get_blobs_v4(
|
||||
&self,
|
||||
versioned_hashes: Vec<B256>,
|
||||
indices_bitarray: B128,
|
||||
) -> RpcResult<Option<Vec<Option<BlobCellsAndProofsV1>>>> {
|
||||
trace!(target: "rpc::engine", "Serving engine_getBlobsV4");
|
||||
Ok(self.get_blobs_v4_metered(versioned_hashes, indices_bitarray)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Provider, EngineT, Pool, Validator, ChainSpec> IntoEngineApiRpcModule
|
||||
@@ -1660,6 +1720,37 @@ mod tests {
|
||||
assert_matches!(res, Ok(None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_blobs_v4_returns_null_when_syncing() {
|
||||
let chain_spec: Arc<ChainSpec> =
|
||||
Arc::new(ChainSpecBuilder::mainnet().amsterdam_activated().build());
|
||||
let provider = Arc::new(MockEthProvider::default());
|
||||
let payload_store = spawn_test_payload_service::<EthEngineTypes>();
|
||||
let (to_engine, _engine_rx) = unbounded_channel::<BeaconEngineMessage<EthEngineTypes>>();
|
||||
|
||||
let api = EngineApi::new(
|
||||
provider,
|
||||
chain_spec.clone(),
|
||||
ConsensusEngineHandle::new(to_engine),
|
||||
payload_store.into(),
|
||||
NoopTransactionPool::default(),
|
||||
Runtime::test(),
|
||||
ClientVersionV1 {
|
||||
code: ClientCode::RH,
|
||||
name: "Reth".to_string(),
|
||||
version: "v0.0.0-test".to_string(),
|
||||
commit: "test".to_string(),
|
||||
},
|
||||
EngineCapabilities::default(),
|
||||
EthereumEngineValidator::new(chain_spec),
|
||||
false,
|
||||
TestNetworkInfo { syncing: true },
|
||||
);
|
||||
|
||||
let res = api.get_blobs_v4_metered(vec![B256::ZERO], B128::from(1u128));
|
||||
assert_matches!(res, Ok(None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fcu_v3_syncing_precedes_invalid_payload_attributes_validation() {
|
||||
let (mut handle, api) = setup_engine_api();
|
||||
|
||||
@@ -58,6 +58,8 @@ pub(crate) struct EngineApiLatencyMetrics {
|
||||
pub(crate) get_blobs_v2: Histogram,
|
||||
/// Latency for `engine_getBlobsV3`
|
||||
pub(crate) get_blobs_v3: Histogram,
|
||||
/// Latency for `engine_getBlobsV4`
|
||||
pub(crate) get_blobs_v4: Histogram,
|
||||
}
|
||||
|
||||
#[derive(Metrics)]
|
||||
|
||||
@@ -9,9 +9,10 @@ use alloy_primitives::{B256, U256};
|
||||
use alloy_rpc_types_eth::BlockNumberOrTag;
|
||||
use futures::Future;
|
||||
use reth_chain_state::{BlockState, ComputedTrieData, ExecutedBlock};
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec};
|
||||
use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks};
|
||||
use reth_errors::{BlockExecutionError, BlockValidationError, ProviderError, RethError};
|
||||
use reth_evm::{
|
||||
block::TxResult,
|
||||
execute::{BlockBuilder, BlockBuilderOutcome, BlockExecutionOutput},
|
||||
ConfigureEvm, Evm, EvmEnvFor, NextBlockEnvAttributes,
|
||||
};
|
||||
@@ -30,7 +31,7 @@ use reth_transaction_pool::{
|
||||
error::InvalidPoolTransactionError, BestTransactions, BestTransactionsAttributes,
|
||||
PoolTransaction, TransactionPool,
|
||||
};
|
||||
use revm::context_interface::Block;
|
||||
use revm::context_interface::{Block, Cfg as _};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
@@ -263,6 +264,10 @@ pub trait LoadPendingBlock:
|
||||
builder.apply_pre_execution_changes().map_err(Self::Error::from_eth_err)?;
|
||||
|
||||
let block_gas_limit: u64 = builder.evm().block().gas_limit();
|
||||
let is_amsterdam = self
|
||||
.provider()
|
||||
.chain_spec()
|
||||
.is_amsterdam_active_at_timestamp(builder.evm().block().timestamp().saturating_to());
|
||||
let basefee = builder.evm().block().basefee();
|
||||
let blob_gasprice = builder.evm().block().blob_gasprice().map(|p| p as u64);
|
||||
|
||||
@@ -271,8 +276,11 @@ pub trait LoadPendingBlock:
|
||||
.chain_spec()
|
||||
.blob_params_at_timestamp(parent.timestamp())
|
||||
.unwrap_or_else(BlobParams::cancun);
|
||||
let mut cumulative_gas_used = 0;
|
||||
let mut cumulative_tx_gas_used = 0;
|
||||
let mut block_regular_gas_used = 0;
|
||||
let mut block_state_gas_used = 0;
|
||||
let mut sum_blob_gas_used = 0;
|
||||
let tx_gas_limit_cap = builder.evm().cfg_env().tx_gas_limit_cap();
|
||||
|
||||
// Only include transactions if not configured as Empty
|
||||
if !self.pending_block_kind().is_empty() {
|
||||
@@ -287,15 +295,35 @@ pub trait LoadPendingBlock:
|
||||
|
||||
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 {
|
||||
let exceeds_gas_limit = if is_amsterdam {
|
||||
let regular_available_gas =
|
||||
block_gas_limit.saturating_sub(block_regular_gas_used);
|
||||
let state_available_gas = block_gas_limit.saturating_sub(block_state_gas_used);
|
||||
let regular_tx_gas_limit = pool_tx.gas_limit().min(tx_gas_limit_cap);
|
||||
|
||||
if regular_tx_gas_limit > regular_available_gas {
|
||||
Some((regular_tx_gas_limit, regular_available_gas))
|
||||
} else if pool_tx.gas_limit() > state_available_gas {
|
||||
Some((pool_tx.gas_limit(), state_available_gas))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
let block_available_gas =
|
||||
block_gas_limit.saturating_sub(cumulative_tx_gas_used);
|
||||
(pool_tx.gas_limit() > block_available_gas)
|
||||
.then_some((pool_tx.gas_limit(), block_available_gas))
|
||||
};
|
||||
|
||||
if let Some((transaction_gas_limit, block_available_gas)) = exceeds_gas_limit {
|
||||
// we can't fit this transaction into the block, so we need to mark it as
|
||||
// invalid which also removes all dependent transaction from
|
||||
// the iterator before we can continue
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
pool_tx.gas_limit(),
|
||||
block_gas_limit,
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
@@ -337,29 +365,48 @@ pub trait LoadPendingBlock:
|
||||
continue
|
||||
}
|
||||
|
||||
let gas_used = match builder.execute_transaction(tx) {
|
||||
Ok(gas_used) => gas_used.tx_gas_used(),
|
||||
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
|
||||
error,
|
||||
..
|
||||
})) => {
|
||||
if error.is_nonce_too_low() {
|
||||
// if the nonce is too low, we can skip this transaction
|
||||
} else {
|
||||
// if the transaction is invalid, we can skip it and all of its
|
||||
// descendants
|
||||
let mut tx_regular_gas_used = 0;
|
||||
let gas_output =
|
||||
match builder.execute_transaction_with_result_closure(tx, |result| {
|
||||
tx_regular_gas_used = result.result().result.gas().block_regular_gas_used();
|
||||
}) {
|
||||
Ok(gas_output) => gas_output,
|
||||
Err(BlockExecutionError::Validation(BlockValidationError::InvalidTx {
|
||||
error,
|
||||
..
|
||||
})) => {
|
||||
if error.is_nonce_too_low() {
|
||||
// if the nonce is too low, we can skip this transaction
|
||||
} else {
|
||||
// if the transaction is invalid, we can skip it and all of its
|
||||
// descendants
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::Consensus(
|
||||
InvalidTransactionError::TxTypeNotSupported,
|
||||
),
|
||||
);
|
||||
}
|
||||
continue
|
||||
}
|
||||
Err(BlockExecutionError::Validation(
|
||||
BlockValidationError::TransactionGasLimitMoreThanAvailableBlockGas {
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
},
|
||||
)) => {
|
||||
best_txs.mark_invalid(
|
||||
&pool_tx,
|
||||
&InvalidPoolTransactionError::Consensus(
|
||||
InvalidTransactionError::TxTypeNotSupported,
|
||||
&InvalidPoolTransactionError::ExceedsGasLimit(
|
||||
transaction_gas_limit,
|
||||
block_available_gas,
|
||||
),
|
||||
);
|
||||
continue
|
||||
}
|
||||
continue
|
||||
}
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
Err(err) => return Err(Self::Error::from_eth_err(err)),
|
||||
};
|
||||
// this is an error that we should treat as fatal for this attempt
|
||||
Err(err) => return Err(Self::Error::from_eth_err(err)),
|
||||
};
|
||||
|
||||
// add to the total blob gas used if the transaction successfully executed
|
||||
if let Some(tx_blob_gas) = tx_blob_gas {
|
||||
@@ -371,9 +418,11 @@ pub trait LoadPendingBlock:
|
||||
}
|
||||
}
|
||||
|
||||
// add gas used by the transaction to cumulative gas used, before creating the
|
||||
// receipt
|
||||
cumulative_gas_used += gas_used;
|
||||
// Track receipt gas and the Amsterdam block-capacity counter separately.
|
||||
let gas_used = gas_output.tx_gas_used();
|
||||
cumulative_tx_gas_used += gas_used;
|
||||
block_regular_gas_used += tx_regular_gas_used;
|
||||
block_state_gas_used += gas_output.state_gas_used();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -737,18 +737,21 @@ where
|
||||
is_multi_block_range &&
|
||||
all_logs.len() > max_logs_per_response
|
||||
{
|
||||
let retry_to_block =
|
||||
if num_hash.number == from_block { from_block } else { num_hash.number - 1 };
|
||||
|
||||
debug!(
|
||||
target: "rpc::eth::filter",
|
||||
logs_found = all_logs.len(),
|
||||
max_logs_per_response,
|
||||
from_block,
|
||||
to_block = num_hash.number,
|
||||
to_block = retry_to_block,
|
||||
"Query exceeded max logs per response limit"
|
||||
);
|
||||
return Err(EthFilterError::QueryExceedsMaxResults {
|
||||
max_logs: max_logs_per_response,
|
||||
from_block,
|
||||
to_block: num_hash.number,
|
||||
to_block: retry_to_block,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1816,6 +1819,87 @@ mod tests {
|
||||
assert!(result.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_log_limit_retry_range_excludes_overflow_block() {
|
||||
let provider = MockEthProvider::default();
|
||||
|
||||
use alloy_consensus::TxLegacy;
|
||||
use reth_db_api::models::StoredBlockBodyIndices;
|
||||
use reth_ethereum_primitives::{TransactionSigned, TxType};
|
||||
|
||||
let tx_inner = TxLegacy {
|
||||
chain_id: Some(1),
|
||||
nonce: 0,
|
||||
gas_price: 21_000,
|
||||
gas_limit: 21_000,
|
||||
to: alloy_primitives::TxKind::Call(alloy_primitives::Address::ZERO),
|
||||
value: alloy_primitives::U256::ZERO,
|
||||
input: alloy_primitives::Bytes::new(),
|
||||
};
|
||||
let signature = alloy_primitives::Signature::test_signature();
|
||||
let tx = TransactionSigned::new_unhashed(tx_inner.into(), signature);
|
||||
|
||||
let mock_log = alloy_primitives::Log {
|
||||
address: alloy_primitives::Address::ZERO,
|
||||
data: alloy_primitives::LogData::new_unchecked(vec![], alloy_primitives::Bytes::new()),
|
||||
};
|
||||
|
||||
let receipt = reth_ethereum_primitives::Receipt {
|
||||
tx_type: TxType::Legacy,
|
||||
cumulative_gas_used: 21_000,
|
||||
logs: vec![mock_log],
|
||||
success: true,
|
||||
};
|
||||
|
||||
let mut prev_hash = alloy_primitives::B256::default();
|
||||
for (idx, block_number) in (100u64..=102).enumerate() {
|
||||
let header = alloy_consensus::Header {
|
||||
number: block_number,
|
||||
parent_hash: prev_hash,
|
||||
logs_bloom: alloy_primitives::Bloom::from([1u8; 256]),
|
||||
..Default::default()
|
||||
};
|
||||
let hash = header.hash_slow();
|
||||
prev_hash = hash;
|
||||
|
||||
let block = reth_ethereum_primitives::Block {
|
||||
header,
|
||||
body: reth_ethereum_primitives::BlockBody {
|
||||
transactions: vec![tx.clone()],
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
provider.add_block(hash, block);
|
||||
provider.add_receipts(block_number, vec![receipt.clone()]);
|
||||
provider.add_block_body_indices(
|
||||
block_number,
|
||||
StoredBlockBodyIndices { first_tx_num: idx as u64, tx_count: 1 },
|
||||
);
|
||||
}
|
||||
|
||||
let eth_api = build_test_eth_api(provider);
|
||||
let eth_filter = EthFilter::new(eth_api, EthFilterConfig::default(), Runtime::test());
|
||||
let err = eth_filter
|
||||
.inner
|
||||
.clone()
|
||||
.get_logs_in_block_range(
|
||||
Filter::default(),
|
||||
100,
|
||||
102,
|
||||
QueryLimits { max_blocks_per_filter: None, max_logs_per_response: Some(2) },
|
||||
)
|
||||
.await
|
||||
.expect_err("range should exceed max logs");
|
||||
|
||||
let EthFilterError::QueryExceedsMaxResults { max_logs, from_block, to_block } = err else {
|
||||
panic!("unexpected error: {err:?}");
|
||||
};
|
||||
|
||||
assert_eq!(max_logs, 2);
|
||||
assert_eq!(from_block, 100);
|
||||
assert_eq!(to_block, 101);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_non_consecutive_headers_after_bloom_filter() {
|
||||
let provider = MockEthProvider::default();
|
||||
|
||||
@@ -9,7 +9,7 @@ use reth_primitives_traits::constants::BEACON_CONSENSUS_REORG_UNWIND_DEPTH;
|
||||
use reth_provider::{
|
||||
providers::ProviderNodeTypes, BlockHashReader, BlockNumReader, ChainStateBlockReader,
|
||||
ChainStateBlockWriter, DBProvider, DatabaseProviderFactory, ProviderFactory,
|
||||
PruneCheckpointReader, StageCheckpointReader, StageCheckpointWriter,
|
||||
PruneCheckpointReader, StageCheckpointReader, StageCheckpointWriter, StorageSettingsCache,
|
||||
};
|
||||
use reth_prune::PrunerBuilder;
|
||||
use reth_static_file::StaticFileProducer;
|
||||
@@ -269,9 +269,16 @@ impl<N: ProviderNodeTypes> Pipeline<N> {
|
||||
/// - [`StaticFileSegment::Transactions`](reth_static_file_types::StaticFileSegment::Transactions)
|
||||
/// -> [`StageId::Bodies`]
|
||||
///
|
||||
/// This is a legacy storage.v1 backfill step. Storage.v2 writes directly to static files and
|
||||
/// `RocksDB`, so there is no MDBX -> static-file migration to perform.
|
||||
///
|
||||
/// CAUTION: This method locks the static file producer Mutex, hence can block the thread if the
|
||||
/// lock is occupied.
|
||||
pub fn move_to_static_files(&self) -> RethResult<()> {
|
||||
if self.provider_factory.cached_storage_settings().is_v2() {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
// Copies data from database to static files
|
||||
let lowest_static_file_height =
|
||||
self.static_file_producer.lock().copy_to_static_files()?.min_block_num();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use super::utils::*;
|
||||
use crate::{
|
||||
metrics::{DatabaseEnvMetrics, Operation},
|
||||
metrics::{Operation, TableOperationMetrics},
|
||||
DatabaseError,
|
||||
};
|
||||
use reth_db_api::{
|
||||
@@ -15,7 +15,7 @@ use reth_db_api::{
|
||||
};
|
||||
use reth_libmdbx::{Error as MDBXError, TransactionKind, WriteFlags, RO, RW};
|
||||
use reth_storage_errors::db::{DatabaseErrorInfo, DatabaseWriteError, DatabaseWriteOperation};
|
||||
use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds, sync::Arc};
|
||||
use std::{borrow::Cow, collections::Bound, marker::PhantomData, ops::RangeBounds};
|
||||
|
||||
/// Read only Cursor.
|
||||
pub type CursorRO<T> = Cursor<RO, T>;
|
||||
@@ -29,8 +29,8 @@ pub struct Cursor<K: TransactionKind, T: Table> {
|
||||
pub(crate) inner: reth_libmdbx::Cursor<K>,
|
||||
/// Cache buffer that receives compressed values.
|
||||
buf: Vec<u8>,
|
||||
/// Reference to metric handles in the DB environment. If `None`, metrics are not recorded.
|
||||
metrics: Option<Arc<DatabaseEnvMetrics>>,
|
||||
/// Per-table operation metrics. If `None`, metrics are not recorded.
|
||||
metrics: Option<TableOperationMetrics>,
|
||||
/// Phantom data to enforce encoding/decoding.
|
||||
_dbi: PhantomData<T>,
|
||||
}
|
||||
@@ -38,7 +38,7 @@ pub struct Cursor<K: TransactionKind, T: Table> {
|
||||
impl<K: TransactionKind, T: Table> Cursor<K, T> {
|
||||
pub(crate) const fn new_with_metrics(
|
||||
inner: reth_libmdbx::Cursor<K>,
|
||||
metrics: Option<Arc<DatabaseEnvMetrics>>,
|
||||
metrics: Option<TableOperationMetrics>,
|
||||
) -> Self {
|
||||
Self { inner, buf: Vec::new(), metrics, _dbi: PhantomData }
|
||||
}
|
||||
@@ -54,7 +54,7 @@ impl<K: TransactionKind, T: Table> Cursor<K, T> {
|
||||
f: impl FnOnce(&mut Self) -> R,
|
||||
) -> R {
|
||||
if let Some(metrics) = self.metrics.clone() {
|
||||
metrics.record_operation(T::NAME, operation, value_size, || f(self))
|
||||
metrics[operation.index()].record(value_size, || f(self))
|
||||
} else {
|
||||
f(self)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ impl<K: TransactionKind> Tx<K> {
|
||||
|
||||
Ok(Cursor::new_with_metrics(
|
||||
inner,
|
||||
self.metrics_handler.as_ref().map(|h| h.env_metrics.clone()),
|
||||
self.metrics_handler.as_ref().map(|h| h.env_metrics.table_operation_metrics(T::NAME)),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use metrics::Histogram;
|
||||
use quanta::Instant;
|
||||
use reth_metrics::{metrics::Counter, Metrics};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::time::Duration;
|
||||
use std::{array, sync::Arc, time::Duration};
|
||||
use strum::{EnumCount, EnumIter, IntoEnumIterator};
|
||||
|
||||
const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096;
|
||||
@@ -15,8 +15,8 @@ const LARGE_VALUE_THRESHOLD_BYTES: usize = 4096;
|
||||
/// Otherwise, metric recording will no-op.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DatabaseEnvMetrics {
|
||||
/// Caches `OperationMetrics` handles for each table and operation tuple.
|
||||
operations: FxHashMap<(&'static str, Operation), OperationMetrics>,
|
||||
/// Caches per-table operation metric handles for all database operation metrics.
|
||||
operations: FxHashMap<&'static str, TableOperationMetrics>,
|
||||
/// Caches `TransactionMetrics` handles for counters grouped by only transaction mode.
|
||||
/// Updated both at tx open and close.
|
||||
transactions: FxHashMap<TransactionMode, TransactionMetrics>,
|
||||
@@ -26,6 +26,9 @@ pub(crate) struct DatabaseEnvMetrics {
|
||||
FxHashMap<(TransactionMode, TransactionOutcome), TransactionOutcomeMetrics>,
|
||||
}
|
||||
|
||||
/// Per-table operation metric handles cached for hot cursor paths.
|
||||
pub(crate) type TableOperationMetrics = Arc<[OperationMetrics; Operation::COUNT]>;
|
||||
|
||||
impl DatabaseEnvMetrics {
|
||||
pub(crate) fn new() -> Self {
|
||||
// Pre-populate metric handle maps with all possible combinations of labels
|
||||
@@ -37,24 +40,23 @@ impl DatabaseEnvMetrics {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a map of all possible operation handles for each table and operation tuple.
|
||||
/// Used for tracking all operation metrics.
|
||||
fn generate_operation_handles() -> FxHashMap<(&'static str, Operation), OperationMetrics> {
|
||||
let mut operations = FxHashMap::with_capacity_and_hasher(
|
||||
Tables::COUNT * Operation::COUNT,
|
||||
Default::default(),
|
||||
);
|
||||
/// Generate a map of pre-bound operation handles for each table.
|
||||
fn generate_operation_handles() -> FxHashMap<&'static str, TableOperationMetrics> {
|
||||
let mut operations = FxHashMap::with_capacity_and_hasher(Tables::COUNT, Default::default());
|
||||
|
||||
for table in Tables::ALL {
|
||||
for operation in Operation::iter() {
|
||||
operations.insert(
|
||||
(table.name(), operation),
|
||||
OperationMetrics::new_with_labels(&[
|
||||
(Labels::Table.as_str(), table.name()),
|
||||
(Labels::Operation.as_str(), operation.as_str()),
|
||||
]),
|
||||
);
|
||||
}
|
||||
let table_name = table.name();
|
||||
let metrics = array::from_fn(|index| {
|
||||
let operation = Operation::from_index(index);
|
||||
OperationMetrics::new_with_labels(&[
|
||||
(Labels::Table.as_str(), table_name),
|
||||
(Labels::Operation.as_str(), operation.as_str()),
|
||||
])
|
||||
});
|
||||
|
||||
operations.insert(table_name, Arc::new(metrics));
|
||||
}
|
||||
|
||||
operations
|
||||
}
|
||||
|
||||
@@ -105,13 +107,18 @@ impl DatabaseEnvMetrics {
|
||||
value_size: Option<usize>,
|
||||
f: impl FnOnce() -> R,
|
||||
) -> R {
|
||||
if let Some(metrics) = self.operations.get(&(table, operation)) {
|
||||
metrics.record(value_size, f)
|
||||
if let Some(metrics) = self.operations.get(table) {
|
||||
metrics[operation.index()].record(value_size, f)
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns pre-bound operation metric handles for a single table.
|
||||
pub(crate) fn table_operation_metrics(&self, table: &'static str) -> TableOperationMetrics {
|
||||
self.operations.get(table).expect("table operation metric handles not found").clone()
|
||||
}
|
||||
|
||||
/// Record metrics for opening a database transaction.
|
||||
pub(crate) fn record_opened_transaction(&self, mode: TransactionMode) {
|
||||
self.transactions
|
||||
@@ -219,6 +226,39 @@ pub(crate) enum Operation {
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
/// Returns the index of the operation in the cached per-table operation array.
|
||||
pub(crate) const fn index(&self) -> usize {
|
||||
match self {
|
||||
Self::Get => 0,
|
||||
Self::PutUpsert => 1,
|
||||
Self::PutAppend => 2,
|
||||
Self::Delete => 3,
|
||||
Self::CursorUpsert => 4,
|
||||
Self::CursorInsert => 5,
|
||||
Self::CursorAppend => 6,
|
||||
Self::CursorAppendDup => 7,
|
||||
Self::CursorDeleteCurrent => 8,
|
||||
Self::CursorDeleteCurrentDuplicates => 9,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the operation for the given index in the cached per-table operation array.
|
||||
const fn from_index(index: usize) -> Self {
|
||||
match index {
|
||||
0 => Self::Get,
|
||||
1 => Self::PutUpsert,
|
||||
2 => Self::PutAppend,
|
||||
3 => Self::Delete,
|
||||
4 => Self::CursorUpsert,
|
||||
5 => Self::CursorInsert,
|
||||
6 => Self::CursorAppend,
|
||||
7 => Self::CursorAppendDup,
|
||||
8 => Self::CursorDeleteCurrent,
|
||||
9 => Self::CursorDeleteCurrentDuplicates,
|
||||
_ => panic!("invalid operation index"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the operation as a string.
|
||||
pub(crate) const fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
|
||||
@@ -97,6 +97,7 @@ fn generate_bindings(mdbx: &Path, out_file: &Path) {
|
||||
let bindings = bindgen::Builder::default()
|
||||
.header(mdbx.join("mdbx.h").to_string_lossy())
|
||||
.allowlist_var("^(MDBX|mdbx)_.*")
|
||||
.blocklist_item("MDBX_NOTLS")
|
||||
.allowlist_type("^(MDBX|mdbx)_.*")
|
||||
.allowlist_function("^(MDBX|mdbx)_.*")
|
||||
.size_t_is_usize(true)
|
||||
|
||||
@@ -199,7 +199,7 @@ impl EnvironmentFlags {
|
||||
flags |= ffi::MDBX_LIFORECLAIM;
|
||||
}
|
||||
|
||||
flags |= ffi::MDBX_NOTLS;
|
||||
flags |= ffi::MDBX_NOSTICKYTHREADS;
|
||||
|
||||
flags
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ use crossbeam_queue::ArrayQueue;
|
||||
|
||||
/// Lock-free pool of reset read-only MDBX transaction handles.
|
||||
///
|
||||
/// With `MDBX_NOTLS` (which reth always sets), every `mdbx_txn_begin_ex` for a read transaction
|
||||
/// calls `mvcc_bind_slot`, which acquires `lck_rdt_lock` — a pthread mutex. Under high
|
||||
/// With `MDBX_NOSTICKYTHREADS` (which reth always sets), every `mdbx_txn_begin_ex` for a read
|
||||
/// transaction calls `mvcc_bind_slot`, which acquires `lck_rdt_lock` — a pthread mutex. Under high
|
||||
/// concurrency (e.g., prewarming), this becomes a contention point.
|
||||
///
|
||||
/// This pool caches transaction handles that have been reset via `mdbx_txn_reset`. A reset handle
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
use crate::blobstore::{BlobStore, BlobStoreCleanupStat, BlobStoreError, BlobStoreSize};
|
||||
use alloy_eips::{
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip7594::BlobTransactionSidecarVariant,
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip7594::{BlobCellMask, BlobTransactionSidecarVariant},
|
||||
eip7840::BlobParams,
|
||||
merge::EPOCH_SLOTS,
|
||||
};
|
||||
use alloy_primitives::{map::B256Set, TxHash, B256};
|
||||
use alloy_primitives::{map::B256Set, TxHash, B128, B256};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use schnellru::{ByLength, LruMap};
|
||||
use std::{fmt, fs, io, path::PathBuf, sync::Arc};
|
||||
@@ -133,6 +133,74 @@ impl DiskFileBlobStore {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Look up EIP-7594 blob cells by their versioned hashes.
|
||||
fn get_by_versioned_hashes_cells_eip7594(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
|
||||
let cell_mask = BlobCellMask::new(indices_bitarray);
|
||||
let mut result = vec![None; versioned_hashes.len()];
|
||||
let mut missing_count = result.len();
|
||||
|
||||
let cached_blob_sidecars = self
|
||||
.inner
|
||||
.blob_cache
|
||||
.lock()
|
||||
.iter()
|
||||
.map(|(_, blob_sidecar)| Arc::clone(blob_sidecar))
|
||||
.collect::<Vec<_>>();
|
||||
for blob_sidecar in cached_blob_sidecars {
|
||||
if let Some(blob_sidecar) = blob_sidecar.as_eip7594() {
|
||||
for (hash_idx, match_result) in blob_sidecar
|
||||
.match_versioned_hashes_cells(versioned_hashes, cell_mask)
|
||||
.map_err(|err| BlobStoreError::Other(Box::new(err)))?
|
||||
{
|
||||
let slot = &mut result[hash_idx];
|
||||
if slot.is_none() {
|
||||
missing_count -= 1;
|
||||
}
|
||||
*slot = Some(match_result);
|
||||
}
|
||||
}
|
||||
|
||||
if missing_count == 0 && result.iter().all(Option::is_some) {
|
||||
return Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
let mut missing_tx_hashes = Vec::new();
|
||||
{
|
||||
let mut versioned_to_txhashes = self.inner.versioned_hashes_to_txhash.lock();
|
||||
for (idx, _) in
|
||||
result.iter().enumerate().filter(|(_, cells_and_proofs)| cells_and_proofs.is_none())
|
||||
{
|
||||
let versioned_hash = versioned_hashes[idx];
|
||||
if let Some(tx_hash) = versioned_to_txhashes.get(&versioned_hash).copied() {
|
||||
missing_tx_hashes.push(tx_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !missing_tx_hashes.is_empty() {
|
||||
let blobs_from_disk = self.inner.read_many_decoded(missing_tx_hashes);
|
||||
for (_, blob_sidecar) in blobs_from_disk {
|
||||
if let Some(blob_sidecar) = blob_sidecar.as_eip7594() {
|
||||
for (hash_idx, match_result) in blob_sidecar
|
||||
.match_versioned_hashes_cells(versioned_hashes, cell_mask)
|
||||
.map_err(|err| BlobStoreError::Other(Box::new(err)))?
|
||||
{
|
||||
if result[hash_idx].is_none() {
|
||||
result[hash_idx] = Some(match_result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl BlobStore for DiskFileBlobStore {
|
||||
@@ -303,6 +371,14 @@ impl BlobStore for DiskFileBlobStore {
|
||||
self.get_by_versioned_hashes_eip7594(versioned_hashes)
|
||||
}
|
||||
|
||||
fn get_by_versioned_hashes_v4(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
|
||||
self.get_by_versioned_hashes_cells_eip7594(versioned_hashes, indices_bitarray)
|
||||
}
|
||||
|
||||
fn data_size_hint(&self) -> Option<usize> {
|
||||
Some(self.inner.size_tracker.data_size())
|
||||
}
|
||||
@@ -925,6 +1001,27 @@ mod tests {
|
||||
assert_eq!(v3, vec![Some(expected), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disk_get_blobs_v4_returns_requested_cells() {
|
||||
let (store, _dir) = tmp_store();
|
||||
|
||||
let (sidecar, versioned_hash, _) = eip7594_single_blob_sidecar();
|
||||
store.insert(TxHash::random(), sidecar).unwrap();
|
||||
|
||||
let indices_bitarray = B128::from((1u128 << 0) | (1u128 << 7));
|
||||
let request = vec![versioned_hash, B256::ZERO];
|
||||
|
||||
let v4 = store.get_by_versioned_hashes_v4(&request, indices_bitarray).unwrap();
|
||||
assert_eq!(v4.len(), request.len());
|
||||
assert!(v4[1].is_none());
|
||||
|
||||
let cells_and_proofs = v4[0].as_ref().unwrap();
|
||||
assert_eq!(cells_and_proofs.blob_cells.len(), 2);
|
||||
assert_eq!(cells_and_proofs.proofs.len(), 2);
|
||||
assert!(cells_and_proofs.blob_cells.iter().all(Option::is_some));
|
||||
assert_eq!(cells_and_proofs.proofs, vec![Some(Bytes48::default()); 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disk_get_blobs_v3_can_fallback_to_disk() {
|
||||
let (store, _dir) = tmp_store();
|
||||
@@ -937,6 +1034,20 @@ mod tests {
|
||||
assert_eq!(v3, vec![Some(expected)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disk_get_blobs_v4_can_fallback_to_disk() {
|
||||
let (store, _dir) = tmp_store();
|
||||
|
||||
let (sidecar, versioned_hash, _) = eip7594_single_blob_sidecar();
|
||||
store.insert(TxHash::random(), sidecar).unwrap();
|
||||
store.clear_cache();
|
||||
|
||||
let v4 = store.get_by_versioned_hashes_v4(&[versioned_hash], B128::from(1u128)).unwrap();
|
||||
let cells_and_proofs = v4[0].as_ref().unwrap();
|
||||
assert_eq!(cells_and_proofs.blob_cells.len(), 1);
|
||||
assert_eq!(cells_and_proofs.proofs, vec![Some(Bytes48::default())]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disk_double_cleanup_no_failure() {
|
||||
let (store, _dir) = tmp_store();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::blobstore::{BlobStore, BlobStoreCleanupStat, BlobStoreError, BlobStoreSize};
|
||||
use alloy_eips::{
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip7594::BlobTransactionSidecarVariant,
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip7594::{BlobCellMask, BlobTransactionSidecarVariant},
|
||||
};
|
||||
use alloy_primitives::{map::B256Map, B256};
|
||||
use alloy_primitives::{map::B256Map, B128, B256};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -48,6 +48,37 @@ impl InMemoryBlobStore {
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Look up EIP-7594 blob cells by their versioned hashes.
|
||||
fn get_by_versioned_hashes_cells_eip7594(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
|
||||
let cell_mask = BlobCellMask::new(indices_bitarray);
|
||||
let mut result = vec![None; versioned_hashes.len()];
|
||||
let mut missing_count = result.len();
|
||||
let blob_sidecars = self.inner.store.read().values().cloned().collect::<Vec<_>>();
|
||||
for blob_sidecar in blob_sidecars {
|
||||
if let Some(blob_sidecar) = blob_sidecar.as_eip7594() {
|
||||
for (hash_idx, match_result) in blob_sidecar
|
||||
.match_versioned_hashes_cells(versioned_hashes, cell_mask)
|
||||
.map_err(|err| BlobStoreError::Other(Box::new(err)))?
|
||||
{
|
||||
let slot = &mut result[hash_idx];
|
||||
if slot.is_none() {
|
||||
missing_count -= 1;
|
||||
}
|
||||
*slot = Some(match_result);
|
||||
}
|
||||
}
|
||||
|
||||
if missing_count == 0 && result.iter().all(Option::is_some) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -186,6 +217,14 @@ impl BlobStore for InMemoryBlobStore {
|
||||
Ok(self.get_by_versioned_hashes_eip7594(versioned_hashes))
|
||||
}
|
||||
|
||||
fn get_by_versioned_hashes_v4(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
|
||||
self.get_by_versioned_hashes_cells_eip7594(versioned_hashes, indices_bitarray)
|
||||
}
|
||||
|
||||
fn data_size_hint(&self) -> Option<usize> {
|
||||
Some(self.inner.size_tracker.data_size())
|
||||
}
|
||||
@@ -255,4 +294,25 @@ mod tests {
|
||||
let v3 = store.get_by_versioned_hashes_v3(&request).unwrap();
|
||||
assert_eq!(v3, vec![Some(expected), None]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mem_get_blobs_v4_returns_requested_cells() {
|
||||
let store = InMemoryBlobStore::default();
|
||||
|
||||
let (sidecar, versioned_hash, _) = eip7594_single_blob_sidecar();
|
||||
store.insert(B256::random(), sidecar).unwrap();
|
||||
|
||||
let indices_bitarray = B128::from((1u128 << 0) | (1u128 << 7));
|
||||
let request = vec![versioned_hash, B256::ZERO];
|
||||
|
||||
let v4 = store.get_by_versioned_hashes_v4(&request, indices_bitarray).unwrap();
|
||||
assert_eq!(v4.len(), request.len());
|
||||
assert!(v4[1].is_none());
|
||||
|
||||
let cells_and_proofs = v4[0].as_ref().unwrap();
|
||||
assert_eq!(cells_and_proofs.blob_cells.len(), 2);
|
||||
assert_eq!(cells_and_proofs.proofs.len(), 2);
|
||||
assert!(cells_and_proofs.blob_cells.iter().all(Option::is_some));
|
||||
assert_eq!(cells_and_proofs.proofs, vec![Some(Bytes48::default()); 2]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! Storage for blob data of EIP4844 transactions.
|
||||
|
||||
use alloy_eips::{
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip7594::BlobTransactionSidecarVariant,
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use alloy_primitives::{B128, B256};
|
||||
pub use converter::BlobSidecarConverter;
|
||||
pub use disk::{DiskFileBlobStore, DiskFileBlobStoreConfig, OpenDiskFileBlobStore};
|
||||
pub use mem::InMemoryBlobStore;
|
||||
@@ -109,6 +109,17 @@ pub trait BlobStore: fmt::Debug + Send + Sync + 'static {
|
||||
versioned_hashes: &[B256],
|
||||
) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError>;
|
||||
|
||||
/// Return the [`BlobCellsAndProofsV1`]s for a list of blob versioned hashes and requested cell
|
||||
/// indices.
|
||||
///
|
||||
/// The response is always the same length as the request. Missing or older-version blobs are
|
||||
/// returned as `None` elements.
|
||||
fn get_by_versioned_hashes_v4(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError>;
|
||||
|
||||
/// Data size of all transactions in the blob store.
|
||||
fn data_size_hint(&self) -> Option<usize>;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::blobstore::{BlobStore, BlobStoreCleanupStat, BlobStoreError};
|
||||
use alloy_eips::{
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip7594::BlobTransactionSidecarVariant,
|
||||
};
|
||||
use alloy_primitives::B256;
|
||||
use alloy_primitives::{B128, B256};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A blobstore implementation that does nothing
|
||||
@@ -85,6 +85,14 @@ impl BlobStore for NoopBlobStore {
|
||||
Ok(vec![None; versioned_hashes.len()])
|
||||
}
|
||||
|
||||
fn get_by_versioned_hashes_v4(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
_indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
|
||||
Ok(vec![None; versioned_hashes.len()])
|
||||
}
|
||||
|
||||
fn data_size_hint(&self) -> Option<usize> {
|
||||
Some(0)
|
||||
}
|
||||
|
||||
@@ -303,10 +303,10 @@ pub use crate::{
|
||||
};
|
||||
use crate::{identifier::TransactionId, pool::PoolInner};
|
||||
use alloy_eips::{
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip7594::BlobTransactionSidecarVariant,
|
||||
};
|
||||
use alloy_primitives::{map::AddressSet, Address, TxHash, B256, U256};
|
||||
use alloy_primitives::{map::AddressSet, Address, TxHash, B128, B256, U256};
|
||||
use aquamarine as _;
|
||||
use reth_chainspec::{ChainSpecProvider, EthereumHardforks};
|
||||
use reth_eth_wire_types::HandleMempoolData;
|
||||
@@ -808,6 +808,14 @@ where
|
||||
) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError> {
|
||||
self.pool.blob_store().get_by_versioned_hashes_v3(versioned_hashes)
|
||||
}
|
||||
|
||||
fn get_blobs_for_versioned_hashes_v4(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
|
||||
self.pool.blob_store().get_by_versioned_hashes_v4(versioned_hashes, indices_bitarray)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V, T, S> TransactionPoolExt for Pool<V, T, S>
|
||||
|
||||
@@ -16,10 +16,10 @@ use crate::{
|
||||
};
|
||||
use alloy_eips::{
|
||||
eip1559::ETHEREUM_BLOCK_GAS_LIMIT_30M,
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2},
|
||||
eip4844::{BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1},
|
||||
eip7594::BlobTransactionSidecarVariant,
|
||||
};
|
||||
use alloy_primitives::{map::AddressSet, Address, TxHash, B256, U256};
|
||||
use alloy_primitives::{map::AddressSet, Address, TxHash, B128, B256, U256};
|
||||
use reth_eth_wire_types::HandleMempoolData;
|
||||
use reth_primitives_traits::Recovered;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
@@ -380,6 +380,14 @@ impl<T: EthPoolTransaction> TransactionPool for NoopTransactionPool<T> {
|
||||
) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError> {
|
||||
Ok(vec![None; versioned_hashes.len()])
|
||||
}
|
||||
|
||||
fn get_blobs_for_versioned_hashes_v4(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
_indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError> {
|
||||
Ok(vec![None; versioned_hashes.len()])
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`TransactionValidator`] that does nothing.
|
||||
|
||||
@@ -65,12 +65,13 @@ use alloy_eips::{
|
||||
eip2718::{Encodable2718, WithEncoded},
|
||||
eip2930::AccessList,
|
||||
eip4844::{
|
||||
env_settings::KzgSettings, BlobAndProofV1, BlobAndProofV2, BlobTransactionValidationError,
|
||||
env_settings::KzgSettings, BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1,
|
||||
BlobTransactionValidationError,
|
||||
},
|
||||
eip7594::BlobTransactionSidecarVariant,
|
||||
eip7702::SignedAuthorization,
|
||||
};
|
||||
use alloy_primitives::{map::AddressSet, Address, Bytes, TxHash, TxKind, B256, U256};
|
||||
use alloy_primitives::{map::AddressSet, Address, Bytes, TxHash, TxKind, B128, B256, U256};
|
||||
use futures_util::{ready, Stream};
|
||||
use reth_eth_wire_types::HandleMempoolData;
|
||||
use reth_ethereum_primitives::{PooledTransactionVariant, TransactionSigned};
|
||||
@@ -722,6 +723,17 @@ pub trait TransactionPool: Clone + Debug + Send + Sync {
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
) -> Result<Vec<Option<BlobAndProofV2>>, BlobStoreError>;
|
||||
|
||||
/// Return the [`BlobCellsAndProofsV1`]s for a list of blob versioned hashes and requested cell
|
||||
/// indices.
|
||||
///
|
||||
/// The response is always the same length as the request. Missing or older-version blobs are
|
||||
/// returned as `None` elements.
|
||||
fn get_blobs_for_versioned_hashes_v4(
|
||||
&self,
|
||||
versioned_hashes: &[B256],
|
||||
indices_bitarray: B128,
|
||||
) -> Result<Vec<Option<BlobCellsAndProofsV1>>, BlobStoreError>;
|
||||
}
|
||||
|
||||
/// Extension for [`TransactionPool`] trait that allows to set the current block info.
|
||||
|
||||
@@ -94,4 +94,5 @@ allow-git = [
|
||||
"https://github.com/alloy-rs/hardforks",
|
||||
"https://github.com/paradigmxyz/jsonrpsee",
|
||||
"https://github.com/DaniPopes/slotmap.git",
|
||||
"https://github.com/sigp/discv5",
|
||||
]
|
||||
|
||||
@@ -101,7 +101,7 @@ target "hive" {
|
||||
BINARY = "reth"
|
||||
MANIFEST_PATH = "bin/reth"
|
||||
}
|
||||
tags = ["reth:local"]
|
||||
tags = ["ghcr.io/paradigmxyz/reth:latest"]
|
||||
output = ["type=docker,dest=${HIVE_OUTPUT_DIR}/reth_image.tar"]
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 0,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "vocs",
|
||||
"dependencies": {
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"vocs": "^1.0.13",
|
||||
"react": "^19.2.5",
|
||||
"react-dom": "^19.2.5",
|
||||
"vocs": "~1.0.13",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.0.14",
|
||||
"@types/react": "^19.1.8",
|
||||
"glob": "^11.0.3",
|
||||
"typescript": "^5.8.3",
|
||||
"@types/node": "^24.12.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"glob": "^11.1.0",
|
||||
"typescript": "^5.9.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -23,33 +24,33 @@
|
||||
|
||||
"@antfu/utils": ["@antfu/utils@8.1.1", "", {}, "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ=="],
|
||||
|
||||
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
"@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
|
||||
|
||||
"@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="],
|
||||
"@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
|
||||
|
||||
"@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
||||
"@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
|
||||
|
||||
"@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="],
|
||||
"@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
|
||||
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
|
||||
|
||||
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
|
||||
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
|
||||
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="],
|
||||
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
|
||||
|
||||
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
|
||||
|
||||
"@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="],
|
||||
"@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="],
|
||||
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
|
||||
|
||||
"@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="],
|
||||
|
||||
@@ -59,11 +60,11 @@
|
||||
|
||||
"@babel/runtime": ["@babel/runtime@7.27.6", "", {}, "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q=="],
|
||||
|
||||
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
"@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
|
||||
|
||||
"@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="],
|
||||
"@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.1", "", {}, "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw=="],
|
||||
|
||||
@@ -135,43 +136,41 @@
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.6", "", { "os": "win32", "cpu": "x64" }, "sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA=="],
|
||||
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.2", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw=="],
|
||||
"@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
|
||||
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.2", "", { "dependencies": { "@floating-ui/core": "^1.7.2", "@floating-ui/utils": "^0.2.10" } }, "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA=="],
|
||||
"@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
|
||||
|
||||
"@floating-ui/react": ["@floating-ui/react@0.27.13", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.4", "@floating-ui/utils": "^0.2.10", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-Qmj6t9TjgWAvbygNEu1hj4dbHI9CY0ziCMIJrmYoDIn9TUAH5lRmiIeZmRd4c6QEZkzdoH7jNnoNyoY1AIESiA=="],
|
||||
"@floating-ui/react": ["@floating-ui/react@0.27.19", "", { "dependencies": { "@floating-ui/react-dom": "^2.1.8", "@floating-ui/utils": "^0.2.11", "tabbable": "^6.0.0" }, "peerDependencies": { "react": ">=17.0.0", "react-dom": ">=17.0.0" } }, "sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog=="],
|
||||
|
||||
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.4", "", { "dependencies": { "@floating-ui/dom": "^1.7.2" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw=="],
|
||||
"@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.8", "", { "dependencies": { "@floating-ui/dom": "^1.7.6" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A=="],
|
||||
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
|
||||
|
||||
"@fortawesome/fontawesome-free": ["@fortawesome/fontawesome-free@6.7.2", "", {}, "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA=="],
|
||||
|
||||
"@hono/node-server": ["@hono/node-server@1.16.0", "", { "peerDependencies": { "hono": "^4" } }, "sha512-9LwRb5XOrTFapOABiQjGC50wRVlzUvWZsDHINCnkBniP+Q+LQf4waN0nzk9t+2kqcTsnGnieSmqpHsr6kH2bdw=="],
|
||||
"@hono/node-server": ["@hono/node-server@1.19.14", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw=="],
|
||||
|
||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||
|
||||
"@iconify/utils": ["@iconify/utils@2.3.0", "", { "dependencies": { "@antfu/install-pkg": "^1.0.0", "@antfu/utils": "^8.1.0", "@iconify/types": "^2.0.0", "debug": "^4.4.0", "globals": "^15.14.0", "kolorist": "^1.8.0", "local-pkg": "^1.0.0", "mlly": "^1.7.4" } }, "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA=="],
|
||||
|
||||
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||
|
||||
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
|
||||
|
||||
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.12", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg=="],
|
||||
|
||||
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="],
|
||||
|
||||
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.29", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ=="],
|
||||
|
||||
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.0", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw=="],
|
||||
"@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="],
|
||||
|
||||
"@mdx-js/react": ["@mdx-js/react@3.1.0", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ=="],
|
||||
"@mdx-js/react": ["@mdx-js/react@3.1.1", "", { "dependencies": { "@types/mdx": "^2.0.0" }, "peerDependencies": { "@types/react": ">=16", "react": ">=16" } }, "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw=="],
|
||||
|
||||
"@mdx-js/rollup": ["@mdx-js/rollup@3.1.0", "", { "dependencies": { "@mdx-js/mdx": "^3.0.0", "@rollup/pluginutils": "^5.0.0", "source-map": "^0.7.0", "vfile": "^6.0.0" }, "peerDependencies": { "rollup": ">=2" } }, "sha512-q4xOtUXpCzeouE8GaJ8StT4rDxm/U5j6lkMHL2srb2Q3Y7cobE0aXyPzXVVlbeIMBi+5R5MpbiaVE5/vJUdnHg=="],
|
||||
"@mdx-js/rollup": ["@mdx-js/rollup@3.1.1", "", { "dependencies": { "@mdx-js/mdx": "^3.0.0", "@rollup/pluginutils": "^5.0.0", "source-map": "^0.7.0", "vfile": "^6.0.0" }, "peerDependencies": { "rollup": ">=2" } }, "sha512-v8satFmBB+DqDzYohnm1u2JOvxx6Hl3pUvqzJvfs2Zk/ngZ1aRUhsWpXvwPkNeGN9c2NCm/38H29ZqXQUjf8dw=="],
|
||||
|
||||
"@mermaid-js/parser": ["@mermaid-js/parser@0.6.2", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ=="],
|
||||
|
||||
@@ -307,7 +306,7 @@
|
||||
|
||||
"@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.19", "", {}, "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA=="],
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
|
||||
|
||||
"@rollup/pluginutils": ["@rollup/pluginutils@5.2.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw=="],
|
||||
|
||||
@@ -373,6 +372,8 @@
|
||||
|
||||
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="],
|
||||
|
||||
"@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
|
||||
|
||||
"@tailwindcss/node": ["@tailwindcss/node@4.0.7", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.7" } }, "sha512-dkFXufkbRB2mu3FPsW5xLAUWJyexpJA+/VtQj18k3SUiJVLdpgzBd1v1gRRcIpEJj7K5KpxBKfOXlZxT3ZZRuA=="],
|
||||
|
||||
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.7", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.7", "@tailwindcss/oxide-darwin-arm64": "4.0.7", "@tailwindcss/oxide-darwin-x64": "4.0.7", "@tailwindcss/oxide-freebsd-x64": "4.0.7", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.7", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.7", "@tailwindcss/oxide-linux-arm64-musl": "4.0.7", "@tailwindcss/oxide-linux-x64-gnu": "4.0.7", "@tailwindcss/oxide-linux-x64-musl": "4.0.7", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.7", "@tailwindcss/oxide-win32-x64-msvc": "4.0.7" } }, "sha512-yr6w5YMgjy+B+zkJiJtIYGXW+HNYOPfRPtSs+aqLnKwdEzNrGv4ZuJh9hYJ3mcA+HMq/K1rtFV+KsEr65S558g=="],
|
||||
@@ -487,9 +488,9 @@
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
|
||||
"@types/node": ["@types/node@24.12.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g=="],
|
||||
|
||||
"@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="],
|
||||
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
@@ -501,19 +502,19 @@
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids": ["@vanilla-extract/babel-plugin-debug-ids@1.2.2", "", { "dependencies": { "@babel/core": "^7.23.9" } }, "sha512-MeDWGICAF9zA/OZLOKwhoRlsUW+fiMwnfuOAqFVohL31Agj7Q/RBWAYweqjHLgFBCsdnr6XIfwjJnmb2znEWxw=="],
|
||||
|
||||
"@vanilla-extract/compiler": ["@vanilla-extract/compiler@0.3.0", "", { "dependencies": { "@vanilla-extract/css": "^1.17.4", "@vanilla-extract/integration": "^8.0.4", "vite": "^5.0.0 || ^6.0.0", "vite-node": "^3.2.2" } }, "sha512-8EbPmDMXhY9NrN38Kh8xYDENgBk4i6s6ce4p7E9F3kHtCqxtEgfaKSNS08z/SVCTmaX3IB3N/kGSO0gr+APffg=="],
|
||||
"@vanilla-extract/compiler": ["@vanilla-extract/compiler@0.7.0", "", { "dependencies": { "@vanilla-extract/css": "^1.20.1", "@vanilla-extract/integration": "^8.0.9", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", "vite-node": "^3.2.2 || ^5.0.0 || ^6.0.0" } }, "sha512-rZQ40HVmsxfGLjoflwwsaUBLfpbpKDoZC19oiDA0FHq4LdrYtyVbFkc0MfqkNo/qBCvaZfsRezCqk0QQxCqZ8w=="],
|
||||
|
||||
"@vanilla-extract/css": ["@vanilla-extract/css@1.17.4", "", { "dependencies": { "@emotion/hash": "^0.9.0", "@vanilla-extract/private": "^1.0.9", "css-what": "^6.1.0", "cssesc": "^3.0.0", "csstype": "^3.0.7", "dedent": "^1.5.3", "deep-object-diff": "^1.1.9", "deepmerge": "^4.2.2", "lru-cache": "^10.4.3", "media-query-parser": "^2.0.2", "modern-ahocorasick": "^1.0.0", "picocolors": "^1.0.0" } }, "sha512-m3g9nQDWPtL+sTFdtCGRMI1Vrp86Ay4PBYq1Bo7Bnchj5ElNtAJpOqD+zg+apthVA4fB7oVpMWNjwpa6ElDWFQ=="],
|
||||
"@vanilla-extract/css": ["@vanilla-extract/css@1.20.1", "", { "dependencies": { "@emotion/hash": "^0.9.0", "@vanilla-extract/private": "^1.0.9", "css-what": "^6.1.0", "csstype": "^3.2.3", "dedent": "^1.5.3", "deep-object-diff": "^1.1.9", "deepmerge": "^4.2.2", "lru-cache": "^10.4.3", "media-query-parser": "^2.0.2", "modern-ahocorasick": "^1.0.0", "picocolors": "^1.0.0" } }, "sha512-5I9RNo5uZW9tsBnqrWzJqELegOqTHBrZyDFnES0gR9gJJHBB9dom1N0bwITM9tKwBcfKrTX4a6DHVeQdJ2ubQA=="],
|
||||
|
||||
"@vanilla-extract/dynamic": ["@vanilla-extract/dynamic@2.1.5", "", { "dependencies": { "@vanilla-extract/private": "^1.0.9" } }, "sha512-QGIFGb1qyXQkbzx6X6i3+3LMc/iv/ZMBttMBL+Wm/DetQd36KsKsFg5CtH3qy+1hCA/5w93mEIIAiL4fkM8ycw=="],
|
||||
|
||||
"@vanilla-extract/integration": ["@vanilla-extract/integration@8.0.4", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/plugin-syntax-typescript": "^7.23.3", "@vanilla-extract/babel-plugin-debug-ids": "^1.2.2", "@vanilla-extract/css": "^1.17.4", "dedent": "^1.5.3", "esbuild": "npm:esbuild@>=0.17.6 <0.26.0", "eval": "0.1.8", "find-up": "^5.0.0", "javascript-stringify": "^2.0.1", "mlly": "^1.4.2" } }, "sha512-cmOb7tR+g3ulKvFtSbmdw3YUyIS1d7MQqN+FcbwNhdieyno5xzUyfDCMjeWJhmCSMvZ6WlinkrOkgs6SHB+FRg=="],
|
||||
"@vanilla-extract/integration": ["@vanilla-extract/integration@8.0.10", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/plugin-syntax-typescript": "^7.23.3", "@vanilla-extract/babel-plugin-debug-ids": "^1.2.2", "@vanilla-extract/css": "^1.20.1", "dedent": "^1.5.3", "esbuild": "npm:esbuild@>=0.17.6 <0.29.0", "eval": "0.1.8", "find-up": "^5.0.0", "javascript-stringify": "^2.0.1", "mlly": "^1.4.2" } }, "sha512-01IB5gbrgTe8IIrtfRXXTmACl5D8Enzqp2cKbCWaMKXmnoilXXVCPbJoA96q88PXkNDXsXepCxUugMvEmL3c7A=="],
|
||||
|
||||
"@vanilla-extract/private": ["@vanilla-extract/private@1.0.9", "", {}, "sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA=="],
|
||||
|
||||
"@vanilla-extract/vite-plugin": ["@vanilla-extract/vite-plugin@5.1.0", "", { "dependencies": { "@vanilla-extract/compiler": "^0.3.0", "@vanilla-extract/integration": "^8.0.4" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0" } }, "sha512-BzVdmBD+FUyJnY6I29ZezwtDBc1B78l+VvHvIgoJYbgfPj0hvY0RmrGL8B4oNNGY/lOt7KgQflXY5kBMd3MGZg=="],
|
||||
"@vanilla-extract/vite-plugin": ["@vanilla-extract/vite-plugin@5.2.2", "", { "dependencies": { "@vanilla-extract/compiler": "^0.7.0", "@vanilla-extract/integration": "^8.0.9" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-AUyB4fDR2b/Mo0lcXhhlf6RxnDPYwFMyKKopalJ4BwQNKYzZSoTwHJ1PLPO9SKhpz7lzXc0Z18GHQZOewzl3YA=="],
|
||||
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.6.0", "", { "dependencies": { "@babel/core": "^7.27.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.19", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" } }, "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ=="],
|
||||
"@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="],
|
||||
|
||||
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
|
||||
|
||||
@@ -531,7 +532,7 @@
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||
|
||||
@@ -541,7 +542,7 @@
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
|
||||
"brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
|
||||
|
||||
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
|
||||
|
||||
@@ -591,7 +592,7 @@
|
||||
|
||||
"compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="],
|
||||
|
||||
"compression": ["compression@1.8.0", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.0.2", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA=="],
|
||||
"compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="],
|
||||
|
||||
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
|
||||
@@ -609,9 +610,7 @@
|
||||
|
||||
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
||||
|
||||
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
|
||||
|
||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"cytoscape": ["cytoscape@3.32.1", "", {}, "sha512-dbeqFTLYEwlFg7UGtcZhCCG/2WayX72zK3Sq323CEX29CY81tYfVhw1MIdduCtpstB0cTOhJswWlM/OEB3Xp+Q=="],
|
||||
|
||||
@@ -729,7 +728,7 @@
|
||||
|
||||
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.18.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ=="],
|
||||
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
|
||||
|
||||
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
@@ -775,7 +774,7 @@
|
||||
|
||||
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
|
||||
|
||||
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
|
||||
"fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
|
||||
|
||||
"fault": ["fault@2.0.1", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ=="],
|
||||
|
||||
@@ -793,7 +792,7 @@
|
||||
|
||||
"fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
|
||||
|
||||
"fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
||||
"fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
|
||||
|
||||
@@ -805,7 +804,7 @@
|
||||
|
||||
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
|
||||
|
||||
"glob": ["glob@11.0.3", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.0.3", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA=="],
|
||||
"glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="],
|
||||
|
||||
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
|
||||
|
||||
@@ -851,7 +850,7 @@
|
||||
|
||||
"hastscript": ["hastscript@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw=="],
|
||||
|
||||
"hono": ["hono@4.8.5", "", {}, "sha512-Up2cQbtNz1s111qpnnECdTGqSIUIhZJMLikdKkshebQSEBcoUKq6XJayLGqSZWidiH0zfHRCJqFu062Mz5UuRA=="],
|
||||
"hono": ["hono@4.12.14", "", {}, "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w=="],
|
||||
|
||||
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||
|
||||
@@ -901,7 +900,7 @@
|
||||
|
||||
"javascript-stringify": ["javascript-stringify@2.1.0", "", {}, "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg=="],
|
||||
|
||||
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
|
||||
|
||||
@@ -921,27 +920,29 @@
|
||||
|
||||
"layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
|
||||
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
|
||||
|
||||
"local-pkg": ["local-pkg@1.1.1", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.0.1", "quansync": "^0.2.8" } }, "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg=="],
|
||||
|
||||
@@ -1093,7 +1094,7 @@
|
||||
|
||||
"mini-svg-data-uri": ["mini-svg-data-uri@1.4.4", "", { "bin": { "mini-svg-data-uri": "cli.js" } }, "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg=="],
|
||||
|
||||
"minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||
"minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
|
||||
|
||||
@@ -1117,9 +1118,11 @@
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"nuqs": ["nuqs@2.8.9", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^5 || ^6 || ^7", "react-router-dom": "^5 || ^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ=="],
|
||||
|
||||
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||
|
||||
"on-headers": ["on-headers@1.0.2", "", {}, "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="],
|
||||
"on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="],
|
||||
|
||||
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||
|
||||
@@ -1181,9 +1184,9 @@
|
||||
|
||||
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||
|
||||
"react": ["react@19.1.0", "", {}, "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg=="],
|
||||
"react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="],
|
||||
|
||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
||||
"react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="],
|
||||
|
||||
"react-intersection-observer": ["react-intersection-observer@9.16.0", "", { "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["react-dom"] }, "sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA=="],
|
||||
|
||||
@@ -1193,7 +1196,7 @@
|
||||
|
||||
"react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
||||
|
||||
"react-router": ["react-router@7.7.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-3FUYSwlvB/5wRJVTL/aavqHmfUKe0+Xm9MllkYgGo9eDwNdkvwlJGjpPxono1kCycLt6AnDTgjmXvK3/B4QGuw=="],
|
||||
"react-router": ["react-router@7.14.1", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-5BCvFskyAAVumqhEKh/iPhLOIkfxcEUz8WqFIARCkMg8hZZzDYX9CtwxXA0e+qT8zAxmMC0x3Ckb9iMONwc5jg=="],
|
||||
|
||||
"react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
||||
|
||||
@@ -1229,7 +1232,7 @@
|
||||
|
||||
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
||||
|
||||
"remark-mdx": ["remark-mdx@3.1.0", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA=="],
|
||||
"remark-mdx": ["remark-mdx@3.1.1", "", { "dependencies": { "mdast-util-mdx": "^3.0.0", "micromark-extension-mdxjs": "^3.0.0" } }, "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg=="],
|
||||
|
||||
"remark-mdx-frontmatter": ["remark-mdx-frontmatter@5.2.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "estree-util-value-to-estree": "^3.0.0", "toml": "^3.0.0", "unified": "^11.0.0", "unist-util-mdx-define": "^1.0.0", "yaml": "^2.0.0" } }, "sha512-U/hjUYTkQqNjjMRYyilJgLXSPF65qbLPdoESOkXyrwz2tVyhAnm4GUKhfXqOOS9W34M3545xEMq+aMpHgVjEeQ=="],
|
||||
|
||||
@@ -1259,7 +1262,7 @@
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
|
||||
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
||||
|
||||
@@ -1317,7 +1320,7 @@
|
||||
|
||||
"tailwindcss": ["tailwindcss@4.0.7", "", {}, "sha512-yH5bPPyapavo7L+547h3c4jcBXcrKwybQRjwdEIVAd9iXRvy/3T1CC6XSQEgZtRySjKfqvo3Cc0ZF1DTheuIdA=="],
|
||||
|
||||
"tapable": ["tapable@2.2.2", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
|
||||
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="],
|
||||
|
||||
@@ -1341,13 +1344,13 @@
|
||||
|
||||
"twoslash-protocol": ["twoslash-protocol@0.2.12", "", {}, "sha512-5qZLXVYfZ9ABdjqbvPc4RWMr7PrpPaaDSeaYY55vl/w1j6H6kzsWK/urAEIXlzYlyrFmyz1UbwIt+AA0ck+wbg=="],
|
||||
|
||||
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"ua-parser-js": ["ua-parser-js@1.0.40", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew=="],
|
||||
|
||||
"ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
|
||||
|
||||
"undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
|
||||
|
||||
@@ -1397,7 +1400,7 @@
|
||||
|
||||
"vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="],
|
||||
|
||||
"vocs": ["vocs@1.0.13", "", { "dependencies": { "@floating-ui/react": "^0.27.4", "@hono/node-server": "^1.13.8", "@mdx-js/react": "^3.1.0", "@mdx-js/rollup": "^3.1.0", "@noble/hashes": "^1.7.1", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-navigation-menu": "^1.2.5", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-tabs": "^1.1.3", "@shikijs/rehype": "^1", "@shikijs/transformers": "^1", "@shikijs/twoslash": "^1", "@tailwindcss/vite": "4.0.7", "@vanilla-extract/css": "^1.17.1", "@vanilla-extract/dynamic": "^2.1.2", "@vanilla-extract/vite-plugin": "^5.0.1", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "cac": "^6.7.14", "chroma-js": "^3.1.2", "clsx": "^2.1.1", "compression": "^1.8.0", "create-vocs": "^1.0.0-alpha.5", "cross-spawn": "^7.0.6", "fs-extra": "^11.3.0", "globby": "^14.1.0", "hastscript": "^8.0.0", "hono": "^4.7.1", "mark.js": "^8.11.1", "mdast-util-directive": "^3.1.0", "mdast-util-from-markdown": "^2.0.2", "mdast-util-frontmatter": "^2.0.1", "mdast-util-gfm": "^3.1.0", "mdast-util-mdx": "^3.0.0", "mdast-util-mdx-jsx": "^3.2.0", "mdast-util-to-hast": "^13.2.0", "mdast-util-to-markdown": "^2.1.2", "minimatch": "^9.0.5", "minisearch": "^6.3.0", "ora": "^7.0.1", "p-limit": "^5.0.0", "playwright": "^1.52.0", "postcss": "^8.5.2", "radix-ui": "^1.1.3", "react-intersection-observer": "^9.15.1", "react-router": "^7.2.0", "rehype-autolink-headings": "^7.1.0", "rehype-class-names": "^2.0.0", "rehype-mermaid": "^3.0.0", "rehype-slug": "^6.0.0", "remark-directive": "^3.0.1", "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-mdx": "^3.1.0", "remark-mdx-frontmatter": "^5.0.0", "remark-parse": "^11.0.0", "serve-static": "^1.16.2", "shiki": "^1", "toml": "^3.0.0", "twoslash": "~0.2.12", "ua-parser-js": "^1.0.40", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "vite": "^6.1.0" }, "peerDependencies": { "react": "^19", "react-dom": "^19" }, "bin": { "vocs": "_lib/cli/index.js" } }, "sha512-V/ogXG5xw7jMFXI2Wv0d0ZdCeeT5jzaX0PKdRKcqhnd21UtLZrqa5pKZkStNIZyVpvfsLW0WB7wjB4iBOpueiw=="],
|
||||
"vocs": ["vocs@1.0.16", "", { "dependencies": { "@floating-ui/react": "^0.27.4", "@hono/node-server": "^1.13.8", "@mdx-js/react": "^3.1.0", "@mdx-js/rollup": "^3.1.0", "@noble/hashes": "^1.7.1", "@radix-ui/colors": "^3.0.0", "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-navigation-menu": "^1.2.5", "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-tabs": "^1.1.3", "@shikijs/rehype": "^1", "@shikijs/transformers": "^1", "@shikijs/twoslash": "^1", "@tailwindcss/vite": "4.0.7", "@vanilla-extract/css": "^1.17.1", "@vanilla-extract/dynamic": "^2.1.2", "@vanilla-extract/vite-plugin": "^5.0.1", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "cac": "^6.7.14", "chroma-js": "^3.1.2", "clsx": "^2.1.1", "compression": "^1.8.0", "create-vocs": "^1.0.0-alpha.5", "cross-spawn": "^7.0.6", "fs-extra": "^11.3.0", "globby": "^14.1.0", "hastscript": "^8.0.0", "hono": "^4.7.1", "mark.js": "^8.11.1", "mdast-util-directive": "^3.1.0", "mdast-util-from-markdown": "^2.0.2", "mdast-util-frontmatter": "^2.0.1", "mdast-util-gfm": "^3.1.0", "mdast-util-mdx": "^3.0.0", "mdast-util-mdx-jsx": "^3.2.0", "mdast-util-to-hast": "^13.2.0", "mdast-util-to-markdown": "^2.1.2", "minimatch": "^9.0.5", "minisearch": "^6.3.0", "nuqs": "^2.4.3", "ora": "^7.0.1", "p-limit": "^5.0.0", "playwright": "^1.52.0", "postcss": "^8.5.2", "radix-ui": "^1.1.3", "react-intersection-observer": "^9.15.1", "react-router": "^7.2.0", "rehype-autolink-headings": "^7.1.0", "rehype-class-names": "^2.0.0", "rehype-mermaid": "^3.0.0", "rehype-slug": "^6.0.0", "remark-directive": "^3.0.1", "remark-frontmatter": "^5.0.0", "remark-gfm": "^4.0.1", "remark-mdx": "^3.1.0", "remark-mdx-frontmatter": "^5.0.0", "remark-parse": "^11.0.0", "serve-static": "^1.16.2", "shiki": "^1", "toml": "^3.0.0", "twoslash": "~0.2.12", "ua-parser-js": "^1.0.40", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "vite": "^6.1.0" }, "peerDependencies": { "react": "^19", "react-dom": "^19" }, "bin": { "vocs": "_lib/cli/index.js" } }, "sha512-Jqe9SgqAxdjpWFV0WzvL/OHVha8ArF1wvwQd+WEBfzRLFHlFGIl35zUW0CczM0EuoePi85LU0axF9s1ToAR0Wg=="],
|
||||
|
||||
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
|
||||
|
||||
@@ -1427,11 +1430,11 @@
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@babel/core/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
"@babel/core/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"@babel/traverse/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
"@babel/traverse/debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"@clack/prompts/is-unicode-supported": ["is-unicode-supported@1.3.0", "", { "bundled": true }, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
|
||||
|
||||
@@ -1439,12 +1442,34 @@
|
||||
|
||||
"@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
"@radix-ui/react-popper/@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.4", "", { "dependencies": { "@floating-ui/dom": "^1.7.2" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw=="],
|
||||
|
||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"@types/babel__core/@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="],
|
||||
|
||||
"@types/babel__core/@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
|
||||
"@types/babel__generator/@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
|
||||
"@types/babel__template/@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="],
|
||||
|
||||
"@types/babel__template/@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
|
||||
"@types/babel__traverse/@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
|
||||
"@typescript/vfs/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core": ["@babel/core@7.28.0", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.27.3", "@babel/helpers": "^7.27.6", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
|
||||
|
||||
"@vanilla-extract/css/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
|
||||
|
||||
"create-vocs/fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="],
|
||||
|
||||
"cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="],
|
||||
|
||||
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||
@@ -1453,6 +1478,8 @@
|
||||
|
||||
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
|
||||
|
||||
"eval/@types/node": ["@types/node@24.0.14", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw=="],
|
||||
|
||||
"execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||
|
||||
"hast-util-from-dom/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
|
||||
@@ -1473,7 +1500,7 @@
|
||||
|
||||
"micromark/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||
"micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
|
||||
|
||||
"p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
@@ -1497,7 +1524,7 @@
|
||||
|
||||
"vite-node/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"vocs/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||
"vocs/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
|
||||
|
||||
"wrap-ansi/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
|
||||
|
||||
@@ -1515,12 +1542,104 @@
|
||||
|
||||
"@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.7.2", "", { "dependencies": { "@floating-ui/core": "^1.7.2", "@floating-ui/utils": "^0.2.10" } }, "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA=="],
|
||||
|
||||
"@types/babel__core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@types/babel__generator/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@types/babel__template/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@types/babel__traverse/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@typescript/vfs/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/generator": ["@babel/generator@7.28.0", "", { "dependencies": { "@babel/parser": "^7.28.0", "@babel/types": "^7.28.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helpers": ["@babel/helpers@7.27.6", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.27.6" } }, "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/parser": ["@babel/parser@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.0" }, "bin": "./bin/babel-parser.js" }, "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/traverse": ["@babel/traverse@7.28.0", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.0", "@babel/template": "^7.27.2", "@babel/types": "^7.28.0", "debug": "^4.3.1" } }, "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/types": ["@babel/types@7.28.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="],
|
||||
|
||||
"@vanilla-extract/integration/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
|
||||
|
||||
"cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="],
|
||||
|
||||
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
|
||||
|
||||
"eval/@types/node/undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="],
|
||||
|
||||
"hast-util-from-dom/hastscript/property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
"local-pkg/pkg-types/confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="],
|
||||
@@ -1533,10 +1652,86 @@
|
||||
|
||||
"vite-node/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"vocs/minimatch/brace-expansion": ["brace-expansion@2.1.0", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w=="],
|
||||
|
||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
|
||||
|
||||
"@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom/@floating-ui/core": ["@floating-ui/core@1.7.2", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw=="],
|
||||
|
||||
"@radix-ui/react-popper/@floating-ui/react-dom/@floating-ui/dom/@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.0", "", {}, "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-module-transforms/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
|
||||
|
||||
"@vanilla-extract/babel-plugin-debug-ids/@babel/core/debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
|
||||
|
||||
"@vanilla-extract/compiler/vite/tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"vocs/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,9 @@ Networking:
|
||||
[default: 9200]
|
||||
|
||||
--discovery.v5.port.ipv6 <DISCOVERY_V5_PORT_IPV6>
|
||||
The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set
|
||||
The UDP IPv6 port to use for devp2p peer discovery version 5. Not used unless `--addr` is IPv6, or `--discovery.addr.ipv6` is set.
|
||||
|
||||
If not provided, discovery V5 defaults to same port as discovery V4 (--discovery.port).
|
||||
|
||||
[default: 9200]
|
||||
|
||||
@@ -249,6 +251,16 @@ Networking:
|
||||
|
||||
[default: 25600]
|
||||
|
||||
--tx-channel-memory-limit <BYTES>
|
||||
Memory limit (in bytes) for the channel that buffers transaction events flowing
|
||||
from the network manager to the transactions manager.
|
||||
|
||||
When the budget is exhausted, new events are dropped (see metric
|
||||
`total_dropped_tx_events_at_full_capacity`). Acts as a backstop against unbounded
|
||||
memory growth under sustained P2P transaction flooding.
|
||||
|
||||
[default: 1073741824]
|
||||
|
||||
--net-if.experimental <IF_NAME>
|
||||
Name of network interface used to communicate with peers.
|
||||
|
||||
@@ -1074,13 +1086,13 @@ Engine:
|
||||
When enabled, persistence cycles are deferred from the moment an FCU with payload attributes arrives until the next FCU clears the build. Useful on chains with short block times where persistence I/O can interfere with block building latency.
|
||||
|
||||
--engine.disable-bal-parallel-execution
|
||||
Disable BAL (Block Access List, EIP-7928) based parallel execution. When set, falls back to transaction-based prewarming even when a BAL is available
|
||||
Disable BAL (Block Access List, EIP-7928) based parallel execution. Defaults to disabled, falling back to transaction-based prewarming even when a BAL is available
|
||||
|
||||
--engine.disable-bal-parallel-state-root
|
||||
Disable BAL-driven parallel state root computation. When set, the BAL hashed post state is not sent to the multiproof task for early parallel state root computation
|
||||
|
||||
--engine.disable-bal-batch-io
|
||||
Disable BAL (Block Access List) batched IO during prewarming. When set, falls back to individual per-slot storage reads instead of batched cursor reads
|
||||
Disable BAL (Block Access List) storage prefetch IO during prewarming. When set, BAL storage slots are not read into the execution cache
|
||||
|
||||
ERA:
|
||||
--era.enable
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user