Compare commits

...

31 Commits

Author SHA1 Message Date
DaniPopes
fcfa8287f6 chore(mdbx): replace deprecated MDBX_NOTLS with MDBX_NOSTICKYTHREADS (#23378) 2026-04-30 03:19:09 +00:00
Arsenii Kulikov
d25de30050 feat: customizable discovery defaults (#23843)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2026-04-29 22:59:10 +00:00
Derek Cofausper
4ffde69d94 fix(engine): apply finalized state after syncing FCU head import (#23838)
Co-authored-by: Centaur AI <ai@centaur.local>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: klkvr <klkvrr@gmail.com>
2026-04-29 15:29:42 +00:00
Arsenii Kulikov
077e5eecfe chore: don't enforce non-empty blocks in e2e payload building (#23837)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2026-04-29 14:36:44 +00:00
Sergei Shulepov
709485dcb7 perf(bench): buffer RPC fetches in generate-big-block (#23830)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 12:57:05 +00:00
Arsenii Kulikov
88505c7fcb fix(re-execute): properly handle selfdestructed storage slots (#23832) 2026-04-29 12:30:27 +00:00
Emma Jamieson-Hoare
c14bc59236 chore: release 2.2.0 (#23831)
Co-authored-by: Amp <amp@ampcode.com>
2026-04-29 12:25:15 +00:00
Derek Cofausper
347c1325cc fix: skip move_to_static_files for storage.v2 (#23814) 2026-04-29 12:19:43 +00:00
Matthias Seitz
5f85eb7ac8 feat(engine): add getBlobsV4 endpoint (#23767) 2026-04-29 12:02:25 +00:00
Brian Picciano
a12454d2e6 perf(db): prebind cursor operation metrics (#23654)
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com>
2026-04-29 11:40:17 +00:00
Matthias Seitz
c194c17a27 chore(deps): bump alloy to 2.0.4 (#23828) 2026-04-29 13:41:02 +02:00
figtracer
43a7452b0e fix(rpc): narrow getLogs retry range (#23818)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2026-04-29 10:09:49 +00:00
Matthias Seitz
73ec2c9d56 docs(engine): clarify BAL storage prefetch flag (#23815) 2026-04-29 08:37:51 +00:00
Alexey Shekhirin
76e886578b ci(hive): update amsterdam fixtures and branch (#23807) 2026-04-29 07:48:24 +00:00
Arsenii Kulikov
ad08829288 feat: introduce memory-bound channel for network<->tx manager messages (#23802)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: Amp <amp@ampcode.com>
2026-04-28 21:29:19 +00:00
grandizzy
b89288582b ci: harden supply chain across all workflows (#23785) 2026-04-28 15:44:05 +00:00
Arsenii Kulikov
87d878a979 feat: support binding discv5 and discv4 to the same port (#23613) 2026-04-28 15:08:19 +00:00
Matthias Seitz
473f85c558 test(rpc): cover admin node info discv5 port (#23781) 2026-04-28 15:02:24 +00:00
Brian Picciano
a8eee6028f fix(bench): run feature first in GitHub workflow (#23777)
Co-authored-by: Brian Picciano <933154+mediocregopher@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
2026-04-28 14:56:06 +00:00
Sergei Shulepov
671da55884 refactor: expose executor transaction result type (#23759) 2026-04-28 14:36:45 +00:00
Alexey Shekhirin
a8fc13dc25 deps: bump alloy-evm to 0.33.3 (#23778) 2026-04-28 16:07:13 +02:00
Matthias Seitz
4c1f6b6507 fix(payload): track Amsterdam block gas in builders (#23743) 2026-04-28 13:31:25 +00:00
RandoomWalks
b850f2a81d fix(net): apply count cap to BlockAccessLists request handler (#23754)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
2026-04-28 12:57:59 +00:00
Sergei Shulepov
79144cb430 fix(bench): reth-bb multiple executors (#23763) 2026-04-28 11:53:53 +00:00
Alexey Shekhirin
b04346ffe5 feat(engine): disable BAL storage prefetch on CLI arg (#23770) 2026-04-28 11:40:53 +00:00
Alexey Shekhirin
674623f14e ci(hive): ignore more EIP-7610 tests (#23769) 2026-04-28 11:37:42 +00:00
Sergei Shulepov
97b1b56b2d fix(bench): dedupe merged BAL storage reads (#23758) 2026-04-28 10:39:31 +00:00
Alexey Shekhirin
af6d20b5ea ci(hive): tag Reth image correctly and update fixtures (#23765) 2026-04-28 09:59:55 +00:00
Sergei Shulepov
64cf412aaf chore(engine): disable BAL parallel execution by default (#23764) 2026-04-28 09:27:19 +00:00
Matthias Seitz
5b10e03c5c perf(engine): spawn BAL hashed state before storage prefetch (#23761) 2026-04-28 08:39:55 +00:00
Arsenii Kulikov
91d248e6fa feat: bound memory footpring of p2p messages (#23718) 2026-04-27 14:37:40 +00:00
108 changed files with 3760 additions and 1289 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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: |

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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: |

View File

@@ -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

View File

@@ -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:"

View File

@@ -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

View File

@@ -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}\`)."

View File

@@ -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 "

View File

@@ -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 }}"

View File

@@ -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

View File

@@ -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 }}"

View File

@@ -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')

View File

@@ -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)

View File

@@ -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) }}

View File

@@ -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"'"
}
}'

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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/

View File

@@ -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

View File

@@ -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

View File

@@ -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 }}

View File

@@ -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 }}

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}));
}
}

View File

@@ -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",
),

View File

@@ -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

View File

@@ -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")]

View File

@@ -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.

View File

@@ -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 {

View File

@@ -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;
}

View File

@@ -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()),
)
}

View File

@@ -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());
}

View File

@@ -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(())
}

View File

@@ -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 {

View File

@@ -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>>;

View File

@@ -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)?;

View File

@@ -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"]

View File

@@ -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)
}

View File

@@ -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.

View File

@@ -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,
}
}

View File

@@ -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::{

View File

@@ -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::*;

View File

@@ -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.

View File

@@ -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> + '_ {

View File

@@ -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);

View File

@@ -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 {

View File

@@ -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"] }

View File

@@ -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);

View File

@@ -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");
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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 -- */

View File

@@ -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();

View File

@@ -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,
}
}
}

View File

@@ -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).

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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")]

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,
}
}
}

View File

@@ -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>

View File

@@ -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"));

View File

@@ -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();

View File

@@ -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)]

View File

@@ -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();
}
}

View File

@@ -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();

View File

@@ -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();

View File

@@ -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)
}

View File

@@ -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)),
))
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -199,7 +199,7 @@ impl EnvironmentFlags {
flags |= ffi::MDBX_LIFORECLAIM;
}
flags |= ffi::MDBX_NOTLS;
flags |= ffi::MDBX_NOSTICKYTHREADS;
flags
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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]);
}
}

View File

@@ -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>;

View File

@@ -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)
}

View File

@@ -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>

View File

@@ -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.

View File

@@ -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.

View File

@@ -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",
]

View File

@@ -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"]
}

View File

@@ -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=="],
}
}

View File

@@ -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