@aztec/archiver 0.63.1 → 0.65.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/archiver/archiver.d.ts +12 -3
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +37 -10
- package/dest/archiver/archiver_store.d.ts +25 -2
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +79 -19
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +4 -4
- package/dest/archiver/epoch_helpers.d.ts +12 -7
- package/dest/archiver/epoch_helpers.d.ts.map +1 -1
- package/dest/archiver/epoch_helpers.js +14 -2
- package/dest/archiver/instrumentation.d.ts +6 -0
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +15 -2
- package/dest/archiver/kv_archiver_store/block_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +18 -8
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +16 -2
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +24 -3
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +12 -0
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +1 -0
- package/dest/archiver/kv_archiver_store/nullifier_store.js +71 -0
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +11 -2
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +51 -6
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +21 -3
- package/dest/test/mock_l2_block_source.d.ts +5 -1
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +9 -3
- package/package.json +12 -11
- package/src/archiver/archiver.ts +51 -11
- package/src/archiver/archiver_store.ts +24 -1
- package/src/archiver/archiver_store_test_suite.ts +92 -17
- package/src/archiver/data_retrieval.ts +13 -4
- package/src/archiver/epoch_helpers.ts +30 -6
- package/src/archiver/instrumentation.ts +22 -0
- package/src/archiver/kv_archiver_store/block_store.ts +22 -10
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +27 -3
- package/src/archiver/kv_archiver_store/nullifier_store.ts +78 -0
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +59 -5
- package/src/factory.ts +21 -3
- package/src/test/mock_l2_block_source.ts +8 -2
|
@@ -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';
|
|
2
2
|
import '@aztec/circuit-types/jest';
|
|
3
3
|
import {
|
|
4
4
|
AztecAddress,
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Fr,
|
|
8
8
|
INITIAL_L2_BLOCK_NUM,
|
|
9
9
|
L1_TO_L2_MSG_SUBTREE_HEIGHT,
|
|
10
|
+
MAX_NULLIFIERS_PER_TX,
|
|
10
11
|
SerializableContractInstance,
|
|
11
12
|
} from '@aztec/circuits.js';
|
|
12
13
|
import {
|
|
@@ -37,12 +38,18 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
37
38
|
[5, 2, () => blocks.slice(4, 6)],
|
|
38
39
|
];
|
|
39
40
|
|
|
41
|
+
const makeL1Published = (block: L2Block, l1BlockNumber: number): L1Published<L2Block> => ({
|
|
42
|
+
data: block,
|
|
43
|
+
l1: {
|
|
44
|
+
blockNumber: BigInt(l1BlockNumber),
|
|
45
|
+
blockHash: `0x${l1BlockNumber}`,
|
|
46
|
+
timestamp: BigInt(l1BlockNumber * 1000),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
40
50
|
beforeEach(() => {
|
|
41
51
|
store = getStore();
|
|
42
|
-
blocks = times(10, i => (
|
|
43
|
-
data: L2Block.random(i + 1),
|
|
44
|
-
l1: { blockNumber: BigInt(i + 10), blockHash: `0x${i}`, timestamp: BigInt(i * 1000) },
|
|
45
|
-
}));
|
|
52
|
+
blocks = times(10, i => makeL1Published(L2Block.random(i + 1), i + 10));
|
|
46
53
|
});
|
|
47
54
|
|
|
48
55
|
describe('addBlocks', () => {
|
|
@@ -68,6 +75,21 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
68
75
|
expect(await store.getSynchedL2BlockNumber()).toBe(blockNumber - 1);
|
|
69
76
|
expect(await store.getBlocks(blockNumber, 1)).toEqual([]);
|
|
70
77
|
});
|
|
78
|
+
|
|
79
|
+
it('can unwind multiple empty blocks', async () => {
|
|
80
|
+
const emptyBlocks = times(10, i => makeL1Published(L2Block.random(i + 1, 0), i + 10));
|
|
81
|
+
await store.addBlocks(emptyBlocks);
|
|
82
|
+
expect(await store.getSynchedL2BlockNumber()).toBe(10);
|
|
83
|
+
|
|
84
|
+
await store.unwindBlocks(10, 3);
|
|
85
|
+
expect(await store.getSynchedL2BlockNumber()).toBe(7);
|
|
86
|
+
expect((await store.getBlocks(1, 10)).map(b => b.data.number)).toEqual([1, 2, 3, 4, 5, 6, 7]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('refuses to unwind blocks if the tip is not the last block', async () => {
|
|
90
|
+
await store.addBlocks(blocks);
|
|
91
|
+
await expect(store.unwindBlocks(5, 1)).rejects.toThrow(/can only unwind blocks from the tip/i);
|
|
92
|
+
});
|
|
71
93
|
});
|
|
72
94
|
|
|
73
95
|
describe('getBlocks', () => {
|
|
@@ -191,14 +213,14 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
191
213
|
});
|
|
192
214
|
|
|
193
215
|
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],
|
|
216
|
+
() => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
|
|
217
|
+
() => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
|
|
218
|
+
() => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
|
|
219
|
+
() => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
|
|
220
|
+
() => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
|
|
199
221
|
])('retrieves a previously stored transaction', async getExpectedTx => {
|
|
200
222
|
const expectedTx = getExpectedTx();
|
|
201
|
-
const actualTx = await store.getTxEffect(expectedTx.txHash);
|
|
223
|
+
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
|
|
202
224
|
expect(actualTx).toEqual(expectedTx);
|
|
203
225
|
});
|
|
204
226
|
|
|
@@ -207,16 +229,16 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
207
229
|
});
|
|
208
230
|
|
|
209
231
|
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],
|
|
232
|
+
() => wrapInBlock(blocks[0].data.body.txEffects[0], blocks[0].data),
|
|
233
|
+
() => wrapInBlock(blocks[9].data.body.txEffects[3], blocks[9].data),
|
|
234
|
+
() => wrapInBlock(blocks[3].data.body.txEffects[1], blocks[3].data),
|
|
235
|
+
() => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
|
|
236
|
+
() => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
|
|
215
237
|
])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
|
|
216
238
|
await store.unwindBlocks(blocks.length, blocks.length);
|
|
217
239
|
|
|
218
240
|
const expectedTx = getExpectedTx();
|
|
219
|
-
const actualTx = await store.getTxEffect(expectedTx.txHash);
|
|
241
|
+
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
|
|
220
242
|
expect(actualTx).toEqual(undefined);
|
|
221
243
|
});
|
|
222
244
|
|
|
@@ -705,5 +727,58 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
705
727
|
}
|
|
706
728
|
});
|
|
707
729
|
});
|
|
730
|
+
|
|
731
|
+
describe('findNullifiersIndexesWithBlock', () => {
|
|
732
|
+
let blocks: L2Block[];
|
|
733
|
+
const numBlocks = 10;
|
|
734
|
+
const nullifiersPerBlock = new Map<number, Fr[]>();
|
|
735
|
+
|
|
736
|
+
beforeEach(() => {
|
|
737
|
+
blocks = times(numBlocks, (index: number) => L2Block.random(index + 1, 1));
|
|
738
|
+
|
|
739
|
+
blocks.forEach((block, blockIndex) => {
|
|
740
|
+
nullifiersPerBlock.set(
|
|
741
|
+
blockIndex,
|
|
742
|
+
block.body.txEffects.flatMap(txEffect => txEffect.nullifiers),
|
|
743
|
+
);
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
it('returns wrapped nullifiers with blocks if they exist', async () => {
|
|
748
|
+
await store.addNullifiers(blocks);
|
|
749
|
+
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!, Fr.random()];
|
|
750
|
+
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(10, nullifiersToRetrieve);
|
|
751
|
+
|
|
752
|
+
expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
|
|
753
|
+
const [undefinedNullifier] = blockScopedNullifiers.slice(-1);
|
|
754
|
+
const realNullifiers = blockScopedNullifiers.slice(0, -1);
|
|
755
|
+
realNullifiers.forEach((blockScopedNullifier, index) => {
|
|
756
|
+
expect(blockScopedNullifier).not.toBeUndefined();
|
|
757
|
+
const { data, l2BlockNumber } = blockScopedNullifier!;
|
|
758
|
+
expect(data).toEqual(expect.any(BigInt));
|
|
759
|
+
expect(l2BlockNumber).toEqual(index < MAX_NULLIFIERS_PER_TX ? 1 : 6);
|
|
760
|
+
});
|
|
761
|
+
expect(undefinedNullifier).toBeUndefined();
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it('returns wrapped nullifiers filtering by blockNumber', async () => {
|
|
765
|
+
await store.addNullifiers(blocks);
|
|
766
|
+
const nullifiersToRetrieve = [...nullifiersPerBlock.get(0)!, ...nullifiersPerBlock.get(5)!];
|
|
767
|
+
const blockScopedNullifiers = await store.findNullifiersIndexesWithBlock(5, nullifiersToRetrieve);
|
|
768
|
+
|
|
769
|
+
expect(blockScopedNullifiers).toHaveLength(nullifiersToRetrieve.length);
|
|
770
|
+
const undefinedNullifiers = blockScopedNullifiers.slice(-MAX_NULLIFIERS_PER_TX);
|
|
771
|
+
const realNullifiers = blockScopedNullifiers.slice(0, -MAX_NULLIFIERS_PER_TX);
|
|
772
|
+
realNullifiers.forEach(blockScopedNullifier => {
|
|
773
|
+
expect(blockScopedNullifier).not.toBeUndefined();
|
|
774
|
+
const { data, l2BlockNumber } = blockScopedNullifier!;
|
|
775
|
+
expect(data).toEqual(expect.any(BigInt));
|
|
776
|
+
expect(l2BlockNumber).toEqual(1);
|
|
777
|
+
});
|
|
778
|
+
undefinedNullifiers.forEach(undefinedNullifier => {
|
|
779
|
+
expect(undefinedNullifier).toBeUndefined();
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
});
|
|
708
783
|
});
|
|
709
784
|
}
|
|
@@ -139,9 +139,18 @@ async function getBlockFromRollupTx(
|
|
|
139
139
|
if (!allowedMethods.includes(functionName)) {
|
|
140
140
|
throw new Error(`Unexpected method called ${functionName}`);
|
|
141
141
|
}
|
|
142
|
-
const [
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
const [decodedArgs, , bodyHex] = args! as readonly [
|
|
143
|
+
{
|
|
144
|
+
header: Hex;
|
|
145
|
+
archive: Hex;
|
|
146
|
+
blockHash: Hex;
|
|
147
|
+
txHashes: Hex[];
|
|
148
|
+
},
|
|
149
|
+
ViemSignature[],
|
|
150
|
+
Hex,
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const header = Header.fromBuffer(Buffer.from(hexToBytes(decodedArgs.header)));
|
|
145
154
|
const blockBody = Body.fromBuffer(Buffer.from(hexToBytes(bodyHex)));
|
|
146
155
|
|
|
147
156
|
const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();
|
|
@@ -152,7 +161,7 @@ async function getBlockFromRollupTx(
|
|
|
152
161
|
|
|
153
162
|
const archive = AppendOnlyTreeSnapshot.fromBuffer(
|
|
154
163
|
Buffer.concat([
|
|
155
|
-
Buffer.from(hexToBytes(
|
|
164
|
+
Buffer.from(hexToBytes(decodedArgs.archive)), // L2Block.archive.root
|
|
156
165
|
numToUInt32BE(Number(l2BlockNum + 1n)), // L2Block.archive.nextAvailableLeafIndex
|
|
157
166
|
]),
|
|
158
167
|
);
|
|
@@ -1,30 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+
// REFACTOR: This file should go in a package lower in the dependency graph.
|
|
2
|
+
|
|
3
|
+
export type EpochConstants = {
|
|
4
|
+
l1GenesisBlock: bigint;
|
|
2
5
|
l1GenesisTime: bigint;
|
|
3
6
|
epochDuration: number;
|
|
4
7
|
slotDuration: number;
|
|
5
8
|
};
|
|
6
9
|
|
|
7
10
|
/** Returns the slot number for a given timestamp. */
|
|
8
|
-
export function getSlotAtTimestamp(ts: bigint, constants: Pick<
|
|
11
|
+
export function getSlotAtTimestamp(ts: bigint, constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration'>) {
|
|
9
12
|
return ts < constants.l1GenesisTime ? 0n : (ts - constants.l1GenesisTime) / BigInt(constants.slotDuration);
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
/** Returns the epoch number for a given timestamp. */
|
|
13
|
-
export function getEpochNumberAtTimestamp(
|
|
16
|
+
export function getEpochNumberAtTimestamp(
|
|
17
|
+
ts: bigint,
|
|
18
|
+
constants: Pick<EpochConstants, 'epochDuration' | 'slotDuration' | 'l1GenesisTime'>,
|
|
19
|
+
) {
|
|
14
20
|
return getSlotAtTimestamp(ts, constants) / BigInt(constants.epochDuration);
|
|
15
21
|
}
|
|
16
22
|
|
|
17
|
-
/** Returns the range of slots (inclusive) for a given epoch number. */
|
|
18
|
-
export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick<
|
|
23
|
+
/** Returns the range of L2 slots (inclusive) for a given epoch number. */
|
|
24
|
+
export function getSlotRangeForEpoch(epochNumber: bigint, constants: Pick<EpochConstants, 'epochDuration'>) {
|
|
19
25
|
const startSlot = epochNumber * BigInt(constants.epochDuration);
|
|
20
26
|
return [startSlot, startSlot + BigInt(constants.epochDuration) - 1n];
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
/** Returns the range of L1 timestamps (inclusive) for a given epoch number. */
|
|
24
|
-
export function getTimestampRangeForEpoch(
|
|
30
|
+
export function getTimestampRangeForEpoch(
|
|
31
|
+
epochNumber: bigint,
|
|
32
|
+
constants: Pick<EpochConstants, 'l1GenesisTime' | 'slotDuration' | 'epochDuration'>,
|
|
33
|
+
) {
|
|
25
34
|
const [startSlot, endSlot] = getSlotRangeForEpoch(epochNumber, constants);
|
|
26
35
|
return [
|
|
27
36
|
constants.l1GenesisTime + startSlot * BigInt(constants.slotDuration),
|
|
28
37
|
constants.l1GenesisTime + endSlot * BigInt(constants.slotDuration),
|
|
29
38
|
];
|
|
30
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns the range of L1 blocks (inclusive) for a given epoch number.
|
|
43
|
+
* @remarks This assumes no time warp has happened.
|
|
44
|
+
*/
|
|
45
|
+
export function getL1BlockRangeForEpoch(
|
|
46
|
+
epochNumber: bigint,
|
|
47
|
+
constants: Pick<EpochConstants, 'l1GenesisBlock' | 'epochDuration' | 'slotDuration'>,
|
|
48
|
+
) {
|
|
49
|
+
const epochDurationInL1Blocks = BigInt(constants.epochDuration) * BigInt(constants.slotDuration);
|
|
50
|
+
return [
|
|
51
|
+
epochNumber * epochDurationInL1Blocks + constants.l1GenesisBlock,
|
|
52
|
+
(epochNumber + 1n) * epochDurationInL1Blocks + constants.l1GenesisBlock - 1n,
|
|
53
|
+
];
|
|
54
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Attributes,
|
|
5
5
|
type Gauge,
|
|
6
6
|
type Histogram,
|
|
7
|
+
LmdbMetrics,
|
|
7
8
|
Metrics,
|
|
8
9
|
type TelemetryClient,
|
|
9
10
|
type UpDownCounter,
|
|
@@ -18,6 +19,7 @@ export class ArchiverInstrumentation {
|
|
|
18
19
|
private syncDuration: Histogram;
|
|
19
20
|
private proofsSubmittedDelay: Histogram;
|
|
20
21
|
private proofsSubmittedCount: UpDownCounter;
|
|
22
|
+
private dbMetrics: LmdbMetrics;
|
|
21
23
|
|
|
22
24
|
private log = createDebugLogger('aztec:archiver:instrumentation');
|
|
23
25
|
|
|
@@ -55,6 +57,26 @@ export class ArchiverInstrumentation {
|
|
|
55
57
|
explicitBucketBoundaries: millisecondBuckets(1, 80), // 10ms -> ~3hs
|
|
56
58
|
},
|
|
57
59
|
});
|
|
60
|
+
|
|
61
|
+
this.dbMetrics = new LmdbMetrics(
|
|
62
|
+
meter,
|
|
63
|
+
{
|
|
64
|
+
name: Metrics.ARCHIVER_DB_MAP_SIZE,
|
|
65
|
+
description: 'Database map size for the archiver',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: Metrics.ARCHIVER_DB_USED_SIZE,
|
|
69
|
+
description: 'Database used size for the archiver',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: Metrics.ARCHIVER_DB_NUM_ITEMS,
|
|
73
|
+
description: 'Num items in the archiver database',
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public recordDBMetrics(metrics: { mappingSize: number; numItems: number; actualSize: number }) {
|
|
79
|
+
this.dbMetrics.recordDBMetrics(metrics);
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
public isEnabled(): boolean {
|
|
@@ -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';
|
|
2
2
|
import { AppendOnlyTreeSnapshot, type AztecAddress, Header, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
|
|
3
3
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
4
|
import { type AztecKVStore, type AztecMap, type AztecSingleton, type Range } from '@aztec/kv-store';
|
|
@@ -20,7 +20,7 @@ export class BlockStore {
|
|
|
20
20
|
/** Map block number to block data */
|
|
21
21
|
#blocks: AztecMap<number, BlockStorage>;
|
|
22
22
|
|
|
23
|
-
/** Map block
|
|
23
|
+
/** Map block hash to block body */
|
|
24
24
|
#blockBodies: AztecMap<string, Buffer>;
|
|
25
25
|
|
|
26
26
|
/** Stores L1 block number in which the last processed L2 block was included */
|
|
@@ -72,7 +72,7 @@ export class BlockStore {
|
|
|
72
72
|
void this.#txIndex.set(tx.txHash.toString(), [block.data.number, i]);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
-
void this.#blockBodies.set(block.data.
|
|
75
|
+
void this.#blockBodies.set(block.data.hash().toString(), block.data.body.toBuffer());
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
void this.#lastSynchedL1Block.set(blocks[blocks.length - 1].l1.blockNumber);
|
|
@@ -92,7 +92,7 @@ export class BlockStore {
|
|
|
92
92
|
return this.db.transaction(() => {
|
|
93
93
|
const last = this.getSynchedL2BlockNumber();
|
|
94
94
|
if (from != last) {
|
|
95
|
-
throw new Error(`Can only
|
|
95
|
+
throw new Error(`Can only unwind blocks from the tip (requested ${from} but current tip is ${last})`);
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
for (let i = 0; i < blocksToUnwind; i++) {
|
|
@@ -106,7 +106,9 @@ export class BlockStore {
|
|
|
106
106
|
block.data.body.txEffects.forEach(tx => {
|
|
107
107
|
void this.#txIndex.delete(tx.txHash.toString());
|
|
108
108
|
});
|
|
109
|
-
|
|
109
|
+
const blockHash = block.data.hash().toString();
|
|
110
|
+
void this.#blockBodies.delete(blockHash);
|
|
111
|
+
this.#log.debug(`Unwound block ${blockNumber} ${blockHash}`);
|
|
110
112
|
}
|
|
111
113
|
|
|
112
114
|
return true;
|
|
@@ -154,10 +156,12 @@ export class BlockStore {
|
|
|
154
156
|
private getBlockFromBlockStorage(blockStorage: BlockStorage) {
|
|
155
157
|
const header = Header.fromBuffer(blockStorage.header);
|
|
156
158
|
const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
|
|
157
|
-
|
|
158
|
-
const blockBodyBuffer = this.#blockBodies.get(
|
|
159
|
+
const blockHash = header.hash().toString();
|
|
160
|
+
const blockBodyBuffer = this.#blockBodies.get(blockHash);
|
|
159
161
|
if (blockBodyBuffer === undefined) {
|
|
160
|
-
throw new Error(
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Could not retrieve body for block ${header.globalVariables.blockNumber.toNumber()} ${blockHash}`,
|
|
164
|
+
);
|
|
161
165
|
}
|
|
162
166
|
const body = Body.fromBuffer(blockBodyBuffer);
|
|
163
167
|
|
|
@@ -170,14 +174,22 @@ export class BlockStore {
|
|
|
170
174
|
* @param txHash - The txHash of the tx corresponding to the tx effect.
|
|
171
175
|
* @returns The requested tx effect (or undefined if not found).
|
|
172
176
|
*/
|
|
173
|
-
getTxEffect(txHash: TxHash): TxEffect | undefined {
|
|
177
|
+
getTxEffect(txHash: TxHash): InBlock<TxEffect> | undefined {
|
|
174
178
|
const [blockNumber, txIndex] = this.getTxLocation(txHash) ?? [];
|
|
175
179
|
if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
|
|
176
180
|
return undefined;
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
const block = this.getBlock(blockNumber);
|
|
180
|
-
|
|
184
|
+
if (!block) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
data: block.data.body.txEffects[txIndex],
|
|
190
|
+
l2BlockNumber: block.data.number,
|
|
191
|
+
l2BlockHash: block.data.hash().toString(),
|
|
192
|
+
};
|
|
181
193
|
}
|
|
182
194
|
|
|
183
195
|
/**
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type FromLogType,
|
|
3
3
|
type GetUnencryptedLogsResponse,
|
|
4
|
+
type InBlock,
|
|
4
5
|
type InboxLeaf,
|
|
5
6
|
type L2Block,
|
|
6
7
|
type L2BlockL2Logs,
|
|
7
8
|
type LogFilter,
|
|
8
9
|
type LogType,
|
|
9
|
-
type TxEffect,
|
|
10
10
|
type TxHash,
|
|
11
11
|
type TxReceipt,
|
|
12
12
|
type TxScopedL2Log,
|
|
@@ -33,6 +33,7 @@ import { ContractClassStore } from './contract_class_store.js';
|
|
|
33
33
|
import { ContractInstanceStore } from './contract_instance_store.js';
|
|
34
34
|
import { LogStore } from './log_store.js';
|
|
35
35
|
import { MessageStore } from './message_store.js';
|
|
36
|
+
import { NullifierStore } from './nullifier_store.js';
|
|
36
37
|
|
|
37
38
|
/**
|
|
38
39
|
* LMDB implementation of the ArchiverDataStore interface.
|
|
@@ -40,6 +41,7 @@ import { MessageStore } from './message_store.js';
|
|
|
40
41
|
export class KVArchiverDataStore implements ArchiverDataStore {
|
|
41
42
|
#blockStore: BlockStore;
|
|
42
43
|
#logStore: LogStore;
|
|
44
|
+
#nullifierStore: NullifierStore;
|
|
43
45
|
#messageStore: MessageStore;
|
|
44
46
|
#contractClassStore: ContractClassStore;
|
|
45
47
|
#contractInstanceStore: ContractInstanceStore;
|
|
@@ -47,13 +49,14 @@ export class KVArchiverDataStore implements ArchiverDataStore {
|
|
|
47
49
|
|
|
48
50
|
#log = createDebugLogger('aztec:archiver:data-store');
|
|
49
51
|
|
|
50
|
-
constructor(db: AztecKVStore, logsMaxPageSize: number = 1000) {
|
|
52
|
+
constructor(private db: AztecKVStore, logsMaxPageSize: number = 1000) {
|
|
51
53
|
this.#blockStore = new BlockStore(db);
|
|
52
54
|
this.#logStore = new LogStore(db, this.#blockStore, logsMaxPageSize);
|
|
53
55
|
this.#messageStore = new MessageStore(db);
|
|
54
56
|
this.#contractClassStore = new ContractClassStore(db);
|
|
55
57
|
this.#contractInstanceStore = new ContractInstanceStore(db);
|
|
56
58
|
this.#contractArtifactStore = new ContractArtifactsStore(db);
|
|
59
|
+
this.#nullifierStore = new NullifierStore(db);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
|
|
@@ -159,7 +162,7 @@ export class KVArchiverDataStore implements ArchiverDataStore {
|
|
|
159
162
|
* @param txHash - The txHash of the tx corresponding to the tx effect.
|
|
160
163
|
* @returns The requested tx effect (or undefined if not found).
|
|
161
164
|
*/
|
|
162
|
-
getTxEffect(txHash: TxHash)
|
|
165
|
+
getTxEffect(txHash: TxHash) {
|
|
163
166
|
return Promise.resolve(this.#blockStore.getTxEffect(txHash));
|
|
164
167
|
}
|
|
165
168
|
|
|
@@ -185,6 +188,23 @@ export class KVArchiverDataStore implements ArchiverDataStore {
|
|
|
185
188
|
return this.#logStore.deleteLogs(blocks);
|
|
186
189
|
}
|
|
187
190
|
|
|
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
|
+
|
|
188
208
|
getTotalL1ToL2MessageCount(): Promise<bigint> {
|
|
189
209
|
return Promise.resolve(this.#messageStore.getTotalL1ToL2MessageCount());
|
|
190
210
|
}
|
|
@@ -324,4 +344,8 @@ export class KVArchiverDataStore implements ArchiverDataStore {
|
|
|
324
344
|
messagesSynchedTo: this.#messageStore.getSynchedL1BlockNumber(),
|
|
325
345
|
});
|
|
326
346
|
}
|
|
347
|
+
|
|
348
|
+
public estimateSize(): { mappingSize: number; actualSize: number; numItems: number } {
|
|
349
|
+
return this.db.estimateSize();
|
|
350
|
+
}
|
|
327
351
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type InBlock, type L2Block } from '@aztec/circuit-types';
|
|
2
|
+
import { type Fr, MAX_NULLIFIERS_PER_TX } from '@aztec/circuits.js';
|
|
3
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
|
|
5
|
+
|
|
6
|
+
export class NullifierStore {
|
|
7
|
+
#nullifiersToBlockNumber: AztecMap<string, number>;
|
|
8
|
+
#nullifiersToBlockHash: AztecMap<string, string>;
|
|
9
|
+
#nullifiersToIndex: AztecMap<string, number>;
|
|
10
|
+
#log = createDebugLogger('aztec:archiver:log_store');
|
|
11
|
+
|
|
12
|
+
constructor(private db: AztecKVStore) {
|
|
13
|
+
this.#nullifiersToBlockNumber = db.openMap('archiver_nullifiers_to_block_number');
|
|
14
|
+
this.#nullifiersToBlockHash = db.openMap('archiver_nullifiers_to_block_hash');
|
|
15
|
+
this.#nullifiersToIndex = db.openMap('archiver_nullifiers_to_index');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async addNullifiers(blocks: L2Block[]): Promise<boolean> {
|
|
19
|
+
await this.db.transaction(() => {
|
|
20
|
+
blocks.forEach(block => {
|
|
21
|
+
const dataStartIndexForBlock =
|
|
22
|
+
block.header.state.partial.nullifierTree.nextAvailableLeafIndex -
|
|
23
|
+
block.body.numberOfTxsIncludingPadded * MAX_NULLIFIERS_PER_TX;
|
|
24
|
+
block.body.txEffects.forEach((txEffects, txIndex) => {
|
|
25
|
+
const dataStartIndexForTx = dataStartIndexForBlock + txIndex * MAX_NULLIFIERS_PER_TX;
|
|
26
|
+
txEffects.nullifiers.forEach((nullifier, nullifierIndex) => {
|
|
27
|
+
void this.#nullifiersToBlockNumber.set(nullifier.toString(), block.number);
|
|
28
|
+
void this.#nullifiersToBlockHash.set(nullifier.toString(), block.hash().toString());
|
|
29
|
+
void this.#nullifiersToIndex.set(nullifier.toString(), dataStartIndexForTx + nullifierIndex);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async deleteNullifiers(blocks: L2Block[]): Promise<boolean> {
|
|
38
|
+
await this.db.transaction(() => {
|
|
39
|
+
for (const block of blocks) {
|
|
40
|
+
for (const nullifier of block.body.txEffects.flatMap(tx => tx.nullifiers)) {
|
|
41
|
+
void this.#nullifiersToBlockNumber.delete(nullifier.toString());
|
|
42
|
+
void this.#nullifiersToBlockHash.delete(nullifier.toString());
|
|
43
|
+
void this.#nullifiersToIndex.delete(nullifier.toString());
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async findNullifiersIndexesWithBlock(
|
|
51
|
+
blockNumber: number,
|
|
52
|
+
nullifiers: Fr[],
|
|
53
|
+
): Promise<(InBlock<bigint> | undefined)[]> {
|
|
54
|
+
const maybeNullifiers = await this.db.transaction(() => {
|
|
55
|
+
return nullifiers.map(nullifier => ({
|
|
56
|
+
data: this.#nullifiersToIndex.get(nullifier.toString()),
|
|
57
|
+
l2BlockNumber: this.#nullifiersToBlockNumber.get(nullifier.toString()),
|
|
58
|
+
l2BlockHash: this.#nullifiersToBlockHash.get(nullifier.toString()),
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
61
|
+
return maybeNullifiers.map(({ data, l2BlockNumber, l2BlockHash }) => {
|
|
62
|
+
if (
|
|
63
|
+
data === undefined ||
|
|
64
|
+
l2BlockNumber === undefined ||
|
|
65
|
+
l2BlockHash === undefined ||
|
|
66
|
+
l2BlockNumber > blockNumber
|
|
67
|
+
) {
|
|
68
|
+
return undefined;
|
|
69
|
+
} else {
|
|
70
|
+
return {
|
|
71
|
+
data: BigInt(data),
|
|
72
|
+
l2BlockNumber,
|
|
73
|
+
l2BlockHash,
|
|
74
|
+
} as InBlock<bigint>;
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
ExtendedUnencryptedL2Log,
|
|
7
7
|
type FromLogType,
|
|
8
8
|
type GetUnencryptedLogsResponse,
|
|
9
|
+
type InBlock,
|
|
9
10
|
type InboxLeaf,
|
|
10
11
|
type L2Block,
|
|
11
12
|
type L2BlockL2Logs,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
TxReceipt,
|
|
18
19
|
TxScopedL2Log,
|
|
19
20
|
type UnencryptedL2BlockL2Logs,
|
|
21
|
+
wrapInBlock,
|
|
20
22
|
} from '@aztec/circuit-types';
|
|
21
23
|
import {
|
|
22
24
|
type ContractClassPublic,
|
|
@@ -27,6 +29,7 @@ import {
|
|
|
27
29
|
type Header,
|
|
28
30
|
INITIAL_L2_BLOCK_NUM,
|
|
29
31
|
MAX_NOTE_HASHES_PER_TX,
|
|
32
|
+
MAX_NULLIFIERS_PER_TX,
|
|
30
33
|
type UnconstrainedFunctionWithMembershipProof,
|
|
31
34
|
} from '@aztec/circuits.js';
|
|
32
35
|
import { type ContractArtifact } from '@aztec/foundation/abi';
|
|
@@ -50,7 +53,7 @@ export class MemoryArchiverStore implements ArchiverDataStore {
|
|
|
50
53
|
/**
|
|
51
54
|
* An array containing all the tx effects in the L2 blocks that have been fetched so far.
|
|
52
55
|
*/
|
|
53
|
-
private txEffects: TxEffect[] = [];
|
|
56
|
+
private txEffects: InBlock<TxEffect>[] = [];
|
|
54
57
|
|
|
55
58
|
private noteEncryptedLogsPerBlock: Map<number, EncryptedNoteL2BlockL2Logs> = new Map();
|
|
56
59
|
|
|
@@ -64,6 +67,8 @@ export class MemoryArchiverStore implements ArchiverDataStore {
|
|
|
64
67
|
|
|
65
68
|
private contractClassLogsPerBlock: Map<number, ContractClass2BlockL2Logs> = new Map();
|
|
66
69
|
|
|
70
|
+
private blockScopedNullifiers: Map<string, { blockNumber: number; blockHash: string; index: bigint }> = new Map();
|
|
71
|
+
|
|
67
72
|
/**
|
|
68
73
|
* Contains all L1 to L2 messages.
|
|
69
74
|
*/
|
|
@@ -181,7 +186,7 @@ export class MemoryArchiverStore implements ArchiverDataStore {
|
|
|
181
186
|
|
|
182
187
|
this.lastL1BlockNewBlocks = blocks[blocks.length - 1].l1.blockNumber;
|
|
183
188
|
this.l2Blocks.push(...blocks);
|
|
184
|
-
this.txEffects.push(...blocks.flatMap(b => b.data.body.txEffects));
|
|
189
|
+
this.txEffects.push(...blocks.flatMap(b => b.data.body.txEffects.map(txEffect => wrapInBlock(txEffect, b.data))));
|
|
185
190
|
|
|
186
191
|
return Promise.resolve(true);
|
|
187
192
|
}
|
|
@@ -196,7 +201,7 @@ export class MemoryArchiverStore implements ArchiverDataStore {
|
|
|
196
201
|
public async unwindBlocks(from: number, blocksToUnwind: number): Promise<boolean> {
|
|
197
202
|
const last = await this.getSynchedL2BlockNumber();
|
|
198
203
|
if (from != last) {
|
|
199
|
-
throw new Error(`Can only
|
|
204
|
+
throw new Error(`Can only unwind blocks from the tip (requested ${from} but current tip is ${last})`);
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
const stopAt = from - blocksToUnwind;
|
|
@@ -297,6 +302,51 @@ export class MemoryArchiverStore implements ArchiverDataStore {
|
|
|
297
302
|
return Promise.resolve(true);
|
|
298
303
|
}
|
|
299
304
|
|
|
305
|
+
addNullifiers(blocks: L2Block[]): Promise<boolean> {
|
|
306
|
+
blocks.forEach(block => {
|
|
307
|
+
const dataStartIndexForBlock =
|
|
308
|
+
block.header.state.partial.nullifierTree.nextAvailableLeafIndex -
|
|
309
|
+
block.body.numberOfTxsIncludingPadded * MAX_NULLIFIERS_PER_TX;
|
|
310
|
+
block.body.txEffects.forEach((txEffects, txIndex) => {
|
|
311
|
+
const dataStartIndexForTx = dataStartIndexForBlock + txIndex * MAX_NULLIFIERS_PER_TX;
|
|
312
|
+
txEffects.nullifiers.forEach((nullifier, nullifierIndex) => {
|
|
313
|
+
this.blockScopedNullifiers.set(nullifier.toString(), {
|
|
314
|
+
index: BigInt(dataStartIndexForTx + nullifierIndex),
|
|
315
|
+
blockNumber: block.number,
|
|
316
|
+
blockHash: block.hash().toString(),
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
return Promise.resolve(true);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
deleteNullifiers(blocks: L2Block[]): Promise<boolean> {
|
|
325
|
+
blocks.forEach(block => {
|
|
326
|
+
block.body.txEffects.forEach(txEffect => {
|
|
327
|
+
txEffect.nullifiers.forEach(nullifier => {
|
|
328
|
+
this.blockScopedNullifiers.delete(nullifier.toString());
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
return Promise.resolve(true);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
findNullifiersIndexesWithBlock(blockNumber: number, nullifiers: Fr[]): Promise<(InBlock<bigint> | undefined)[]> {
|
|
336
|
+
const blockScopedNullifiers = nullifiers.map(nullifier => {
|
|
337
|
+
const nullifierData = this.blockScopedNullifiers.get(nullifier.toString());
|
|
338
|
+
if (nullifierData !== undefined && nullifierData.blockNumber <= blockNumber) {
|
|
339
|
+
return {
|
|
340
|
+
data: nullifierData.index,
|
|
341
|
+
l2BlockHash: nullifierData.blockHash,
|
|
342
|
+
l2BlockNumber: nullifierData.blockNumber,
|
|
343
|
+
} as InBlock<bigint>;
|
|
344
|
+
}
|
|
345
|
+
return undefined;
|
|
346
|
+
});
|
|
347
|
+
return Promise.resolve(blockScopedNullifiers);
|
|
348
|
+
}
|
|
349
|
+
|
|
300
350
|
getTotalL1ToL2MessageCount(): Promise<bigint> {
|
|
301
351
|
return Promise.resolve(this.l1ToL2Messages.getTotalL1ToL2MessageCount());
|
|
302
352
|
}
|
|
@@ -365,8 +415,8 @@ export class MemoryArchiverStore implements ArchiverDataStore {
|
|
|
365
415
|
* @param txHash - The txHash of the tx effect.
|
|
366
416
|
* @returns The requested tx effect.
|
|
367
417
|
*/
|
|
368
|
-
public getTxEffect(txHash: TxHash): Promise<TxEffect | undefined> {
|
|
369
|
-
const txEffect = this.txEffects.find(tx => tx.txHash.equals(txHash));
|
|
418
|
+
public getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
|
|
419
|
+
const txEffect = this.txEffects.find(tx => tx.data.txHash.equals(txHash));
|
|
370
420
|
return Promise.resolve(txEffect);
|
|
371
421
|
}
|
|
372
422
|
|
|
@@ -686,4 +736,8 @@ export class MemoryArchiverStore implements ArchiverDataStore {
|
|
|
686
736
|
public getContractArtifact(address: AztecAddress): Promise<ContractArtifact | undefined> {
|
|
687
737
|
return Promise.resolve(this.contractArtifacts.get(address.toString()));
|
|
688
738
|
}
|
|
739
|
+
|
|
740
|
+
public estimateSize(): { mappingSize: number; actualSize: number; numItems: number } {
|
|
741
|
+
return { mappingSize: 0, actualSize: 0, numItems: 0 };
|
|
742
|
+
}
|
|
689
743
|
}
|