Skip to content

perf(comm): cache RLP-encoded block bytes to avoid per-peer re-serialization#40

Open
simonzg wants to merge 2 commits into
mainnetfrom
perf/block-rlp-cache
Open

perf(comm): cache RLP-encoded block bytes to avoid per-peer re-serialization#40
simonzg wants to merge 2 commits into
mainnetfrom
perf/block-rlp-cache

Conversation

@simonzg

@simonzg simonzg commented May 1, 2026

Copy link
Copy Markdown
Contributor

Why

During block propagation, every peer that doesn't yet have a block will request it — typically within seconds of it being produced. The MsgGetBlockByID and MsgGetBlocksFromNumber RPC handlers call rlp.EncodeToBytes from scratch for every individual peer request:

bbytes, _ := rlp.EncodeToBytes(&block.EscortedBlock{Block: blk, EscortQC: escortQC})

RLP-encoding a full block (header + all transactions + QC) allocates large byte slices and traverses the entire block structure. With 100 connected peers, this work is repeated 100 times for the same block — O(peers) encoding cost for what is logically a one-time O(1) operation. The repeated allocations also add GC pressure during the high-traffic window right after each new block.

What Changed

Added a 512-entry LRU cache encodedBlocksCache (keyed by meter.Bytes32 block ID, values are []byte) to Communicator.

Both MsgGetBlockByID and MsgGetBlocksFromNumber now:

  1. Check the cache first — return the cached bytes immediately on hit.
  2. Encode and populate the cache on miss.

Blocks are immutable once committed to the chain, so cached encoded bytes are permanently valid and require no invalidation. The 512-entry cap covers well over an epoch's worth of blocks with negligible memory overhead.

Expected Impact

  • Block RLP encoding: O(peers) → O(1) per block during propagation (encode once, serve many).
  • Reduced allocation spikes and GC pressure in the high-traffic window immediately after each new block.
  • More consistent block propagation latency under high peer counts.

simonzg and others added 2 commits July 10, 2025 09:54
…tion per peer

During block propagation, the same newly-produced block is requested by many
peers in a short window. MsgGetBlockByID and MsgGetBlocksFromNumber handlers
called rlp.EncodeToBytes on every individual peer request with no caching —
O(peers) encoding work for what is logically an O(1) operation.

RLP-encoding a full block (header + transactions + QC) allocates large byte
slices and traverses the entire block structure. With 100 peers requesting
the same block within seconds, this encoding ran 100 times.

Add a 512-entry LRU cache (encodedBlocksCache) on Communicator keyed by
block ID storing the encoded bytes. Both RPC handlers check the cache before
encoding and populate it on miss. Blocks are immutable once committed so
cached bytes are always valid. 512 entries covers well over an epoch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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