Skip to content

Commit 267d472

Browse files
AztecBotvezenovm
andauthored
fix(pxe): cap fresh secret pending tag indexes to the probed window (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>
1 parent 7ba6805 commit 267d472

6 files changed

Lines changed: 85 additions & 22 deletions

File tree

yarn-project/pxe/src/storage/tagging_store/sender_tagging_store.test.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import {
1111
import { randomAppTaggingSecret } from '@aztec/stdlib/testing';
1212
import { TxEffect, TxHash } from '@aztec/stdlib/tx';
1313

14-
import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.js';
15-
import { SenderTaggingStore } from './sender_tagging_store.js';
14+
import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN, unfinalizedTaggingIndexesWindowEnd } from '../../tagging/constants.js';
15+
import { SenderTaggingStore, windowExceededError } from './sender_tagging_store.js';
1616

1717
/** Helper to create a single-index range (lowestIndex === highestIndex). */
1818
function range(secret: AppTaggingSecret, lowest: number, highest?: number): TaggingIndexRange {
@@ -195,7 +195,7 @@ describe('SenderTaggingStore', () => {
195195
await expect(
196196
taggingStore.storePendingIndexes([range(secret1, indexBeyondWindow)], txHash2, 'test'),
197197
).rejects.toThrow(
198-
`Highest used index ${indexBeyondWindow} is further than window length from the highest finalized index ${finalizedIndex}`,
198+
windowExceededError(indexBeyondWindow, unfinalizedTaggingIndexesWindowEnd(finalizedIndex), finalizedIndex),
199199
);
200200
});
201201

@@ -218,6 +218,44 @@ describe('SenderTaggingStore', () => {
218218
expect(txHashes).toHaveLength(1);
219219
expect(txHashes[0]).toEqual(txHash2);
220220
});
221+
222+
it('throws after pending txs exhaust window', async () => {
223+
// One single-index pending tx per index, mirroring how an un-mined backlog accumulates one log per tx on a
224+
// shared secret (e.g. the self-send chain in bench_build_block). With no index finalized yet, exactly
225+
// WINDOW_LEN indexes (0..WINDOW_LEN - 1) fit...
226+
for (let i = 0; i < UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN; i++) {
227+
await taggingStore.storePendingIndexes([range(secret1, i)], TxHash.random(), 'test');
228+
}
229+
230+
// ...and the next tx throws, even with a single additional tag.
231+
await expect(
232+
taggingStore.storePendingIndexes(
233+
[range(secret1, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN)],
234+
TxHash.random(),
235+
'test',
236+
),
237+
).rejects.toThrow(/no index finalized yet/);
238+
});
239+
240+
it('permits exactly WINDOW_LEN pending indexes for a fresh secret', async () => {
241+
// Fresh-secret counterpart of the two boundary tests above: with no index finalized yet, the last permitted
242+
// pending index is WINDOW_LEN - 1, the same WINDOW_LEN-sized allowance as after any real finalization.
243+
await expect(
244+
taggingStore.storePendingIndexes(
245+
[range(secret1, 0, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN - 1)],
246+
TxHash.random(),
247+
'test',
248+
),
249+
).resolves.not.toThrow();
250+
251+
await expect(
252+
taggingStore.storePendingIndexes(
253+
[range(secret1, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN)],
254+
TxHash.random(),
255+
'test',
256+
),
257+
).rejects.toThrow(/configured too low/);
258+
});
221259
});
222260
});
223261

yarn-project/pxe/src/storage/tagging_store/sender_tagging_store.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { AppTaggingSecret, SiloedTag, type TaggingIndexRange } from '@aztec/stdl
33
import { TxEffect, TxHash } from '@aztec/stdlib/tx';
44

55
import type { StagedStore } from '../../job_coordinator/job_coordinator.js';
6-
import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../../tagging/constants.js';
6+
import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN, unfinalizedTaggingIndexesWindowEnd } from '../../tagging/constants.js';
77

