@aztec/archiver 0.55.1 → 0.57.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/README.md +1 -1
- package/dest/archiver/archiver.d.ts +27 -25
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +391 -169
- package/dest/archiver/archiver_store.d.ts +47 -23
- package/dest/archiver/archiver_store.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.d.ts.map +1 -1
- package/dest/archiver/archiver_store_test_suite.js +75 -42
- package/dest/archiver/config.js +6 -6
- package/dest/archiver/data_retrieval.d.ts +32 -5
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +126 -16
- package/dest/archiver/epoch_helpers.d.ts +15 -0
- package/dest/archiver/epoch_helpers.d.ts.map +1 -0
- package/dest/archiver/epoch_helpers.js +23 -0
- package/dest/archiver/kv_archiver_store/block_store.d.ts +22 -3
- package/dest/archiver/kv_archiver_store/block_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/block_store.js +75 -12
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts +2 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_class_store.js +11 -4
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts +1 -0
- package/dest/archiver/kv_archiver_store/contract_instance_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +4 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts +31 -23
- package/dest/archiver/kv_archiver_store/kv_archiver_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/kv_archiver_store.js +65 -38
- package/dest/archiver/kv_archiver_store/log_store.d.ts +4 -5
- package/dest/archiver/kv_archiver_store/log_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/log_store.js +18 -14
- package/dest/archiver/kv_archiver_store/message_store.d.ts +2 -0
- package/dest/archiver/kv_archiver_store/message_store.d.ts.map +1 -1
- package/dest/archiver/kv_archiver_store/message_store.js +18 -8
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts +1 -0
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/l1_to_l2_message_store.js +4 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts +23 -39
- package/dest/archiver/memory_archiver_store/memory_archiver_store.d.ts.map +1 -1
- package/dest/archiver/memory_archiver_store/memory_archiver_store.js +132 -91
- package/dest/index.d.ts +0 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -2
- package/dest/test/index.d.ts +2 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +2 -0
- package/dest/test/mock_l2_block_source.d.ts +73 -0
- package/dest/test/mock_l2_block_source.d.ts.map +1 -0
- package/dest/test/mock_l2_block_source.js +134 -0
- package/package.json +15 -11
- package/src/archiver/archiver.ts +531 -248
- package/src/archiver/archiver_store.ts +53 -31
- package/src/archiver/archiver_store_test_suite.ts +93 -81
- package/src/archiver/config.ts +5 -5
- package/src/archiver/data_retrieval.ts +189 -30
- package/src/archiver/epoch_helpers.ts +26 -0
- package/src/archiver/kv_archiver_store/block_store.ts +87 -12
- package/src/archiver/kv_archiver_store/contract_class_store.ts +18 -5
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +4 -0
- package/src/archiver/kv_archiver_store/kv_archiver_store.ts +74 -47
- package/src/archiver/kv_archiver_store/log_store.ts +18 -18
- package/src/archiver/kv_archiver_store/message_store.ts +18 -5
- package/src/archiver/memory_archiver_store/l1_to_l2_message_store.ts +4 -0
- package/src/archiver/memory_archiver_store/memory_archiver_store.ts +155 -108
- package/src/index.ts +1 -2
- package/src/test/index.ts +1 -0
- package/src/test/mock_l2_block_source.ts +165 -0
- package/dest/archiver/eth_log_handlers.d.ts +0 -59
- package/dest/archiver/eth_log_handlers.d.ts.map +0 -1
- package/dest/archiver/eth_log_handlers.js +0 -155
- package/dest/archiver/kv_archiver_store/block_body_store.d.ts +0 -34
- package/dest/archiver/kv_archiver_store/block_body_store.d.ts.map +0 -1
- package/dest/archiver/kv_archiver_store/block_body_store.js +0 -65
- package/dest/archiver/kv_archiver_store/proven_store.d.ts +0 -14
- package/dest/archiver/kv_archiver_store/proven_store.d.ts.map +0 -1
- package/dest/archiver/kv_archiver_store/proven_store.js +0 -30
- package/src/archiver/eth_log_handlers.ts +0 -213
- package/src/archiver/kv_archiver_store/block_body_store.ts +0 -74
- package/src/archiver/kv_archiver_store/proven_store.ts +0 -34
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Fr,
|
|
1
|
+
import { Body, InboxLeaf, L2Block } from '@aztec/circuit-types';
|
|
2
|
+
import { AppendOnlyTreeSnapshot, Fr, Header, Proof } from '@aztec/circuits.js';
|
|
3
3
|
import { type EthAddress } from '@aztec/foundation/eth-address';
|
|
4
|
+
import { type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
4
5
|
import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
import { type Hex, type PublicClient, getAbiItem } from 'viem';
|
|
6
|
+
import { numToUInt32BE } from '@aztec/foundation/serialize';
|
|
7
|
+
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
type Chain,
|
|
11
|
+
type GetContractEventsReturnType,
|
|
12
|
+
type GetContractReturnType,
|
|
13
|
+
type Hex,
|
|
14
|
+
type HttpTransport,
|
|
15
|
+
type PublicClient,
|
|
16
|
+
decodeFunctionData,
|
|
17
|
+
getAbiItem,
|
|
18
|
+
hexToBytes,
|
|
19
|
+
} from 'viem';
|
|
20
|
+
|
|
16
21
|
import { type DataRetrieval } from './structs/data_retrieval.js';
|
|
17
|
-
import { type L1Published } from './structs/published.js';
|
|
22
|
+
import { type L1Published, type L1PublishedData } from './structs/published.js';
|
|
18
23
|
|
|
19
24
|
/**
|
|
20
25
|
* Fetches new L2 blocks.
|
|
@@ -27,12 +32,11 @@ import { type L1Published } from './structs/published.js';
|
|
|
27
32
|
* @returns An array of block; as well as the next eth block to search from.
|
|
28
33
|
*/
|
|
29
34
|
export async function retrieveBlockFromRollup(
|
|
35
|
+
rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
|
|
30
36
|
publicClient: PublicClient,
|
|
31
|
-
rollupAddress: EthAddress,
|
|
32
37
|
blockUntilSynced: boolean,
|
|
33
38
|
searchStartBlock: bigint,
|
|
34
39
|
searchEndBlock: bigint,
|
|
35
|
-
expectedNextL2BlockNum: bigint,
|
|
36
40
|
logger: DebugLogger = createDebugLogger('aztec:archiver'),
|
|
37
41
|
): Promise<L1Published<L2Block>[]> {
|
|
38
42
|
const retrievedBlocks: L1Published<L2Block>[] = [];
|
|
@@ -40,29 +44,122 @@ export async function retrieveBlockFromRollup(
|
|
|
40
44
|
if (searchStartBlock > searchEndBlock) {
|
|
41
45
|
break;
|
|
42
46
|
}
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
const l2BlockProposedLogs = await rollup.getEvents.L2BlockProposed(
|
|
48
|
+
{},
|
|
49
|
+
{
|
|
50
|
+
fromBlock: searchStartBlock,
|
|
51
|
+
toBlock: searchEndBlock + 1n,
|
|
52
|
+
},
|
|
48
53
|
);
|
|
49
|
-
|
|
54
|
+
|
|
55
|
+
if (l2BlockProposedLogs.length === 0) {
|
|
50
56
|
break;
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
const lastLog =
|
|
59
|
+
const lastLog = l2BlockProposedLogs[l2BlockProposedLogs.length - 1];
|
|
54
60
|
logger.debug(
|
|
55
|
-
`Got L2 block processed logs for ${
|
|
61
|
+
`Got L2 block processed logs for ${l2BlockProposedLogs[0].blockNumber}-${lastLog.blockNumber} between ${searchStartBlock}-${searchEndBlock} L1 blocks`,
|
|
56
62
|
);
|
|
57
63
|
|
|
58
|
-
const newBlocks = await processL2BlockProposedLogs(publicClient,
|
|
64
|
+
const newBlocks = await processL2BlockProposedLogs(rollup, publicClient, l2BlockProposedLogs, logger);
|
|
59
65
|
retrievedBlocks.push(...newBlocks);
|
|
60
66
|
searchStartBlock = lastLog.blockNumber! + 1n;
|
|
61
|
-
expectedNextL2BlockNum += BigInt(newBlocks.length);
|
|
62
67
|
} while (blockUntilSynced && searchStartBlock <= searchEndBlock);
|
|
63
68
|
return retrievedBlocks;
|
|
64
69
|
}
|
|
65
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Processes newly received L2BlockProposed logs.
|
|
73
|
+
* @param rollup - The rollup contract
|
|
74
|
+
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
75
|
+
* @param logs - L2BlockProposed logs.
|
|
76
|
+
* @returns - An array blocks.
|
|
77
|
+
*/
|
|
78
|
+
export async function processL2BlockProposedLogs(
|
|
79
|
+
rollup: GetContractReturnType<typeof RollupAbi, PublicClient<HttpTransport, Chain>>,
|
|
80
|
+
publicClient: PublicClient,
|
|
81
|
+
logs: GetContractEventsReturnType<typeof RollupAbi, 'L2BlockProposed'>,
|
|
82
|
+
logger: DebugLogger,
|
|
83
|
+
): Promise<L1Published<L2Block>[]> {
|
|
84
|
+
const retrievedBlocks: L1Published<L2Block>[] = [];
|
|
85
|
+
for (const log of logs) {
|
|
86
|
+
const l2BlockNumber = log.args.blockNumber!;
|
|
87
|
+
const archive = log.args.archive!;
|
|
88
|
+
const archiveFromChain = await rollup.read.archiveAt([l2BlockNumber]);
|
|
89
|
+
|
|
90
|
+
// The value from the event and contract will match only if the block is in the chain.
|
|
91
|
+
if (archive === archiveFromChain) {
|
|
92
|
+
// TODO: Fetch blocks from calldata in parallel
|
|
93
|
+
const block = await getBlockFromRollupTx(publicClient, log.transactionHash!, l2BlockNumber);
|
|
94
|
+
|
|
95
|
+
const l1: L1PublishedData = {
|
|
96
|
+
blockNumber: log.blockNumber,
|
|
97
|
+
blockHash: log.blockHash,
|
|
98
|
+
timestamp: await getL1BlockTime(publicClient, log.blockNumber),
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
retrievedBlocks.push({ data: block, l1 });
|
|
102
|
+
} else {
|
|
103
|
+
logger.warn(
|
|
104
|
+
`Archive mismatch matching, ignoring block ${l2BlockNumber} with archive: ${archive}, expected ${archiveFromChain}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return retrievedBlocks;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export async function getL1BlockTime(publicClient: PublicClient, blockNumber: bigint): Promise<bigint> {
|
|
113
|
+
const block = await publicClient.getBlock({ blockNumber, includeTransactions: false });
|
|
114
|
+
return block.timestamp;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Gets block from the calldata of an L1 transaction.
|
|
119
|
+
* Assumes that the block was published from an EOA.
|
|
120
|
+
* TODO: Add retries and error management.
|
|
121
|
+
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
122
|
+
* @param txHash - Hash of the tx that published it.
|
|
123
|
+
* @param l2BlockNum - L2 block number.
|
|
124
|
+
* @returns L2 block from the calldata, deserialized
|
|
125
|
+
*/
|
|
126
|
+
async function getBlockFromRollupTx(
|
|
127
|
+
publicClient: PublicClient,
|
|
128
|
+
txHash: `0x${string}`,
|
|
129
|
+
l2BlockNum: bigint,
|
|
130
|
+
): Promise<L2Block> {
|
|
131
|
+
const { input: data } = await publicClient.getTransaction({ hash: txHash });
|
|
132
|
+
const { functionName, args } = decodeFunctionData({
|
|
133
|
+
abi: RollupAbi,
|
|
134
|
+
data,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const allowedMethods = ['propose', 'proposeAndClaim'];
|
|
138
|
+
|
|
139
|
+
if (!allowedMethods.includes(functionName)) {
|
|
140
|
+
throw new Error(`Unexpected method called ${functionName}`);
|
|
141
|
+
}
|
|
142
|
+
const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex];
|
|
143
|
+
|
|
144
|
+
const header = Header.fromBuffer(Buffer.from(hexToBytes(headerHex)));
|
|
145
|
+
const blockBody = Body.fromBuffer(Buffer.from(hexToBytes(bodyHex)));
|
|
146
|
+
|
|
147
|
+
const blockNumberFromHeader = header.globalVariables.blockNumber.toBigInt();
|
|
148
|
+
|
|
149
|
+
if (blockNumberFromHeader !== l2BlockNum) {
|
|
150
|
+
throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${blockNumberFromHeader}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const archive = AppendOnlyTreeSnapshot.fromBuffer(
|
|
154
|
+
Buffer.concat([
|
|
155
|
+
Buffer.from(hexToBytes(archiveRootHex)), // L2Block.archive.root
|
|
156
|
+
numToUInt32BE(Number(l2BlockNum + 1n)), // L2Block.archive.nextAvailableLeafIndex
|
|
157
|
+
]),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
return new L2Block(archive, header, blockBody);
|
|
161
|
+
}
|
|
162
|
+
|
|
66
163
|
/**
|
|
67
164
|
* Fetch L1 to L2 messages.
|
|
68
165
|
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
@@ -73,8 +170,7 @@ export async function retrieveBlockFromRollup(
|
|
|
73
170
|
* @returns An array of InboxLeaf and next eth block to search from.
|
|
74
171
|
*/
|
|
75
172
|
export async function retrieveL1ToL2Messages(
|
|
76
|
-
|
|
77
|
-
inboxAddress: EthAddress,
|
|
173
|
+
inbox: GetContractReturnType<typeof InboxAbi, PublicClient<HttpTransport, Chain>>,
|
|
78
174
|
blockUntilSynced: boolean,
|
|
79
175
|
searchStartBlock: bigint,
|
|
80
176
|
searchEndBlock: bigint,
|
|
@@ -84,12 +180,24 @@ export async function retrieveL1ToL2Messages(
|
|
|
84
180
|
if (searchStartBlock > searchEndBlock) {
|
|
85
181
|
break;
|
|
86
182
|
}
|
|
87
|
-
|
|
183
|
+
|
|
184
|
+
const messageSentLogs = await inbox.getEvents.MessageSent(
|
|
185
|
+
{},
|
|
186
|
+
{
|
|
187
|
+
fromBlock: searchStartBlock,
|
|
188
|
+
toBlock: searchEndBlock + 1n,
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
88
192
|
if (messageSentLogs.length === 0) {
|
|
89
193
|
break;
|
|
90
194
|
}
|
|
91
|
-
|
|
92
|
-
|
|
195
|
+
|
|
196
|
+
for (const log of messageSentLogs) {
|
|
197
|
+
const { l2BlockNumber, index, hash } = log.args;
|
|
198
|
+
retrievedL1ToL2Messages.push(new InboxLeaf(l2BlockNumber!, index!, Fr.fromString(hash!)));
|
|
199
|
+
}
|
|
200
|
+
|
|
93
201
|
// handles the case when there are no new messages:
|
|
94
202
|
searchStartBlock = (messageSentLogs.findLast(msgLog => !!msgLog)?.blockNumber || searchStartBlock) + 1n;
|
|
95
203
|
} while (blockUntilSynced && searchStartBlock <= searchEndBlock);
|
|
@@ -131,7 +239,7 @@ export async function retrieveL2ProofsFromRollup(
|
|
|
131
239
|
const lastProcessedL1BlockNumber = logs.length > 0 ? logs.at(-1)!.l1BlockNumber : searchStartBlock - 1n;
|
|
132
240
|
|
|
133
241
|
for (const { txHash, proverId, l2BlockNumber } of logs) {
|
|
134
|
-
const proofData = await
|
|
242
|
+
const proofData = await getProofFromSubmitProofTx(publicClient, txHash, proverId);
|
|
135
243
|
retrievedData.push({ proof: proofData.proof, proverId: proofData.proverId, l2BlockNumber, txHash });
|
|
136
244
|
}
|
|
137
245
|
return {
|
|
@@ -139,3 +247,54 @@ export async function retrieveL2ProofsFromRollup(
|
|
|
139
247
|
lastProcessedL1BlockNumber,
|
|
140
248
|
};
|
|
141
249
|
}
|
|
250
|
+
|
|
251
|
+
export type SubmitBlockProof = {
|
|
252
|
+
archiveRoot: Fr;
|
|
253
|
+
proverId: Fr;
|
|
254
|
+
aggregationObject: Buffer;
|
|
255
|
+
proof: Proof;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Gets block metadata (header and archive snapshot) from the calldata of an L1 transaction.
|
|
260
|
+
* Assumes that the block was published from an EOA.
|
|
261
|
+
* TODO: Add retries and error management.
|
|
262
|
+
* @param publicClient - The viem public client to use for transaction retrieval.
|
|
263
|
+
* @param txHash - Hash of the tx that published it.
|
|
264
|
+
* @param l2BlockNum - L2 block number.
|
|
265
|
+
* @returns L2 block metadata (header and archive) from the calldata, deserialized
|
|
266
|
+
*/
|
|
267
|
+
export async function getProofFromSubmitProofTx(
|
|
268
|
+
publicClient: PublicClient,
|
|
269
|
+
txHash: `0x${string}`,
|
|
270
|
+
expectedProverId: Fr,
|
|
271
|
+
): Promise<SubmitBlockProof> {
|
|
272
|
+
const { input: data } = await publicClient.getTransaction({ hash: txHash });
|
|
273
|
+
const { functionName, args } = decodeFunctionData({ abi: RollupAbi, data });
|
|
274
|
+
|
|
275
|
+
let proverId: Fr;
|
|
276
|
+
let archiveRoot: Fr;
|
|
277
|
+
let aggregationObject: Buffer;
|
|
278
|
+
let proof: Proof;
|
|
279
|
+
|
|
280
|
+
if (functionName === 'submitEpochRootProof') {
|
|
281
|
+
const [_epochSize, nestedArgs, _fees, aggregationObjectHex, proofHex] = args!;
|
|
282
|
+
aggregationObject = Buffer.from(hexToBytes(aggregationObjectHex));
|
|
283
|
+
proverId = Fr.fromString(nestedArgs[6]);
|
|
284
|
+
archiveRoot = Fr.fromString(nestedArgs[1]);
|
|
285
|
+
proof = Proof.fromBuffer(Buffer.from(hexToBytes(proofHex)));
|
|
286
|
+
} else {
|
|
287
|
+
throw new Error(`Unexpected proof method called ${functionName}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (!proverId.equals(expectedProverId)) {
|
|
291
|
+
throw new Error(`Prover ID mismatch: expected ${expectedProverId} but got ${proverId}`);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
proverId,
|
|
296
|
+
aggregationObject,
|
|
297
|
+
archiveRoot,
|
|
298
|
+
proof,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION } from '@aztec/circuits.js';
|
|
2
|
+
|
|
3
|
+
/** Returns the slot number for a given timestamp. */
|
|
4
|
+
export function getSlotAtTimestamp(ts: bigint, constants: { l1GenesisTime: bigint }) {
|
|
5
|
+
return ts < constants.l1GenesisTime ? 0n : (ts - constants.l1GenesisTime) / BigInt(AZTEC_SLOT_DURATION);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Returns the epoch number for a given timestamp. */
|
|
9
|
+
export function getEpochNumberAtTimestamp(ts: bigint, constants: { l1GenesisTime: bigint }) {
|
|
10
|
+
return getSlotAtTimestamp(ts, constants) / BigInt(AZTEC_EPOCH_DURATION);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Returns the range of slots (inclusive) for a given epoch number. */
|
|
14
|
+
export function getSlotRangeForEpoch(epochNumber: bigint) {
|
|
15
|
+
const startSlot = epochNumber * BigInt(AZTEC_EPOCH_DURATION);
|
|
16
|
+
return [startSlot, startSlot + BigInt(AZTEC_EPOCH_DURATION) - 1n];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Returns the range of L1 timestamps (inclusive) for a given epoch number. */
|
|
20
|
+
export function getTimestampRangeForEpoch(epochNumber: bigint, constants: { l1GenesisTime: bigint }) {
|
|
21
|
+
const [startSlot, endSlot] = getSlotRangeForEpoch(epochNumber);
|
|
22
|
+
return [
|
|
23
|
+
constants.l1GenesisTime + startSlot * BigInt(AZTEC_SLOT_DURATION),
|
|
24
|
+
constants.l1GenesisTime + endSlot * BigInt(AZTEC_SLOT_DURATION),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
|
|
1
|
+
import { Body, L2Block, type TxEffect, type TxHash, TxReceipt } from '@aztec/circuit-types';
|
|
2
2
|
import { AppendOnlyTreeSnapshot, type AztecAddress, Header, INITIAL_L2_BLOCK_NUM } from '@aztec/circuits.js';
|
|
3
3
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
4
|
import { type AztecKVStore, type AztecMap, type AztecSingleton, type Range } from '@aztec/kv-store';
|
|
5
5
|
|
|
6
6
|
import { type L1Published, type L1PublishedData } from '../structs/published.js';
|
|
7
|
-
import { type BlockBodyStore } from './block_body_store.js';
|
|
8
7
|
|
|
9
8
|
type BlockIndexValue = [blockNumber: number, index: number];
|
|
10
9
|
|
|
@@ -20,9 +19,19 @@ type BlockStorage = {
|
|
|
20
19
|
export class BlockStore {
|
|
21
20
|
/** Map block number to block data */
|
|
22
21
|
#blocks: AztecMap<number, BlockStorage>;
|
|
22
|
+
|
|
23
|
+
/** Map block body hash to block body */
|
|
24
|
+
#blockBodies: AztecMap<string, Buffer>;
|
|
25
|
+
|
|
23
26
|
/** Stores L1 block number in which the last processed L2 block was included */
|
|
24
27
|
#lastSynchedL1Block: AztecSingleton<bigint>;
|
|
25
28
|
|
|
29
|
+
/** Stores l2 block number of the last proven block */
|
|
30
|
+
#lastProvenL2Block: AztecSingleton<number>;
|
|
31
|
+
|
|
32
|
+
/** Stores l2 epoch number of the last proven epoch */
|
|
33
|
+
#lastProvenL2Epoch: AztecSingleton<number>;
|
|
34
|
+
|
|
26
35
|
/** Index mapping transaction hash (as a string) to its location in a block */
|
|
27
36
|
#txIndex: AztecMap<string, BlockIndexValue>;
|
|
28
37
|
|
|
@@ -31,15 +40,14 @@ export class BlockStore {
|
|
|
31
40
|
|
|
32
41
|
#log = createDebugLogger('aztec:archiver:block_store');
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
constructor(private db: AztecKVStore, blockBodyStore: BlockBodyStore) {
|
|
37
|
-
this.#blockBodyStore = blockBodyStore;
|
|
38
|
-
|
|
43
|
+
constructor(private db: AztecKVStore) {
|
|
39
44
|
this.#blocks = db.openMap('archiver_blocks');
|
|
45
|
+
this.#blockBodies = db.openMap('archiver_block_bodies');
|
|
40
46
|
this.#txIndex = db.openMap('archiver_tx_index');
|
|
41
47
|
this.#contractIndex = db.openMap('archiver_contract_index');
|
|
42
48
|
this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block');
|
|
49
|
+
this.#lastProvenL2Block = db.openSingleton('archiver_last_proven_l2_block');
|
|
50
|
+
this.#lastProvenL2Epoch = db.openSingleton('archiver_last_proven_l2_epoch');
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
/**
|
|
@@ -63,6 +71,8 @@ export class BlockStore {
|
|
|
63
71
|
block.data.body.txEffects.forEach((tx, i) => {
|
|
64
72
|
void this.#txIndex.set(tx.txHash.toString(), [block.data.number, i]);
|
|
65
73
|
});
|
|
74
|
+
|
|
75
|
+
void this.#blockBodies.set(block.data.body.getTxsEffectsHash().toString('hex'), block.data.body.toBuffer());
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
void this.#lastSynchedL1Block.set(blocks[blocks.length - 1].l1.blockNumber);
|
|
@@ -71,6 +81,38 @@ export class BlockStore {
|
|
|
71
81
|
});
|
|
72
82
|
}
|
|
73
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Unwinds blocks from the database
|
|
86
|
+
* @param from - The tip of the chain, passed for verification purposes,
|
|
87
|
+
* ensuring that we don't end up deleting something we did not intend
|
|
88
|
+
* @param blocksToUnwind - The number of blocks we are to unwind
|
|
89
|
+
* @returns True if the operation is successful
|
|
90
|
+
*/
|
|
91
|
+
unwindBlocks(from: number, blocksToUnwind: number) {
|
|
92
|
+
return this.db.transaction(() => {
|
|
93
|
+
const last = this.getSynchedL2BlockNumber();
|
|
94
|
+
if (from != last) {
|
|
95
|
+
throw new Error(`Can only remove from the tip`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < blocksToUnwind; i++) {
|
|
99
|
+
const blockNumber = from - i;
|
|
100
|
+
const block = this.getBlock(blockNumber);
|
|
101
|
+
|
|
102
|
+
if (block === undefined) {
|
|
103
|
+
throw new Error(`Cannot remove block ${blockNumber} from the store, we don't have it`);
|
|
104
|
+
}
|
|
105
|
+
void this.#blocks.delete(block.data.number);
|
|
106
|
+
block.data.body.txEffects.forEach(tx => {
|
|
107
|
+
void this.#txIndex.delete(tx.txHash.toString());
|
|
108
|
+
});
|
|
109
|
+
void this.#blockBodies.delete(block.data.body.getTxsEffectsHash().toString('hex'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return true;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
74
116
|
/**
|
|
75
117
|
* Gets up to `limit` amount of L2 blocks starting from `from`.
|
|
76
118
|
* @param start - Number of the first block to return (inclusive).
|
|
@@ -97,16 +139,29 @@ export class BlockStore {
|
|
|
97
139
|
return this.getBlockFromBlockStorage(blockStorage);
|
|
98
140
|
}
|
|
99
141
|
|
|
142
|
+
/**
|
|
143
|
+
* Gets the headers for a sequence of L2 blocks.
|
|
144
|
+
* @param start - Number of the first block to return (inclusive).
|
|
145
|
+
* @param limit - The number of blocks to return.
|
|
146
|
+
* @returns The requested L2 block headers
|
|
147
|
+
*/
|
|
148
|
+
*getBlockHeaders(start: number, limit: number): IterableIterator<Header> {
|
|
149
|
+
for (const blockStorage of this.#blocks.values(this.#computeBlockRange(start, limit))) {
|
|
150
|
+
yield Header.fromBuffer(blockStorage.header);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
100
154
|
private getBlockFromBlockStorage(blockStorage: BlockStorage) {
|
|
101
155
|
const header = Header.fromBuffer(blockStorage.header);
|
|
102
156
|
const archive = AppendOnlyTreeSnapshot.fromBuffer(blockStorage.archive);
|
|
103
|
-
const body = this.#blockBodyStore.getBlockBody(header.contentCommitment.txsEffectsHash);
|
|
104
157
|
|
|
105
|
-
|
|
106
|
-
|
|
158
|
+
const blockBodyBuffer = this.#blockBodies.get(header.contentCommitment.txsEffectsHash.toString('hex'));
|
|
159
|
+
if (blockBodyBuffer === undefined) {
|
|
160
|
+
throw new Error('Body could not be retrieved');
|
|
107
161
|
}
|
|
162
|
+
const body = Body.fromBuffer(blockBodyBuffer);
|
|
108
163
|
|
|
109
|
-
const l2Block = L2Block
|
|
164
|
+
const l2Block = new L2Block(archive, header, body);
|
|
110
165
|
return { data: l2Block, l1: blockStorage.l1 };
|
|
111
166
|
}
|
|
112
167
|
|
|
@@ -184,13 +239,33 @@ export class BlockStore {
|
|
|
184
239
|
return this.#lastSynchedL1Block.get();
|
|
185
240
|
}
|
|
186
241
|
|
|
242
|
+
setSynchedL1BlockNumber(l1BlockNumber: bigint) {
|
|
243
|
+
void this.#lastSynchedL1Block.set(l1BlockNumber);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
getProvenL2BlockNumber(): number {
|
|
247
|
+
return this.#lastProvenL2Block.get() ?? 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
setProvenL2BlockNumber(blockNumber: number) {
|
|
251
|
+
void this.#lastProvenL2Block.set(blockNumber);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
getProvenL2EpochNumber(): number | undefined {
|
|
255
|
+
return this.#lastProvenL2Epoch.get();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
setProvenL2EpochNumber(epochNumber: number) {
|
|
259
|
+
void this.#lastProvenL2Epoch.set(epochNumber);
|
|
260
|
+
}
|
|
261
|
+
|
|
187
262
|
#computeBlockRange(start: number, limit: number): Required<Pick<Range<number>, 'start' | 'end'>> {
|
|
188
263
|
if (limit < 1) {
|
|
189
264
|
throw new Error(`Invalid limit: ${limit}`);
|
|
190
265
|
}
|
|
191
266
|
|
|
192
267
|
if (start < INITIAL_L2_BLOCK_NUM) {
|
|
193
|
-
start
|
|
268
|
+
throw new Error(`Invalid start: ${start}`);
|
|
194
269
|
}
|
|
195
270
|
|
|
196
271
|
const end = start + limit;
|
|
@@ -3,6 +3,7 @@ import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/s
|
|
|
3
3
|
import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
|
|
4
4
|
import {
|
|
5
5
|
type ContractClassPublic,
|
|
6
|
+
type ContractClassPublicWithBlockNumber,
|
|
6
7
|
type ExecutablePrivateFunctionWithMembershipProof,
|
|
7
8
|
type UnconstrainedFunctionWithMembershipProof,
|
|
8
9
|
} from '@aztec/types/contracts';
|
|
@@ -17,8 +18,18 @@ export class ContractClassStore {
|
|
|
17
18
|
this.#contractClasses = db.openMap('archiver_contract_classes');
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
addContractClass(contractClass: ContractClassPublic): Promise<void> {
|
|
21
|
-
|
|
21
|
+
async addContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
|
|
22
|
+
await this.#contractClasses.setIfNotExists(
|
|
23
|
+
contractClass.id.toString(),
|
|
24
|
+
serializeContractClassPublic({ ...contractClass, l2BlockNumber: blockNumber }),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async deleteContractClasses(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
|
|
29
|
+
const restoredContractClass = this.#contractClasses.get(contractClass.id.toString());
|
|
30
|
+
if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
|
|
31
|
+
await this.#contractClasses.delete(contractClass.id.toString());
|
|
32
|
+
}
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
getContractClass(id: Fr): ContractClassPublic | undefined {
|
|
@@ -44,7 +55,7 @@ export class ContractClassStore {
|
|
|
44
55
|
const existingClass = deserializeContractClassPublic(existingClassBuffer);
|
|
45
56
|
const { privateFunctions: existingPrivateFns, unconstrainedFunctions: existingUnconstrainedFns } = existingClass;
|
|
46
57
|
|
|
47
|
-
const updatedClass: Omit<
|
|
58
|
+
const updatedClass: Omit<ContractClassPublicWithBlockNumber, 'id'> = {
|
|
48
59
|
...existingClass,
|
|
49
60
|
privateFunctions: [
|
|
50
61
|
...existingPrivateFns,
|
|
@@ -63,8 +74,9 @@ export class ContractClassStore {
|
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
|
|
66
|
-
function serializeContractClassPublic(contractClass: Omit<
|
|
77
|
+
function serializeContractClassPublic(contractClass: Omit<ContractClassPublicWithBlockNumber, 'id'>): Buffer {
|
|
67
78
|
return serializeToBuffer(
|
|
79
|
+
contractClass.l2BlockNumber,
|
|
68
80
|
numToUInt8(contractClass.version),
|
|
69
81
|
contractClass.artifactHash,
|
|
70
82
|
contractClass.publicFunctions.length,
|
|
@@ -108,9 +120,10 @@ function serializeUnconstrainedFunction(fn: UnconstrainedFunctionWithMembershipP
|
|
|
108
120
|
);
|
|
109
121
|
}
|
|
110
122
|
|
|
111
|
-
function deserializeContractClassPublic(buffer: Buffer): Omit<
|
|
123
|
+
function deserializeContractClassPublic(buffer: Buffer): Omit<ContractClassPublicWithBlockNumber, 'id'> {
|
|
112
124
|
const reader = BufferReader.asReader(buffer);
|
|
113
125
|
return {
|
|
126
|
+
l2BlockNumber: reader.readNumber(),
|
|
114
127
|
version: reader.readUInt8() as 1,
|
|
115
128
|
artifactHash: reader.readObject(Fr),
|
|
116
129
|
publicFunctions: reader.readVector({
|
|
@@ -19,6 +19,10 @@ export class ContractInstanceStore {
|
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
|
|
23
|
+
return this.#contractInstances.delete(contractInstance.address.toString());
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
getContractInstance(address: AztecAddress): ContractInstanceWithAddress | undefined {
|
|
23
27
|
const contractInstance = this.#contractInstances.get(address.toString());
|
|
24
28
|
return contractInstance && SerializableContractInstance.fromBuffer(contractInstance).withAddress(address);
|