Skip to content

feat(p2p): add inbound BlocksByRange req/resp support#348

Merged
MegaRedHand merged 7 commits into
lambdaclass:mainfrom
dicethedev:feat/blocks-by-range-inbound-support
May 12, 2026
Merged

feat(p2p): add inbound BlocksByRange req/resp support#348
MegaRedHand merged 7 commits into
lambdaclass:mainfrom
dicethedev:feat/blocks-by-range-inbound-support

Conversation

@dicethedev
Copy link
Copy Markdown
Contributor

🗒️ Description / Motivation

This PR adds inbound BlocksByRange request-response support to the P2P req/resp protocol implementation.

The change follows the recently merged spec update:

This is needed so peers can request canonical blocks by slot range, similar to the existing BlocksByRoot protocol.

The implementation:

  • registers the new protocol
  • adds SSZ request/response handling
  • supports serving canonical blocks from local storage
  • validates malformed requests

This improves interoperability with other clients implementing the updated spec.


What Changed

Req/Resp Protocol

  • Added BlocksByRangeRequest
  • Added BlocksByRange response payload variant
  • Added protocol ID:
    • /leanconsensus/req/blocks_by_range/1/ssz_snappy

Codec

  • Updated request/response codec read paths
  • Updated request/response codec write paths

Behaviour Registration

  • Registered BlocksByRange in the libp2p request-response behaviour

Inbound Request Handling

  • Added inbound request handler for BlocksByRange
  • Serves canonical blocks by walking backward from the current fork-choice head
  • Skips:
    • empty slots
    • side forks

Validation

Added validation for:

  • step == 0
  • count > 1024

Invalid requests return protocol error responses.

Tests

  • Added unit test for canonical range selection and ordering

Correctness / Behavior Guarantees

Preserved Invariants

  • Only canonical blocks are returned
  • Returned blocks preserve requested slot ordering
  • Empty slots are skipped
  • Non-canonical side forks are ignored

Behavior Notes

  • Invalid requests are rejected early with error responses
  • Maximum request size is capped at 1024 blocks
  • Implementation mirrors existing BlocksByRoot handling patterns for consistency

Tests Added / Run

Added

  • blocks_by_range_returns_canonical_blocks_in_requested_order

Verified With

cargo fmt --check
cargo check -p ethlambda-p2p
cargo test -p ethlambda-p2p blocks_by_range_returns_canonical_blocks_in_requested_order
git diff --check

Related Issues / PRs

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR adds inbound BlocksByRange request-response support to the P2P layer, enabling peers to request canonical blocks by slot range. It follows the existing BlocksByRoot pattern closely — registering the new protocol, adding SSZ codec paths, validating requests (step == 0, count == 0, count > 1024), and serving canonical blocks by walking backward from the fork-choice head.

  • canonical_blocks_by_range collects matching canonical blocks by traversing the chain from store.head() to start_slot, skipping non-canonical side forks and empty slots before assembling the result in ascending slot order.
  • ResponsePayload::BlocksByRoot is renamed to ResponsePayload::Blocks, unifying the response type for both protocols; the existing unit test is complemented by a new blocks_by_range_returns_canonical_blocks_in_requested_order test.
  • Validation now rejects step == 0, count == 0, and count > 1024 with INVALID_REQUEST — the error_message helper and ResponseCode that were previously dead code are now live.

Confidence Score: 4/5

Safe to merge with the traversal performance concern addressed; no correctness bugs in block selection or validation.

The block-selection logic is correct and the validation catches the required bad inputs. The one concern is that canonical_blocks_by_range always starts from store.head() and walks all the way back to start_slot on every request — a peer asking for very old slots forces O(head_slot) chain traversal regardless of how small the requested range is, which is exploitable as a cheap DoS.

crates/net/p2p/src/req_resp/handlers.rs — specifically the canonical_blocks_by_range traversal loop.

Important Files Changed

