@aztec/archiver 3.0.0-nightly.20251127 → 3.0.0-nightly.20251201.2
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 +25 -18
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +221 -163
- package/dest/archiver/archiver_store.d.ts +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 +5 -4
- package/dest/archiver/config.d.ts +1 -1
- package/dest/archiver/data_retrieval.d.ts +15 -15
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +56 -61
- package/dest/archiver/errors.d.ts +1 -1
- package/dest/archiver/errors.d.ts.map +1 -1
- package/dest/archiver/index.d.ts +1 -1
- package/dest/archiver/instrumentation.d.ts +3 -3
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.d.ts +1 -1
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.d.ts +1 -1
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.d.ts +1 -1
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/structs/data_retrieval.d.ts +1 -1
- package/dest/archiver/structs/inbox_message.d.ts +1 -1
- package/dest/archiver/structs/published.d.ts +3 -2
- package/dest/archiver/structs/published.d.ts.map +1 -1
- package/dest/archiver/validation.d.ts +10 -4
- package/dest/archiver/validation.d.ts.map +1 -1
- package/dest/archiver/validation.js +25 -17
- package/dest/factory.d.ts +1 -1
- package/dest/index.d.ts +2 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -1
- package/dest/rpc/index.d.ts +2 -2
- package/dest/test/index.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts +1 -1
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.d.ts +7 -6
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +1 -1
- package/dest/test/mock_structs.d.ts +1 -1
- package/package.json +16 -16
- package/src/archiver/archiver.ts +294 -196
- package/src/archiver/archiver_store_test_suite.ts +5 -4
- package/src/archiver/data_retrieval.ts +70 -81
- package/src/archiver/instrumentation.ts +2 -2
- package/src/archiver/structs/published.ts +2 -1
- package/src/archiver/validation.ts +40 -19
- package/src/index.ts +1 -1
- package/src/test/mock_l2_block_source.ts +7 -6
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
PRIVATE_LOG_SIZE_IN_FIELDS,
|
|
5
5
|
} from '@aztec/constants';
|
|
6
6
|
import { makeTuple } from '@aztec/foundation/array';
|
|
7
|
+
import { EpochNumber } from '@aztec/foundation/branded-types';
|
|
7
8
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
8
9
|
import { times, timesParallel } from '@aztec/foundation/collection';
|
|
9
10
|
import { randomInt } from '@aztec/foundation/crypto';
|
|
@@ -1189,7 +1190,7 @@ export function describeArchiverDataStore(
|
|
|
1189
1190
|
valid: false,
|
|
1190
1191
|
block: randomBlockInfo(1),
|
|
1191
1192
|
committee: [EthAddress.random(), EthAddress.random()],
|
|
1192
|
-
epoch:
|
|
1193
|
+
epoch: EpochNumber(123),
|
|
1193
1194
|
seed: 456n,
|
|
1194
1195
|
attestors: [EthAddress.random()],
|
|
1195
1196
|
attestations: [CommitteeAttestation.random()],
|
|
@@ -1208,7 +1209,7 @@ export function describeArchiverDataStore(
|
|
|
1208
1209
|
block: randomBlockInfo(2),
|
|
1209
1210
|
committee: [EthAddress.random()],
|
|
1210
1211
|
attestors: [EthAddress.random()],
|
|
1211
|
-
epoch:
|
|
1212
|
+
epoch: EpochNumber(789),
|
|
1212
1213
|
seed: 101n,
|
|
1213
1214
|
attestations: [CommitteeAttestation.random()],
|
|
1214
1215
|
reason: 'invalid-attestation',
|
|
@@ -1227,7 +1228,7 @@ export function describeArchiverDataStore(
|
|
|
1227
1228
|
valid: false,
|
|
1228
1229
|
block: randomBlockInfo(3),
|
|
1229
1230
|
committee: [EthAddress.random()],
|
|
1230
|
-
epoch:
|
|
1231
|
+
epoch: EpochNumber(999),
|
|
1231
1232
|
seed: 888n,
|
|
1232
1233
|
attestors: [EthAddress.random()],
|
|
1233
1234
|
attestations: [CommitteeAttestation.random()],
|
|
@@ -1246,7 +1247,7 @@ export function describeArchiverDataStore(
|
|
|
1246
1247
|
valid: false,
|
|
1247
1248
|
block: randomBlockInfo(4),
|
|
1248
1249
|
committee: [],
|
|
1249
|
-
epoch:
|
|
1250
|
+
epoch: EpochNumber(0),
|
|
1250
1251
|
seed: 0n,
|
|
1251
1252
|
attestors: [],
|
|
1252
1253
|
attestations: [],
|
|
@@ -12,7 +12,6 @@ import type {
|
|
|
12
12
|
ViemCommitteeAttestations,
|
|
13
13
|
ViemHeader,
|
|
14
14
|
ViemPublicClient,
|
|
15
|
-
ViemStateReference,
|
|
16
15
|
} from '@aztec/ethereum';
|
|
17
16
|
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
18
17
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
@@ -21,7 +20,8 @@ import type { ViemSignature } from '@aztec/foundation/eth-signature';
|
|
|
21
20
|
import { Fr } from '@aztec/foundation/fields';
|
|
22
21
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
23
22
|
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
24
|
-
import { Body, CommitteeAttestation,
|
|
23
|
+
import { Body, CommitteeAttestation, L2BlockNew } from '@aztec/stdlib/block';
|
|
24
|
+
import { Checkpoint, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
25
25
|
import { Proof } from '@aztec/stdlib/proofs';
|
|
26
26
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
27
27
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
@@ -42,9 +42,9 @@ import type { DataRetrieval } from './structs/data_retrieval.js';
|
|
|
42
42
|
import type { InboxMessage } from './structs/inbox_message.js';
|
|
43
43
|
import type { L1PublishedData } from './structs/published.js';
|
|
44
44
|
|
|
45
|
-
export type
|
|
45
|
+
export type RetrievedCheckpoint = {
|
|
46
|
+
checkpointNumber: number;
|
|
46
47
|
archiveRoot: Fr;
|
|
47
|
-
stateReference: StateReference;
|
|
48
48
|
header: CheckpointHeader;
|
|
49
49
|
checkpointBlobData: CheckpointBlobData;
|
|
50
50
|
l1: L1PublishedData;
|
|
@@ -53,16 +53,16 @@ export type RetrievedL2Block = {
|
|
|
53
53
|
attestations: CommitteeAttestation[];
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
export async function
|
|
56
|
+
export async function retrievedToPublishedCheckpoint({
|
|
57
|
+
checkpointNumber,
|
|
57
58
|
archiveRoot,
|
|
58
|
-
stateReference,
|
|
59
59
|
header: checkpointHeader,
|
|
60
60
|
checkpointBlobData,
|
|
61
61
|
l1,
|
|
62
62
|
chainId,
|
|
63
63
|
version,
|
|
64
64
|
attestations,
|
|
65
|
-
}:
|
|
65
|
+
}: RetrievedCheckpoint): Promise<PublishedCheckpoint> {
|
|
66
66
|
const { blocks: blocksBlobData } = checkpointBlobData;
|
|
67
67
|
|
|
68
68
|
// The lastArchiveRoot of a block is the new archive for the previous block.
|
|
@@ -76,7 +76,7 @@ export async function retrievedBlockToPublishedL2Block({
|
|
|
76
76
|
const l1toL2MessageTreeRoot = blocksBlobData[0].l1ToL2MessageRoot!;
|
|
77
77
|
|
|
78
78
|
const spongeBlob = SpongeBlob.init();
|
|
79
|
-
const l2Blocks:
|
|
79
|
+
const l2Blocks: L2BlockNew[] = [];
|
|
80
80
|
for (let i = 0; i < blocksBlobData.length; i++) {
|
|
81
81
|
const blockBlobData = blocksBlobData[i];
|
|
82
82
|
const { blockEndMarker, blockEndStateField, lastArchiveRoot, noteHashRoot, nullifierRoot, publicDataRoot } =
|
|
@@ -115,7 +115,7 @@ export async function retrievedBlockToPublishedL2Block({
|
|
|
115
115
|
const clonedSpongeBlob = spongeBlob.clone();
|
|
116
116
|
const spongeBlobHash = await clonedSpongeBlob.squeeze();
|
|
117
117
|
|
|
118
|
-
const
|
|
118
|
+
const header = BlockHeader.from({
|
|
119
119
|
lastArchive: new AppendOnlyTreeSnapshot(lastArchiveRoot, l2BlockNumber),
|
|
120
120
|
state,
|
|
121
121
|
spongeBlobHash,
|
|
@@ -124,31 +124,24 @@ export async function retrievedBlockToPublishedL2Block({
|
|
|
124
124
|
totalManaUsed: new Fr(blockEndStateField.totalManaUsed),
|
|
125
125
|
});
|
|
126
126
|
|
|
127
|
-
const header = L2BlockHeader.from({
|
|
128
|
-
...blockHeader,
|
|
129
|
-
blockHeadersHash: checkpointHeader.blockHeadersHash,
|
|
130
|
-
contentCommitment: checkpointHeader.contentCommitment,
|
|
131
|
-
});
|
|
132
|
-
|
|
133
127
|
const newArchive = new AppendOnlyTreeSnapshot(newArchiveRoots[i], l2BlockNumber + 1);
|
|
134
128
|
|
|
135
|
-
l2Blocks.push(new
|
|
129
|
+
l2Blocks.push(new L2BlockNew(newArchive, header, body));
|
|
136
130
|
}
|
|
137
131
|
|
|
138
|
-
const lastBlock = l2Blocks
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
132
|
+
const lastBlock = l2Blocks.at(-1)!;
|
|
133
|
+
const checkpoint = Checkpoint.from({
|
|
134
|
+
archive: new AppendOnlyTreeSnapshot(archiveRoot, lastBlock.number + 1),
|
|
135
|
+
header: checkpointHeader,
|
|
136
|
+
blocks: l2Blocks,
|
|
137
|
+
number: checkpointNumber,
|
|
138
|
+
});
|
|
144
139
|
|
|
145
|
-
|
|
146
|
-
// There's only one block per checkpoint at the moment.
|
|
147
|
-
return PublishedL2Block.fromFields({ block: l2Blocks[0], l1, attestations });
|
|
140
|
+
return PublishedCheckpoint.from({ checkpoint, l1, attestations });
|
|
148
141
|
}
|
|
149
142
|
|
|
150
143
|
/**
|
|
151
|
-
* Fetches new
|
|
144
|
+
* Fetches new checkpoints.
|
|
152
145
|
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
153
146
|
* @param rollupAddress - The address of the rollup contract.
|
|
154
147
|
* @param searchStartBlock - The block number to use for starting the search.
|
|
@@ -156,15 +149,15 @@ export async function retrievedBlockToPublishedL2Block({
|
|
|
156
149
|
* @param expectedNextL2BlockNum - The next L2 block number that we expect to find.
|
|
157
150
|
* @returns An array of block; as well as the next eth block to search from.
|
|
158
151
|
*/
|
|
159
|
-
export async function
|
|
152
|
+
export async function retrieveCheckpointsFromRollup(
|
|
160
153
|
rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
|
|
161
154
|
publicClient: ViemPublicClient,
|
|
162
155
|
blobSinkClient: BlobSinkClientInterface,
|
|
163
156
|
searchStartBlock: bigint,
|
|
164
157
|
searchEndBlock: bigint,
|
|
165
158
|
logger: Logger = createLogger('archiver'),
|
|
166
|
-
): Promise<
|
|
167
|
-
const
|
|
159
|
+
): Promise<RetrievedCheckpoint[]> {
|
|
160
|
+
const retrievedCheckpoints: RetrievedCheckpoint[] = [];
|
|
168
161
|
|
|
169
162
|
let rollupConstants: { chainId: Fr; version: Fr; targetCommitteeSize: number } | undefined;
|
|
170
163
|
|
|
@@ -172,7 +165,7 @@ export async function retrieveBlocksFromRollup(
|
|
|
172
165
|
if (searchStartBlock > searchEndBlock) {
|
|
173
166
|
break;
|
|
174
167
|
}
|
|
175
|
-
const
|
|
168
|
+
const checkpointProposedLogs = (
|
|
176
169
|
await rollup.getEvents.CheckpointProposed(
|
|
177
170
|
{},
|
|
178
171
|
{
|
|
@@ -182,13 +175,13 @@ export async function retrieveBlocksFromRollup(
|
|
|
182
175
|
)
|
|
183
176
|
).filter(log => log.blockNumber! >= searchStartBlock && log.blockNumber! <= searchEndBlock);
|
|
184
177
|
|
|
185
|
-
if (
|
|
178
|
+
if (checkpointProposedLogs.length === 0) {
|
|
186
179
|
break;
|
|
187
180
|
}
|
|
188
181
|
|
|
189
|
-
const lastLog =
|
|
182
|
+
const lastLog = checkpointProposedLogs.at(-1)!;
|
|
190
183
|
logger.debug(
|
|
191
|
-
`Got ${
|
|
184
|
+
`Got ${checkpointProposedLogs.length} processed logs for checkpoints ${checkpointProposedLogs[0].args.checkpointNumber}-${lastLog.args.checkpointNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
|
|
192
185
|
);
|
|
193
186
|
|
|
194
187
|
if (rollupConstants === undefined) {
|
|
@@ -204,52 +197,52 @@ export async function retrieveBlocksFromRollup(
|
|
|
204
197
|
};
|
|
205
198
|
}
|
|
206
199
|
|
|
207
|
-
const
|
|
200
|
+
const newCheckpoints = await processCheckpointProposedLogs(
|
|
208
201
|
rollup,
|
|
209
202
|
publicClient,
|
|
210
203
|
blobSinkClient,
|
|
211
|
-
|
|
204
|
+
checkpointProposedLogs,
|
|
212
205
|
rollupConstants,
|
|
213
206
|
logger,
|
|
214
207
|
);
|
|
215
|
-
|
|
208
|
+
retrievedCheckpoints.push(...newCheckpoints);
|
|
216
209
|
searchStartBlock = lastLog.blockNumber! + 1n;
|
|
217
210
|
} while (searchStartBlock <= searchEndBlock);
|
|
218
211
|
|
|
219
|
-
// The
|
|
220
|
-
return
|
|
212
|
+
// The asyncPool from processCheckpointProposedLogs will not necessarily return the checkpoints in order, so we sort them before returning.
|
|
213
|
+
return retrievedCheckpoints.sort((a, b) => Number(a.l1.blockNumber - b.l1.blockNumber));
|
|
221
214
|
}
|
|
222
215
|
|
|
223
216
|
/**
|
|
224
|
-
* Processes newly received
|
|
217
|
+
* Processes newly received CheckpointProposed logs.
|
|
225
218
|
* @param rollup - The rollup contract
|
|
226
219
|
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
227
|
-
* @param logs -
|
|
228
|
-
* @returns - An array
|
|
220
|
+
* @param logs - CheckpointProposed logs.
|
|
221
|
+
* @returns - An array of checkpoints.
|
|
229
222
|
*/
|
|
230
|
-
async function
|
|
223
|
+
async function processCheckpointProposedLogs(
|
|
231
224
|
rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
|
|
232
225
|
publicClient: ViemPublicClient,
|
|
233
226
|
blobSinkClient: BlobSinkClientInterface,
|
|
234
227
|
logs: GetContractEventsReturnType<typeof RollupAbi, 'CheckpointProposed'>,
|
|
235
228
|
{ chainId, version, targetCommitteeSize }: { chainId: Fr; version: Fr; targetCommitteeSize: number },
|
|
236
229
|
logger: Logger,
|
|
237
|
-
): Promise<
|
|
238
|
-
const
|
|
230
|
+
): Promise<RetrievedCheckpoint[]> {
|
|
231
|
+
const retrievedCheckpoints: RetrievedCheckpoint[] = [];
|
|
239
232
|
await asyncPool(10, logs, async log => {
|
|
240
|
-
const
|
|
233
|
+
const checkpointNumber = Number(log.args.checkpointNumber!);
|
|
241
234
|
const archive = log.args.archive!;
|
|
242
|
-
const archiveFromChain = await rollup.read.archiveAt([BigInt(
|
|
235
|
+
const archiveFromChain = await rollup.read.archiveAt([BigInt(checkpointNumber)]);
|
|
243
236
|
const blobHashes = log.args.versionedBlobHashes!.map(blobHash => Buffer.from(blobHash.slice(2), 'hex'));
|
|
244
237
|
|
|
245
|
-
// The value from the event and contract will match only if the
|
|
238
|
+
// The value from the event and contract will match only if the checkpoint is in the chain.
|
|
246
239
|
if (archive === archiveFromChain) {
|
|
247
|
-
const
|
|
240
|
+
const checkpoint = await getCheckpointFromRollupTx(
|
|
248
241
|
publicClient,
|
|
249
242
|
blobSinkClient,
|
|
250
243
|
log.transactionHash!,
|
|
251
244
|
blobHashes,
|
|
252
|
-
|
|
245
|
+
checkpointNumber,
|
|
253
246
|
rollup.address,
|
|
254
247
|
targetCommitteeSize,
|
|
255
248
|
logger,
|
|
@@ -261,22 +254,22 @@ async function processL2BlockProposedLogs(
|
|
|
261
254
|
timestamp: await getL1BlockTime(publicClient, log.blockNumber),
|
|
262
255
|
};
|
|
263
256
|
|
|
264
|
-
|
|
265
|
-
logger.trace(`Retrieved
|
|
257
|
+
retrievedCheckpoints.push({ ...checkpoint, l1, chainId, version });
|
|
258
|
+
logger.trace(`Retrieved checkpoint ${checkpointNumber} from L1 tx ${log.transactionHash}`, {
|
|
266
259
|
l1BlockNumber: log.blockNumber,
|
|
267
|
-
|
|
260
|
+
checkpointNumber,
|
|
268
261
|
archive: archive.toString(),
|
|
269
|
-
attestations:
|
|
262
|
+
attestations: checkpoint.attestations,
|
|
270
263
|
});
|
|
271
264
|
} else {
|
|
272
|
-
logger.warn(`Ignoring
|
|
265
|
+
logger.warn(`Ignoring checkpoint ${checkpointNumber} due to archive root mismatch`, {
|
|
273
266
|
actual: archive,
|
|
274
267
|
expected: archiveFromChain,
|
|
275
268
|
});
|
|
276
269
|
}
|
|
277
270
|
});
|
|
278
271
|
|
|
279
|
-
return
|
|
272
|
+
return retrievedCheckpoints;
|
|
280
273
|
}
|
|
281
274
|
|
|
282
275
|
export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber: bigint): Promise<bigint> {
|
|
@@ -335,25 +328,25 @@ function extractRollupProposeCalldata(multicall3Data: Hex, rollupAddress: Hex):
|
|
|
335
328
|
}
|
|
336
329
|
|
|
337
330
|
/**
|
|
338
|
-
* Gets
|
|
339
|
-
* Assumes that the
|
|
331
|
+
* Gets checkpoint from the calldata of an L1 transaction.
|
|
332
|
+
* Assumes that the checkpoint was published from an EOA.
|
|
340
333
|
* TODO: Add retries and error management.
|
|
341
334
|
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
342
335
|
* @param txHash - Hash of the tx that published it.
|
|
343
|
-
* @param
|
|
344
|
-
* @returns
|
|
336
|
+
* @param checkpointNumber - Checkpoint number.
|
|
337
|
+
* @returns Checkpoint from the calldata, deserialized
|
|
345
338
|
*/
|
|
346
|
-
async function
|
|
339
|
+
async function getCheckpointFromRollupTx(
|
|
347
340
|
publicClient: ViemPublicClient,
|
|
348
341
|
blobSinkClient: BlobSinkClientInterface,
|
|
349
342
|
txHash: `0x${string}`,
|
|
350
343
|
blobHashes: Buffer[], // TODO(md): buffer32?
|
|
351
|
-
|
|
344
|
+
checkpointNumber: number,
|
|
352
345
|
rollupAddress: Hex,
|
|
353
346
|
targetCommitteeSize: number,
|
|
354
347
|
logger: Logger,
|
|
355
|
-
): Promise<Omit<
|
|
356
|
-
logger.trace(`Fetching
|
|
348
|
+
): Promise<Omit<RetrievedCheckpoint, 'l1' | 'chainId' | 'version'>> {
|
|
349
|
+
logger.trace(`Fetching checkpoint ${checkpointNumber} from rollup tx ${txHash}`);
|
|
357
350
|
const { input: forwarderData, blockHash } = await publicClient.getTransaction({ hash: txHash });
|
|
358
351
|
|
|
359
352
|
const rollupData = extractRollupProposeCalldata(forwarderData, rollupAddress);
|
|
@@ -369,7 +362,6 @@ async function getBlockFromRollupTx(
|
|
|
369
362
|
const [decodedArgs, packedAttestations, _signers, _blobInput] = rollupArgs! as readonly [
|
|
370
363
|
{
|
|
371
364
|
archive: Hex;
|
|
372
|
-
stateReference: ViemStateReference;
|
|
373
365
|
oracleInput: {
|
|
374
366
|
feeAssetPriceModifier: bigint;
|
|
375
367
|
};
|
|
@@ -385,9 +377,8 @@ async function getBlockFromRollupTx(
|
|
|
385
377
|
const attestations = CommitteeAttestation.fromPacked(packedAttestations, targetCommitteeSize);
|
|
386
378
|
|
|
387
379
|
logger.trace(`Recovered propose calldata from tx ${txHash}`, {
|
|
388
|
-
|
|
380
|
+
checkpointNumber,
|
|
389
381
|
archive: decodedArgs.archive,
|
|
390
|
-
stateReference: decodedArgs.stateReference,
|
|
391
382
|
header: decodedArgs.header,
|
|
392
383
|
l1BlockHash: blockHash,
|
|
393
384
|
blobHashes,
|
|
@@ -399,7 +390,7 @@ async function getBlockFromRollupTx(
|
|
|
399
390
|
const header = CheckpointHeader.fromViem(decodedArgs.header);
|
|
400
391
|
const blobBodies = await blobSinkClient.getBlobSidecar(blockHash, blobHashes);
|
|
401
392
|
if (blobBodies.length === 0) {
|
|
402
|
-
throw new NoBlobBodiesFoundError(
|
|
393
|
+
throw new NoBlobBodiesFoundError(checkpointNumber);
|
|
403
394
|
}
|
|
404
395
|
|
|
405
396
|
let checkpointBlobData: CheckpointBlobData;
|
|
@@ -417,11 +408,9 @@ async function getBlockFromRollupTx(
|
|
|
417
408
|
|
|
418
409
|
const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
|
|
419
410
|
|
|
420
|
-
const stateReference = StateReference.fromViem(decodedArgs.stateReference);
|
|
421
|
-
|
|
422
411
|
return {
|
|
412
|
+
checkpointNumber,
|
|
423
413
|
archiveRoot,
|
|
424
|
-
stateReference,
|
|
425
414
|
header,
|
|
426
415
|
checkpointBlobData,
|
|
427
416
|
attestations,
|
|
@@ -492,7 +481,7 @@ export async function retrieveL2ProofVerifiedEvents(
|
|
|
492
481
|
rollupAddress: EthAddress,
|
|
493
482
|
searchStartBlock: bigint,
|
|
494
483
|
searchEndBlock?: bigint,
|
|
495
|
-
): Promise<{ l1BlockNumber: bigint;
|
|
484
|
+
): Promise<{ l1BlockNumber: bigint; checkpointNumber: number; proverId: Fr; txHash: Hex }[]> {
|
|
496
485
|
const logs = await publicClient.getLogs({
|
|
497
486
|
address: rollupAddress.toString(),
|
|
498
487
|
fromBlock: searchStartBlock,
|
|
@@ -503,7 +492,7 @@ export async function retrieveL2ProofVerifiedEvents(
|
|
|
503
492
|
|
|
504
493
|
return logs.map(log => ({
|
|
505
494
|
l1BlockNumber: log.blockNumber,
|
|
506
|
-
|
|
495
|
+
checkpointNumber: Number(log.args.checkpointNumber),
|
|
507
496
|
proverId: Fr.fromHexString(log.args.proverId),
|
|
508
497
|
txHash: log.transactionHash,
|
|
509
498
|
}));
|
|
@@ -515,14 +504,14 @@ export async function retrieveL2ProofsFromRollup(
|
|
|
515
504
|
rollupAddress: EthAddress,
|
|
516
505
|
searchStartBlock: bigint,
|
|
517
506
|
searchEndBlock?: bigint,
|
|
518
|
-
): Promise<DataRetrieval<{ proof: Proof; proverId: Fr;
|
|
507
|
+
): Promise<DataRetrieval<{ proof: Proof; proverId: Fr; checkpointNumber: number; txHash: `0x${string}` }>> {
|
|
519
508
|
const logs = await retrieveL2ProofVerifiedEvents(publicClient, rollupAddress, searchStartBlock, searchEndBlock);
|
|
520
|
-
const retrievedData: { proof: Proof; proverId: Fr;
|
|
509
|
+
const retrievedData: { proof: Proof; proverId: Fr; checkpointNumber: number; txHash: `0x${string}` }[] = [];
|
|
521
510
|
const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n;
|
|
522
511
|
|
|
523
|
-
for (const { txHash, proverId,
|
|
512
|
+
for (const { txHash, proverId, checkpointNumber } of logs) {
|
|
524
513
|
const proofData = await getProofFromSubmitProofTx(publicClient, txHash, proverId);
|
|
525
|
-
retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId,
|
|
514
|
+
retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, checkpointNumber, txHash });
|
|
526
515
|
}
|
|
527
516
|
return {
|
|
528
517
|
retrievedData,
|
|
@@ -530,26 +519,26 @@ export async function retrieveL2ProofsFromRollup(
|
|
|
530
519
|
};
|
|
531
520
|
}
|
|
532
521
|
|
|
533
|
-
export type
|
|
522
|
+
export type SubmitEpochProof = {
|
|
534
523
|
archiveRoot: Fr;
|
|
535
524
|
proverId: Fr;
|
|
536
525
|
proof: Proof;
|
|
537
526
|
};
|
|
538
527
|
|
|
539
528
|
/**
|
|
540
|
-
* Gets
|
|
529
|
+
* Gets epoch proof metadata (archive root and proof) from the calldata of an L1 transaction.
|
|
541
530
|
* Assumes that the block was published from an EOA.
|
|
542
531
|
* TODO: Add retries and error management.
|
|
543
532
|
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
544
533
|
* @param txHash - Hash of the tx that published it.
|
|
545
|
-
* @param
|
|
546
|
-
* @returns
|
|
534
|
+
* @param expectedProverId - Expected prover ID.
|
|
535
|
+
* @returns Epoch proof metadata from the calldata, deserialized.
|
|
547
536
|
*/
|
|
548
537
|
export async function getProofFromSubmitProofTx(
|
|
549
538
|
publicClient: ViemPublicClient,
|
|
550
539
|
txHash: `0x${string}`,
|
|
551
540
|
expectedProverId: Fr,
|
|
552
|
-
): Promise<
|
|
541
|
+
): Promise<SubmitEpochProof> {
|
|
553
542
|
const { input: data } = await publicClient.getTransaction({ hash: txHash });
|
|
554
543
|
const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data });
|
|
555
544
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
|
-
import type {
|
|
2
|
+
import type { L2BlockNew } from '@aztec/stdlib/block';
|
|
3
3
|
import {
|
|
4
4
|
Attributes,
|
|
5
5
|
type Gauge,
|
|
@@ -139,7 +139,7 @@ export class ArchiverInstrumentation {
|
|
|
139
139
|
return this.telemetry.isEnabled();
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
public processNewBlocks(syncTimePerBlock: number, blocks:
|
|
142
|
+
public processNewBlocks(syncTimePerBlock: number, blocks: L2BlockNew[]) {
|
|
143
143
|
this.syncDurationPerBlock.record(Math.ceil(syncTimePerBlock));
|
|
144
144
|
this.blockHeight.record(Math.max(...blocks.map(b => b.number)));
|
|
145
145
|
this.syncBlockCount.add(blocks.length);
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export type { PublishedL2Block
|
|
1
|
+
export type { PublishedL2Block } from '@aztec/stdlib/block';
|
|
2
|
+
export type { L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
@@ -1,45 +1,63 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import { EpochNumber } from '@aztec/foundation/branded-types';
|
|
2
3
|
import { compactArray } from '@aztec/foundation/collection';
|
|
3
4
|
import type { Logger } from '@aztec/foundation/log';
|
|
4
5
|
import {
|
|
5
|
-
type
|
|
6
|
+
type AttestationInfo,
|
|
6
7
|
type ValidateBlockNegativeResult,
|
|
7
8
|
type ValidateBlockResult,
|
|
8
|
-
|
|
9
|
+
getAttestationInfoFromPayload,
|
|
9
10
|
} from '@aztec/stdlib/block';
|
|
11
|
+
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
10
12
|
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
13
|
+
import { ConsensusPayload } from '@aztec/stdlib/p2p';
|
|
11
14
|
|
|
12
15
|
export type { ValidateBlockResult };
|
|
13
16
|
|
|
14
17
|
/**
|
|
15
|
-
*
|
|
18
|
+
* Extracts attestation information from a published checkpoint.
|
|
19
|
+
* Returns info for each attestation, preserving array indices.
|
|
20
|
+
*/
|
|
21
|
+
export function getAttestationInfoFromPublishedCheckpoint({
|
|
22
|
+
checkpoint,
|
|
23
|
+
attestations,
|
|
24
|
+
}: PublishedCheckpoint): AttestationInfo[] {
|
|
25
|
+
const payload = ConsensusPayload.fromCheckpoint(checkpoint);
|
|
26
|
+
return getAttestationInfoFromPayload(payload, attestations);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates the attestations submitted for the given checkpoint.
|
|
16
31
|
* Returns true if the attestations are valid and sufficient, false otherwise.
|
|
17
32
|
*/
|
|
18
|
-
export async function
|
|
19
|
-
|
|
33
|
+
export async function validateCheckpointAttestations(
|
|
34
|
+
publishedCheckpoint: PublishedCheckpoint,
|
|
20
35
|
epochCache: EpochCache,
|
|
21
36
|
constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
22
37
|
logger?: Logger,
|
|
23
38
|
): Promise<ValidateBlockResult> {
|
|
24
|
-
const attestorInfos =
|
|
39
|
+
const attestorInfos = getAttestationInfoFromPublishedCheckpoint(publishedCheckpoint);
|
|
25
40
|
const attestors = compactArray(attestorInfos.map(info => ('address' in info ? info.address : undefined)));
|
|
26
|
-
const {
|
|
27
|
-
const
|
|
28
|
-
const archiveRoot =
|
|
29
|
-
const slot =
|
|
30
|
-
const epoch = getEpochAtSlot(slot, constants);
|
|
41
|
+
const { checkpoint, attestations } = publishedCheckpoint;
|
|
42
|
+
const headerHash = checkpoint.header.hash();
|
|
43
|
+
const archiveRoot = checkpoint.archive.root.toString();
|
|
44
|
+
const slot = checkpoint.header.slotNumber;
|
|
45
|
+
const epoch: EpochNumber = getEpochAtSlot(slot, constants);
|
|
31
46
|
const { committee, seed } = await epochCache.getCommitteeForEpoch(epoch);
|
|
32
|
-
const logData = {
|
|
47
|
+
const logData = { checkpointNumber: checkpoint.number, slot, epoch, headerHash, archiveRoot };
|
|
33
48
|
|
|
34
|
-
logger?.debug(`Validating attestations for
|
|
49
|
+
logger?.debug(`Validating attestations for checkpoint ${checkpoint.number} at slot ${slot} in epoch ${epoch}`, {
|
|
35
50
|
committee: (committee ?? []).map(member => member.toString()),
|
|
36
51
|
recoveredAttestors: attestorInfos,
|
|
37
|
-
postedAttestations:
|
|
52
|
+
postedAttestations: attestations.map(a => (a.address.isZero() ? a.signature : a.address).toString()),
|
|
38
53
|
...logData,
|
|
39
54
|
});
|
|
40
55
|
|
|
41
56
|
if (!committee || committee.length === 0) {
|
|
42
|
-
logger?.warn(
|
|
57
|
+
logger?.warn(
|
|
58
|
+
`No committee found for epoch ${epoch} at slot ${slot}. Accepting checkpoint without validation.`,
|
|
59
|
+
logData,
|
|
60
|
+
);
|
|
43
61
|
return { valid: true };
|
|
44
62
|
}
|
|
45
63
|
|
|
@@ -48,12 +66,12 @@ export async function validateBlockAttestations(
|
|
|
48
66
|
const failedValidationResult = <TReason extends ValidateBlockNegativeResult['reason']>(reason: TReason) => ({
|
|
49
67
|
valid: false as const,
|
|
50
68
|
reason,
|
|
51
|
-
block:
|
|
69
|
+
block: checkpoint.blocks[0].toBlockInfo(),
|
|
52
70
|
committee,
|
|
53
71
|
seed,
|
|
54
72
|
epoch,
|
|
55
73
|
attestors,
|
|
56
|
-
attestations
|
|
74
|
+
attestations,
|
|
57
75
|
});
|
|
58
76
|
|
|
59
77
|
for (let i = 0; i < attestorInfos.length; i++) {
|
|
@@ -90,7 +108,7 @@ export async function validateBlockAttestations(
|
|
|
90
108
|
|
|
91
109
|
const validAttestationCount = attestorInfos.filter(info => info.status === 'recovered-from-signature').length;
|
|
92
110
|
if (validAttestationCount < requiredAttestationCount) {
|
|
93
|
-
logger?.warn(`Insufficient attestations for
|
|
111
|
+
logger?.warn(`Insufficient attestations for checkpoint at slot ${slot}`, {
|
|
94
112
|
requiredAttestations: requiredAttestationCount,
|
|
95
113
|
actualAttestations: validAttestationCount,
|
|
96
114
|
...logData,
|
|
@@ -98,6 +116,9 @@ export async function validateBlockAttestations(
|
|
|
98
116
|
return failedValidationResult('insufficient-attestations');
|
|
99
117
|
}
|
|
100
118
|
|
|
101
|
-
logger?.debug(
|
|
119
|
+
logger?.debug(
|
|
120
|
+
`Checkpoint attestations validated successfully for checkpoint ${checkpoint.number} at slot ${slot}`,
|
|
121
|
+
logData,
|
|
122
|
+
);
|
|
102
123
|
return { valid: true };
|
|
103
124
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,4 +2,4 @@ export * from './archiver/index.js';
|
|
|
2
2
|
export * from './factory.js';
|
|
3
3
|
export * from './rpc/index.js';
|
|
4
4
|
|
|
5
|
-
export {
|
|
5
|
+
export { retrieveL2ProofVerifiedEvents } from './archiver/data_retrieval.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants';
|
|
2
2
|
import { DefaultL1ContractsConfig } from '@aztec/ethereum';
|
|
3
|
+
import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
4
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
4
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
6
|
import { Fr } from '@aztec/foundation/fields';
|
|
@@ -182,17 +183,17 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
182
183
|
return Promise.resolve(this.l2Blocks.at(typeof number === 'number' ? number - 1 : -1)?.getBlockHeader());
|
|
183
184
|
}
|
|
184
185
|
|
|
185
|
-
getBlocksForEpoch(epochNumber:
|
|
186
|
+
getBlocksForEpoch(epochNumber: EpochNumber): Promise<L2Block[]> {
|
|
186
187
|
const epochDuration = DefaultL1ContractsConfig.aztecEpochDuration;
|
|
187
188
|
const [start, end] = getSlotRangeForEpoch(epochNumber, { epochDuration });
|
|
188
189
|
const blocks = this.l2Blocks.filter(b => {
|
|
189
|
-
const slot = b.header.globalVariables.slotNumber
|
|
190
|
+
const slot = b.header.globalVariables.slotNumber;
|
|
190
191
|
return slot >= start && slot <= end;
|
|
191
192
|
});
|
|
192
193
|
return Promise.resolve(blocks);
|
|
193
194
|
}
|
|
194
195
|
|
|
195
|
-
async getBlockHeadersForEpoch(epochNumber:
|
|
196
|
+
async getBlockHeadersForEpoch(epochNumber: EpochNumber): Promise<BlockHeader[]> {
|
|
196
197
|
const blocks = await this.getBlocksForEpoch(epochNumber);
|
|
197
198
|
return blocks.map(b => b.getBlockHeader());
|
|
198
199
|
}
|
|
@@ -268,15 +269,15 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
268
269
|
};
|
|
269
270
|
}
|
|
270
271
|
|
|
271
|
-
getL2EpochNumber(): Promise<
|
|
272
|
+
getL2EpochNumber(): Promise<EpochNumber> {
|
|
272
273
|
throw new Error('Method not implemented.');
|
|
273
274
|
}
|
|
274
275
|
|
|
275
|
-
getL2SlotNumber(): Promise<
|
|
276
|
+
getL2SlotNumber(): Promise<SlotNumber> {
|
|
276
277
|
throw new Error('Method not implemented.');
|
|
277
278
|
}
|
|
278
279
|
|
|
279
|
-
isEpochComplete(_epochNumber:
|
|
280
|
+
isEpochComplete(_epochNumber: EpochNumber): Promise<boolean> {
|
|
280
281
|
throw new Error('Method not implemented.');
|
|
281
282
|
}
|
|
282
283
|
|