fix(pxe): cap fresh secret pending tag indexes to the probed window - #24667
Conversation
…nalized The store permits pending indexes up to (lastFinalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN inclusive, but with nothing finalized the first sync window probed [0, WINDOW_LEN), one index short of that bound. Since the sync loop only advances past its first window on a finalized-index change, a still-pending tx from another PXE at the boundary index was never discovered, and the next locally chosen index would collide with its onchain tag. The first window now uses the same formula as the window advance, changing behavior only for a fresh secret (one extra probed index).
|
Automatically closing this stale claudebox draft PR (no updates for 5+ days). Re-open if still needed. |
…ex -1 Rework of the fix: instead of widening the sender-sync first probe window to the store's fresh-secret permit (which made the fresh window WINDOW_LEN + 1 slots wide), tighten the permit so a fresh secret allows exactly WINDOW_LEN pending indexes (0..WINDOW_LEN - 1), the same allowance as after any real finalization. The first probe window [0, WINDOW_LEN) is correct again and UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN stays the single source of truth for window width. The recipient scan keeps its ?? 0 bound as a deliberate one-index margin for txs sent by clients predating the tightened permit.
…' into mv/fix-fresh-secret-probe-window # Conflicts: # yarn-project/pxe/src/tagging/recipient_sync/sync_tagged_private_logs.ts
| it('throws after pending txs exhaust window', async () => { | ||
| // One single-index pending tx per index, mirroring how an un-mined backlog accumulates one log per tx on a | ||
| // shared secret (e.g. the self-send chain in bench_build_block). A fresh secret treats the | ||
| // finalized floor as 0, so indexes 0..WINDOW fit... |
There was a problem hiding this comment.
The crux of the issue. We allowed storing WINDOW + 1 indices rather than WINDOW indices for a fresh secret.
…nt fresh-secret scan
| // Process in memory and validate | ||
| for (const { range, secretStr, pendingData, finalizedIndex } of rangeData) { | ||
| // Check that the highest index is not further than window length from the highest finalized index. | ||
| if (range.highestIndex > (finalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) { |
There was a problem hiding this comment.
Highest index can be UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN which is off by one.
| // an index the syncs never scan would let two stores sharing a secret pick colliding indexes. | ||
| if (range.highestIndex >= unfinalizedTaggingIndexesWindowEnd(finalizedIndex)) { | ||
| throw new Error( | ||
| `Highest used index ${range.highestIndex} is further than window length from the highest finalized index ${finalizedIndex ?? 0}. |
There was a problem hiding this comment.
From my understanding, if there is no finalized index, the highest finalized index is not 0, 0 is still available to be used as a finalized index.
| export function unfinalizedTaggingIndexesWindowEnd(finalizedIndex: number | undefined): number { | ||
| return (finalizedIndex ?? -1) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1; | ||
| } |
There was a problem hiding this comment.
Can we make this return number | undefined instead? Not fond of the sentinel value
There was a problem hiding this comment.
I didn't want to return undefined as we then have to repeat this logic at every call site which will defeat the purpose of the helper. I switched to clarifying the window start more clearly and removed the clever usage of -1 for both the no finalized index and finalized index cases. Let me know what you think.
|
@vezenovm thanks for LMK with these review requests on PRs fixing my code that I am retarded. Greatly appreciated |
|
❌ Failed to cherry-pick to |
…port #24667) (#24977) ## Summary Port of #24667 to `next` via `port-to-next-staging`. - Added `unfinalizedTaggingIndexesWindowEnd` and used it for sender pending bounds plus sender/recipient scan windows. - Preserved the current `next` window length and recipient sync structure while adapting fresh-secret bounds to `[0, WINDOW_LEN)`. ## Conflicts resolved - `constants.ts`: kept `next`'s `UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN = MAX_PRIVATE_LOGS_PER_TX` and added the shared helper. - Recipient sync and tests were resolved against the current `next` implementation, without pulling in source-branch-only constrained probe changes. - Sender store tests were adapted to assert exactly `WINDOW_LEN` fresh pending indexes and rejection at `WINDOW_LEN`. ## Testing - `JEST_MAX_WORKERS=1 yarn workspace @aztec/pxe test src/storage/tagging_store/sender_tagging_store.test.ts src/tagging/recipient_sync/sync_tagged_private_logs.test.ts` passed: 61 tests. - `yarn build` was attempted but this partial checkout is missing generated dependency outputs such as `@aztec/l1-artifacts`; the failure was in setup dependencies, not the PXE changes. --- *Created by [claudebox](https://claudebox.work/v2/sessions/0ca34c3ad2537dfa/jobs/1) · group: `slackbot` · [Slack thread](https://aztecprotocol.slack.com/archives/C0AGN2WT3CP/p1784928640004259?thread_ts=1784928640.004259&cid=C0AGN2WT3CP)* --------- Co-authored-by: Maxim Vezenov <mvezenov@gmail.com>
BEGIN_COMMIT_OVERRIDE fix(foundation): always include a result key in json-rpc responses (AztecProtocol#24840) fix(pxe): cap fresh secret pending tag indexes to the probed window (port AztecProtocol#24667) (AztecProtocol#24977) fix(archiver): resolve L2-to-L1 witness from a single store snapshot (AztecProtocol#24754) fix(sequencer): stop signalling already-executed governance payloads (AztecProtocol#24764) fix(archiver): clean up removed blocks from raw rows and ownership-check tx-effect deletes (AztecProtocol#24765) fix(node): warm KZG trusted setup at startup (AztecProtocol#24775) feat(prover-node): stop caching checkpoint txs; re-fetch from the pool for failure upload (A-1216) (AztecProtocol#24983) fix(sequencer): log tx failure reason at warn when dropping from mempool (AztecProtocol#25000) feat(prover-client): stop duplicating broker job inputs/results in memory (A-1215) (AztecProtocol#24990) fix(prover-client): don't retain inline job inputs in the facade without a failed-proof store (A-1517) (AztecProtocol#25027) END_COMMIT_OVERRIDE
We essentially have an off by one error for the starting finalized index for a fresh secret.
0..WINDOW_LEN, but sender sync only probes[0, WINDOW_LEN)and only advances the window after a finalization.WINDOW_LENfrom another store sharing the secret (second PXE, or restored state) was therefore outside the initial probe, so the next locally picked index could reuse its onchain tag.Fix: cap fresh secrets at
0..WINDOW_LEN - 1, the sameWINDOW_LENindexes allowed after any finalization. Every absolute window bound (sender permit, sender probe, recipient scan) now comes from one helper,unfinalizedTaggingIndexesWindowEnd, which also aligns the recipient's fresh-secret scan instead of leaving it one index wider than anything a sender can create.Behavior change: fresh secrets hit the "tagging window length configured too low" error at index
WINDOW_LEN, one index earlier, and the recipient no longer scans indexWINDOW_LENfor fresh secrets.Tests: the updated store and recipient sync tests are red on base and green with the fix; the existing straddle test pins the sender probe boundary.