Skip to content

Commit aafef9c

Browse files
spalladinoThunkarnventuro
authored
feat: PXE handles reorgs (#9913)
- Archiver and node return L2 block number and hash for `getTxEffect` - PXE database stores L2 block number and hash for each note - PXE database exposes a method for pruning notes after a given block number - PXE synchronizer uses L2 block stream to detect reorgs and clean up notes Pending: reorg'ing nullifiers. Fixes #8463 --------- Co-authored-by: thunkar <gregojquiros@gmail.com> Co-authored-by: Nicolás Venturo <nicolas.venturo@gmail.com>
1 parent 280d169 commit aafef9c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1125
-335
lines changed

yarn-project/archiver/src/archiver/archiver.ts

+27-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
type EncryptedL2Log,
33
type FromLogType,
44
type GetUnencryptedLogsResponse,
5+
type InBlock,
56
type InboxLeaf,
67
type L1ToL2MessageSource,
78
type L2Block,
@@ -12,6 +13,7 @@ import {
1213
type L2Tips,
1314
type LogFilter,
1415
type LogType,
16+
type NullifierWithBlockSource,
1517
type TxEffect,
1618
type TxHash,
1719
type TxReceipt,
@@ -73,7 +75,11 @@ import { type L1Published } from './structs/published.js';
7375
/**
7476
* Helper interface to combine all sources this archiver implementation provides.
7577
*/
76-
export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
78+
export type ArchiveSource = L2BlockSource &
79+
L2LogsSource &
80+
ContractDataSource &
81+
L1ToL2MessageSource &
82+
NullifierWithBlockSource;
7783

7884
/**
7985
* Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
@@ -589,7 +595,7 @@ export class Archiver implements ArchiveSource {
589595
}
590596
}
591597

592-
public getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
598+
public getTxEffect(txHash: TxHash) {
593599
return this.store.getTxEffect(txHash);
594600
}
595601

@@ -643,6 +649,17 @@ export class Archiver implements ArchiveSource {
643649
return this.store.getLogsByTags(tags);
644650
}
645651

652+
/**
653+
* Returns the provided nullifier indexes scoped to the block
654+
* they were first included in, or undefined if they're not present in the tree
655+
* @param blockNumber Max block number to search for the nullifiers
656+
* @param nullifiers Nullifiers to get
657+
* @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
658+
*/
659+
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
660+
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
661+
}
662+
646663
/**
647664
* Gets unencrypted logs based on the provided filter.
648665
* @param filter - The filter to apply to the logs.
@@ -770,6 +787,9 @@ class ArchiverStoreHelper
770787
ArchiverDataStore,
771788
| 'addLogs'
772789
| 'deleteLogs'
790+
| 'addNullifiers'
791+
| 'deleteNullifiers'
792+
| 'addContractClasses'
773793
| 'deleteContractClasses'
774794
| 'addContractInstances'
775795
| 'deleteContractInstances'
@@ -904,6 +924,7 @@ class ArchiverStoreHelper
904924
).every(Boolean);
905925
}),
906926
)),
927+
this.store.addNullifiers(blocks.map(block => block.data)),
907928
this.store.addBlocks(blocks),
908929
].every(Boolean);
909930
}
@@ -943,7 +964,7 @@ class ArchiverStoreHelper
943964
getBlockHeaders(from: number, limit: number): Promise<Header[]> {
944965
return this.store.getBlockHeaders(from, limit);
945966
}
946-
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
967+
getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
947968
return this.store.getTxEffect(txHash);
948969
}
949970
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
@@ -968,6 +989,9 @@ class ArchiverStoreHelper
968989
getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
969990
return this.store.getLogsByTags(tags);
970991
}
992+
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
993+
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
994+
}
971995
getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
972996
return this.store.getUnencryptedLogs(filter);
973997
}

yarn-project/archiver/src/archiver/archiver_store.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
type FromLogType,
33
type GetUnencryptedLogsResponse,
4+
type InBlock,
45
type InboxLeaf,
56
type L2Block,
67
type L2BlockL2Logs,
@@ -79,7 +80,7 @@ export interface ArchiverDataStore {
7980
* @param txHash - The txHash of the tx corresponding to the tx effect.
8081
* @returns The requested tx effect (or undefined if not found).
8182
*/
82-
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined>;
83+
getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined>;
8384

8485
/**
8586
* Gets a receipt of a settled tx.
@@ -96,6 +97,23 @@ export interface ArchiverDataStore {
9697
addLogs(blocks: L2Block[]): Promise<boolean>;
9798
deleteLogs(blocks: L2Block[]): Promise<boolean>;
9899

100+
/**
101+
* Append new nullifiers to the store's list.
102+
* @param blocks - The blocks for which to add the nullifiers.
103+
* @returns True if the operation is successful.
104+
*/
105+
addNullifiers(blocks: L2Block[]): Promise<boolean>;
106+
deleteNullifiers(blocks: L2Block[]): Promise<boolean>;
107+
108+
/**
109+
* Returns the provided nullifier indexes scoped to the block
110+
* they were first included in, or undefined if they're not present in the tree
111+
* @param blockNumber Max block number to search for the nullifiers
112+
* @param nullifiers Nullifiers to get
113+
* @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
114+
*/
115+
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]>;
116+
99117
/**
100118
* Append L1 to L2 messages to the store.
101119
* @param messages - The L1 to L2 messages to be added to the store and the last processed L1 block.

yarn-project/archiver/src/archiver/archiver_store_test_suite.ts

+67-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InboxLeaf, L2Block, LogId, LogType, TxHash } from '@aztec/circuit-types';
1+
import { InboxLeaf, L2Block, LogId, LogType, TxHash, wrapInBlock } from '@aztec/circuit-types';
22
import '@aztec/circuit-types/jest';
33
import {
44
AztecAddress,
@@ -7,6 +7,7 @@ import {
77
Fr,
88
INITIAL_L2_BLOCK_NUM,
99
L1_TO_L2_MSG_SUBTREE_HEIGHT,
10+
MAX_NULLIFIERS_PER_TX,
1011
SerializableContractInstance,
1112
} from '@aztec/circuits.js';
1213
import {
@@ -191,14 +192,14 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
191192
});
192193

193194
it.each([
194-
() => blocks[0].data.body.txEffects[0],
195-
() => blocks[9].data.body.txEffects[3],
196-
() => blocks[3].data.body.txEffects[1],
197-
() => blocks[5].data.body.txEffects[2],
198-
() => blocks[1].data.body.txEffects[0],
195+
() => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
196+
() => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
197+
() => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
198+
() => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
199+
() => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
199200
])('retrieves a previously stored transaction', async getExpectedTx => {
200201
const expectedTx = getExpectedTx();
201-
const actualTx = await store.getTxEffect(expectedTx.txHash);
202+
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
202203
expect(actualTx).toEqual(expectedTx);
203204
});
204205

@@ -207,16 +208,16 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
207208
});
208209

209210
it.each([
210-
() => blocks[0].data.body.txEffects[0],
211-
() => blocks[9].data.body.txEffects[3],
212-
() => blocks[3].data.body.txEffects[1],
213-
() => blocks[5].data.body.txEffects[2],
214-
() => blocks[1].data.body.txEffects[0],
211+
() => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
212+
() => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
213+
() => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
214+
() => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
215+
() => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
215216
])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
216217
await store.unwindBlocks(blocks.length, blocks.length);
217218

218219
const expectedTx = getExpectedTx();
219-
const actualTx = await store.getTxEffect(expectedTx.txHash);
220+
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
220221
expect(actualTx).toEqual(undefined);
221222
});
222223

@@ -705,5 +706,58 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
705706
}
706707
});
707708
});
709+
710+
describe('findNullifiersIndexesWithBlock', () => {
711+
let blocks: L2Block[];
712+
const numBlocks = 10;
713+
const nullifiersPerBlock = new Map<number, Fr[]>();
714+
715+
beforeEach(() => {
716+
blocks = times(numBlocks, (index: number) => L2Block.random(index + 1, 1));
717+
718+
blocks.forEach((block, blockIndex) => {
719+
nullifiersPerBlock.set(
720+
blockIndex,
721+
block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
722+
);
723+
});
724+
});
725+
726+
it('returns wrapped nullifiers with blocks if they exist', async () => {
727+
await store.addNullifiers(blocks);
728+
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!, Fr.random()];
729+
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(10, nullifiersToRetrieve);
730+
731+
expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
732+
const [undefinedNullifier] = blockScopedNullifiers.slice(-1);
733+
const realNullifiers = blockScopedNullifiers.slice(0, -1);
734+
realNullifiers.forEach((blockScopedNullifier, index) => {
735+
expect(blockScopedNullifier).not.toBeUndefined();
736+
const { data, l2BlockNumber } = blockScopedNullifier!;
737+
expect(data).toEqual(expect.any(BigInt));
738+
expect(l2BlockNumber).toEqual(index < MAX_NULLIFIERS_PER_TX ? 1 : 6);
739+
});
740+
expect(undefinedNullifier).toBeUndefined();
741+
});
742+
743+
it('returns wrapped nullifiers filtering by blockNumber', async () => {
744+
await store.addNullifiers(blocks);
745+
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!];
746+
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(5, nullifiersToRetrieve);
747+
748+
expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
749+
const undefinedNullifiers = blockScopedNullifiers.slice(-MAX_NULLIFIERS_PER_TX);
750+
const realNullifiers = blockScopedNullifiers.slice(0, -MAX_NULLIFIERS_PER_TX);
751+
realNullifiers.forEach(blockScopedNullifier => {
752+
expect(blockScopedNullifier).not.toBeUndefined();
753+
const { data, l2BlockNumber } = blockScopedNullifier!;
754+
expect(data).toEqual(expect.any(BigInt));
755+
expect(l2BlockNumber).toEqual(1);
756+
});
757+
undefinedNullifiers.forEach(undefinedNullifier => {
758+
expect(undefinedNullifier).toBeUndefined();
759+
});
760+
});
761+
});
708762
});
709763
}

yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Body, L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
1+
import { Body, type InBlock, L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
22
import { AppendOnlyTreeSnapshot, type AztecAddress, Header, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
33
import { createDebugLogger } from '@aztec/foundation/log';
44
import { type AztecKVStore, type AztecMap, type AztecSingleton, type Range } from '@aztec/kv-store';
@@ -170,14 +170,22 @@ export class BlockStore {
170170
* @param txHash - The txHash of the tx corresponding to the tx effect.
171171
* @returns The requested tx effect (or undefined if not found).
172172
*/
173-
getTxEffect(txHash: TxHash): TxEffect | undefined {
173+
getTxEffect(txHash: TxHash): InBlock<TxEffect> | undefined {
174174
const [blockNumber, txIndex] = this.getTxLocation(txHash) ?? [];
175175
if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
176176
return undefined;
177177
}
178178

179179
const block = this.getBlock(blockNumber);
180-
return block?.data.body.txEffects[txIndex];
180+
if (!block) {
181+
return undefined;
182+
}
183+
184+
return {
185+
data: block.data.body.txEffects[txIndex],
186+
l2BlockNumber: block.data.number,
187+
l2BlockHash: block.data.hash().toString(),
188+
};
181189
}
182190