88
/** Internal representation of a pending index range entry. */
99
type PendingIndexesEntry = { lowestIndex: number; highestIndex: number; txHash: string };
@@ -195,13 +195,9 @@ export class SenderTaggingStore implements StagedStore {
195195

196196
// Process in memory and validate
197197
for (const { range, secretStr, pendingData, finalizedIndex } of rangeData) {
198-
// Check that the highest index is not further than window length from the highest finalized index.
199-
if (range.highestIndex > (finalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) {
200-
throw new Error(
201-
`Highest used index ${range.highestIndex} is further than window length from the highest finalized index ${finalizedIndex ?? 0}.
202-
Tagging window length ${UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN} is configured too low. Contact the Aztec team
203-
to increase it!`,
204-
);
198+
const windowEnd = unfinalizedTaggingIndexesWindowEnd(finalizedIndex);
199+
if (range.highestIndex >= windowEnd) {
200+
throw windowExceededError(range.highestIndex, windowEnd, finalizedIndex);
205201
}
206202

207203
// Throw if the lowest index is lower than or equal to the last finalized index
@@ -521,3 +517,18 @@ export class SenderTaggingStore implements StagedStore {
521517
}
522518
}
523519
}
520+
521+
/** Builds the error thrown when a pending tag index is at or past the unfinalized tagging window end. */
522+
export function windowExceededError(
523+
highestIndex: number,
524+
windowEnd: number,
525+
finalizedIndex: number | undefined,
526+
): Error {
527+
const finalizedDescription =
528+
finalizedIndex === undefined ? 'no index finalized yet' : `highest finalized index ${finalizedIndex}`;
529+
return new Error(
530+
`Highest used index ${highestIndex} is at or past the window end ${windowEnd} (${finalizedDescription}). ` +
531+
`Tagging window length ${UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN} is configured too low. ` +
532+
`Contact the Aztec team to increase it!`,
533+
);
534+
}

yarn-project/pxe/src/tagging/constants.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ import { MAX_PRIVATE_LOGS_PER_TX } from '@aztec/constants';
66
// Having a large window significantly slowed down `e2e_l1_with_wall_time` test as there we perform sync for more than
77
// 1000 secrets, so keep this bounded to the per-tx private log limit.
88
export const UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN = MAX_PRIVATE_LOGS_PER_TX;
9+
10+
/**
11+
* Exclusive upper bound of the tag indexes that can exist for a secret whose highest finalized index is
12+
* `finalizedIndex` (undefined when nothing is finalized yet). The sender store refuses pending indexes at or past it,
13+
* and both sender and recipient sync scan exactly up to it. Every absolute window bound must come from this helper:
14+
* a site with a wider or narrower bound lets a tx land at an index the syncs never scan, so two stores sharing the
15+
* secret could later pick a colliding index.
16+
*/
17+
export function unfinalizedTaggingIndexesWindowEnd(finalizedIndex: number | undefined): number {
18+
const windowStart = finalizedIndex === undefined ? 0 : finalizedIndex + 1;
19+
return windowStart + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN;
20+
}

yarn-project/pxe/src/tagging/recipient_sync/sync_tagged_private_logs.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ describe('syncTaggedPrivateLogs', () => {
103103

104104
const expectedTags = await Promise.all(
105105
secrets.flatMap(secret =>
106-
Array.from({ length: UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1 }, (_, i) =>
106+
Array.from({ length: UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN }, (_, i) =>
107107
computeSiloedTagForIndex(secret, i),
108108
),
109109
),
@@ -173,9 +173,9 @@ describe('syncTaggedPrivateLogs', () => {
173173
const finalizedBlockNumber = BlockNumber(10);
174174
const agedBlockTimestamp = CURRENT_TIMESTAMP - BigInt(MAX_TX_LIFETIME) - 1000n;
175175

176-
// A log at the last index of the initial window [0, WINDOW_LEN] moves the finalized index to WINDOW_LEN,
176+
// A log at the last index of the initial window [0, WINDOW_LEN) moves the finalized index to WINDOW_LEN - 1,
177177
// which shifts the next window forward and triggers a second iteration.
178-
const lastIndexInInitialWindow = UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN;
178+
const lastIndexInInitialWindow = UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN - 1;
179179
const log1Tag = await computeSiloedTagForIndex(secret, lastIndexInInitialWindow);
180180

181181
// A second log sits in the advanced window, only reachable in the second iteration.

yarn-project/pxe/src/tagging/recipient_sync/sync_tagged_private_logs.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { AppTaggingSecretKind, SiloedTag } from '@aztec/stdlib/logs';
77
import type { BlockHeader } from '@aztec/stdlib/tx';
88

99
import type { RecipientTaggingStore } from '../../storage/tagging_store/recipient_tagging_store.js';
10-
import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../constants.js';
10+
import { unfinalizedTaggingIndexesWindowEnd } from '../constants.js';
1111
import { getAllPrivateLogsByTags } from '../get_all_logs_by_tags.js';
1212
import { findHighestIndexes } from './utils/find_highest_indexes.js';
1313

@@ -147,7 +147,7 @@ function getIndexRangesForSecrets(
147147
: await taggingStore.getHighestAgedIndex(secret, jobId);
148148
const start = highestIndexBeforeStart === undefined ? 0 : highestIndexBeforeStart + 1;
149149

150-
const end = (currentHighestFinalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
150+
const end = unfinalizedTaggingIndexesWindowEnd(currentHighestFinalizedIndex);
151151

152152
return { secret, start, end };
153153
}),
@@ -235,7 +235,7 @@ async function processConstrainedResults(
235235
return {
236236
secret: pending.secret,
237237
start: pending.end,
238-
end: highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1,
238+
end: unfinalizedTaggingIndexesWindowEnd(highestFinalizedIndex),
239239
};
240240
}
241241
}
@@ -282,10 +282,11 @@ async function processUnconstrainedResults(
282282

283283
// For the next iteration we want to look only at indexes for which we have not yet fetched logs while
284284
// ensuring that we do not look further than WINDOW_LEN ahead of the highest finalized index.
285+
const end = unfinalizedTaggingIndexesWindowEnd(highestFinalizedIndex);
285286
return {
286287
secret: pending.secret,
287288
start: pending.end,
288-
end: highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1,
289+
end,
289290
};
290291
}
291292

yarn-project/pxe/src/tagging/sender_sync/sync_sender_tagging_indexes.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { AztecNode } from '@aztec/stdlib/interfaces/server';
33
import type { AppTaggingSecret } from '@aztec/stdlib/logs';
44

55
import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js';
6-
import { UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../constants.js';
6+
import { unfinalizedTaggingIndexesWindowEnd } from '../constants.js';
77
import {
88
EMPTY_STATUS_CHANGE,
99
getStatusChangeOfPending,
@@ -49,7 +49,9 @@ export async function syncSenderTaggingIndexes(
4949
const finalizedIndex = await taggingStore.getLastFinalizedIndex(secret, jobId);
5050

5151
let start = finalizedIndex === undefined ? 0 : finalizedIndex + 1;
52-
let end = start + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN;
52+
// The loop only extends the window when the finalized index moves,
53+
// so this first window must cover the entire permitted range on its own.
54+
let end = unfinalizedTaggingIndexesWindowEnd(finalizedIndex);
5355

5456
let previousFinalizedIndex = finalizedIndex;
5557
let newFinalizedIndex = undefined;
@@ -122,8 +124,7 @@ export async function syncSenderTaggingIndexes(
122124
// New window: [21, 22, 23]
123125

124126
const previousEnd = end;
125-
// Add 1 because `end` is exclusive and the known finalized index is not included in the window.
126-
end = newFinalizedIndex! + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
127+
end = unfinalizedTaggingIndexesWindowEnd(newFinalizedIndex);
127128
start = previousEnd;
128129
previousFinalizedIndex = newFinalizedIndex;
129130
} else {

0 commit comments

Comments
 (0)