Send no backlog when the requested sequence number is past its end - #4714
Send no backlog when the requested sequence number is past its end#4714smypmsa wants to merge 2 commits into
Conversation
A client that connects with an Arbitrum-Requested-Sequence-Number after the end of the backlog is already ahead of everything the broadcaster has, so it should be sent nothing. Instead it was sent the entire backlog: backlog.Lookup is an index probe that fails for a number above the tail exactly as it fails for one below the head, and the lookup failure fell back to the backlog head. Bound the requested sequence number against the end of the backlog before the lookup, and record that end as the last sequence number sent so that the catch up after registration does not treat the whole backlog as a gap and resend it through backlog.Get. The end of the backlog is recorded rather than the requested number, which can be arbitrarily far ahead and would then drop every message sent to the client. This restores the behaviour specified in OffchainLabs#883, which the old sequencenumbercatchupbuffer implemented explicitly and tested. The guard and its coverage were dropped in the WebSocket library refactor in OffchainLabs#1930. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Nice fix — the steady-state boundary behavior and the test design both check out. One race window worth guarding before merge:
It also makes the new comment slightly inaccurate: "a concurrent Append can only move it forward" — the observable The window is tiny (between two adjacent statements in if backlogEnd != 0 && uint64(cc.requestedSeqNum) > backlogEnd {( 🤖 Posted with Claude Code |
|
@ganeshvanahalli thanks, fixing. |
backlog.Append publishes a tail segment before appending to it, so a segment read in that window is empty and End reports zero for it. A client connecting then computed a backlog end of zero, took the past the end branch, was sent none of the backlog, and had zero recorded as the last sequence number sent. The catch up on the next broadcast then treated the whole backlog as a gap and sent it, which is the amplification the past the end check exists to prevent. Fall through to Lookup when the end reads as zero instead, which is what a client connecting in that window was sent before the check existed. Also log the past the end case at warn rather than debug, matching the warning the condition this check replaces used to emit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
LGTM.
@smypmsa After receiving another review and design-approved This PR will be pulled into our internal version, and will be closed once that repo is merged back to the public repo.
Anything that reads a sequencer feed — a nitro node, or a relay passing the feed on — tells the broadcaster which sequence number it wants to start from, using the
Arbitrum-Requested-Sequence-Numberheader. If it asks for a number past the last message the broadcaster still holds, it is already up to date, so the right answer is to send it nothing. Instead it is sent the entire backlog, and the further ahead it claims to be, the more it gets. The header is unauthenticated, so any client can trigger this.The cause is that
backlog.Lookupis a plain index lookup, and it fails the same way whether the number is too high or too low.clientconnection.gotreats any failure as "send everything from the head", which is right for a number that is too low and backwards for one that is too high.This used to work. #883 specified that the relay sends cached messages "equal to or greater than requested sequence number", and the old
sequencenumbercatchupbufferreturned nothing when the request was past the end, with a test covering the2**64 - 1case. The WebSocket refactor in #1930 replaced that buffer and dropped both the check and the test.What changed:
backloggains aTail()accessor for this, alongside the existingHead().Nothing changes for a client that sends no header, asks for a number below the start of the backlog, or asks for one inside it.
TestBroadcasterRequestedSequenceNumberconnects a real client and checks exactly which sequence numbers come back, for five cases: no header, before the start, inside, at the end, and past the end. The last two fail on master and pass here.I also ran a relay built from this branch alongside an unpatched one, both on a public Orbit chain feed. Asking for
2**64 - 1returned 1,103 stale messages (~8 MB) before and none now, and the unpatched relay logged twosending the entire backlog insteadwarnings where this one logged none.🤖 Generated with Claude Code