Skip to content

feat(eth): raw-RLP block fetch mode (debug_getRawBlock/Receipts) + archiver --fetch-mode - #1328

Draft
DylanVerstraete wants to merge 4 commits into
usc-devfrom
feat/eth-raw-rlp-fetch
Draft

DylanVerstraete wants to merge 4 commits into
usc-devfrom
feat/eth-raw-rlp-fetch

Conversation

@DylanVerstraete

Copy link
Copy Markdown
Contributor

Why

For the BSC historical sweep (plan: archiver → checkpoint ladder → attestors) the bottleneck is how fast we can pull blocks + receipts out of our own node. The JSON path makes geth build a JSON object per transaction and receipt and ships payloads of up to ~1 MB per BSC block. debug_getRawBlock + debug_getRawReceipts return the RLP the node already stores: several times smaller, no marshalling on the node.

What

eth::Client gains BlockFetchMode { Json (default), RawRlp } and with_fetch_mode. In RawRlp the client fetches both raw payloads from the same provider, decodes them with alloy consensus types and rebuilds the rpc-shaped Block + Vec<TransactionReceipt>:

  • sender recovered from the signature (recover_signer), like the node does for JSON
  • gas_used = delta of the cumulative counters; status, logs, bloom straight from the stored receipt
  • positional metadata (block hash/number, tx and log indices) filled from the block
  • header hash sliced from the raw block RLP, not re-encoded, so it holds even if alloy lacks a chain's newest header field

Everything then flows into the unchanged OrderedBlock::try_from_fetched_block (tx-root and receipt-root checks) and the V1 leaf encoding, so roots are byte-identical between modes. Decoding + ECDSA recovery run on the blocking pool. Public providers don't expose debug_*, so json stays the default and the mode is explicit.

Archiver: --fetch-mode {json,raw-rlp} / FETCH_MODE, applied to every fetch client incl. the reconnect path; --max-compute-threads / MAX_COMPUTE_THREADS to size the merkle pool explicitly (the cores − (fetch_tasks+1) default starves it under cgroup quotas or multi-shard boxes).

Attestor and proof-gen are untouched (default Json).

Verification

  • Unit tests (raw_rlp_tests): two-tx block (legacy transfer + 1559 contract creation with 2 logs), header whose roots match → raw path equals hand-built rpc types (sender, gas deltas, contract address, log indices, bloom) and yields the same merkle root; header-hash slicing equals hash_slow(); error classification (receipt count mismatch, wrong number, garbage block, garbage receipt); mode parsing.
  • Ignored integration test common/eth/tests/raw_rlp_vs_json.rs (ETH_RAW_RLP_TEST_URL): run against reth node --dev seeded with legacy/1559/transfer/create txs, blocks 75..=95: identical hash, item count, root, senders and gas_used on all 21 blocks (10 with txs).
  • Archiver binary in --fetch-mode raw-rlp and json over the same range on that node: 26 stored roots identical (10 non-empty).
  • cargo clippy --workspace --all-targets -- -D warnings (excl. node crate), cargo fmt, taplo, cargo machete: clean.

@cursor

cursor Bot commented Sep 8, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes core block-fetch and attestation input shaping; incorrect RLP decoding or sender recovery could produce wrong roots, though parity tests and shared try_from_fetched_block checks mitigate this.

Overview
Adds an optional raw-RLP path for pulling blocks and receipts so historical sweeps against a node with the debug namespace avoid large JSON payloads and node-side marshalling. eth::Client gets BlockFetchMode (json default, raw-rlp), with_fetch_mode, and a decode pipeline (debug_getRawBlock / debug_getRawReceiptsrpc_block_from_raw on the blocking pool) that rebuilds the same RPC-shaped block/receipts as the existing JSON fetch before OrderedBlock::try_from_fetched_block, so merkle roots stay identical between modes.

The archiver exposes --fetch-mode / FETCH_MODE on all WS fetch clients (including backfill and reconnect) and --max-compute-threads / MAX_COMPUTE_THREADS to override the CPU-based merkle thread formula. Unit tests cover RLP decoding parity with JSON-shaped data; an ignored integration test compares both modes on a live debug-enabled node.

Reviewed by Cursor Bugbot for commit 70c9b97. Bugbot is set up for automated code reviews on this repo. Configure here.

@gluwa-bot

gluwa-bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Overview

Image reference gluwa/creditcoin3:latest gluwa/creditcoin3:latest
- digest dfb918c3c546 99ee47f55127
- tag latest latest
- provenance 7e92a94 e5e5479
- vulnerabilities critical: 3 high: 16 medium: 21 low: 4 unspecified: 13 critical: 2 high: 13 medium: 11 low: 3 unspecified: 11
- platform linux/amd64 linux/amd64
- size 430 MB 431 MB (+1.1 MB)
- packages 405 404 (-1)
Base Image ubuntu:26.04
also known as:
latest
resolute
ubuntu:26.04
also known as:
latest
resolute
rolling
- vulnerabilities critical: 1 high: 7 medium: 12 low: 1 unspecified: 2 critical: 1 high: 5 medium: 2 low: 0
Labels (1 changes)
  • ± 1 changed
  • 3 unchanged
