Skip to content

fix: try every completion log when completing a partial note - #25121

Closed
vezenovm wants to merge 1 commit into
merge-train/fairiesfrom
mv/partial-note-completion-log-selection
Closed

fix: try every completion log when completing a partial note#25121
vezenovm wants to merge 1 commit into
merge-train/fairiesfrom
mv/partial-note-completion-log-selection

Conversation

@vezenovm

@vezenovm vezenovm commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

step_pending only ever tried completion_logs.get(0), and completion marked the FSM completed whether or not that log discovered anything. The completion log tag is not unique, so a log yielding no note sitting ahead of the genuine completion permanently consumed it. The note was silently lost.

Every fetched log is now tried in turn, and the first that yields a note completes the reception. If none does, the reception still advances on the first log, preserving #24668's property that a poisoned delivery cannot keep it re-running nonce discovery on every sync.

Red/green

step_discovers_the_note_when_an_earlier_completion_log_yields_none fails on next: enqueued_note_count() is 0, expected 1, while the FSM still reaches is_completed.

Green after the fix: partial_notes::fsm 15/15, token_contract test::transfer_to_private 8/8 (including #24668's discovery_tolerates_a_partial_note_completed_twice).

Known gap

A genuine completion mined after a non-discovering log is still lost if a sync lands between the two blocks. Every fetch returns the tag's full history (LogRetrievalRequest::new leaves from_block unset), so it only bites in that window.

Closing it needs the reception to stay pending when nothing discovers, which is only safe once it has a TTL. Delivery is unconstrained, so anyone can otherwise spam bogus partial notes that never leave Pending and cost a full nonce-discovery pass every sync. The sibling offchain-reception FSM already does this (messages/processing/offchain/reception.nr:242). Tracked in F-828.

step_pending only ever tried completion_logs.get(0), and completion marked
the FSM completed whether or not that log discovered anything. The completion
log tag is not unique, so a log yielding no note sitting ahead of the genuine
completion permanently consumed it and the note was silently lost.

Every fetched log is now tried in turn and the first that yields a note
completes the reception. If none does the reception still advances on the
first log, preserving #24668's property that a poisoned delivery cannot keep
it re-running nonce discovery on every sync.
@vezenovm vezenovm added the ci-draft Run CI on draft PRs. label Aug 5, 2026
@vezenovm
vezenovm changed the base branch from next to merge-train/fairies August 5, 2026 22:26
@vezenovm

vezenovm commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Closing after further review of reachability. The loss needs a log yielding no note ordered ahead of the genuine completion under one tag, and no correctly written contract can produce that pairing: completion is completer gated by the validity commitment check in PartialUintNote::complete, every completion emits the log and its matching note hash in the same call (so every log under a tag yields for a genuinely delivered private half), and recreating a commitment to reuse a tag reverts on the validity nullifier. Malicious senders and bogus deliveries cannot manufacture it either; they only produce receptions where every log fails, which advance and lose nothing.

What remains is footgun class: hand rolled custom partial note emission (aztec-nr has no shared emission helper today, uint-note is the only emitter of completion logs) and the #11636 trailing zero trimming wart, which uint-note guards with its value != 0 assert but custom types will miss. The better fix is emission side validation that prevents those contracts from being written: a shared completion helper that pairs log and note hash, rejects trailing zero public content, and optionally makes completion single use via a nullifier so at most one log can ever exist per tag. That keeps the FSM's first log rule correct by construction. Tracked in F-845.

The branch keeps the red test (step_discovers_the_note_when_an_earlier_completion_log_yields_none) if the reading side rule is ever revisited.

@vezenovm vezenovm closed this Aug 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci-draft Run CI on draft PRs.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant