Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: PXE handles reorgs #9913

Merged
merged 19 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
type EncryptedL2Log,
type FromLogType,
type GetUnencryptedLogsResponse,
type InBlock,
type InboxLeaf,
type L1ToL2MessageSource,
type L2Block,
Expand All @@ -12,6 +13,7 @@ import {
type L2Tips,
type LogFilter,
type LogType,
type NullifierWithBlockSource,
type TxEffect,
type TxHash,
type TxReceipt,
Expand Down Expand Up @@ -73,7 +75,11 @@ import { type L1Published } from './structs/published.js';
/**
* Helper interface to combine all sources this archiver implementation provides.
*/
export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
export type ArchiveSource = L2BlockSource &
L2LogsSource &
ContractDataSource &
L1ToL2MessageSource &
NullifierWithBlockSource;

/**
* Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
Expand Down Expand Up @@ -589,7 +595,7 @@ export class Archiver implements ArchiveSource {
}
}

public getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
public getTxEffect(txHash: TxHash) {
return this.store.getTxEffect(txHash);
}

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

/**
* Returns the provided nullifier indexes scoped to the block
* they were first included in, or undefined if they're not present in the tree
* @param blockNumber Max block number to search for the nullifiers
* @param nullifiers Nullifiers to get
* @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
*/
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
}

/**
* Gets unencrypted logs based on the provided filter.
* @param filter - The filter to apply to the logs.
Expand Down Expand Up @@ -770,6 +787,9 @@ class ArchiverStoreHelper
ArchiverDataStore,
| 'addLogs'
| 'deleteLogs'
| 'addNullifiers'
| 'deleteNullifiers'
| 'addContractClasses'
| 'deleteContractClasses'
| 'addContractInstances'
| 'deleteContractInstances'
Expand Down Expand Up @@ -904,6 +924,7 @@ class ArchiverStoreHelper
).every(Boolean);
}),
)),
this.store.addNullifiers(blocks.map(block => block.data)),
this.store.addBlocks(blocks),
].every(Boolean);
}
Expand Down Expand Up @@ -943,7 +964,7 @@ class ArchiverStoreHelper
getBlockHeaders(from: number, limit: number): Promise<Header[]> {
return this.store.getBlockHeaders(from, limit);
}
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
return this.store.getTxEffect(txHash);
}
getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
Expand All @@ -968,6 +989,9 @@ class ArchiverStoreHelper
getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
return this.store.getLogsByTags(tags);
}
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
return this.store.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
}
getUnencryptedLogs(filter: LogFilter): Promise<GetUnencryptedLogsResponse> {
return this.store.getUnencryptedLogs(filter);
}
Expand Down
20 changes: 19 additions & 1 deletion yarn-project/archiver/src/archiver/archiver_store.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
type FromLogType,
type GetUnencryptedLogsResponse,
type InBlock,
type InboxLeaf,
type L2Block,
type L2BlockL2Logs,
Expand Down Expand Up @@ -79,7 +80,7 @@ export interface ArchiverDataStore {
* @param txHash - The txHash of the tx corresponding to the tx effect.
* @returns The requested tx effect (or undefined if not found).
*/
getTxEffect(txHash: TxHash): Promise<TxEffect | undefined>;
getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined>;

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

/**
* Append new nullifiers to the store's list.
* @param blocks - The blocks for which to add the nullifiers.
* @returns True if the operation is successful.
*/
addNullifiers(blocks: L2Block[]): Promise<boolean>;
deleteNullifiers(blocks: L2Block[]): Promise<boolean>;

/**
* Returns the provided nullifier indexes scoped to the block
* they were first included in, or undefined if they're not present in the tree
* @param blockNumber Max block number to search for the nullifiers
* @param nullifiers Nullifiers to get
* @returns The block scoped indexes of the provided nullifiers, or undefined if the nullifier doesn't exist in the tree
*/
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]>;

/**
* Append L1 to L2 messages to the store.
* @param messages - The L1 to L2 messages to be added to the store and the last processed L1 block.
Expand Down
80 changes: 67 additions & 13 deletions yarn-project/archiver/src/archiver/archiver_store_test_suite.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InboxLeaf, L2Block, LogId, LogType, TxHash } from '@aztec/circuit-types';
import { InboxLeaf, L2Block, LogId, LogType, TxHash, wrapInBlock } from '@aztec/circuit-types';
import '@aztec/circuit-types/jest';
import {
AztecAddress,
Expand All @@ -7,6 +7,7 @@ import {
Fr,
INITIAL_L2_BLOCK_NUM,
L1_TO_L2_MSG_SUBTREE_HEIGHT,
MAX_NULLIFIERS_PER_TX,
SerializableContractInstance,
} from '@aztec/circuits.js';
import {
Expand Down Expand Up @@ -191,14 +192,14 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
});

it.each([
() => blocks[0].data.body.txEffects[0],
() => blocks[9].data.body.txEffects[3],
() => blocks[3].data.body.txEffects[1],
() => blocks[5].data.body.txEffects[2],
() => blocks[1].data.body.txEffects[0],
() => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
() => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
() => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
() => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
() => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
])('retrieves a previously stored transaction', async getExpectedTx => {
const expectedTx = getExpectedTx();
const actualTx = await store.getTxEffect(expectedTx.txHash);
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
expect(actualTx).toEqual(expectedTx);
});

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

it.each([
() => blocks[0].data.body.txEffects[0],
() => blocks[9].data.body.txEffects[3],
() => blocks[3].data.body.txEffects[1],
() => blocks[5].data.body.txEffects[2],
() => blocks[1].data.body.txEffects[0],
() => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
() => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
() => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
() => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
() => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
await store.unwindBlocks(blocks.length, blocks.length);

const expectedTx = getExpectedTx();
const actualTx = await store.getTxEffect(expectedTx.txHash);
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
expect(actualTx).toEqual(undefined);
});

Expand Down Expand Up @@ -705,5 +706,58 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
}
});
});

describe('findNullifiersIndexesWithBlock', () => {
let blocks: L2Block[];
const numBlocks = 10;
const nullifiersPerBlock = new Map<number, Fr[]>();

beforeEach(() => {
blocks = times(numBlocks, (index: number) => L2Block.random(index + 1, 1));

blocks.forEach((block, blockIndex) => {
nullifiersPerBlock.set(
blockIndex,
block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
);
});
});

it('returns wrapped nullifiers with blocks if they exist', async () => {
await store.addNullifiers(blocks);
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!, Fr.random()];
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(10, nullifiersToRetrieve);

expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
const [undefinedNullifier] = blockScopedNullifiers.slice(-1);
const realNullifiers = blockScopedNullifiers.slice(0, -1);
realNullifiers.forEach((blockScopedNullifier, index) => {
expect(blockScopedNullifier).not.toBeUndefined();
const { data, l2BlockNumber } = blockScopedNullifier!;
expect(data).toEqual(expect.any(BigInt));
expect(l2BlockNumber).toEqual(index < MAX_NULLIFIERS_PER_TX ? 1 : 6);
});
expect(undefinedNullifier).toBeUndefined();
});

it('returns wrapped nullifiers filtering by blockNumber', async () => {
await store.addNullifiers(blocks);
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!];
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(5, nullifiersToRetrieve);

expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
const undefinedNullifiers = blockScopedNullifiers.slice(-MAX_NULLIFIERS_PER_TX);
const realNullifiers = blockScopedNullifiers.slice(0, -MAX_NULLIFIERS_PER_TX);
realNullifiers.forEach(blockScopedNullifier => {
expect(blockScopedNullifier).not.toBeUndefined();
const { data, l2BlockNumber } = blockScopedNullifier!;
expect(data).toEqual(expect.any(BigInt));
expect(l2BlockNumber).toEqual(1);
});
undefinedNullifiers.forEach(undefinedNullifier => {
expect(undefinedNullifier).toBeUndefined();
});
});
});
});
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Body, L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
import { Body, type InBlock, L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
import { AppendOnlyTreeSnapshot, type AztecAddress, Header, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';
import { type AztecKVStore, type AztecMap, type AztecSingleton, type Range } from '@aztec/kv-store';
Expand Down Expand Up @@ -170,14 +170,22 @@ export class BlockStore {
* @param txHash - The txHash of the tx corresponding to the tx effect.
* @returns The requested tx effect (or undefined if not found).
*/
getTxEffect(txHash: TxHash): TxEffect | undefined {
getTxEffect(txHash: TxHash): InBlock<TxEffect> | undefined {
const [blockNumber, txIndex] = this.getTxLocation(txHash) ?? [];
if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
return undefined;
}

const block = this.getBlock(blockNumber);
return block?.data.body.txEffects[txIndex];
if (!block) {
return undefined;
}

return {
data: block.data.body.txEffects[txIndex],
l2BlockNumber: block.data.number,
l2BlockHash: block.data.hash().toString(),
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
type FromLogType,
type GetUnencryptedLogsResponse,
type InBlock,
type InboxLeaf,
type L2Block,
type L2BlockL2Logs,
type LogFilter,
type LogType,
type TxEffect,
type TxHash,
type TxReceipt,
type TxScopedL2Log,
Expand All @@ -33,13 +33,15 @@ import { ContractClassStore } from './contract_class_store.js';
import { ContractInstanceStore } from './contract_instance_store.js';
import { LogStore } from './log_store.js';
import { MessageStore } from './message_store.js';
import { NullifierStore } from './nullifier_store.js';

/**
* LMDB implementation of the ArchiverDataStore interface.
*/
export class KVArchiverDataStore implements ArchiverDataStore {
#blockStore: BlockStore;
#logStore: LogStore;
#nullifierStore: NullifierStore;
#messageStore: MessageStore;
#contractClassStore: ContractClassStore;
#contractInstanceStore: ContractInstanceStore;
Expand All @@ -54,6 +56,7 @@ export class KVArchiverDataStore implements ArchiverDataStore {
this.#contractClassStore = new ContractClassStore(db);
this.#contractInstanceStore = new ContractInstanceStore(db);
this.#contractArtifactStore = new ContractArtifactsStore(db);
this.#nullifierStore = new NullifierStore(db);
}

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

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

/**
* Append new nullifiers to the store's list.
* @param blocks - The blocks for which to add the nullifiers.
* @returns True if the operation is successful.
*/
addNullifiers(blocks: L2Block[]): Promise<boolean> {
return this.#nullifierStore.addNullifiers(blocks);
}

deleteNullifiers(blocks: L2Block[]): Promise<boolean> {
return this.#nullifierStore.deleteNullifiers(blocks);
}

findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
return this.#nullifierStore.findNullifiersIndexesWithBlock(blockNumber, nullifiers);
}

getTotalL1ToL2MessageCount(): Promise<bigint> {
return Promise.resolve(this.#messageStore.getTotalL1ToL2MessageCount());
}
Expand Down
Loading
Loading