-org.opencontainers.image.created=2026-06-27T04:19:04.617438+00:00
+org.opencontainers.image.created=2026-08-17T09:02:45.677319+00:00
 org.opencontainers.image.description=The Ubuntu container image maintained by Canonical

Ubuntu is a Debian-based Linux operating system that runs from the desktop to the cloud, to all your internet connected things.
It is the world's most popular operating system across public clouds and OpenStack clouds.
It is the number one platform for containers; from Docker to Kubernetes to LXD, Ubuntu can run your containers at scale.
Fast, secure and simple, Ubuntu powers millions of PCs worldwide.

 org.opencontainers.image.title=ubuntu
 org.opencontainers.image.version=26.04
Policies (2 improved, 0 worsened)
Policy Name gluwa/creditcoin3:latest gluwa/creditcoin3:latest Change Standing
Default non-root user No Change
No copyleft licenses ⚠️ 373 ⚠️ 361 -12 Improved
No fixable critical or high vulnerabilities ⚠️ 19 ⚠️ 15 -4 Improved
No high-profile vulnerabilities No Change
No outdated base images ⚠️ ⚠️ No Change
No unapproved base images No Change
Supply chain attestations No Change
Packages and Vulnerabilities (47 package changes and 15 vulnerability changes)
  • ➖ 1 packages removed
  • ♾️ 46 packages changed
  • 337 packages unchanged
  • ✔️ 15 vulnerabilities removed
Changes for packages of type deb (35 changes)
Package Version
gluwa/creditcoin3:latest
Version
gluwa/creditcoin3:latest
♾️ base-files 14ubuntu6.1 14ubuntu6.2
♾️ bsdutils 1:2.41.3-3ubuntu2 1:2.41.3-3ubuntu2.2
♾️ curl 8.18.0-1ubuntu2.3 8.18.0-1ubuntu2.5
♾️ diffutils 1:3.12-1 1:3.12-1ubuntu0.1
♾️ gnu-coreutils 9.7-3ubuntu2 9.7-3ubuntu2.1
♾️ gpgv 2.4.8-4ubuntu3 2.4.8-4ubuntu3.1
♾️ libattr1 1:2.5.2-4 1:2.5.2-4ubuntu0.1
♾️ libaudit-common 1:4.1.2-1build1 1:4.1.2-1ubuntu0.1
♾️ libaudit1 1:4.1.2-1build1 1:4.1.2-1ubuntu0.1
♾️ libblkid1 2.41.3-3ubuntu2 2.41.3-3ubuntu2.2
♾️ libbz2-1.0 1.0.8-6build2 1.0.8-6ubuntu0.1
♾️ libc-bin 2.43-2ubuntu2.3 2.43-2ubuntu2.4
♾️ libc-gconv-modules-extra 2.43-2ubuntu2.3 2.43-2ubuntu2.4
♾️ libc6 2.43-2ubuntu2.3 2.43-2ubuntu2.4
♾️ libcurl4t64 8.18.0-1ubuntu2.3 8.18.0-1ubuntu2.5
♾️ libgcrypt20 1.12.0-2ubuntu1 1.12.0-2ubuntu1.1
♾️ libmount1 2.41.3-3ubuntu2 2.41.3-3ubuntu2.2
♾️ libpam-modules 1.7.0-5ubuntu3.1 1.7.0-5ubuntu3.2
♾️ libpam-modules-bin 1.7.0-5ubuntu3.1 1.7.0-5ubuntu3.2
♾️ libpam-runtime 1.7.0-5ubuntu3.1 1.7.0-5ubuntu3.2
♾️ libpam0g 1.7.0-5ubuntu3.1 1.7.0-5ubuntu3.2
♾️ libpq5 18.4-0ubuntu0.26.04.1 18.6-0ubuntu0.26.04.1
♾️ libsmartcols1 2.41.3-3ubuntu2 2.41.3-3ubuntu2.2
♾️ libssh2-1t64 1.11.1-1ubuntu0.26.04.3 1.11.1-1ubuntu0.26.04.4
♾️ libssl3t64 3.5.5-1ubuntu3.3 3.5.5-1ubuntu3.5
♾️ libsystemd0 259.5-0ubuntu3.3 259.5-0ubuntu3.4
♾️ libudev1 259.5-0ubuntu3.3 259.5-0ubuntu3.4
♾️ libuuid1 2.41.3-3ubuntu2 2.41.3-3ubuntu2.2
♾️ login 1:4.16.0-2+really2.41.3-3ubuntu2 1:4.16.0-2+really2.41.3-3ubuntu2.2
♾️ mount 2.41.3-3ubuntu2 2.41.3-3ubuntu2.2
♾️ openssl 3.5.5-1ubuntu3.3 3.5.5-1ubuntu3.5
♾️ openssl-provider-legacy 3.5.5-1ubuntu3.3 3.5.5-1ubuntu3.5
♾️ perl-base 5.40.1-7ubuntu0.1 5.40.1-7ubuntu0.3
♾️ util-linux 2.41.3-3ubuntu2 2.41.3-3ubuntu2.2
♾️ zlib1g 1:1.3.dfsg+really1.3.1-1ubuntu3 1:1.3.dfsg+really1.3.1-1ubuntu3.1
Changes for packages of type golang (6 changes)
Package Version
gluwa/creditcoin3:latest
Version
gluwa/creditcoin3:latest
♾️ github.com/canonical/pebble 1.31.1-0.20260528050051-33f10658d3fd 1.32.2-0.20260721212935-faa1696b477d
♾️ github.com/gorilla/websocket 1.5.1 1.5.3
critical: 0 high: 0 medium: 1 low: 0
Removed vulnerabilities (1):
  • medium : GHSA--w67g--5rqw--f597
