@aztec/archiver 4.0.0-nightly.20250907 → 4.0.0-nightly.20260107
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/README.md +27 -6
- package/dest/archiver/archiver.d.ts +127 -84
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +1128 -380
- package/dest/archiver/archiver_store.d.ts +122 -45
- 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 +2013 -343
- package/dest/archiver/config.d.ts +7 -20
- package/dest/archiver/config.d.ts.map +1 -1
- package/dest/archiver/config.js +21 -5
- package/dest/archiver/errors.d.ts +25 -1
- package/dest/archiver/errors.d.ts.map +1 -1
- package/dest/archiver/errors.js +37 -0
- package/dest/archiver/index.d.ts +2 -2
- package/dest/archiver/index.d.ts.map +1 -1
- package/dest/archiver/instrumentation.d.ts +5 -3
- package/dest/archiver/instrumentation.d.ts.map +1 -1
- package/dest/archiver/instrumentation.js +14 -0
- package/dest/archiver/kv_archiver_store/block_store.d.ts +83 -15
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +396 -73
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -2
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +2 -2
- 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 +51 -55
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +82 -46
- package/dest/archiver/kv_archiver_store/log_store.d.ts +12 -16
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +149 -84
- package/dest/archiver/kv_archiver_store/message_store.d.ts +6 -5
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +15 -14
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts +3 -0
- package/dest/archiver/l1/bin/retrieve-calldata.d.ts.map +1 -0
- package/dest/archiver/l1/bin/retrieve-calldata.js +149 -0
- package/dest/archiver/l1/calldata_retriever.d.ts +112 -0
- package/dest/archiver/l1/calldata_retriever.d.ts.map +1 -0
- package/dest/archiver/l1/calldata_retriever.js +471 -0
- package/dest/archiver/l1/data_retrieval.d.ts +90 -0
- package/dest/archiver/l1/data_retrieval.d.ts.map +1 -0
- package/dest/archiver/l1/data_retrieval.js +331 -0
- package/dest/archiver/l1/debug_tx.d.ts +19 -0
- package/dest/archiver/l1/debug_tx.d.ts.map +1 -0
- package/dest/archiver/l1/debug_tx.js +73 -0
- package/dest/archiver/l1/spire_proposer.d.ts +70 -0
- package/dest/archiver/l1/spire_proposer.d.ts.map +1 -0
- package/dest/archiver/l1/spire_proposer.js +157 -0
- package/dest/archiver/l1/trace_tx.d.ts +97 -0
- package/dest/archiver/l1/trace_tx.d.ts.map +1 -0
- package/dest/archiver/l1/trace_tx.js +91 -0
- package/dest/archiver/l1/types.d.ts +12 -0
- package/dest/archiver/l1/types.d.ts.map +1 -0
- package/dest/archiver/l1/types.js +3 -0
- package/dest/archiver/l1/validate_trace.d.ts +29 -0
- package/dest/archiver/l1/validate_trace.d.ts.map +1 -0
- package/dest/archiver/l1/validate_trace.js +150 -0
- package/dest/archiver/structs/data_retrieval.d.ts +1 -1
- package/dest/archiver/structs/inbox_message.d.ts +4 -4
- package/dest/archiver/structs/inbox_message.d.ts.map +1 -1
- package/dest/archiver/structs/inbox_message.js +6 -5
- package/dest/archiver/structs/published.d.ts +2 -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 +66 -44
- package/dest/factory.d.ts +4 -6
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +5 -4
- 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 +16 -8
- package/dest/test/mock_archiver.d.ts.map +1 -1
- package/dest/test/mock_archiver.js +19 -14
- package/dest/test/mock_l1_to_l2_message_source.d.ts +7 -6
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +10 -9
- package/dest/test/mock_l2_block_source.d.ts +31 -20
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +85 -18
- package/dest/test/mock_structs.d.ts +3 -2
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +9 -8
- package/package.json +18 -17
- package/src/archiver/archiver.ts +971 -475
- package/src/archiver/archiver_store.ts +141 -44
- package/src/archiver/archiver_store_test_suite.ts +2114 -331
- package/src/archiver/config.ts +30 -35
- package/src/archiver/errors.ts +64 -0
- package/src/archiver/index.ts +1 -1
- package/src/archiver/instrumentation.ts +19 -2
- package/src/archiver/kv_archiver_store/block_store.ts +541 -83
- package/src/archiver/kv_archiver_store/contract_class_store.ts +1 -1
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +1 -1
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +107 -67
- package/src/archiver/kv_archiver_store/log_store.ts +209 -99
- package/src/archiver/kv_archiver_store/message_store.ts +21 -18
- package/src/archiver/l1/README.md +98 -0
- package/src/archiver/l1/bin/retrieve-calldata.ts +182 -0
- package/src/archiver/l1/calldata_retriever.ts +641 -0
- package/src/archiver/l1/data_retrieval.ts +512 -0
- package/src/archiver/l1/debug_tx.ts +99 -0
- package/src/archiver/l1/spire_proposer.ts +160 -0
- package/src/archiver/l1/trace_tx.ts +128 -0
- package/src/archiver/l1/types.ts +13 -0
- package/src/archiver/l1/validate_trace.ts +211 -0
- package/src/archiver/structs/inbox_message.ts +8 -8
- package/src/archiver/structs/published.ts +1 -1
- package/src/archiver/validation.ts +86 -32
- package/src/factory.ts +6 -7
- package/src/index.ts +1 -1
- package/src/test/fixtures/debug_traceTransaction-multicall3.json +88 -0
- package/src/test/fixtures/debug_traceTransaction-multiplePropose.json +153 -0
- package/src/test/fixtures/debug_traceTransaction-proxied.json +122 -0
- package/src/test/fixtures/trace_transaction-multicall3.json +65 -0
- package/src/test/fixtures/trace_transaction-multiplePropose.json +319 -0
- package/src/test/fixtures/trace_transaction-proxied.json +128 -0
- package/src/test/fixtures/trace_transaction-randomRevert.json +216 -0
- package/src/test/mock_archiver.ts +22 -16
- package/src/test/mock_l1_to_l2_message_source.ts +10 -9
- package/src/test/mock_l2_block_source.ts +114 -27
- package/src/test/mock_structs.ts +10 -9
- package/dest/archiver/data_retrieval.d.ts +0 -78
- package/dest/archiver/data_retrieval.d.ts.map +0 -1
- package/dest/archiver/data_retrieval.js +0 -354
- package/src/archiver/data_retrieval.ts +0 -535
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import type { BlobClientInterface } from '@aztec/blob-client/client';
|
|
2
|
+
import {
|
|
3
|
+
BlobDeserializationError,
|
|
4
|
+
type CheckpointBlobData,
|
|
5
|
+
SpongeBlob,
|
|
6
|
+
decodeCheckpointBlobDataFromBlobs,
|
|
7
|
+
encodeBlockBlobData,
|
|
8
|
+
} from '@aztec/blob-lib';
|
|
9
|
+
import type { EpochProofPublicInputArgs } from '@aztec/ethereum/contracts';
|
|
10
|
+
import type { ViemClient, ViemPublicClient, ViemPublicDebugClient } from '@aztec/ethereum/types';
|
|
11
|
+
import { asyncPool } from '@aztec/foundation/async-pool';
|
|
12
|
+
import { CheckpointNumber } from '@aztec/foundation/branded-types';
|
|
13
|
+
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
14
|
+
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
15
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
16
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
17
|
+
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
18
|
+
import { Body, CommitteeAttestation, L2BlockNew } from '@aztec/stdlib/block';
|
|
19
|
+
import { Checkpoint, L1PublishedData, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
20
|
+
import { Proof } from '@aztec/stdlib/proofs';
|
|
21
|
+
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
22
|
+
import { AppendOnlyTreeSnapshot } from '@aztec/stdlib/trees';
|
|
23
|
+
import { BlockHeader, GlobalVariables, PartialStateReference, StateReference } from '@aztec/stdlib/tx';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
type GetContractEventsReturnType,
|
|
27
|
+
type GetContractReturnType,
|
|
28
|
+
type Hex,
|
|
29
|
+
decodeFunctionData,
|
|
30
|
+
getAbiItem,
|
|
31
|
+
hexToBytes,
|
|
32
|
+
} from 'viem';
|
|
33
|
+
|
|
34
|
+
import { NoBlobBodiesFoundError } from '../errors.js';
|
|
35
|
+
import type { ArchiverInstrumentation } from '../instrumentation.js';
|
|
36
|
+
import type { DataRetrieval } from '../structs/data_retrieval.js';
|
|
37
|
+
import type { InboxMessage } from '../structs/inbox_message.js';
|
|
38
|
+
import { CalldataRetriever } from './calldata_retriever.js';
|
|
39
|
+
|
|
40
|
+
export type RetrievedCheckpoint = {
|
|
41
|
+
checkpointNumber: CheckpointNumber;
|
|
42
|
+
archiveRoot: Fr;
|
|
43
|
+
header: CheckpointHeader;
|
|
44
|
+
checkpointBlobData: CheckpointBlobData;
|
|
45
|
+
l1: L1PublishedData;
|
|
46
|
+
chainId: Fr;
|
|
47
|
+
version: Fr;
|
|
48
|
+
attestations: CommitteeAttestation[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export async function retrievedToPublishedCheckpoint({
|
|
52
|
+
checkpointNumber,
|
|
53
|
+
archiveRoot,
|
|
54
|
+
header: checkpointHeader,
|
|
55
|
+
checkpointBlobData,
|
|
56
|
+
l1,
|
|
57
|
+
chainId,
|
|
58
|
+
version,
|
|
59
|
+
attestations,
|
|
60
|
+
}: RetrievedCheckpoint): Promise<PublishedCheckpoint> {
|
|
61
|
+
const { blocks: blocksBlobData } = checkpointBlobData;
|
|
62
|
+
|
|
63
|
+
// The lastArchiveRoot of a block is the new archive for the previous block.
|
|
64
|
+
const newArchiveRoots = blocksBlobData
|
|
65
|
+
.map(b => b.lastArchiveRoot)
|
|
66
|
+
.slice(1)
|
|
67
|
+
.concat([archiveRoot]);
|
|
68
|
+
|
|
69
|
+
// An error will be thrown from `decodeCheckpointBlobDataFromBlobs` if it can't read a field for the
|
|
70
|
+
// `l1ToL2MessageRoot` of the first block. So below we can safely assume it exists:
|
|
71
|
+
const l1toL2MessageTreeRoot = blocksBlobData[0].l1ToL2MessageRoot!;
|
|
72
|
+
|
|
73
|
+
const spongeBlob = SpongeBlob.init();
|
|
74
|
+
const l2Blocks: L2BlockNew[] = [];
|
|
75
|
+
for (let i = 0; i < blocksBlobData.length; i++) {
|
|
76
|
+
const blockBlobData = blocksBlobData[i];
|
|
77
|
+
const { blockEndMarker, blockEndStateField, lastArchiveRoot, noteHashRoot, nullifierRoot, publicDataRoot } =
|
|
78
|
+
blockBlobData;
|
|
79
|
+
|
|
80
|
+
const l2BlockNumber = blockEndMarker.blockNumber;
|
|
81
|
+
|
|
82
|
+
const globalVariables = GlobalVariables.from({
|
|
83
|
+
chainId,
|
|
84
|
+
version,
|
|
85
|
+
blockNumber: l2BlockNumber,
|
|
86
|
+
slotNumber: checkpointHeader.slotNumber,
|
|
87
|
+
timestamp: blockEndMarker.timestamp,
|
|
88
|
+
coinbase: checkpointHeader.coinbase,
|
|
89
|
+
feeRecipient: checkpointHeader.feeRecipient,
|
|
90
|
+
gasFees: checkpointHeader.gasFees,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const state = StateReference.from({
|
|
94
|
+
l1ToL2MessageTree: new AppendOnlyTreeSnapshot(
|
|
95
|
+
l1toL2MessageTreeRoot,
|
|
96
|
+
blockEndStateField.l1ToL2MessageNextAvailableLeafIndex,
|
|
97
|
+
),
|
|
98
|
+
partial: PartialStateReference.from({
|
|
99
|
+
noteHashTree: new AppendOnlyTreeSnapshot(noteHashRoot, blockEndStateField.noteHashNextAvailableLeafIndex),
|
|
100
|
+
nullifierTree: new AppendOnlyTreeSnapshot(nullifierRoot, blockEndStateField.nullifierNextAvailableLeafIndex),
|
|
101
|
+
publicDataTree: new AppendOnlyTreeSnapshot(publicDataRoot, blockEndStateField.publicDataNextAvailableLeafIndex),
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const body = Body.fromTxBlobData(checkpointBlobData.blocks[0].txs);
|
|
106
|
+
|
|
107
|
+
const blobFields = encodeBlockBlobData(blockBlobData);
|
|
108
|
+
await spongeBlob.absorb(blobFields);
|
|
109
|
+
|
|
110
|
+
const clonedSpongeBlob = spongeBlob.clone();
|
|
111
|
+
const spongeBlobHash = await clonedSpongeBlob.squeeze();
|
|
112
|
+
|
|
113
|
+
const header = BlockHeader.from({
|
|
114
|
+
lastArchive: new AppendOnlyTreeSnapshot(lastArchiveRoot, l2BlockNumber),
|
|
115
|
+
state,
|
|
116
|
+
spongeBlobHash,
|
|
117
|
+
globalVariables,
|
|
118
|
+
totalFees: body.txEffects.reduce((accum, txEffect) => accum.add(txEffect.transactionFee), Fr.ZERO),
|
|
119
|
+
totalManaUsed: new Fr(blockEndStateField.totalManaUsed),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const newArchive = new AppendOnlyTreeSnapshot(newArchiveRoots[i], l2BlockNumber + 1);
|
|
123
|
+
|
|
124
|
+
l2Blocks.push(new L2BlockNew(newArchive, header, body, checkpointNumber, i));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const lastBlock = l2Blocks.at(-1)!;
|
|
128
|
+
const checkpoint = Checkpoint.from({
|
|
129
|
+
archive: new AppendOnlyTreeSnapshot(archiveRoot, lastBlock.number + 1),
|
|
130
|
+
header: checkpointHeader,
|
|
131
|
+
blocks: l2Blocks,
|
|
132
|
+
number: checkpointNumber,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return PublishedCheckpoint.from({ checkpoint, l1, attestations });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Fetches new checkpoints.
|
|
140
|
+
* @param rollup - The rollup contract instance.
|
|
141
|
+
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
142
|
+
* @param debugClient - The viem debug client to use for trace/debug RPC methods (optional).
|
|
143
|
+
* @param blobClient - The blob client client for fetching blob data.
|
|
144
|
+
* @param searchStartBlock - The block number to use for starting the search.
|
|
145
|
+
* @param searchEndBlock - The highest block number that we should search up to.
|
|
146
|
+
* @param contractAddresses - The contract addresses (governanceProposerAddress, slashFactoryAddress, slashingProposerAddress).
|
|
147
|
+
* @param instrumentation - The archiver instrumentation instance.
|
|
148
|
+
* @param logger - The logger instance.
|
|
149
|
+
* @param isHistoricalSync - Whether this is a historical sync.
|
|
150
|
+
* @returns An array of retrieved checkpoints.
|
|
151
|
+
*/
|
|
152
|
+
export async function retrieveCheckpointsFromRollup(
|
|
153
|
+
rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
|
|
154
|
+
publicClient: ViemPublicClient,
|
|
155
|
+
debugClient: ViemPublicDebugClient,
|
|
156
|
+
blobClient: BlobClientInterface,
|
|
157
|
+
searchStartBlock: bigint,
|
|
158
|
+
searchEndBlock: bigint,
|
|
159
|
+
contractAddresses: {
|
|
160
|
+
governanceProposerAddress: EthAddress;
|
|
161
|
+
slashFactoryAddress?: EthAddress;
|
|
162
|
+
slashingProposerAddress: EthAddress;
|
|
163
|
+
},
|
|
164
|
+
instrumentation: ArchiverInstrumentation,
|
|
165
|
+
logger: Logger = createLogger('archiver'),
|
|
166
|
+
isHistoricalSync: boolean = false,
|
|
167
|
+
): Promise<RetrievedCheckpoint[]> {
|
|
168
|
+
const retrievedCheckpoints: RetrievedCheckpoint[] = [];
|
|
169
|
+
|
|
170
|
+
let rollupConstants: { chainId: Fr; version: Fr; targetCommitteeSize: number } | undefined;
|
|
171
|
+
|
|
172
|
+
do {
|
|
173
|
+
if (searchStartBlock > searchEndBlock) {
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
const checkpointProposedLogs = (
|
|
177
|
+
await rollup.getEvents.CheckpointProposed(
|
|
178
|
+
{},
|
|
179
|
+
{
|
|
180
|
+
fromBlock: searchStartBlock,
|
|
181
|
+
toBlock: searchEndBlock,
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
).filter(log => log.blockNumber! >= searchStartBlock && log.blockNumber! <= searchEndBlock);
|
|
185
|
+
|
|
186
|
+
if (checkpointProposedLogs.length === 0) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const lastLog = checkpointProposedLogs.at(-1)!;
|
|
191
|
+
logger.debug(
|
|
192
|
+
`Got ${checkpointProposedLogs.length} processed logs for checkpoints ${checkpointProposedLogs[0].args.checkpointNumber}-${lastLog.args.checkpointNumber} between L1 blocks ${searchStartBlock}-${searchEndBlock}`,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (rollupConstants === undefined) {
|
|
196
|
+
const [chainId, version, targetCommitteeSize] = await Promise.all([
|
|
197
|
+
publicClient.getChainId(),
|
|
198
|
+
rollup.read.getVersion(),
|
|
199
|
+
rollup.read.getTargetCommitteeSize(),
|
|
200
|
+
]);
|
|
201
|
+
rollupConstants = {
|
|
202
|
+
chainId: new Fr(chainId),
|
|
203
|
+
version: new Fr(version),
|
|
204
|
+
targetCommitteeSize: Number(targetCommitteeSize),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const newCheckpoints = await processCheckpointProposedLogs(
|
|
209
|
+
rollup,
|
|
210
|
+
publicClient,
|
|
211
|
+
debugClient,
|
|
212
|
+
blobClient,
|
|
213
|
+
checkpointProposedLogs,
|
|
214
|
+
rollupConstants,
|
|
215
|
+
contractAddresses,
|
|
216
|
+
instrumentation,
|
|
217
|
+
logger,
|
|
218
|
+
isHistoricalSync,
|
|
219
|
+
);
|
|
220
|
+
retrievedCheckpoints.push(...newCheckpoints);
|
|
221
|
+
searchStartBlock = lastLog.blockNumber! + 1n;
|
|
222
|
+
} while (searchStartBlock <= searchEndBlock);
|
|
223
|
+
|
|
224
|
+
// The asyncPool from processCheckpointProposedLogs will not necessarily return the checkpoints in order, so we sort them before returning.
|
|
225
|
+
return retrievedCheckpoints.sort((a, b) => Number(a.l1.blockNumber - b.l1.blockNumber));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Processes newly received CheckpointProposed logs.
|
|
230
|
+
* @param rollup - The rollup contract instance.
|
|
231
|
+
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
232
|
+
* @param debugClient - The viem debug client to use for trace/debug RPC methods (optional).
|
|
233
|
+
* @param blobClient - The blob client client for fetching blob data.
|
|
234
|
+
* @param logs - CheckpointProposed logs.
|
|
235
|
+
* @param rollupConstants - The rollup constants (chainId, version, targetCommitteeSize).
|
|
236
|
+
* @param contractAddresses - The contract addresses (governanceProposerAddress, slashFactoryAddress, slashingProposerAddress).
|
|
237
|
+
* @param instrumentation - The archiver instrumentation instance.
|
|
238
|
+
* @param logger - The logger instance.
|
|
239
|
+
* @param isHistoricalSync - Whether this is a historical sync.
|
|
240
|
+
* @returns An array of retrieved checkpoints.
|
|
241
|
+
*/
|
|
242
|
+
async function processCheckpointProposedLogs(
|
|
243
|
+
rollup: GetContractReturnType<typeof RollupAbi, ViemPublicClient>,
|
|
244
|
+
publicClient: ViemPublicClient,
|
|
245
|
+
debugClient: ViemPublicDebugClient,
|
|
246
|
+
blobClient: BlobClientInterface,
|
|
247
|
+
logs: GetContractEventsReturnType<typeof RollupAbi, 'CheckpointProposed'>,
|
|
248
|
+
{ chainId, version, targetCommitteeSize }: { chainId: Fr; version: Fr; targetCommitteeSize: number },
|
|
249
|
+
contractAddresses: {
|
|
250
|
+
governanceProposerAddress: EthAddress;
|
|
251
|
+
slashFactoryAddress?: EthAddress;
|
|
252
|
+
slashingProposerAddress: EthAddress;
|
|
253
|
+
},
|
|
254
|
+
instrumentation: ArchiverInstrumentation,
|
|
255
|
+
logger: Logger,
|
|
256
|
+
isHistoricalSync: boolean,
|
|
257
|
+
): Promise<RetrievedCheckpoint[]> {
|
|
258
|
+
const retrievedCheckpoints: RetrievedCheckpoint[] = [];
|
|
259
|
+
const calldataRetriever = new CalldataRetriever(
|
|
260
|
+
publicClient,
|
|
261
|
+
debugClient,
|
|
262
|
+
targetCommitteeSize,
|
|
263
|
+
instrumentation,
|
|
264
|
+
logger,
|
|
265
|
+
{ ...contractAddresses, rollupAddress: EthAddress.fromString(rollup.address) },
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
await asyncPool(10, logs, async log => {
|
|
269
|
+
const checkpointNumber = CheckpointNumber.fromBigInt(log.args.checkpointNumber!);
|
|
270
|
+
const archive = log.args.archive!;
|
|
271
|
+
const archiveFromChain = await rollup.read.archiveAt([BigInt(checkpointNumber)]);
|
|
272
|
+
const blobHashes = log.args.versionedBlobHashes!.map(blobHash => Buffer.from(blobHash.slice(2), 'hex'));
|
|
273
|
+
|
|
274
|
+
// The value from the event and contract will match only if the checkpoint is in the chain.
|
|
275
|
+
if (archive === archiveFromChain) {
|
|
276
|
+
// Build expected hashes object (fields may be undefined for backwards compatibility with older events)
|
|
277
|
+
const expectedHashes = {
|
|
278
|
+
attestationsHash: log.args.attestationsHash,
|
|
279
|
+
payloadDigest: log.args.payloadDigest,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const checkpoint = await calldataRetriever.getCheckpointFromRollupTx(
|
|
283
|
+
log.transactionHash!,
|
|
284
|
+
blobHashes,
|
|
285
|
+
checkpointNumber,
|
|
286
|
+
expectedHashes,
|
|
287
|
+
);
|
|
288
|
+
const checkpointBlobData = await getCheckpointBlobDataFromBlobs(
|
|
289
|
+
blobClient,
|
|
290
|
+
checkpoint.blockHash,
|
|
291
|
+
blobHashes,
|
|
292
|
+
checkpointNumber,
|
|
293
|
+
logger,
|
|
294
|
+
isHistoricalSync,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const l1 = new L1PublishedData(
|
|
298
|
+
log.blockNumber,
|
|
299
|
+
await getL1BlockTime(publicClient, log.blockNumber),
|
|
300
|
+
log.blockHash,
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
retrievedCheckpoints.push({ ...checkpoint, checkpointBlobData, l1, chainId, version });
|
|
304
|
+
logger.trace(`Retrieved checkpoint ${checkpointNumber} from L1 tx ${log.transactionHash}`, {
|
|
305
|
+
l1BlockNumber: log.blockNumber,
|
|
306
|
+
checkpointNumber,
|
|
307
|
+
archive: archive.toString(),
|
|
308
|
+
attestations: checkpoint.attestations,
|
|
309
|
+
});
|
|
310
|
+
} else {
|
|
311
|
+
logger.warn(`Ignoring checkpoint ${checkpointNumber} due to archive root mismatch`, {
|
|
312
|
+
actual: archive,
|
|
313
|
+
expected: archiveFromChain,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
return retrievedCheckpoints;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export async function getL1BlockTime(publicClient: ViemPublicClient, blockNumber: bigint): Promise<bigint> {
|
|
322
|
+
const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
|
|
323
|
+
return block.timestamp;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export async function getCheckpointBlobDataFromBlobs(
|
|
327
|
+
blobClient: BlobClientInterface,
|
|
328
|
+
blockHash: string,
|
|
329
|
+
blobHashes: Buffer<ArrayBufferLike>[],
|
|
330
|
+
checkpointNumber: CheckpointNumber,
|
|
331
|
+
logger: Logger,
|
|
332
|
+
isHistoricalSync: boolean,
|
|
333
|
+
): Promise<CheckpointBlobData> {
|
|
334
|
+
const blobBodies = await blobClient.getBlobSidecar(blockHash, blobHashes, { isHistoricalSync });
|
|
335
|
+
if (blobBodies.length === 0) {
|
|
336
|
+
throw new NoBlobBodiesFoundError(checkpointNumber);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let checkpointBlobData: CheckpointBlobData;
|
|
340
|
+
try {
|
|
341
|
+
// Attempt to decode the checkpoint blob data.
|
|
342
|
+
checkpointBlobData = decodeCheckpointBlobDataFromBlobs(blobBodies);
|
|
343
|
+
} catch (err: any) {
|
|
344
|
+
if (err instanceof BlobDeserializationError) {
|
|
345
|
+
logger.fatal(err.message);
|
|
346
|
+
} else {
|
|
347
|
+
logger.fatal('Unable to sync: failed to decode fetched blob, this blob was likely not created by us');
|
|
348
|
+
}
|
|
349
|
+
// Throwing an error since this is most likely caused by a bug.
|
|
350
|
+
throw err;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return checkpointBlobData;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Given an L1 to L2 message, retrieves its corresponding event from the Inbox within a specific block range. */
|
|
357
|
+
export async function retrieveL1ToL2Message(
|
|
358
|
+
inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
|
|
359
|
+
leaf: Fr,
|
|
360
|
+
fromBlock: bigint,
|
|
361
|
+
toBlock: bigint,
|
|
362
|
+
): Promise<InboxMessage | undefined> {
|
|
363
|
+
const logs = await inbox.getEvents.MessageSent({ hash: leaf.toString() }, { fromBlock, toBlock });
|
|
364
|
+
|
|
365
|
+
const messages = mapLogsInboxMessage(logs);
|
|
366
|
+
return messages.length > 0 ? messages[0] : undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Fetch L1 to L2 messages.
|
|
371
|
+
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
372
|
+
* @param inboxAddress - The address of the inbox contract to fetch messages from.
|
|
373
|
+
* @param blockUntilSynced - If true, blocks until the archiver has fully synced.
|
|
374
|
+
* @param searchStartBlock - The block number to use for starting the search.
|
|
375
|
+
* @param searchEndBlock - The highest block number that we should search up to.
|
|
376
|
+
* @returns An array of InboxLeaf and next eth block to search from.
|
|
377
|
+
*/
|
|
378
|
+
export async function retrieveL1ToL2Messages(
|
|
379
|
+
inbox: GetContractReturnType<typeof InboxAbi, ViemClient>,
|
|
380
|
+
searchStartBlock: bigint,
|
|
381
|
+
searchEndBlock: bigint,
|
|
382
|
+
): Promise<InboxMessage[]> {
|
|
383
|
+
const retrievedL1ToL2Messages: InboxMessage[] = [];
|
|
384
|
+
while (searchStartBlock <= searchEndBlock) {
|
|
385
|
+
const messageSentLogs = (
|
|
386
|
+
await inbox.getEvents.MessageSent({}, { fromBlock: searchStartBlock, toBlock: searchEndBlock })
|
|
387
|
+
).filter(log => log.blockNumber! >= searchStartBlock && log.blockNumber! <= searchEndBlock);
|
|
388
|
+
|
|
389
|
+
if (messageSentLogs.length === 0) {
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
retrievedL1ToL2Messages.push(...mapLogsInboxMessage(messageSentLogs));
|
|
394
|
+
searchStartBlock = messageSentLogs.at(-1)!.blockNumber + 1n;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return retrievedL1ToL2Messages;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function mapLogsInboxMessage(logs: GetContractEventsReturnType<typeof InboxAbi, 'MessageSent'>): InboxMessage[] {
|
|
401
|
+
return logs.map(log => {
|
|
402
|
+
const { index, hash, checkpointNumber, rollingHash } = log.args;
|
|
403
|
+
return {
|
|
404
|
+
index: index!,
|
|
405
|
+
leaf: Fr.fromHexString(hash!),
|
|
406
|
+
l1BlockNumber: log.blockNumber,
|
|
407
|
+
l1BlockHash: Buffer32.fromString(log.blockHash),
|
|
408
|
+
checkpointNumber: CheckpointNumber.fromBigInt(checkpointNumber!),
|
|
409
|
+
rollingHash: Buffer16.fromString(rollingHash!),
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** Retrieves L2ProofVerified events from the rollup contract. */
|
|
415
|
+
export async function retrieveL2ProofVerifiedEvents(
|
|
416
|
+
publicClient: ViemPublicClient,
|
|
417
|
+
rollupAddress: EthAddress,
|
|
418
|
+
searchStartBlock: bigint,
|
|
419
|
+
searchEndBlock?: bigint,
|
|
420
|
+
): Promise<{ l1BlockNumber: bigint; checkpointNumber: CheckpointNumber; proverId: Fr; txHash: Hex }[]> {
|
|
421
|
+
const logs = await publicClient.getLogs({
|
|
422
|
+
address: rollupAddress.toString(),
|
|
423
|
+
fromBlock: searchStartBlock,
|
|
424
|
+
toBlock: searchEndBlock ? searchEndBlock : undefined,
|
|
425
|
+
strict: true,
|
|
426
|
+
event: getAbiItem({ abi: RollupAbi, name: 'L2ProofVerified' }),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return logs.map(log => ({
|
|
430
|
+
l1BlockNumber: log.blockNumber,
|
|
431
|
+
checkpointNumber: CheckpointNumber.fromBigInt(log.args.checkpointNumber),
|
|
432
|
+
proverId: Fr.fromHexString(log.args.proverId),
|
|
433
|
+
txHash: log.transactionHash,
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Retrieve submitted proofs from the rollup contract */
|
|
438
|
+
export async function retrieveL2ProofsFromRollup(
|
|
439
|
+
publicClient: ViemPublicClient,
|
|
440
|
+
rollupAddress: EthAddress,
|
|
441
|
+
searchStartBlock: bigint,
|
|
442
|
+
searchEndBlock?: bigint,
|
|
443
|
+
): Promise<DataRetrieval<{ proof: Proof; proverId: Fr; checkpointNumber: number; txHash: `0x${string}` }>> {
|
|
444
|
+
const logs = await retrieveL2ProofVerifiedEvents(publicClient, rollupAddress, searchStartBlock, searchEndBlock);
|
|
445
|
+
const retrievedData: { proof: Proof; proverId: Fr; checkpointNumber: number; txHash: `0x${string}` }[] = [];
|
|
446
|
+
const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n;
|
|
447
|
+
|
|
448
|
+
for (const { txHash, proverId, checkpointNumber } of logs) {
|
|
449
|
+
const proofData = await getProofFromSubmitProofTx(publicClient, txHash, proverId);
|
|
450
|
+
retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, checkpointNumber, txHash });
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
retrievedData,
|
|
454
|
+
lastProcessedL1BlockNumber,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export type SubmitEpochProof = {
|
|
459
|
+
archiveRoot: Fr;
|
|
460
|
+
proverId: Fr;
|
|
461
|
+
proof: Proof;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Gets epoch proof metadata (archive root and proof) from the calldata of an L1 transaction.
|
|
466
|
+
* Assumes that the block was published from an EOA.
|
|
467
|
+
* TODO: Add retries and error management.
|
|
468
|
+
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
469
|
+
* @param txHash - Hash of the tx that published it.
|
|
470
|
+
* @param expectedProverId - Expected prover ID.
|
|
471
|
+
* @returns Epoch proof metadata from the calldata, deserialized.
|
|
472
|
+
*/
|
|
473
|
+
export async function getProofFromSubmitProofTx(
|
|
474
|
+
publicClient: ViemPublicClient,
|
|
475
|
+
txHash: `0x${string}`,
|
|
476
|
+
expectedProverId: Fr,
|
|
477
|
+
): Promise<SubmitEpochProof> {
|
|
478
|
+
const { input: data } = await publicClient.getTransaction({ hash: txHash });
|
|
479
|
+
const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data });
|
|
480
|
+
|
|
481
|
+
let proverId: Fr;
|
|
482
|
+
let archiveRoot: Fr;
|
|
483
|
+
let proof: Proof;
|
|
484
|
+
|
|
485
|
+
if (functionName === 'submitEpochRootProof') {
|
|
486
|
+
const [decodedArgs] = args as readonly [
|
|
487
|
+
{
|
|
488
|
+
start: bigint;
|
|
489
|
+
end: bigint;
|
|
490
|
+
args: EpochProofPublicInputArgs;
|
|
491
|
+
fees: readonly Hex[];
|
|
492
|
+
proof: Hex;
|
|
493
|
+
},
|
|
494
|
+
];
|
|
495
|
+
|
|
496
|
+
proverId = Fr.fromHexString(decodedArgs.args.proverId);
|
|
497
|
+
archiveRoot = Fr.fromHexString(decodedArgs.args.endArchive);
|
|
498
|
+
proof = Proof.fromBuffer(Buffer.from(hexToBytes(decodedArgs.proof)));
|
|
499
|
+
} else {
|
|
500
|
+
throw new Error(`Unexpected proof method called ${functionName}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!proverId.equals(expectedProverId)) {
|
|
504
|
+
throw new Error(`Prover ID mismatch: expected ${expectedProverId} but got ${proverId}`);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return {
|
|
508
|
+
proverId,
|
|
509
|
+
archiveRoot,
|
|
510
|
+
proof,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { DebugCallTrace, ViemPublicDebugClient } from '@aztec/ethereum/types';
|
|
2
|
+
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
4
|
+
import { type ZodFor, schemas } from '@aztec/foundation/schemas';
|
|
5
|
+
import { withHexPrefix } from '@aztec/foundation/string';
|
|
6
|
+
|
|
7
|
+
import type { Hex } from 'viem';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
|
|
10
|
+
import type { CallInfo } from './types.js';
|
|
11
|
+
|
|
12
|
+
/** Zod schema for validating call trace from debug_traceTransaction */
|
|
13
|
+
export const callTraceSchema: ZodFor<DebugCallTrace> = z.lazy(() =>
|
|
14
|
+
z.object({
|
|
15
|
+
from: schemas.HexStringWith0x,
|
|
16
|
+
to: schemas.HexStringWith0x.optional(),
|
|
17
|
+
type: z.string(),
|
|
18
|
+
input: schemas.HexStringWith0x.optional(),
|
|
19
|
+
output: schemas.HexStringWith0x.optional(),
|
|
20
|
+
gas: schemas.HexStringWith0x.optional(),
|
|
21
|
+
gasUsed: schemas.HexStringWith0x.optional(),
|
|
22
|
+
value: schemas.HexStringWith0x.optional(),
|
|
23
|
+
error: z.string().optional(),
|
|
24
|
+
calls: z.array(callTraceSchema).optional(),
|
|
25
|
+
}),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Traces a transaction and extracts all CALL operations to a specific contract and function selector.
|
|
30
|
+
*
|
|
31
|
+
* @param client - The Viem public client
|
|
32
|
+
* @param txHash - The transaction hash to trace
|
|
33
|
+
* @param targetAddress - The contract address to filter for
|
|
34
|
+
* @param functionSelector - The 4-byte function selector to filter for (with or without 0x prefix)
|
|
35
|
+
* @returns Array of CallInfo objects containing from, gasUsed, input, and value for matching calls
|
|
36
|
+
*/
|
|
37
|
+
export async function getSuccessfulCallsFromDebug(
|
|
38
|
+
client: ViemPublicDebugClient,
|
|
39
|
+
txHash: Hex,
|
|
40
|
+
targetAddress: EthAddress,
|
|
41
|
+
functionSelector: string,
|
|
42
|
+
logger?: Logger,
|
|
43
|
+
): Promise<CallInfo[]> {
|
|
44
|
+
// Normalize inputs for comparison
|
|
45
|
+
const normalizedTarget = targetAddress.toString().toLowerCase();
|
|
46
|
+
const normalizedSelector = withHexPrefix(functionSelector.toLowerCase());
|
|
47
|
+
|
|
48
|
+
// Call debug_traceTransaction with callTracer
|
|
49
|
+
// Using 'any' here because debug_traceTransaction is not in viem's standard RPC types
|
|
50
|
+
const rawTrace = await client.request({
|
|
51
|
+
method: 'debug_traceTransaction',
|
|
52
|
+
params: [txHash, { tracer: 'callTracer' }],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (rawTrace === null || rawTrace === undefined) {
|
|
56
|
+
throw new Error(`Failed to retrieve debug_traceTransaction for ${txHash}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
logger?.trace(`Retrieved debug_traceTransaction for ${txHash}`, { trace: rawTrace });
|
|
60
|
+
|
|
61
|
+
// Validate the response with zod
|
|
62
|
+
const trace = callTraceSchema.parse(rawTrace);
|
|
63
|
+
|
|
64
|
+
const results: CallInfo[] = [];
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Recursively traverse the call trace tree
|
|
68
|
+
*/
|
|
69
|
+
function traverseCalls(callTrace: DebugCallTrace) {
|
|
70
|
+
// Skip calls that have errors, and all its descendants
|
|
71
|
+
if (callTrace.error) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check if this is a CALL (not DELEGATECALL or STATICCALL) to the target address with matching selector
|
|
76
|
+
if (
|
|
77
|
+
callTrace.type.toUpperCase() === 'CALL' &&
|
|
78
|
+
callTrace.to?.toLowerCase() === normalizedTarget &&
|
|
79
|
+
callTrace.input?.toLowerCase().startsWith(normalizedSelector)
|
|
80
|
+
) {
|
|
81
|
+
results.push({
|
|
82
|
+
from: EthAddress.fromString(callTrace.from),
|
|
83
|
+
gasUsed: callTrace.gasUsed === undefined ? undefined : BigInt(callTrace.gasUsed),
|
|
84
|
+
input: callTrace.input,
|
|
85
|
+
value: callTrace.value ? BigInt(callTrace.value) : 0n,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Recursively process nested calls
|
|
90
|
+
for (const nestedCall of callTrace.calls ?? []) {
|
|
91
|
+
traverseCalls(nestedCall);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Start traversal from the root trace
|
|
96
|
+
traverseCalls(trace);
|
|
97
|
+
|
|
98
|
+
return results;
|
|
99
|
+
}
|