@aztec/archiver 0.72.1 → 0.74.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 +2 -2
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +31 -16
- package/dest/archiver/archiver_store.d.ts +2 -2
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +14 -14
- package/dest/archiver/config.d.ts +1 -1
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +4 -4
- package/dest/archiver/data_retrieval.d.ts +3 -2
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +83 -16
- package/dest/archiver/errors.d.ts +4 -0
- package/dest/archiver/errors.d.ts.map +1 -0
- package/dest/archiver/errors.js +6 -0
- package/dest/archiver/kv_archiver_store/block_store.d.ts +16 -16
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +53 -53
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +5 -5
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +13 -12
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +3 -3
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +3 -3
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +3 -7
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +39 -61
- package/dest/archiver/kv_archiver_store/log_store.d.ts +5 -5
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +54 -53
- package/dest/archiver/kv_archiver_store/message_store.d.ts +6 -6
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +16 -16
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/nullifier_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/nullifier_store.js +31 -22
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +2 -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 +24 -22
- package/dest/factory.js +7 -7
- package/dest/test/mock_l2_block_source.d.ts +2 -2
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +12 -9
- package/package.json +12 -12
- package/src/archiver/archiver.ts +38 -18
- package/src/archiver/archiver_store.ts +1 -1
- package/src/archiver/archiver_store_test_suite.ts +17 -14
- package/src/archiver/config.ts +4 -4
- package/src/archiver/data_retrieval.ts +108 -12
- package/src/archiver/errors.ts +5 -0
- package/src/archiver/kv_archiver_store/block_store.ts +66 -67
- package/src/archiver/kv_archiver_store/contract_class_store.ts +17 -15
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +5 -5
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +43 -62
- package/src/archiver/kv_archiver_store/log_store.ts +79 -71
- package/src/archiver/kv_archiver_store/message_store.ts +22 -22
- package/src/archiver/kv_archiver_store/nullifier_store.ts +48 -30
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +44 -43
- package/src/factory.ts +10 -8
- package/src/test/mock_l2_block_source.ts +18 -16
|
@@ -30,7 +30,10 @@ import { type L1Published } from './structs/published.js';
|
|
|
30
30
|
* @param testName - The name of the test suite.
|
|
31
31
|
* @param getStore - Returns an instance of a store that's already been initialized.
|
|
32
32
|
*/
|
|
33
|
-
export function describeArchiverDataStore(
|
|
33
|
+
export function describeArchiverDataStore(
|
|
34
|
+
testName: string,
|
|
35
|
+
getStore: () => ArchiverDataStore | Promise<ArchiverDataStore>,
|
|
36
|
+
) {
|
|
34
37
|
describe(testName, () => {
|
|
35
38
|
let store: ArchiverDataStore;
|
|
36
39
|
let blocks: L1Published<L2Block>[];
|
|
@@ -52,7 +55,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
52
55
|
});
|
|
53
56
|
|
|
54
57
|
beforeEach(async () => {
|
|
55
|
-
store = getStore();
|
|
58
|
+
store = await getStore();
|
|
56
59
|
blocks = await timesParallel(10, async i => makeL1Published(await L2Block.random(i + 1), i + 10));
|
|
57
60
|
});
|
|
58
61
|
|
|
@@ -209,7 +212,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
209
212
|
() => wrapInBlock(blocks[5].data.body.txEffects[2], blocks[5].data),
|
|
210
213
|
() => wrapInBlock(blocks[1].data.body.txEffects[0], blocks[1].data),
|
|
211
214
|
])('retrieves a previously stored transaction', async getExpectedTx => {
|
|
212
|
-
const expectedTx = getExpectedTx();
|
|
215
|
+
const expectedTx = await getExpectedTx();
|
|
213
216
|
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
|
|
214
217
|
expect(actualTx).toEqual(expectedTx);
|
|
215
218
|
});
|
|
@@ -227,7 +230,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
227
230
|
])('tries to retrieves a previously stored transaction after deleted', async getExpectedTx => {
|
|
228
231
|
await store.unwindBlocks(blocks.length, blocks.length);
|
|
229
232
|
|
|
230
|
-
const expectedTx = getExpectedTx();
|
|
233
|
+
const expectedTx = await getExpectedTx();
|
|
231
234
|
const actualTx = await store.getTxEffect(expectedTx.data.txHash);
|
|
232
235
|
expect(actualTx).toEqual(undefined);
|
|
233
236
|
});
|
|
@@ -300,10 +303,10 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
300
303
|
const blockNum = 10;
|
|
301
304
|
|
|
302
305
|
beforeEach(async () => {
|
|
303
|
-
contractClass = makeContractClassPublic();
|
|
306
|
+
contractClass = await makeContractClassPublic();
|
|
304
307
|
await store.addContractClasses(
|
|
305
308
|
[contractClass],
|
|
306
|
-
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
309
|
+
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
307
310
|
blockNum,
|
|
308
311
|
);
|
|
309
312
|
});
|
|
@@ -320,7 +323,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
320
323
|
it('returns contract class if later "deployment" class was deleted', async () => {
|
|
321
324
|
await store.addContractClasses(
|
|
322
325
|
[contractClass],
|
|
323
|
-
[computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
326
|
+
[await computePublicBytecodeCommitment(contractClass.packedBytecode)],
|
|
324
327
|
blockNum + 1,
|
|
325
328
|
);
|
|
326
329
|
await store.deleteContractClasses([contractClass], blockNum + 1);
|
|
@@ -374,11 +377,11 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
374
377
|
new Fr((blockNumber * 100 + txIndex * 10 + logIndex) * (isPublic ? 123 : 1));
|
|
375
378
|
|
|
376
379
|
// See parseLogFromPublic
|
|
377
|
-
|
|
380
|
+
// Search the codebase for "disgusting encoding" to see other hardcoded instances of this encoding, that you might need to change if you ever find yourself here.
|
|
381
|
+
const makeLengthsField = (publicValuesLen: number, privateValuesLen: number) => {
|
|
378
382
|
const buf = Buffer.alloc(32);
|
|
379
|
-
buf.writeUint16BE(publicValuesLen,
|
|
380
|
-
buf.writeUint16BE(privateValuesLen,
|
|
381
|
-
buf.writeUint16BE(ciphertextLen, 30);
|
|
383
|
+
buf.writeUint16BE(publicValuesLen, 27);
|
|
384
|
+
buf.writeUint16BE(privateValuesLen, 30);
|
|
382
385
|
return Fr.fromBuffer(buf);
|
|
383
386
|
};
|
|
384
387
|
|
|
@@ -390,7 +393,7 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
390
393
|
const makePublicLog = (tag: Fr) =>
|
|
391
394
|
PublicLog.fromFields([
|
|
392
395
|
AztecAddress.fromNumber(1).toField(), // log address
|
|
393
|
-
makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 3
|
|
396
|
+
makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 3), // field 0
|
|
394
397
|
tag, // field 1
|
|
395
398
|
...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)), // fields 2 to end
|
|
396
399
|
]);
|
|
@@ -532,13 +535,13 @@ export function describeArchiverDataStore(testName: string, getStore: () => Arch
|
|
|
532
535
|
const invalidLogs = [
|
|
533
536
|
PublicLog.fromFields([
|
|
534
537
|
AztecAddress.fromNumber(1).toField(),
|
|
535
|
-
makeLengthsField(2, 3
|
|
538
|
+
makeLengthsField(2, 3), // This field claims we have 5 items, but we actually have more
|
|
536
539
|
tag,
|
|
537
540
|
...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
|
|
538
541
|
]),
|
|
539
542
|
PublicLog.fromFields([
|
|
540
543
|
AztecAddress.fromNumber(1).toField(),
|
|
541
|
-
makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS
|
|
544
|
+
makeLengthsField(2, PUBLIC_LOG_DATA_SIZE_IN_FIELDS), // This field claims we have more than the max items
|
|
542
545
|
tag,
|
|
543
546
|
...times(PUBLIC_LOG_DATA_SIZE_IN_FIELDS - 1, i => new Fr(tag.toNumber() + i)),
|
|
544
547
|
]),
|
package/src/archiver/config.ts
CHANGED
|
@@ -22,7 +22,7 @@ export type ArchiverConfig = {
|
|
|
22
22
|
archiverUrl?: string;
|
|
23
23
|
|
|
24
24
|
/** URL for an L1 consensus client */
|
|
25
|
-
|
|
25
|
+
l1ConsensusHostUrl?: string;
|
|
26
26
|
|
|
27
27
|
/** The polling interval in ms for retrieving new L2 blocks and encrypted logs. */
|
|
28
28
|
archiverPollingIntervalMS?: number;
|
|
@@ -47,10 +47,10 @@ export const archiverConfigMappings: ConfigMappingsType<ArchiverConfig> = {
|
|
|
47
47
|
description:
|
|
48
48
|
'URL for an archiver service. If set, will return an archiver client as opposed to starting a new one.',
|
|
49
49
|
},
|
|
50
|
-
|
|
51
|
-
env: '
|
|
50
|
+
l1ConsensusHostUrl: {
|
|
51
|
+
env: 'L1_CONSENSUS_HOST_URL',
|
|
52
52
|
description: 'URL for an L1 consensus client.',
|
|
53
|
-
parseEnv: (val: string) =>
|
|
53
|
+
parseEnv: (val: string) => val,
|
|
54
54
|
},
|
|
55
55
|
archiverPollingIntervalMS: {
|
|
56
56
|
env: 'ARCHIVER_POLLING_INTERVAL_MS',
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { type BlobSinkClientInterface } from '@aztec/blob-sink/client';
|
|
1
2
|
import { Body, InboxLeaf, L2Block } from '@aztec/circuit-types';
|
|
2
3
|
import { AppendOnlyTreeSnapshot, BlockHeader, Fr, Proof } from '@aztec/circuits.js';
|
|
3
4
|
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
4
|
-
import { Blob } from '@aztec/foundation/blob';
|
|
5
|
+
import { Blob, BlobDeserializationError } from '@aztec/foundation/blob';
|
|
5
6
|
import { type EthAddress } from '@aztec/foundation/eth-address';
|
|
6
7
|
import { type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
7
8
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
8
9
|
import { numToUInt32BE } from '@aztec/foundation/serialize';
|
|
9
|
-
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
10
|
+
import { ForwarderAbi, type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
10
11
|
|
|
11
12
|
import {
|
|
12
13
|
type Chain,
|
|
@@ -20,6 +21,7 @@ import {
|
|
|
20
21
|
hexToBytes,
|
|
21
22
|
} from 'viem';
|
|
22
23
|
|
|
24
|
+
import { NoBlobBodiesFoundError } from './errors.js';
|
|
23
25
|
import { type DataRetrieval } from './structs/data_retrieval.js';
|
|
24
26
|
import { type L1Published, type L1PublishedData } from './structs/published.js';
|
|
25
27
|
|
|
@@ -35,6 +37,7 @@ import { type L1Published, type L1PublishedData } from './structs/published.js';
|
|
|
35
37
|
export async function retrieveBlocksFromRollup(
|
|
36
38
|
rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
|
|
37
39
|
publicClient: PublicClient,
|
|
40
|
+
blobSinkClient: BlobSinkClientInterface,
|
|
38
41
|
searchStartBlock: bigint,
|
|
39
42
|
searchEndBlock: bigint,
|
|
40
43
|
logger: Logger = createLogger('archiver'),
|
|
@@ -63,7 +66,13 @@ export async function retrieveBlocksFromRollup(
|
|
|
63
66
|
`Got ${l2BlockProposedLogs.length} L2 block processed logs for L2 blocks ${l2BlockProposedLogs[0].args.blockNumber}-${lastLog.args.blockNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
|
|
64
67
|
);
|
|
65
68
|
|
|
66
|
-
const newBlocks = await processL2BlockProposedLogs(
|
|
69
|
+
const newBlocks = await processL2BlockProposedLogs(
|
|
70
|
+
rollup,
|
|
71
|
+
publicClient,
|
|
72
|
+
blobSinkClient,
|
|
73
|
+
l2BlockProposedLogs,
|
|
74
|
+
logger,
|
|
75
|
+
);
|
|
67
76
|
retrievedBlocks.push(...newBlocks);
|
|
68
77
|
searchStartBlock = lastLog.blockNumber! + 1n;
|
|
69
78
|
} while (searchStartBlock <= searchEndBlock);
|
|
@@ -80,6 +89,7 @@ export async function retrieveBlocksFromRollup(
|
|
|
80
89
|
export async function processL2BlockProposedLogs(
|
|
81
90
|
rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
|
|
82
91
|
publicClient: PublicClient,
|
|
92
|
+
blobSinkClient: BlobSinkClientInterface,
|
|
83
93
|
logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
|
|
84
94
|
logger: Logger,
|
|
85
95
|
): Promise<L1Published<L2Block>[]> {
|
|
@@ -88,10 +98,19 @@ export async function processL2BlockProposedLogs(
|
|
|
88
98
|
const l2BlockNumber = log.args.blockNumber!;
|
|
89
99
|
const archive = log.args.archive!;
|
|
90
100
|
const archiveFromChain = await rollup.read.archiveAt([l2BlockNumber]);
|
|
101
|
+
const blobHashes = log.args.versionedBlobHashes!.map(blobHash => Buffer.from(blobHash.slice(2), 'hex'));
|
|
91
102
|
|
|
92
103
|
// The value from the event and contract will match only if the block is in the chain.
|
|
93
104
|
if (archive === archiveFromChain) {
|
|
94
|
-
const block = await getBlockFromRollupTx(
|
|
105
|
+
const block = await getBlockFromRollupTx(
|
|
106
|
+
publicClient,
|
|
107
|
+
blobSinkClient,
|
|
108
|
+
log.transactionHash!,
|
|
109
|
+
blobHashes,
|
|
110
|
+
l2BlockNumber,
|
|
111
|
+
rollup.address,
|
|
112
|
+
logger,
|
|
113
|
+
);
|
|
95
114
|
|
|
96
115
|
const l1: L1PublishedData = {
|
|
97
116
|
blockNumber: log.blockNumber,
|
|
@@ -116,6 +135,57 @@ export async function getL1BlockTime(publicClient: PublicClient, blockNumber: bi
|
|
|
116
135
|
return block.timestamp;
|
|
117
136
|
}
|
|
118
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Extracts the first 'propose' method calldata from a forwarder transaction's data.
|
|
140
|
+
* @param forwarderData - The forwarder transaction input data
|
|
141
|
+
* @param rollupAddress - The address of the rollup contract
|
|
142
|
+
* @returns The calldata for the first 'propose' method call to the rollup contract
|
|
143
|
+
*/
|
|
144
|
+
function extractRollupProposeCalldata(forwarderData: Hex, rollupAddress: Hex): Hex {
|
|
145
|
+
// TODO(#11451): custom forwarders
|
|
146
|
+
const { functionName: forwarderFunctionName, args: forwarderArgs } = decodeFunctionData({
|
|
147
|
+
abi: ForwarderAbi,
|
|
148
|
+
data: forwarderData,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (forwarderFunctionName !== 'forward') {
|
|
152
|
+
throw new Error(`Unexpected forwarder method called ${forwarderFunctionName}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (forwarderArgs.length !== 2) {
|
|
156
|
+
throw new Error(`Unexpected number of arguments for forwarder`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const [to, data] = forwarderArgs;
|
|
160
|
+
|
|
161
|
+
// Find all rollup calls
|
|
162
|
+
const rollupAddressLower = rollupAddress.toLowerCase();
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < to.length; i++) {
|
|
165
|
+
const addr = to[i];
|
|
166
|
+
if (addr.toLowerCase() !== rollupAddressLower) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const callData = data[i];
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const { functionName: rollupFunctionName } = decodeFunctionData({
|
|
173
|
+
abi: RollupAbi,
|
|
174
|
+
data: callData,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (rollupFunctionName === 'propose') {
|
|
178
|
+
return callData;
|
|
179
|
+
}
|
|
180
|
+
} catch (err) {
|
|
181
|
+
// Skip invalid function data
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
throw new Error(`Rollup address not found in forwarder args`);
|
|
187
|
+
}
|
|
188
|
+
|
|
119
189
|
/**
|
|
120
190
|
* Gets block from the calldata of an L1 transaction.
|
|
121
191
|
* Assumes that the block was published from an EOA.
|
|
@@ -127,19 +197,27 @@ export async function getL1BlockTime(publicClient: PublicClient, blockNumber: bi
|
|
|
127
197
|
*/
|
|
128
198
|
async function getBlockFromRollupTx(
|
|
129
199
|
publicClient: PublicClient,
|
|
200
|
+
blobSinkClient: BlobSinkClientInterface,
|
|
130
201
|
txHash: `0x${string}`,
|
|
202
|
+
blobHashes: Buffer[], // WORKTODO(md): buffer32?
|
|
131
203
|
l2BlockNum: bigint,
|
|
204
|
+
rollupAddress: Hex,
|
|
205
|
+
logger: Logger,
|
|
132
206
|
): Promise<L2Block> {
|
|
133
|
-
const { input:
|
|
134
|
-
const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data });
|
|
207
|
+
const { input: forwarderData, blockHash } = await publicClient.getTransaction({ hash: txHash });
|
|
135
208
|
|
|
136
|
-
const
|
|
209
|
+
const rollupData = extractRollupProposeCalldata(forwarderData, rollupAddress);
|
|
210
|
+
const { functionName: rollupFunctionName, args: rollupArgs } = decodeFunctionData({
|
|
211
|
+
abi: RollupAbi,
|
|
212
|
+
data: rollupData,
|
|
213
|
+
});
|
|
137
214
|
|
|
138
|
-
if (
|
|
139
|
-
throw new Error(`Unexpected method called ${
|
|
215
|
+
if (rollupFunctionName !== 'propose') {
|
|
216
|
+
throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
|
|
140
217
|
}
|
|
218
|
+
|
|
141
219
|
// TODO(#9101): 'bodyHex' will be removed from below
|
|
142
|
-
const [decodedArgs, , bodyHex, blobInputs] =
|
|
220
|
+
const [decodedArgs, , bodyHex, blobInputs] = rollupArgs! as readonly [
|
|
143
221
|
{
|
|
144
222
|
header: Hex;
|
|
145
223
|
archive: Hex;
|
|
@@ -156,12 +234,30 @@ async function getBlockFromRollupTx(
|
|
|
156
234
|
];
|
|
157
235
|
|
|
158
236
|
const header = BlockHeader.fromBuffer(Buffer.from(hexToBytes(decodedArgs.header)));
|
|
237
|
+
const blobBodies = await blobSinkClient.getBlobSidecar(blockHash, blobHashes);
|
|
238
|
+
if (blobBodies.length === 0) {
|
|
239
|
+
throw new NoBlobBodiesFoundError(Number(l2BlockNum));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// TODO(#9101): Once calldata is removed, we can remove this field encoding and update
|
|
243
|
+
// Body.fromBlobFields to accept blob buffers directly
|
|
244
|
+
let blockFields: Fr[];
|
|
245
|
+
try {
|
|
246
|
+
blockFields = blobBodies.flatMap(b => b.toEncodedFields());
|
|
247
|
+
} catch (err: any) {
|
|
248
|
+
if (err instanceof BlobDeserializationError) {
|
|
249
|
+
logger.fatal(err.message);
|
|
250
|
+
} else {
|
|
251
|
+
logger.fatal('Unable to sync: failed to decode fetched blob, this blob was likely not created by us');
|
|
252
|
+
}
|
|
253
|
+
throw err;
|
|
254
|
+
}
|
|
255
|
+
|
|
159
256
|
// TODO(#9101): Retreiving the block body from calldata is a temporary soln before we have
|
|
160
257
|
// either a beacon chain client or link to some blob store. Web2 is ok because we will
|
|
161
258
|
// verify the block body vs the blob as below.
|
|
162
259
|
const blockBody = Body.fromBuffer(Buffer.from(hexToBytes(bodyHex)));
|
|
163
260
|
|
|
164
|
-
const blockFields = blockBody.toBlobFields();
|
|
165
261
|
// TODO(#9101): The below reconstruction is currently redundant, but once we extract blobs will be the way to construct blocks.
|
|
166
262
|
// The blob source will give us blockFields, and we must construct the body from them:
|
|
167
263
|
// TODO(#8954): When logs are refactored into fields, we won't need to inject them here.
|
|
@@ -173,7 +269,7 @@ async function getBlockFromRollupTx(
|
|
|
173
269
|
}
|
|
174
270
|
|
|
175
271
|
// TODO(#9101): Once we stop publishing calldata, we will still need the blobCheck below to ensure that the block we are building does correspond to the blob fields
|
|
176
|
-
const blobCheck = Blob.getBlobs(blockFields);
|
|
272
|
+
const blobCheck = await Blob.getBlobs(blockFields);
|
|
177
273
|
if (Blob.getEthBlobEvaluationInputs(blobCheck) !== blobInputs) {
|
|
178
274
|
// NB: We can just check the blobhash here, which is the first 32 bytes of blobInputs
|
|
179
275
|
// A mismatch means that the fields published in the blob in propose() do NOT match those in the extracted block.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Body, type InBlock, L2Block, L2BlockHash, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
|
|
2
2
|
import { AppendOnlyTreeSnapshot, type AztecAddress, BlockHeader, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
|
|
3
|
+
import { toArray } from '@aztec/foundation/iterable';
|
|
3
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
-
import {
|
|
5
|
+
import type { AztecAsyncKVStore, AztecAsyncMap, AztecAsyncSingleton, Range } from '@aztec/kv-store';
|
|
5
6
|
|
|
6
7
|
import { type L1Published, type L1PublishedData } from '../structs/published.js';
|
|
7
8
|
|
|
@@ -18,29 +19,29 @@ type BlockStorage = {
|
|
|
18
19
|
*/
|
|
19
20
|
export class BlockStore {
|
|
20
21
|
/** Map block number to block data */
|
|
21
|
-
#blocks:
|
|
22
|
+
#blocks: AztecAsyncMap<number, BlockStorage>;
|
|
22
23
|
|
|
23
24
|
/** Map block hash to block body */
|
|
24
|
-
#blockBodies:
|
|
25
|
+
#blockBodies: AztecAsyncMap<string, Buffer>;
|
|
25
26
|
|
|
26
27
|
/** Stores L1 block number in which the last processed L2 block was included */
|
|
27
|
-
#lastSynchedL1Block:
|
|
28
|
+
#lastSynchedL1Block: AztecAsyncSingleton<bigint>;
|
|
28
29
|
|
|
29
30
|
/** Stores l2 block number of the last proven block */
|
|
30
|
-
#lastProvenL2Block:
|
|
31
|
+
#lastProvenL2Block: AztecAsyncSingleton<number>;
|
|
31
32
|
|
|
32
33
|
/** Stores l2 epoch number of the last proven epoch */
|
|
33
|
-
#lastProvenL2Epoch:
|
|
34
|
+
#lastProvenL2Epoch: AztecAsyncSingleton<number>;
|
|
34
35
|
|
|
35
36
|
/** Index mapping transaction hash (as a string) to its location in a block */
|
|
36
|
-
#txIndex:
|
|
37
|
+
#txIndex: AztecAsyncMap<string, BlockIndexValue>;
|
|
37
38
|
|
|
38
39
|
/** Index mapping a contract's address (as a string) to its location in a block */
|
|
39
|
-
#contractIndex:
|
|
40
|
+
#contractIndex: AztecAsyncMap<string, BlockIndexValue>;
|
|
40
41
|
|
|
41
42
|
#log = createLogger('archiver:block_store');
|
|
42
43
|
|
|
43
|
-
constructor(private db:
|
|
44
|
+
constructor(private db: AztecAsyncKVStore) {
|
|
44
45
|
this.#blocks = db.openMap('archiver_blocks');
|
|
45
46
|
this.#blockBodies = db.openMap('archiver_block_bodies');
|
|
46
47
|
this.#txIndex = db.openMap('archiver_tx_index');
|
|
@@ -55,28 +56,28 @@ export class BlockStore {
|
|
|
55
56
|
* @param blocks - The L2 blocks to be added to the store.
|
|
56
57
|
* @returns True if the operation is successful.
|
|
57
58
|
*/
|
|
58
|
-
addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean> {
|
|
59
|
+
async addBlocks(blocks: L1Published<L2Block>[]): Promise<boolean> {
|
|
59
60
|
if (blocks.length === 0) {
|
|
60
|
-
return
|
|
61
|
+
return true;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
return this.db.
|
|
64
|
+
return await this.db.transactionAsync(async () => {
|
|
64
65
|
for (const block of blocks) {
|
|
65
|
-
|
|
66
|
+
await this.#blocks.set(block.data.number, {
|
|
66
67
|
header: block.data.header.toBuffer(),
|
|
67
68
|
archive: block.data.archive.toBuffer(),
|
|
68
69
|
l1: block.l1,
|
|
69
70
|
});
|
|
70
71
|
|
|
71
|
-
block.data.body.txEffects.
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
for (let i = 0; i < block.data.body.txEffects.length; i++) {
|
|
73
|
+
const txEffect = block.data.body.txEffects[i];
|
|
74
|
+
await this.#txIndex.set(txEffect.txHash.toString(), [block.data.number, i]);
|
|
75
|
+
}
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
await this.#blockBodies.set((await block.data.hash()).toString(), block.data.body.toBuffer());
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
await this.#lastSynchedL1Block.set(blocks[blocks.length - 1].l1.blockNumber);
|
|
80
81
|
return true;
|
|
81
82
|
});
|
|
82
83
|
}
|
|
@@ -88,26 +89,24 @@ export class BlockStore {
|
|
|
88
89
|
* @param blocksToUnwind - The number of blocks we are to unwind
|
|
89
90
|
* @returns True if the operation is successful
|
|
90
91
|
*/
|
|
91
|
-
unwindBlocks(from: number, blocksToUnwind: number) {
|
|
92
|
-
return this.db.
|
|
93
|
-
const last = this.getSynchedL2BlockNumber();
|
|
94
|
-
if (from
|
|
92
|
+
async unwindBlocks(from: number, blocksToUnwind: number) {
|
|
93
|
+
return await this.db.transactionAsync(async () => {
|
|
94
|
+
const last = await this.getSynchedL2BlockNumber();
|
|
95
|
+
if (from !== last) {
|
|
95
96
|
throw new Error(`Can only unwind blocks from the tip (requested ${from} but current tip is ${last})`);
|
|
96
97
|
}
|
|
97
98
|
|
|
98
99
|
for (let i = 0; i < blocksToUnwind; i++) {
|
|
99
100
|
const blockNumber = from - i;
|
|
100
|
-
const block = this.getBlock(blockNumber);
|
|
101
|
+
const block = await this.getBlock(blockNumber);
|
|
101
102
|
|
|
102
103
|
if (block === undefined) {
|
|
103
104
|
throw new Error(`Cannot remove block ${blockNumber} from the store, we don't have it`);
|
|
104
105
|
}
|
|
105
|
-
|
|
106
|
-
block.data.body.txEffects.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const blockHash = block.data.hash().toString();
|
|
110
|
-
void this.#blockBodies.delete(blockHash);
|
|
106
|
+
await this.#blocks.delete(block.data.number);
|
|
107
|
+
await Promise.all(block.data.body.txEffects.map(tx => this.#txIndex.delete(tx.txHash.toString())));
|
|
108
|
+
const blockHash = (await block.data.hash()).toString();
|
|
109
|
+
await this.#blockBodies.delete(blockHash);
|
|
111
110
|
this.#log.debug(`Unwound block ${blockNumber} ${blockHash}`);
|
|
112
111
|
}
|
|
113
112
|
|
|
@@ -121,9 +120,10 @@ export class BlockStore {
|
|
|
121
120
|
* @param limit - The number of blocks to return.
|
|
122
121
|
* @returns The requested L2 blocks
|
|
123
122
|
*/
|
|
124
|
-
*getBlocks(start: number, limit: number):
|
|
125
|
-
for (const blockStorage of this.#blocks.
|
|
126
|
-
|
|
123
|
+
async *getBlocks(start: number, limit: number): AsyncIterableIterator<L1Published<L2Block>> {
|
|
124
|
+
for await (const blockStorage of this.#blocks.valuesAsync(this.#computeBlockRange(start, limit))) {
|
|
125
|
+
const block = await this.getBlockFromBlockStorage(blockStorage);
|
|
126
|
+
yield block;
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -132,10 +132,10 @@ export class BlockStore {
|
|
|
132
132
|
* @param blockNumber - The number of the block to return.
|
|
133
133
|
* @returns The requested L2 block.
|
|
134
134
|
*/
|
|
135
|
-
getBlock(blockNumber: number): L1Published<L2Block> | undefined {
|
|
136
|
-
const blockStorage = this.#blocks.
|
|
135
|
+
async getBlock(blockNumber: number): Promise<L1Published<L2Block> | undefined> {
|
|
136
|
+
const blockStorage = await this.#blocks.getAsync(blockNumber);
|
|
137
137
|
if (!blockStorage || !blockStorage.header) {
|
|
138
|
-
return undefined;
|
|
138
|
+
return Promise.resolve(undefined);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
return this.getBlockFromBlockStorage(blockStorage);
|
|
@@ -147,17 +147,17 @@ export class BlockStore {
|
|
|
147
147
|
* @param limit - The number of blocks to return.
|
|
148
148
|
* @returns The requested L2 block headers
|
|
149
149
|
*/
|
|
150
|
-
*getBlockHeaders(start: number, limit: number):
|
|
151
|
-
for (const blockStorage of this.#blocks.
|
|
150
|
+
async *getBlockHeaders(start: number, limit: number): AsyncIterableIterator<BlockHeader> {
|
|
151
|
+
for await (const blockStorage of this.#blocks.valuesAsync(this.#computeBlockRange(start, limit))) {
|
|
152
152
|
yield BlockHeader.fromBuffer(blockStorage.header);
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
private getBlockFromBlockStorage(blockStorage: BlockStorage) {
|
|
156
|
+
private async getBlockFromBlockStorage(blockStorage: BlockStorage) {
|
|
157
157
|
const header = BlockHeader.fromBuffer(blockStorage.header);
|
|
158
158
|
const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
|
|
159
|
-
const blockHash = header.hash().toString();
|
|
160
|
-
const blockBodyBuffer = this.#blockBodies.
|
|
159
|
+
const blockHash = (await header.hash()).toString();
|
|
160
|
+
const blockBodyBuffer = await this.#blockBodies.getAsync(blockHash);
|
|
161
161
|
if (blockBodyBuffer === undefined) {
|
|
162
162
|
throw new Error(
|
|
163
163
|
`Could not retrieve body for block ${header.globalVariables.blockNumber.toNumber()} ${blockHash}`,
|
|
@@ -174,13 +174,13 @@ export class BlockStore {
|
|
|
174
174
|
* @param txHash - The txHash of the tx corresponding to the tx effect.
|
|
175
175
|
* @returns The requested tx effect (or undefined if not found).
|
|
176
176
|
*/
|
|
177
|
-
getTxEffect(txHash: TxHash): InBlock<TxEffect> | undefined {
|
|
178
|
-
const [blockNumber, txIndex] = this.getTxLocation(txHash) ?? [];
|
|
177
|
+
async getTxEffect(txHash: TxHash): Promise<InBlock<TxEffect> | undefined> {
|
|
178
|
+
const [blockNumber, txIndex] = (await this.getTxLocation(txHash)) ?? [];
|
|
179
179
|
if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
|
|
180
180
|
return undefined;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
const block = this.getBlock(blockNumber);
|
|
183
|
+
const block = await this.getBlock(blockNumber);
|
|
184
184
|
if (!block) {
|
|
185
185
|
return undefined;
|
|
186
186
|
}
|
|
@@ -188,7 +188,7 @@ export class BlockStore {
|
|
|
188
188
|
return {
|
|
189
189
|
data: block.data.body.txEffects[txIndex],
|
|
190
190
|
l2BlockNumber: block.data.number,
|
|
191
|
-
l2BlockHash: block.data.hash().toString(),
|
|
191
|
+
l2BlockHash: (await block.data.hash()).toString(),
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
194
|
|
|
@@ -197,13 +197,13 @@ export class BlockStore {
|
|
|
197
197
|
* @param txHash - The hash of a tx we try to get the receipt for.
|
|
198
198
|
* @returns The requested tx receipt (or undefined if not found).
|
|
199
199
|
*/
|
|
200
|
-
getSettledTxReceipt(txHash: TxHash): TxReceipt | undefined {
|
|
201
|
-
const [blockNumber, txIndex] = this.getTxLocation(txHash) ?? [];
|
|
200
|
+
async getSettledTxReceipt(txHash: TxHash): Promise<TxReceipt | undefined> {
|
|
201
|
+
const [blockNumber, txIndex] = (await this.getTxLocation(txHash)) ?? [];
|
|
202
202
|
if (typeof blockNumber !== 'number' || typeof txIndex !== 'number') {
|
|
203
203
|
return undefined;
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
const block = this.getBlock(blockNumber)!;
|
|
206
|
+
const block = (await this.getBlock(blockNumber))!;
|
|
207
207
|
const tx = block.data.body.txEffects[txIndex];
|
|
208
208
|
|
|
209
209
|
return new TxReceipt(
|
|
@@ -211,7 +211,7 @@ export class BlockStore {
|
|
|
211
211
|
TxReceipt.statusFromRevertCode(tx.revertCode),
|
|
212
212
|
'',
|
|
213
213
|
tx.transactionFee.toBigInt(),
|
|
214
|
-
L2BlockHash.fromField(block.data.hash()),
|
|
214
|
+
L2BlockHash.fromField(await block.data.hash()),
|
|
215
215
|
block.data.number,
|
|
216
216
|
);
|
|
217
217
|
}
|
|
@@ -221,8 +221,8 @@ export class BlockStore {
|
|
|
221
221
|
* @param txHash - The txHash of the tx.
|
|
222
222
|
* @returns The block number and index of the tx.
|
|
223
223
|
*/
|
|
224
|
-
getTxLocation(txHash: TxHash): [blockNumber: number, txIndex: number] | undefined {
|
|
225
|
-
return this.#txIndex.
|
|
224
|
+
getTxLocation(txHash: TxHash): Promise<[blockNumber: number, txIndex: number] | undefined> {
|
|
225
|
+
return this.#txIndex.getAsync(txHash.toString());
|
|
226
226
|
}
|
|
227
227
|
|
|
228
228
|
/**
|
|
@@ -230,16 +230,16 @@ export class BlockStore {
|
|
|
230
230
|
* @param contractAddress - The address of the contract to look up.
|
|
231
231
|
* @returns The block number and index of the contract.
|
|
232
232
|
*/
|
|
233
|
-
getContractLocation(contractAddress: AztecAddress): [blockNumber: number, index: number] | undefined {
|
|
234
|
-
return this.#contractIndex.
|
|
233
|
+
getContractLocation(contractAddress: AztecAddress): Promise<[blockNumber: number, index: number] | undefined> {
|
|
234
|
+
return this.#contractIndex.getAsync(contractAddress.toString());
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/**
|
|
238
238
|
* Gets the number of the latest L2 block processed.
|
|
239
239
|
* @returns The number of the latest L2 block processed.
|
|
240
240
|
*/
|
|
241
|
-
getSynchedL2BlockNumber(): number {
|
|
242
|
-
const [lastBlockNumber] = this.#blocks.
|
|
241
|
+
async getSynchedL2BlockNumber(): Promise<number> {
|
|
242
|
+
const [lastBlockNumber] = await toArray(this.#blocks.keysAsync({ reverse: true, limit: 1 }));
|
|
243
243
|
return typeof lastBlockNumber === 'number' ? lastBlockNumber : INITIAL_L2_BLOCK_NUM - 1;
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -247,31 +247,31 @@ export class BlockStore {
|
|
|
247
247
|
* Gets the most recent L1 block processed.
|
|
248
248
|
* @returns The L1 block that published the latest L2 block
|
|
249
249
|
*/
|
|
250
|
-
getSynchedL1BlockNumber(): bigint | undefined {
|
|
251
|
-
return this.#lastSynchedL1Block.
|
|
250
|
+
getSynchedL1BlockNumber(): Promise<bigint | undefined> {
|
|
251
|
+
return this.#lastSynchedL1Block.getAsync();
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
setSynchedL1BlockNumber(l1BlockNumber: bigint) {
|
|
255
|
-
|
|
255
|
+
return this.#lastSynchedL1Block.set(l1BlockNumber);
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
getProvenL2BlockNumber(): number {
|
|
259
|
-
return this.#lastProvenL2Block.
|
|
258
|
+
async getProvenL2BlockNumber(): Promise<number> {
|
|
259
|
+
return (await this.#lastProvenL2Block.getAsync()) ?? 0;
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
setProvenL2BlockNumber(blockNumber: number) {
|
|
263
|
-
|
|
263
|
+
return this.#lastProvenL2Block.set(blockNumber);
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
-
getProvenL2EpochNumber(): number | undefined {
|
|
267
|
-
return this.#lastProvenL2Epoch.
|
|
266
|
+
getProvenL2EpochNumber(): Promise<number | undefined> {
|
|
267
|
+
return this.#lastProvenL2Epoch.getAsync();
|
|
268
268
|
}
|
|
269
269
|
|
|
270
270
|
setProvenL2EpochNumber(epochNumber: number) {
|
|
271
|
-
|
|
271
|
+
return this.#lastProvenL2Epoch.set(epochNumber);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
-
#computeBlockRange(start: number, limit: number): Required<Pick<Range<number>, 'start' | '
|
|
274
|
+
#computeBlockRange(start: number, limit: number): Required<Pick<Range<number>, 'start' | 'limit'>> {
|
|
275
275
|
if (limit < 1) {
|
|
276
276
|
throw new Error(`Invalid limit: ${limit}`);
|
|
277
277
|
}
|
|
@@ -280,7 +280,6 @@ export class BlockStore {
|
|
|
280
280
|
throw new Error(`Invalid start: ${start}`);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
|
|
284
|
-
return { start, end };
|
|
283
|
+
return { start, limit };
|
|
285
284
|
}
|
|
286
285
|
}
|