golang.org/x/net 0.40.0
critical: 1 high: 2 medium: 7 low: 0
Removed vulnerabilities (10):
  • critical : CVE--2026--39821
  • high : CVE--2026--46600
  • high : CVE--2026--33814
  • medium : CVE--2026--25680
  • medium : CVE--2026--42506
  • medium : CVE--2026--42502
  • medium : CVE--2026--27136
  • medium : CVE--2026--25681
  • medium : CVE--2025--58190
  • medium : CVE--2025--47911
♾️ golang.org/x/sys 0.33.0 0.46.0
critical: 0 high: 0 medium: 0 low: 1
Removed vulnerabilities (1):
  • low : CVE--2026--39824
♾️ golang.org/x/term 0.32.0 0.44.0
♾️ stdlib 1.26.3 1.26.5
critical: 1 high: 6 medium: 4 low: 0 unspecified: 2 critical: 1 high: 5 medium: 2 low: 0
Removed vulnerabilities (5):
  • high : CVE--2026--42504
  • medium : CVE--2026--27145
  • medium : CVE--2026--42507
  • unspecified : CVE--2026--42505
  • unspecified : CVE--2026--39822
Changes for packages of type npm (6 changes)
Package Version
gluwa/creditcoin3:latest
Version
gluwa/creditcoin3:latest
♾️ @types/node 26.1.2 22.7.5
♾️ node-gyp 13.0.1 13.0.2
♾️ picomatch 4.0.5 4.0.7
♾️ undici 8.10.0 8.10.2
♾️ undici-types 8.3.0 6.21.0
♾️ ws 8.21.2 8.21.3

@DylanVerstraete
DylanVerstraete marked this pull request as draft September 10, 2026 07:24
DylanVerstraete and others added 4 commits September 15, 2026 16:15
…wReceipts

Adds `BlockFetchMode` to the eth `Client`. `Json` is the existing
eth_getBlockByNumber(full) + eth_getBlockReceipts pair. `RawRlp` pulls the
node's stored RLP through `debug_getRawBlock` and `debug_getRawReceipts`,
decodes it with alloy consensus types and rebuilds the rpc-shaped block and
receipts (recovered sender, cumulative-gas delta, positional log metadata),
so `OrderedBlock::try_from_fetched_block` and the V1 leaf encoding run
unchanged on both paths. The header hash is sliced from the raw block RLP
rather than re-encoded, so it does not depend on alloy knowing every header
field a chain uses. Decoding and ECDSA recovery run on the blocking pool.

Intended for a historical sweep against a node we operate: payloads are
several times smaller and the node does no JSON marshalling. Public providers
generally do not expose the debug namespace, so `json` stays the default and
the mode is explicit.

Archiver: `--fetch-mode {json,raw-rlp}` / `FETCH_MODE`, applied to every
fetch client including the reconnect path; logged at startup.

Tests: unit tests build a two-transaction block (legacy transfer + 1559
creation with logs) and check the raw path against hand-built rpc types,
header-hash slicing, and error classification; an ignored integration test
(`ETH_RAW_RLP_TEST_URL`) compares both modes on a live node. Verified against
`reth node --dev`: 21 blocks, identical hashes/roots/senders/gas, and the
archiver binary in both modes stored identical roots over the same range.
The default `available CPUs - (max_fetch_tasks + 1)` sizing starves the
compute pool when several shards share a box or run under a cgroup CPU quota
(Rust's available_parallelism honours the quota). Allow it to be set
explicitly via `--max-compute-threads` / `MAX_COMPUTE_THREADS`.
… independent RPC

Lets us prove archived BSC roots match Chainstack (or any other ETH RPC) using the same simple_merkle_tree path, and keeps local sled/report artifacts out of git.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@jakerumbles
jakerumbles force-pushed the feat/eth-raw-rlp-fetch branch from d82bbae to 5f7c1ad Compare September 15, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants