Conversation
…ath relies on The rules deciding whether a short or misaligned batch response comes from an honest partial-history peer or a dishonest one shipped without tests, because they were inline in `request_block_bodies`/`request_receipts` and tangled with peer-table side effects. That is the code where a scoring bug already slipped through once: success was credited before validation, so a peer returning one valid body and sixty-three invented ones gained score instead of losing it. Extracts three pure functions so the fetch path's decisions can be tested directly, with the callers mapping the results onto the peer table exactly as before: - `classify_body_response`/`classify_receipt_response` return a `ResponseVerdict` of Complete, Compacted or Fabricated. Compacted is the honest case, a peer omitting blocks it does not have, and costs it nothing; Fabricated is an entry matching no requested header at all. - `negotiated_eth_version` reimplements the handshake's highest-mutual rule by hand with nothing guarding against drift. Choosing wrongly sends a peer a request it cannot decode, which is invisible locally and fails only on their side. - `next_batch_range` carries a non-obvious rule: reaching `frontier == floor` is not finished on a run's first batch, because that block is the snap pivot, which snap stored a body for but no receipts. Two behavioural notes. Fabricated receipts now earn a critical failure, bringing them in line with bodies where they were only soft-penalized, and a response can no longer be charged both a critical and a soft failure. And one test documents a limit rather than a behaviour: blocks with no transactions share the same transactions, withdrawals and receipts roots, so an empty body is interchangeable between them and the classification cannot tell compacted from fabricated. That is harmless, since identical bytes are stored either way, but worth pinning.
|
🤖 Kimi Code ReviewI'll review this PR which refactors response classification logic into testable functions and fixes a backfill batch range bug. Let me analyze each part carefully.
|
🤖 Codex Code Review
I didn’t find other obvious correctness or EVM/consensus issues in the diff itself. I couldn’t run the targeted Rust tests here because Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
Lines of code reportTotal lines added: Detailed view |
🤖 Claude Code ReviewReview SummaryOverall this is a well-executed, test-only-motivated refactor. Extracting Finding 1 (real bug, pre-existing for bodies, newly extended to receipts): In both Finding 2 (minor/nit): Finding 3 (minor/observability): The No consensus-correctness, RLP, or gas-accounting concerns here — this is peer-scoring/networking code, and the validation logic ( Automated review by Claude (Anthropic) · sonnet · custom prompt |
Motivation
Historical chain backfill (#7024) shipped with its peer fetch path untested. The
rules that decide whether a short or misaligned batch response comes from an honest
partial-history peer or a dishonest one lived inline in
request_block_bodiesandrequest_receipts, tangled with peer-table side effects, so nothing could exercisethem. That is the code where a scoring bug already slipped through once: success was
credited before validation, so a peer answering with one valid body and sixty-three
invented ones gained score instead of losing it.
Description
Extracts the decision into two pure functions,
classify_body_responseandclassify_receipt_response, returning aResponseVerdict:Complete— every returned entry validated against its header, in order.Compacted— an entry matches a later requested header, so the peer omittedblocks it does not have. Expected after history expiry; costs the peer nothing.
Fabricated— an entry matches no requested header at all.The callers map the verdict onto the peer table as before. Adds 18 tests: aligned
responses, truncated prefixes, compacted responses, invented entries,
one-valid-then-junk, empty responses, and the version negotiation that selects the
receipt wire format.
Two notes beyond a pure refactor:
previously only soft-penalized. A response can no longer be charged both a critical
and a soft failure.
negotiated_eth_versionreimplements the handshake's highest-mutual rule by hand,and had no guard against drifting from it. Picking wrongly there sends a peer a
request it cannot decode — invisible locally, failing only on their side. That is
what broke receipt fetching against most of mainnet before feat(l1): optional historical chain backfill + DB observability #7024 merged, so it is
now pinned, including the case of an eth/68 peer that also speaks eth/71.
One test documents a real limit rather than asserting a behaviour: blocks with no
transactions share the same transactions, withdrawals and receipts roots, so an empty
body is genuinely interchangeable between them and the classification cannot tell
compacted from fabricated. Harmless, since identical bytes get stored either way, but
better pinned than rediscovered.
Also extracts
next_batch_range, the arithmetic choosing which blocks the nextbatch covers. It carries a non-obvious rule with no test behind it: reaching
frontier == flooris not finished on the first batch of a run, because thatblock is the snap pivot, which snap stored a body for but no receipts. A node whose
pivot landed exactly on the floor would otherwise keep that block without receipts
forever while reporting itself complete.
How to Test
Related Issues
Follow-up to #7024. Does not close #5979 (snap-sync record/replay):
backfill_stepend to end still needs a peer harness, and these classifiers are the piece that
harness would drive with recorded mainnet responses.