183191
/**

yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {
22
type FromLogType,
33
type GetUnencryptedLogsResponse,
4+
type InBlock,
45
type InboxLeaf,
56
type L2Block,
67
type L2BlockL2Logs,
78
type LogFilter,
89
type LogType,
9-
type TxEffect,
1010
type TxHash,
1111
type TxReceipt,
1212
type TxScopedL2Log,
@@ -33,13 +33,15 @@ import { ContractClassStore } from './contract_class_store.js';
3333
import { ContractInstanceStore } from './contract_instance_store.js';
3434
import { LogStore } from './log_store.js';
3535
import { MessageStore } from './message_store.js';
36+
import { NullifierStore } from './nullifier_store.js';
3637

3738
/**
3839
* LMDB implementation of the ArchiverDataStore interface.
3940
*/
4041
export class KVArchiverDataStore implements ArchiverDataStore {
4142
#blockStore: BlockStore;
4243
#logStore: LogStore;
44+
#nullifierStore: NullifierStore;
4345
#messageStore: MessageStore;
4446
#contractClassStore: ContractClassStore;
4547
#contractInstanceStore: ContractInstanceStore;
@@ -54,6 +56,7 @@ export class KVArchiverDataStore implements ArchiverDataStore {
5456
this.#contractClassStore = new ContractClassStore(db);
5557
this.#contractInstanceStore = new ContractInstanceStore(db);
5658
this.#contractArtifactStore = new ContractArtifactsStore(db);
59+
this.#nullifierStore = new NullifierStore(db);
5760
}
5861

5962
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
@@ -159,7 +162,7 @@ export class KVArchiverDataStore implements ArchiverDataStore {
159162
* @param txHash - The txHash of the tx corresponding to the tx effect.
160163
* @returns The requested tx effect (or undefined if not found).
161164
*/
162-
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
165+
getTxEffect(txHash: TxHash) {
163166
return Promise.resolve(this.#blockStore.getTxEffect(txHash));
164167
}
165168

@@ -185,6 +188,23 @@ export class KVArchiverDataStore implements ArchiverDataStore {
185188
return this.#logStore.deleteLogs(blocks);
186189
}
187190

191+
/**
192+
* Append new nullifiers to the store's list.
193+
* @param blocks - The blocks for which to add the nullifiers.
194+
* @returns True if the operation is successful.
195+
*/
196+
addNullifiers(blocks: L2Block[]): Promise<boolean> {
197+
return this.#nullifierStore.addNullifiers(blocks);
198+
}
199+
200+
deleteNullifiers(blocks: L2Block[]): Promise<boolean> {
201+
return this.#nullifierStore.deleteNullifiers(blocks);
202+
}
203+
204+
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
205+
return this.#nullifierStore.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
206+
}
207+
188208
getTotalL1ToL2MessageCount(): Promise<bigint> {
189209
return Promise.resolve(this.#messageStore.getTotalL1ToL2MessageCount());
190210
}

0 commit comments

Comments
 (0)