eth/executionclient: order packed logs by logIndex within a transaction - #2993
eth/executionclient: order packed logs by logIndex within a transaction#2993iurii-ssv wants to merge 1 commit into
Conversation
PackLogs sorted by (block, txIndex) with a non-stable sort.Slice, leaving logs emitted by the same transaction (same txIndex) in unspecified relative order. A single transaction can emit multiple order-dependent registry events — bulkRegisterValidator emits one ValidatorAdded per validator, each bumping the owner's per-owner nonce — so reordering them makes the event handler read nonces out of order and reject otherwise-valid registrations (MalformedEventError, with the nonce still bumped). This is usually masked (Go's sort is stable in practice for small inputs) but is unsound and can surface with larger batches or a runtime change. Add a logIndex tiebreaker so packing preserves canonical on-chain order both across and within a transaction, plus a regression test with shuffled same-transaction logs.
Greptile SummaryThis PR makes execution-log packing preserve canonical on-chain order within each transaction and adds regression coverage for shuffled same-transaction logs.
Confidence Score: 5/5The PR appears safe to merge, with the new comparator consistently preserving canonical event order across historical and streaming fetch paths. The production fetch paths populate block, transaction, and log indices before calling PackLogs, and the grouping logic preserves the resulting order; no blocking or independently actionable issue remains.
|
| Filename | Overview |
|---|---|
| eth/executionclient/logs.go | Adds the log-index tiebreaker needed to preserve canonical ordering before block-level event processing. |
| eth/executionclient/logs_test.go | Adds focused regression coverage for shuffled logs from the same transaction. |
Reviews (1): Last reviewed commit: "eth/executionclient: order packed logs b..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
ovidiu-ssv-labs
left a comment
There was a problem hiding this comment.
Correct, minimal, well-targeted fix: adding the logIndex tiebreaker makes PackLogs produce true canonical on-chain order, which the nonce-sequenced ValidatorAdded handler depends on. Verified the new regression test actually fails against the pre-PR comparator, and that the bug was deterministic (not just theoretical) for same-tx batches above ~12 logs. One minor follow-up: a second copy of the old comparator still lives in bloom.go. [verdict: yes]
Finding 1 · [MINOR] Deduplicate the log comparator — bloom.go still sorts by (block, txIndex) only — eth/executionclient/bloom.go:86
verifyLogsWithBloom re-sorts after appending bloom-recovered logs using a comparator that is the exact pre-PR version this PR just fixed — (block, txIndex) only, no logIndex tiebreak. No impact today: verifyLogsWithBloom has exactly one caller, and its output flows straight into PackLogs, which now re-sorts with the full corrected comparator — so the weaker sort here is currently harmless and redundant.
It still matters because the codebase now has two comparators over the same data that disagree, and the weaker one sits directly in the path that appends recovered logs — precisely the operation that destroys incoming order. If verifyLogsWithBloom is ever reused outside the PackLogs path, or these two steps are reordered, the exact nonce-mismatch failure this PR fixes silently returns — and would only trigger on the rare bloom-recovery path, making it extremely hard to reproduce. For scale: the pre-PR comparator, run over 40 same-tx logs in reverse order, returned them still fully reversed — above ~12 elements Go's sort.Slice is not merely unspecified, it's deterministically wrong.
ovidiu-ssv-labs
left a comment
There was a problem hiding this comment.
1 minor comment only, see above.
Summary
PackLogssorted logs by(block, txIndex)using a non-stablesort.Slice, so logs emitted by the same transaction (sametxIndex) were left in unspecified relative order.A single transaction can emit multiple order-dependent registry events:
bulkRegisterValidatoremits oneValidatorAddedper validator, each bumping the owner's per-owner nonce. If those get packed out oflogIndexorder, the event handler'sGetNextNonce/BumpNoncesequence no longer matches the events' signed nonces →verifySignaturefails →MalformedEventError(and the nonce is still bumped), silently rejecting otherwise-valid registrations.It's usually masked in practice — Go's
sort.Sliceis stable for small slices — but it's unsound and can surface with a larger batch or a runtime change.Fix
Add a
logIndex(Index) tiebreaker soPackLogssorts by(block, txIndex, logIndex)—logIndexis unique and monotonic within a block, so packing now preserves canonical on-chain order both across and within a transaction. Existing(block, txIndex)behavior is unchanged for logs in distinct transactions.Regression test added with shuffled same-transaction logs.
Noticed during review of #2991.