@aztec/archiver 0.0.1-commit.7ac86ea28 → 0.0.1-commit.7b86788
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/factory.d.ts +1 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +1 -4
- package/dest/l1/bin/retrieve-calldata.js +32 -28
- package/dest/l1/calldata_retriever.d.ts +70 -53
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +178 -260
- package/dest/l1/data_retrieval.d.ts +7 -8
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +18 -17
- package/dest/l1/spire_proposer.d.ts +5 -5
- package/dest/l1/spire_proposer.d.ts.map +1 -1
- package/dest/l1/spire_proposer.js +9 -17
- package/dest/modules/instrumentation.d.ts +12 -1
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +10 -0
- package/dest/modules/l1_synchronizer.d.ts +2 -7
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +9 -4
- package/dest/test/fake_l1_state.d.ts +3 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +42 -10
- package/package.json +13 -13
- package/src/factory.ts +0 -1
- package/src/l1/README.md +25 -68
- package/src/l1/bin/retrieve-calldata.ts +40 -27
- package/src/l1/calldata_retriever.ts +231 -383
- package/src/l1/data_retrieval.ts +20 -25
- package/src/l1/spire_proposer.ts +7 -15
- package/src/modules/instrumentation.ts +20 -0
- package/src/modules/l1_synchronizer.ts +8 -7
- package/src/test/fake_l1_state.ts +60 -10
package/src/l1/data_retrieval.ts
CHANGED
|
@@ -157,11 +157,6 @@ export async function retrieveCheckpointsFromRollup(
|
|
|
157
157
|
blobClient: BlobClientInterface,
|
|
158
158
|
searchStartBlock: bigint,
|
|
159
159
|
searchEndBlock: bigint,
|
|
160
|
-
contractAddresses: {
|
|
161
|
-
governanceProposerAddress: EthAddress;
|
|
162
|
-
slashFactoryAddress?: EthAddress;
|
|
163
|
-
slashingProposerAddress: EthAddress;
|
|
164
|
-
},
|
|
165
160
|
instrumentation: ArchiverInstrumentation,
|
|
166
161
|
logger: Logger = createLogger('archiver'),
|
|
167
162
|
isHistoricalSync: boolean = false,
|
|
@@ -205,7 +200,6 @@ export async function retrieveCheckpointsFromRollup(
|
|
|
205
200
|
blobClient,
|
|
206
201
|
checkpointProposedLogs,
|
|
207
202
|
rollupConstants,
|
|
208
|
-
contractAddresses,
|
|
209
203
|
instrumentation,
|
|
210
204
|
logger,
|
|
211
205
|
isHistoricalSync,
|
|
@@ -226,7 +220,6 @@ export async function retrieveCheckpointsFromRollup(
|
|
|
226
220
|
* @param blobClient - The blob client client for fetching blob data.
|
|
227
221
|
* @param logs - CheckpointProposed logs.
|
|
228
222
|
* @param rollupConstants - The rollup constants (chainId, version, targetCommitteeSize).
|
|
229
|
-
* @param contractAddresses - The contract addresses (governanceProposerAddress, slashFactoryAddress, slashingProposerAddress).
|
|
230
223
|
* @param instrumentation - The archiver instrumentation instance.
|
|
231
224
|
* @param logger - The logger instance.
|
|
232
225
|
* @param isHistoricalSync - Whether this is a historical sync.
|
|
@@ -239,11 +232,6 @@ async function processCheckpointProposedLogs(
|
|
|
239
232
|
blobClient: BlobClientInterface,
|
|
240
233
|
logs: CheckpointProposedLog[],
|
|
241
234
|
{ chainId, version, targetCommitteeSize }: { chainId: Fr; version: Fr; targetCommitteeSize: number },
|
|
242
|
-
contractAddresses: {
|
|
243
|
-
governanceProposerAddress: EthAddress;
|
|
244
|
-
slashFactoryAddress?: EthAddress;
|
|
245
|
-
slashingProposerAddress: EthAddress;
|
|
246
|
-
},
|
|
247
235
|
instrumentation: ArchiverInstrumentation,
|
|
248
236
|
logger: Logger,
|
|
249
237
|
isHistoricalSync: boolean,
|
|
@@ -255,7 +243,7 @@ async function processCheckpointProposedLogs(
|
|
|
255
243
|
targetCommitteeSize,
|
|
256
244
|
instrumentation,
|
|
257
245
|
logger,
|
|
258
|
-
|
|
246
|
+
EthAddress.fromString(rollup.address),
|
|
259
247
|
);
|
|
260
248
|
|
|
261
249
|
await asyncPool(10, logs, async log => {
|
|
@@ -266,10 +254,9 @@ async function processCheckpointProposedLogs(
|
|
|
266
254
|
|
|
267
255
|
// The value from the event and contract will match only if the checkpoint is in the chain.
|
|
268
256
|
if (archive.equals(archiveFromChain)) {
|
|
269
|
-
// Build expected hashes object (fields may be undefined for backwards compatibility with older events)
|
|
270
257
|
const expectedHashes = {
|
|
271
|
-
attestationsHash: log.args.attestationsHash
|
|
272
|
-
payloadDigest: log.args.payloadDigest
|
|
258
|
+
attestationsHash: log.args.attestationsHash.toString() as Hex,
|
|
259
|
+
payloadDigest: log.args.payloadDigest.toString() as Hex,
|
|
273
260
|
};
|
|
274
261
|
|
|
275
262
|
const checkpoint = await calldataRetriever.getCheckpointFromRollupTx(
|
|
@@ -278,6 +265,9 @@ async function processCheckpointProposedLogs(
|
|
|
278
265
|
checkpointNumber,
|
|
279
266
|
expectedHashes,
|
|
280
267
|
);
|
|
268
|
+
const { timestamp, parentBeaconBlockRoot } = await getL1Block(publicClient, log.l1BlockNumber);
|
|
269
|
+
const l1 = new L1PublishedData(log.l1BlockNumber, timestamp, log.l1BlockHash.toString());
|
|
270
|
+
|
|
281
271
|
const checkpointBlobData = await getCheckpointBlobDataFromBlobs(
|
|
282
272
|
blobClient,
|
|
283
273
|
checkpoint.blockHash,
|
|
@@ -285,12 +275,8 @@ async function processCheckpointProposedLogs(
|
|
|
285
275
|
checkpointNumber,
|
|
286
276
|
logger,
|
|
287
277
|
isHistoricalSync,
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const l1 = new L1PublishedData(
|
|
291
|
-
log.l1BlockNumber,
|
|
292
|
-
await getL1BlockTime(publicClient, log.l1BlockNumber),
|
|
293
|
-
log.l1BlockHash.toString(),
|
|
278
|
+
parentBeaconBlockRoot,
|
|
279
|
+
timestamp,
|
|
294
280
|
);
|
|
295
281
|
|
|
296
282
|
retrievedCheckpoints.push({ ...checkpoint, checkpointBlobData, l1, chainId, version });
|
|
@@ -311,9 +297,12 @@ async function processCheckpointProposedLogs(
|
|
|
311
297
|
return retrievedCheckpoints;
|
|
312
298
|
}
|
|
313
299
|
|
|
314
|
-
export async function
|
|
300
|
+
export async function getL1Block(
|
|
301
|
+
publicClient: ViemPublicClient,
|
|
302
|
+
blockNumber: bigint,
|
|
303
|
+
): Promise<{ timestamp: bigint; parentBeaconBlockRoot: string | undefined }> {
|
|
315
304
|
const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
|
|
316
|
-
return block.timestamp;
|
|
305
|
+
return { timestamp: block.timestamp, parentBeaconBlockRoot: block.parentBeaconBlockRoot };
|
|
317
306
|
}
|
|
318
307
|
|
|
319
308
|
export async function getCheckpointBlobDataFromBlobs(
|
|
@@ -323,8 +312,14 @@ export async function getCheckpointBlobDataFromBlobs(
|
|
|
323
312
|
checkpointNumber: CheckpointNumber,
|
|
324
313
|
logger: Logger,
|
|
325
314
|
isHistoricalSync: boolean,
|
|
315
|
+
parentBeaconBlockRoot?: string,
|
|
316
|
+
l1BlockTimestamp?: bigint,
|
|
326
317
|
): Promise<CheckpointBlobData> {
|
|
327
|
-
const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, {
|
|
318
|
+
const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, {
|
|
319
|
+
isHistoricalSync,
|
|
320
|
+
parentBeaconBlockRoot,
|
|
321
|
+
l1BlockTimestamp,
|
|
322
|
+
});
|
|
328
323
|
if (blobBodies.length === 0) {
|
|
329
324
|
throw new NoBlobBodiesFoundError(checkpointNumber);
|
|
330
325
|
}
|
package/src/l1/spire_proposer.ts
CHANGED
|
@@ -87,17 +87,17 @@ export async function verifyProxyImplementation(
|
|
|
87
87
|
/**
|
|
88
88
|
* Attempts to decode transaction as a Spire Proposer Multicall.
|
|
89
89
|
* Spire Proposer is a proxy contract that wraps multiple calls.
|
|
90
|
-
* Returns
|
|
90
|
+
* Returns all wrapped calls if validation succeeds (caller handles hash matching to find the propose call).
|
|
91
91
|
* @param tx - The transaction to decode
|
|
92
92
|
* @param publicClient - The viem public client for proxy verification
|
|
93
93
|
* @param logger - Logger instance
|
|
94
|
-
* @returns
|
|
94
|
+
* @returns Array of wrapped calls with 'to' and 'data', or undefined if not a valid Spire Proposer tx
|
|
95
95
|
*/
|
|
96
|
-
export async function
|
|
96
|
+
export async function getCallsFromSpireProposer(
|
|
97
97
|
tx: Transaction,
|
|
98
98
|
publicClient: { getStorageAt: (params: { address: Hex; slot: Hex }) => Promise<Hex | undefined> },
|
|
99
99
|
logger: Logger,
|
|
100
|
-
): Promise<{ to: Hex; data: Hex } | undefined> {
|
|
100
|
+
): Promise<{ to: Hex; data: Hex }[] | undefined> {
|
|
101
101
|
const txHash = tx.hash;
|
|
102
102
|
|
|
103
103
|
try {
|
|
@@ -141,17 +141,9 @@ export async function getCallFromSpireProposer(
|
|
|
141
141
|
|
|
142
142
|
const [calls] = spireArgs;
|
|
143
143
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const call = calls[0];
|
|
151
|
-
|
|
152
|
-
// Successfully extracted the single wrapped call
|
|
153
|
-
logger.trace(`Decoded Spire Proposer with single call to ${call.target}`, { txHash });
|
|
154
|
-
return { to: call.target, data: call.data };
|
|
144
|
+
// Return all wrapped calls (hash matching in the caller determines which is the propose call)
|
|
145
|
+
logger.trace(`Decoded Spire Proposer with ${calls.length} call(s)`, { txHash });
|
|
146
|
+
return calls.map(call => ({ to: call.target, data: call.data }));
|
|
155
147
|
} catch (err) {
|
|
156
148
|
// Any decoding error triggers fallback to trace
|
|
157
149
|
logger.warn(`Failed to decode Spire Proposer: ${err}`, { txHash });
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
3
|
import type { L2Block } from '@aztec/stdlib/block';
|
|
3
4
|
import type { CheckpointData } from '@aztec/stdlib/checkpoint';
|
|
5
|
+
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
6
|
+
import { getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
4
7
|
import {
|
|
5
8
|
Attributes,
|
|
6
9
|
type Gauge,
|
|
@@ -38,6 +41,8 @@ export class ArchiverInstrumentation {
|
|
|
38
41
|
|
|
39
42
|
private blockProposalTxTargetCount: UpDownCounter;
|
|
40
43
|
|
|
44
|
+
private checkpointL1InclusionDelay: Histogram;
|
|
45
|
+
|
|
41
46
|
private log = createLogger('archiver:instrumentation');
|
|
42
47
|
|
|
43
48
|
private constructor(
|
|
@@ -85,6 +90,8 @@ export class ArchiverInstrumentation {
|
|
|
85
90
|
},
|
|
86
91
|
);
|
|
87
92
|
|
|
93
|
+
this.checkpointL1InclusionDelay = meter.createHistogram(Metrics.ARCHIVER_CHECKPOINT_L1_INCLUSION_DELAY);
|
|
94
|
+
|
|
88
95
|
this.dbMetrics = new LmdbMetrics(
|
|
89
96
|
meter,
|
|
90
97
|
{
|
|
@@ -161,4 +168,17 @@ export class ArchiverInstrumentation {
|
|
|
161
168
|
[Attributes.L1_BLOCK_PROPOSAL_USED_TRACE]: usedTrace,
|
|
162
169
|
});
|
|
163
170
|
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Records L1 inclusion timing for a checkpoint observed on L1 (seconds into the L2 slot).
|
|
174
|
+
*/
|
|
175
|
+
public processCheckpointL1Timing(data: {
|
|
176
|
+
slotNumber: SlotNumber;
|
|
177
|
+
l1Timestamp: bigint;
|
|
178
|
+
l1Constants: Pick<L1RollupConstants, 'l1GenesisTime' | 'slotDuration'>;
|
|
179
|
+
}): void {
|
|
180
|
+
const slotStartTs = getTimestampForSlot(data.slotNumber, data.l1Constants);
|
|
181
|
+
const inclusionDelaySeconds = Number(data.l1Timestamp - slotStartTs);
|
|
182
|
+
this.checkpointL1InclusionDelay.record(inclusionDelaySeconds);
|
|
183
|
+
}
|
|
164
184
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
2
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
3
3
|
import { InboxContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
4
|
-
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
5
4
|
import type { L1BlockId } from '@aztec/ethereum/l1-types';
|
|
6
5
|
import type { ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
|
|
7
6
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
@@ -9,7 +8,6 @@ import { BlockNumber, CheckpointNumber, EpochNumber } from '@aztec/foundation/br
|
|
|
9
8
|
import { Buffer32 } from '@aztec/foundation/buffer';
|
|
10
9
|
import { pick } from '@aztec/foundation/collection';
|
|
11
10
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
12
|
-
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
13
11
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
14
12
|
import { count } from '@aztec/foundation/string';
|
|
15
13
|
import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
@@ -61,10 +59,6 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
61
59
|
private readonly debugClient: ViemPublicDebugClient,
|
|
62
60
|
private readonly rollup: RollupContract,
|
|
63
61
|
private readonly inbox: InboxContract,
|
|
64
|
-
private readonly l1Addresses: Pick<
|
|
65
|
-
L1ContractAddresses,
|
|
66
|
-
'registryAddress' | 'governanceProposerAddress' | 'slashFactoryAddress'
|
|
67
|
-
> & { slashingProposerAddress: EthAddress },
|
|
68
62
|
private readonly store: KVArchiverDataStore,
|
|
69
63
|
private config: {
|
|
70
64
|
batchSize: number;
|
|
@@ -708,7 +702,6 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
708
702
|
this.blobClient,
|
|
709
703
|
searchStartBlock, // TODO(palla/reorg): If the L2 reorg was due to an L1 reorg, we need to start search earlier
|
|
710
704
|
searchEndBlock,
|
|
711
|
-
this.l1Addresses,
|
|
712
705
|
this.instrumentation,
|
|
713
706
|
this.log,
|
|
714
707
|
!initialSyncComplete, // isHistoricalSync
|
|
@@ -803,6 +796,14 @@ export class ArchiverL1Synchronizer implements Traceable {
|
|
|
803
796
|
);
|
|
804
797
|
}
|
|
805
798
|
|
|
799
|
+
for (const published of validCheckpoints) {
|
|
800
|
+
this.instrumentation.processCheckpointL1Timing({
|
|
801
|
+
slotNumber: published.checkpoint.header.slotNumber,
|
|
802
|
+
l1Timestamp: published.l1.timestamp,
|
|
803
|
+
l1Constants: this.l1Constants,
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
|
|
806
807
|
try {
|
|
807
808
|
const updatedValidationResult =
|
|
808
809
|
rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
|
|
@@ -14,6 +14,7 @@ import { CommitteeAttestation, CommitteeAttestationsAndSigners, L2Block } from '
|
|
|
14
14
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
15
15
|
import { getSlotAtTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
16
16
|
import { InboxLeaf } from '@aztec/stdlib/messaging';
|
|
17
|
+
import { ConsensusPayload, SignatureDomainSeparator } from '@aztec/stdlib/p2p';
|
|
17
18
|
import {
|
|
18
19
|
makeAndSignCommitteeAttestationsAndSigners,
|
|
19
20
|
makeCheckpointAttestationFromCheckpoint,
|
|
@@ -22,7 +23,16 @@ import {
|
|
|
22
23
|
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
23
24
|
|
|
24
25
|
import { type MockProxy, mock } from 'jest-mock-extended';
|
|
25
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
type AbiParameter,
|
|
28
|
+
type FormattedBlock,
|
|
29
|
+
type Transaction,
|
|
30
|
+
encodeAbiParameters,
|
|
31
|
+
encodeFunctionData,
|
|
32
|
+
keccak256,
|
|
33
|
+
multicall3Abi,
|
|
34
|
+
toHex,
|
|
35
|
+
} from 'viem';
|
|
26
36
|
|
|
27
37
|
import { updateRollingHash } from '../structs/inbox_message.js';
|
|
28
38
|
|
|
@@ -87,6 +97,10 @@ type CheckpointData = {
|
|
|
87
97
|
blobHashes: `0x${string}`[];
|
|
88
98
|
blobs: Blob[];
|
|
89
99
|
signers: Secp256k1Signer[];
|
|
100
|
+
/** Hash of the packed attestations, matching what the L1 event emits. */
|
|
101
|
+
attestationsHash: Buffer32;
|
|
102
|
+
/** Payload digest, matching what the L1 event emits. */
|
|
103
|
+
payloadDigest: Buffer32;
|
|
90
104
|
/** If true, archiveAt will ignore it */
|
|
91
105
|
pruned?: boolean;
|
|
92
106
|
};
|
|
@@ -194,8 +208,8 @@ export class FakeL1State {
|
|
|
194
208
|
// Store the messages internally so they match the checkpoint's inHash
|
|
195
209
|
this.addMessages(checkpointNumber, messagesL1BlockNumber, messages);
|
|
196
210
|
|
|
197
|
-
// Create the transaction and
|
|
198
|
-
const tx = await this.makeRollupTx(checkpoint, signers);
|
|
211
|
+
// Create the transaction, blobs, and event hashes
|
|
212
|
+
const { tx, attestationsHash, payloadDigest } = await this.makeRollupTx(checkpoint, signers);
|
|
199
213
|
const blobHashes = await this.makeVersionedBlobHashes(checkpoint);
|
|
200
214
|
const blobs = await this.makeBlobsFromCheckpoint(checkpoint);
|
|
201
215
|
|
|
@@ -208,6 +222,8 @@ export class FakeL1State {
|
|
|
208
222
|
blobHashes,
|
|
209
223
|
blobs,
|
|
210
224
|
signers,
|
|
225
|
+
attestationsHash,
|
|
226
|
+
payloadDigest,
|
|
211
227
|
});
|
|
212
228
|
|
|
213
229
|
// Update last archive for auto-chaining
|
|
@@ -510,10 +526,8 @@ export class FakeL1State {
|
|
|
510
526
|
checkpointNumber: cpData.checkpointNumber,
|
|
511
527
|
archive: cpData.checkpoint.archive.root,
|
|
512
528
|
versionedBlobHashes: cpData.blobHashes.map(h => Buffer.from(h.slice(2), 'hex')),
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
payloadDigest: undefined,
|
|
516
|
-
attestationsHash: undefined,
|
|
529
|
+
attestationsHash: cpData.attestationsHash,
|
|
530
|
+
payloadDigest: cpData.payloadDigest,
|
|
517
531
|
},
|
|
518
532
|
}));
|
|
519
533
|
}
|
|
@@ -539,7 +553,10 @@ export class FakeL1State {
|
|
|
539
553
|
}));
|
|
540
554
|
}
|
|
541
555
|
|
|
542
|
-
private async makeRollupTx(
|
|
556
|
+
private async makeRollupTx(
|
|
557
|
+
checkpoint: Checkpoint,
|
|
558
|
+
signers: Secp256k1Signer[],
|
|
559
|
+
): Promise<{ tx: Transaction; attestationsHash: Buffer32; payloadDigest: Buffer32 }> {
|
|
543
560
|
const attestations = signers
|
|
544
561
|
.map(signer => makeCheckpointAttestationFromCheckpoint(checkpoint, signer))
|
|
545
562
|
.map(attestation => CommitteeAttestation.fromSignature(attestation.signature))
|
|
@@ -557,6 +574,8 @@ export class FakeL1State {
|
|
|
557
574
|
signers[0],
|
|
558
575
|
);
|
|
559
576
|
|
|
577
|
+
const packedAttestations = attestationsAndSigners.getPackedAttestations();
|
|
578
|
+
|
|
560
579
|
const rollupInput = encodeFunctionData({
|
|
561
580
|
abi: RollupAbi,
|
|
562
581
|
functionName: 'propose',
|
|
@@ -566,7 +585,7 @@ export class FakeL1State {
|
|
|
566
585
|
archive,
|
|
567
586
|
oracleInput: { feeAssetPriceModifier: 0n },
|
|
568
587
|
},
|
|
569
|
-
|
|
588
|
+
packedAttestations,
|
|
570
589
|
attestationsAndSigners.getSigners().map(signer => signer.toString()),
|
|
571
590
|
attestationsAndSignersSignature.toViemSignature(),
|
|
572
591
|
blobInput,
|
|
@@ -587,12 +606,43 @@ export class FakeL1State {
|
|
|
587
606
|
],
|
|
588
607
|
});
|
|
589
608
|
|
|
590
|
-
|
|
609
|
+
// Compute attestationsHash (same logic as CalldataRetriever)
|
|
610
|
+
const attestationsHash = Buffer32.fromString(
|
|
611
|
+
keccak256(encodeAbiParameters([this.getCommitteeAttestationsStructDef()], [packedAttestations])),
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
// Compute payloadDigest (same logic as CalldataRetriever)
|
|
615
|
+
const consensusPayload = ConsensusPayload.fromCheckpoint(checkpoint);
|
|
616
|
+
const payloadToSign = consensusPayload.getPayloadToSign(SignatureDomainSeparator.checkpointAttestation);
|
|
617
|
+
const payloadDigest = Buffer32.fromString(keccak256(payloadToSign));
|
|
618
|
+
|
|
619
|
+
const tx = {
|
|
591
620
|
input: multiCallInput,
|
|
592
621
|
hash: archive,
|
|
593
622
|
blockHash: archive,
|
|
594
623
|
to: MULTI_CALL_3_ADDRESS as `0x${string}`,
|
|
595
624
|
} as Transaction<bigint, number>;
|
|
625
|
+
|
|
626
|
+
return { tx, attestationsHash, payloadDigest };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** Extracts the CommitteeAttestations struct definition from RollupAbi for hash computation. */
|
|
630
|
+
private getCommitteeAttestationsStructDef(): AbiParameter {
|
|
631
|
+
const proposeFunction = RollupAbi.find(item => item.type === 'function' && item.name === 'propose') as
|
|
632
|
+
| { type: 'function'; name: string; inputs: readonly AbiParameter[] }
|
|
633
|
+
| undefined;
|
|
634
|
+
|
|
635
|
+
if (!proposeFunction) {
|
|
636
|
+
throw new Error('propose function not found in RollupAbi');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const attestationsParam = proposeFunction.inputs.find(param => param.name === '_attestations');
|
|
640
|
+
if (!attestationsParam) {
|
|
641
|
+
throw new Error('_attestations parameter not found in propose function');
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const tupleParam = attestationsParam as unknown as { type: 'tuple'; components?: readonly AbiParameter[] };
|
|
645
|
+
return { type: 'tuple', components: tupleParam.components || [] } as AbiParameter;
|
|
596
646
|
}
|
|
597
647
|
|
|
598
648
|
private async makeVersionedBlobHashes(checkpoint: Checkpoint): Promise<`0x${string}`[]> {
|