Repository navigation
Refuse large commits at relays of unknown limit; settle exposure once (#2332) - #3396
Open
erskingardner wants to merge 5 commits into
Open
erskingardner wants to merge 5 commits into
erskingardner wants to merge 5 commits into
Conversation
…#2332) A commit over 64 KiB sent to a relay whose event limit is unknown can close the socket mid-frame. The target then reads as possibly exposed and the commit stays pending for good. Commits now meet a 64 KiB floor at such relays: the session asks the adapter before marking a target attempted, and the commit entry point applies the floor again. A relay the device cannot connect to holds the commit as a pre-send retry instead of refusing it. Exposure is settled in one transition out of Attempting. The bit stays clear only for a proven pre-send failure or a definitive refusal of the current attempt, which the fanout tracks in memory; any result on an attempt loaded from disk sets it. A relay that answered rate-limited: or auth-required: received the event, so a later refusal there reports possible exposure. Co-Authored-By: Claude Opus 5.5 <[email protected]>
A row stored before pre-send proof existed records a relay's rate-limited: answer as RetryableUnavailable with a clear bit, and a row stored before the bit existed may be PossiblyExposed with a clear bit. Re-marking either target, or recording a pre-mark refusal on it, dropped that evidence, so a later definitive refusal could end the target Failed and unexposed. Both the single transition and a new attempt now set the bit first. Co-Authored-By: Claude Opus 5.5 <[email protected]>
A stored RetryableUnavailable row holding a rate-limited: answer with no pre-send proof and a clear bit, as the previous release wrote it, stays pending after both a size refusal before marking and a definitive refusal of the next retry. Co-Authored-By: Claude Opus 5.5 <[email protected]>
# Conflicts: # CHANGELOG.md
Contributor
|
Warning Review limit reachedEnable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. Next included review available in 43 minutes. View limit details
|
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Nov 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A group change over 64 KiB sent to a relay whose event limit MDK doesn't know can close the relay's socket mid-frame. The send then reads as "possibly seen", and the commit stays pending for good: the group can't make another change. A relay that answered
rate-limited:orauth-required:had received the commit, yet a later refusal there could still roll it back, which breaks the rule that a commit members may hold never rolls back (#1577).Fix
A commit over 64 KiB is refused, before anything is sent, at a relay whose limit is unknown. The session asks before marking any target attempted, and the commit entry point checks again. A relay the device can't connect to holds the commit for a retry instead of refusing it, so an offline device never gets a false "too large". Welcomes and application messages are unchanged. Exposure is now settled in one transition. A target counts as unexposed only after a proven pre-send failure, or a definitive refusal of the attempt in hand. Retryable relay answers and interrupted attempts count as exposure, and so do rows stored by the previous release.
Verification
cargo fmt --all --check;just tracing-audit;just naming-gate;cargo clippy --workspace --all-targets --all-features --locked -- -D warnings; scoped default clippy for cgka-traits, marmot-session, transport-nostr-adapter, transport-nostr, marmot-apptest-policy-overrides(239); storage-sqlite fanout/outbound, cgka-enginehydration_quarantine, targeted marmot-app tests (90)wn-clitests) andjust simulator-smoke(509 + 4 tests), all at 8448d53Part of #2332 (the size floor, decisions D8/D9, and the single exposure transition). The read-back, Stuck state, rollback rule and host surface follow in later PRs.
Notes for review
Press hardest on two things:
OutboundFanout::settle_exposureandfold_stored_exposureincrates/traits/src/transport_adapter.rs). The bit is never cleared. It stays clear only for a proven pre-send failure, or a definitive refusal, of the attempt this in-memory value started (HeldAttempts, never serialized). A result recorded on anAttemptingloaded from disk sets it, and so does a stored status that records exposure, before anything replaces that status.Interim effect, intended: until the read-back lands, a commit answered
rate-limited:orauth-required:and then refused everywhere stays pending. It gets no read-back and no Stuck state yet.Rows stored before this change: a
RetryableUnavailablewith no pre-send proof is treated as exposed. The previous release could store a category-less one after anauth-required:answer, so its category can't separate the cases. The cost falls only on fanouts pending across the upgrade: if every relay then refuses definitively, they stay pending instead of rolling back.🤖 Generated with Claude Code