Skip to content

perf(node): serve redacted blocks from the sealed header - #1391

Open
mattsse wants to merge 2 commits into
mainfrom
mattsse/perf-redacted-rpc-header-only-block
Open

perf(node): serve redacted blocks from the sealed header#1391
mattsse wants to merge 2 commits into
mainfrom
mattsse/perf-redacted-rpc-header-only-block

Conversation

@mattsse

@mattsse mattsse commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

eth_getBlockByNumber and eth_getBlockByHash on the redacted RPC loaded the recovered block, collected every transaction hash and RLP-encoded the whole block for size, then discarded all of it: redact_block emptied the transaction list and redact_header zeroed size. Header-only requests for old blocks also evicted live traffic from the block cache.

The response is now built from the header alone: the block id is resolved to a hash and the header is read through the eth state cache, which serves it from the cached header or the cached full block and only loads it from the provider on a miss, then it is converted with the same RpcConvert as before with size 0, and paired with empty uncles, an empty transaction list and a withdrawals field derived from the header's withdrawals root. A block has withdrawals exactly when its header has that root, so the serialized JSON is unchanged for every block, including latest, hash lookups and unknown blocks. The one behaviour change is that a header whose body was pruned now returns the redacted header instead of null, which cannot occur on zone nodes since they retain bodies.

eth_coinbase took the same full-block path and now reads the beneficiary from latest_header.

`eth_getBlockByNumber`/`eth_getBlockByHash` on the redacted RPC went through
`EthBlocks::rpc_block(.., false)`, which loads the recovered block — a block
cache hit, or a body read with sender recovery and per-transaction hashing on a
miss — then collects every transaction hash and RLP-encodes the whole block to
fill `size`. `redact_block` immediately threw all of that away: the transaction
list became an empty `BlockTransactions::Hashes`, `redact_header` zeroed `size`,
and withdrawals collapsed to an empty default. Header-only requests for old
blocks also evicted live traffic from the block cache.

The response is now built from `sealed_header_by_id`, converted with the same
`RpcConvert` the full path used (size 0, since it is zeroed anyway) and paired
with an empty uncle list, an empty transaction list, and a withdrawals field
derived from the header's withdrawals root — a block carries withdrawals exactly
when the header has that root, so the serialized JSON is unchanged. `pending` is
already normalized to `latest` before it reaches this path.

`eth_coinbase` used `rpc_block_header`, which loads the recovered block the same
way, and now reads the beneficiary from `latest_header`.
Comment thread crates/node/src/rpc.rs Outdated
Comment on lines 749 to 757
let Some(header) = self
.eth
.api
.provider()
.sealed_header_by_id(id)
.map_err(internal)?
else {
return Ok(raw_null());
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is likely that the block is cache so we should use the get_maybe_block and only on cache miss load the header

The header-only path read the sealed header straight from the provider on
every request. The requested block is usually cached, since clients poll
`latest`, so resolve the block id to a hash and read the header through
`EthStateCache::get_header`: it serves the cached header or the header of
the cached full block, and only on a miss loads it from the provider on the
cache's blocking task and keeps it for the next request. An unknown hash
still returns null.
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.

1 participant