Skip to content

Commit cc9bc0b

Browse files
author
AztecBot
committed
Merge branch 'v5-next' into merge-train/spartan-v5
2 parents 7661977 + 958c663 commit cc9bc0b

6 files changed

Lines changed: 78 additions & 32 deletions

File tree

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

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

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

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

@@ -241,16 +241,36 @@ describe('SenderTaggingStore', () => {
241241

242242
it('throws after pending txs exhaust window', async () => {
243243
// One single-index pending tx per index, mirroring how an un-mined backlog accumulates one log per tx on a
244-
// shared secret (e.g. the self-send chain in bench_build_block). A fresh secret treats the
245-
// finalized floor as 0, so indexes 0..WINDOW fit...
246-
for (let i = 0; i <= UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN; i++) {
244+
// shared secret (e.g. the self-send chain in bench_build_block). With no index finalized yet, exactly
245+
// WINDOW_LEN indexes (0..WINDOW_LEN - 1) fit...
246+
for (let i = 0; i < UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN; i++) {
247247
await taggingStore.storePendingIndexes([range(secret1, i)], TxHash.random(), 'test');
248248
}
249249

250250
// ...and the next tx throws, even with a single additional tag.
251251
await expect(
252252
taggingStore.storePendingIndexes(
253-
[range(secret1, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1)],
253+
[range(secret1, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN)],
254+
TxHash.random(),
255+
'test',
256+
),
257+
).rejects.toThrow(/no index finalized yet/);
258+
});
259+
260+
it('permits exactly WINDOW_LEN pending indexes for a fresh secret', async () => {
261+
// Fresh-secret counterpart of the two boundary tests above: with no index finalized yet, the last permitted
262+
// pending index is WINDOW_LEN - 1, the same WINDOW_LEN-sized allowance as after any real finalization.
263+
await expect(
264+
taggingStore.storePendingIndexes(
265+
[range(secret1, 0, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN - 1)],
266+
TxHash.random(),
267+
'test',
268+
),
269+
).resolves.not.toThrow();
270+
271+
await expect(
272+
taggingStore.storePendingIndexes(
273+
[range(secret1, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN)],
254274
TxHash.random(),
255275
'test',
256276
),

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
@@ -19,6 +19,18 @@ import { MAX_PRIVATE_LOGS_PER_TX } from '@aztec/constants';
1919
// MAX_PRIVATE_LOGS_PER_TX. No fixed window value closes that gap.
2020
export const UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN = MAX_PRIVATE_LOGS_PER_TX + 20;
2121

22+
/**
23+
* Exclusive upper bound of the tag indexes that can exist for a secret whose highest finalized index is
24+
* `finalizedIndex` (undefined when nothing is finalized yet). The sender store refuses pending indexes at or past it,
25+
* and both sender and recipient sync scan exactly up to it. Every absolute window bound must come from this helper:
26+
* a site with a wider or narrower bound lets a tx land at an index the syncs never scan, so two stores sharing the
27+
* secret could later pick a colliding index.
28+
*/
29+
export function unfinalizedTaggingIndexesWindowEnd(finalizedIndex: number | undefined): number {
30+
const windowStart = finalizedIndex === undefined ? 0 : finalizedIndex + 1;
31+
return windowStart + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN;
32+
}
33+
2234
// The number of tags probed per constrained secret in the first round.
2335
//
2436
// The probe doubles each round (2, 4, 8, ..., capped at UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN) while every probed

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

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,7 @@ describe('syncTaggedPrivateLogs', () => {
132132
await sync(secrets);
133133

134134
const expectedTags = (
135-
await Promise.all(
136-
secrets.map(secret => computeSiloedTagRange(secret, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1)),
137-
)
135+
await Promise.all(secrets.map(secret => computeSiloedTagRange(secret, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN)))
138136
).flat();
139137
const asStrings = (tags: SiloedTag[]) => tags.map(t => t.toString()).sort();
140138

@@ -191,10 +189,10 @@ describe('syncTaggedPrivateLogs', () => {
191189
it('updates store correctly when multiple iterations are needed', async () => {
192190
const secret = await randomAppTaggingSecret(AppTaggingSecretKind.UNCONSTRAINED);
193191

194-
// A log at the last index of the initial window [0, WINDOW_LEN] moves the finalized index to WINDOW_LEN,
192+
// A log at the last index of the initial window [0, WINDOW_LEN) moves the finalized index to WINDOW_LEN - 1,
195193
// which shifts the next window forward and triggers a second iteration. A second log sits in the advanced
196194
// window, only reachable in the second iteration.
197-
const lastIndexInInitialWindow = UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN;
195+
const lastIndexInInitialWindow = UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN - 1;
198196
const newWindowIndex = lastIndexInInitialWindow + 3;
199197
mockNodeWithLogs(await computeSiloedTags(secret, [lastIndexInInitialWindow, newWindowIndex]));
200198

@@ -514,11 +512,11 @@ describe('syncTaggedPrivateLogs', () => {
514512
expect(await taggingStore.getHighestFinalizedIndex(secret, JOB_ID)).toBe(totalLogs - 1);
515513
expect(await taggingStore.getHighestAgedIndex(secret, JOB_ID)).toBe(totalLogs - 1);
516514

517-
// The first round spans the full cold-start window (WINDOW_LEN + 1). Because every index hit, the next round
518-
// re-anchors to another full WINDOW_LEN window ahead of the new finalized index: no small initial probe and no
519-
// doubling, in contrast to the constrained scan.
515+
// The first round spans the full cold-start window (WINDOW_LEN, the same bound the sender store permits fresh
516+
// pending indexes under). Because every index hit, the next round re-anchors to another full WINDOW_LEN window
517+
// ahead of the new finalized index: no small initial probe and no doubling, in contrast to the constrained scan.
520518
expect(callSizes().slice(0, 2)).toEqual([
521-
UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1,
519+
UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN,
522520
UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN,
523521
]);
524522
});

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ 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 { INITIAL_CONSTRAINED_PROBE_LEN, UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN } from '../constants.js';
10+
import {
11+
INITIAL_CONSTRAINED_PROBE_LEN,
12+
UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN,
13+
unfinalizedTaggingIndexesWindowEnd,
14+
} from '../constants.js';
1115
import { getAllPrivateLogsByTags } from '../get_all_logs_by_tags.js';
1216
import { findHighestIndexes } from './utils/find_highest_indexes.js';
1317

@@ -149,7 +153,7 @@ function getIndexRangesForSecrets(
149153
return Promise.all(
150154
secrets.map(async (secret): Promise<PendingSecret> => {
151155
const currentHighestFinalizedIndex = await taggingStore.getHighestFinalizedIndex(secret, jobId);
152-
const boundEnd = (currentHighestFinalizedIndex ?? 0) + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
156+
const boundEnd = unfinalizedTaggingIndexesWindowEnd(currentHighestFinalizedIndex);
153157

154158
if (secret.kind === AppTaggingSecretKind.CONSTRAINED) {
155159
// Constrained streams are gapless and resume at the finalized index, so probe a small initial window and stop
@@ -257,7 +261,7 @@ async function processConstrainedResults(
257261
const probeFullyConsumed = firstMissingIndex >= pending.end;
258262
const boundEnd =
259263
highestFinalizedIndex !== undefined
260-
? Math.max(pending.boundEnd, highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1)
264+
? Math.max(pending.boundEnd, unfinalizedTaggingIndexesWindowEnd(highestFinalizedIndex))
261265
: pending.boundEnd;
262266

263267
// Double the probe each round, capped at the window (see INITIAL_CONSTRAINED_PROBE_LEN in ../constants.ts).
@@ -319,7 +323,7 @@ async function processUnconstrainedResults(
319323

320324
// For the next iteration we want to look only at indexes for which we have not yet fetched logs while
321325
// ensuring that we do not look further than WINDOW_LEN ahead of the highest finalized index.
322-
const end = highestFinalizedIndex + UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN + 1;
326+
const end = unfinalizedTaggingIndexesWindowEnd(highestFinalizedIndex);
323327
return {
324328
kind: AppTaggingSecretKind.UNCONSTRAINED,
325329
secret: pending.secret,

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)