Filename Overview
crates/net/p2p/src/req_resp/handlers.rs Adds inbound BlocksByRange handling and canonical_blocks_by_range; the traversal walks from head to start_slot unconditionally, creating an O(head_slot) DoS surface for requests targeting old slot ranges.
crates/net/p2p/src/req_resp/messages.rs Adds BlocksByRangeRequest struct, BLOCKS_BY_RANGE_PROTOCOL_V1 constant, MAX_REQUEST_BLOCKS, and renames ResponsePayload::BlocksByRoot to ResponsePayload::Blocks; changes look correct and well-typed.
crates/net/p2p/src/req_resp/codec.rs Adds BlocksByRange decode path and unifies block response decoding under decode_blocks_response; logic is correct and symmetric with the existing BlocksByRoot path.
crates/net/p2p/src/req_resp/mod.rs Re-exports new types and constants; straightforward bookkeeping, no issues.
crates/net/p2p/src/lib.rs Registers BLOCKS_BY_RANGE_PROTOCOL_V1 with ProtocolSupport::Full; consistent with how BlocksByRoot is registered.

Sequence Diagram

sequenceDiagram
    participant Peer
    participant Codec
    participant Handler
    participant Store

    Peer->>Codec: BlocksByRange SSZ request
    Codec->>Handler: "Request::BlocksByRange { start_slot, count, step }"
    Handler->>Handler: "validate (step==0, count==0, count>1024)"
    alt invalid request
        Handler-->>Peer: Response::Error(INVALID_REQUEST)
    else valid request
        Handler->>Store: store.head()
        loop walk backward from head to start_slot
            Store-->>Handler: block header (slot, parent_root)
        end
        Handler->>Store: store.get_signed_block(root) per matched slot
        Store-->>Handler: "Vec<SignedBlock>"
        Handler-->>Codec: Response::Success(ResponsePayload::Blocks)
        Codec-->>Peer: SSZ-encoded block chunks
    end
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
crates/net/p2p/src/req_resp/handlers.rs:210-236
**Unbounded chain traversal is a DoS vector**

`canonical_blocks_by_range` always starts from `store.head()` and walks backward block-by-block until it passes `start_slot`, regardless of how far `end_slot` is from the head. A peer sending a valid request (passes all validation) for `start_slot=1, count=1024, step=1` on a node whose head is at slot 1,000,000 forces ~1,000,000 `store.get_block_header` calls per request. The 1024-block cap on `count` bounds the range width but places no limit on `head_slot – end_slot`, so repeated requests for old slot ranges can saturate the node with O(head_slot) work.

Adding an early exit once all matching slots have been collected would cap the traversal at the first filled slot in the range, and a separate guard rejecting requests where `end_slot` is unreasonably far below the head would prevent the worst-case walk entirely.

Reviews (2): Last reviewed commit: "Merge branch 'main' into feat/blocks-by-..." | Re-trigger Greptile

Comment thread crates/net/p2p/src/req_resp/handlers.rs Outdated
Comment thread crates/net/p2p/src/req_resp/handlers.rs Outdated
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Copy link
Copy Markdown
Collaborator

@MegaRedHand MegaRedHand left a comment

Choose a reason for hiding this comment

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

Left some comments and questions

Comment thread crates/net/p2p/src/req_resp/codec.rs Outdated
Comment thread crates/net/p2p/src/req_resp/messages.rs Outdated
Comment thread crates/net/p2p/src/req_resp/handlers.rs Outdated
Comment thread crates/net/p2p/src/req_resp/handlers.rs
dicethedev and others added 2 commits May 11, 2026 17:08
Co-authored-by: Tomás Grüner <47506558+MegaRedHand@users.noreply.github.com>
@dicethedev
Copy link
Copy Markdown
Contributor Author

@MegaRedHand You can review again

@MegaRedHand MegaRedHand reopened this May 12, 2026
@MegaRedHand
Copy link
Copy Markdown
Collaborator

CI's failing due to a linter error

Comment thread crates/net/p2p/src/req_resp/handlers.rs
@dicethedev
Copy link
Copy Markdown
Contributor Author

dicethedev commented May 12, 2026

CI's failing due to a linter error

let me see if I can fix from my end. @MegaRedHand You review issue again.

Copy link
Copy Markdown
Collaborator

@MegaRedHand MegaRedHand left a comment

Choose a reason for hiding this comment

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

LGTM

@MegaRedHand MegaRedHand merged commit c5705af into lambdaclass:main May 12, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants