@aztec/sequencer-client 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/dest/block_builder/index.d.ts +5 -24
- package/dest/block_builder/index.d.ts.map +1 -1
- package/dest/block_builder/index.js +3 -40
- package/dest/block_builder/light.d.ts +26 -0
- package/dest/block_builder/light.d.ts.map +1 -0
- package/dest/block_builder/light.js +60 -0
- package/dest/block_builder/orchestrator.d.ts +23 -0
- package/dest/block_builder/orchestrator.d.ts.map +1 -0
- package/dest/block_builder/orchestrator.js +33 -0
- package/dest/client/sequencer-client.d.ts +2 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -3
- package/dest/config.js +8 -5
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +2 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +7 -2
- package/dest/publisher/index.d.ts +1 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -1
- package/dest/publisher/l1-publisher.d.ts +46 -34
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +259 -116
- package/dest/publisher/utils.d.ts +1 -1
- package/dest/publisher/utils.js +1 -1
- package/dest/sequencer/sequencer.d.ts +7 -3
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +62 -23
- package/dest/tx_validator/gas_validator.d.ts +1 -0
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +13 -8
- package/dest/tx_validator/phases_validator.d.ts +1 -0
- package/dest/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/tx_validator/phases_validator.js +20 -20
- package/dest/tx_validator/test_utils.js +4 -4
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +5 -4
- package/package.json +22 -19
- package/src/block_builder/index.ts +5 -49
- package/src/block_builder/light.ts +102 -0
- package/src/block_builder/orchestrator.ts +38 -0
- package/src/client/sequencer-client.ts +4 -3
- package/src/config.ts +7 -4
- package/src/global_variable_builder/global_builder.ts +1 -0
- package/src/publisher/config.ts +6 -1
- package/src/publisher/index.ts +1 -1
- package/src/publisher/l1-publisher.ts +373 -144
- package/src/publisher/utils.ts +1 -1
- package/src/sequencer/sequencer.ts +74 -26
- package/src/tx_validator/gas_validator.ts +13 -10
- package/src/tx_validator/phases_validator.ts +5 -6
- package/src/tx_validator/test_utils.ts +3 -3
- package/src/tx_validator/tx_validator_factory.ts +5 -4
|
@@ -1,39 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
ConsensusPayload,
|
|
3
|
+
type EpochProofClaim,
|
|
4
|
+
type EpochProofQuote,
|
|
5
|
+
type L2Block,
|
|
6
|
+
type TxHash,
|
|
7
|
+
getHashedSignaturePayload,
|
|
8
|
+
} from '@aztec/circuit-types';
|
|
3
9
|
import { type L1PublishBlockStats, type L1PublishProofStats } from '@aztec/circuit-types/stats';
|
|
4
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AGGREGATION_OBJECT_LENGTH,
|
|
12
|
+
ETHEREUM_SLOT_DURATION,
|
|
13
|
+
EthAddress,
|
|
14
|
+
type FeeRecipient,
|
|
15
|
+
type Header,
|
|
16
|
+
type Proof,
|
|
17
|
+
type RootRollupPublicInputs,
|
|
18
|
+
} from '@aztec/circuits.js';
|
|
5
19
|
import { createEthereumChain } from '@aztec/ethereum';
|
|
6
|
-
import {
|
|
20
|
+
import { makeTuple } from '@aztec/foundation/array';
|
|
21
|
+
import { areArraysEqual, times } from '@aztec/foundation/collection';
|
|
22
|
+
import { type Signature } from '@aztec/foundation/eth-signature';
|
|
23
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
7
24
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
8
|
-
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
25
|
+
import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
9
26
|
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
10
27
|
import { Timer } from '@aztec/foundation/timer';
|
|
11
28
|
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
12
29
|
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
13
30
|
|
|
14
31
|
import pick from 'lodash.pick';
|
|
32
|
+
import { inspect } from 'util';
|
|
15
33
|
import {
|
|
34
|
+
type BaseError,
|
|
35
|
+
type Chain,
|
|
36
|
+
type Client,
|
|
16
37
|
ContractFunctionRevertedError,
|
|
17
38
|
type GetContractReturnType,
|
|
18
39
|
type Hex,
|
|
19
40
|
type HttpTransport,
|
|
20
41
|
type PrivateKeyAccount,
|
|
42
|
+
type PublicActions,
|
|
21
43
|
type PublicClient,
|
|
44
|
+
type PublicRpcSchema,
|
|
45
|
+
type WalletActions,
|
|
22
46
|
type WalletClient,
|
|
47
|
+
type WalletRpcSchema,
|
|
23
48
|
createPublicClient,
|
|
24
49
|
createWalletClient,
|
|
25
50
|
encodeFunctionData,
|
|
51
|
+
getAbiItem,
|
|
26
52
|
getAddress,
|
|
27
53
|
getContract,
|
|
28
54
|
hexToBytes,
|
|
29
55
|
http,
|
|
56
|
+
publicActions,
|
|
30
57
|
} from 'viem';
|
|
31
58
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
32
59
|
import type * as chains from 'viem/chains';
|
|
33
60
|
|
|
34
61
|
import { type PublisherConfig, type TxSenderConfig } from './config.js';
|
|
35
62
|
import { L1PublisherMetrics } from './l1-publisher-metrics.js';
|
|
36
|
-
import {
|
|
63
|
+
import { prettyLogViemError } from './utils.js';
|
|
37
64
|
|
|
38
65
|
/**
|
|
39
66
|
* Stats for a sent transaction.
|
|
@@ -64,7 +91,7 @@ export type MinimalTransactionReceipt = {
|
|
|
64
91
|
};
|
|
65
92
|
|
|
66
93
|
/** Arguments to the process method of the rollup contract */
|
|
67
|
-
|
|
94
|
+
type L1ProcessArgs = {
|
|
68
95
|
/** The L2 block header. */
|
|
69
96
|
header: Buffer;
|
|
70
97
|
/** A root of the archive tree after the L2 block is applied. */
|
|
@@ -79,18 +106,18 @@ export type L1ProcessArgs = {
|
|
|
79
106
|
attestations?: Signature[];
|
|
80
107
|
};
|
|
81
108
|
|
|
82
|
-
/** Arguments to the
|
|
83
|
-
export type
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
109
|
+
/** Arguments to the submitEpochProof method of the rollup contract */
|
|
110
|
+
export type L1SubmitEpochProofArgs = {
|
|
111
|
+
epochSize: number;
|
|
112
|
+
previousArchive: Fr;
|
|
113
|
+
endArchive: Fr;
|
|
114
|
+
previousBlockHash: Fr;
|
|
115
|
+
endBlockHash: Fr;
|
|
116
|
+
endTimestamp: Fr;
|
|
117
|
+
outHash: Fr;
|
|
118
|
+
proverId: Fr;
|
|
119
|
+
fees: Tuple<FeeRecipient, 32>;
|
|
120
|
+
proof: Proof;
|
|
94
121
|
};
|
|
95
122
|
|
|
96
123
|
/**
|
|
@@ -112,10 +139,13 @@ export class L1Publisher {
|
|
|
112
139
|
typeof RollupAbi,
|
|
113
140
|
WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>
|
|
114
141
|
>;
|
|
142
|
+
|
|
115
143
|
private publicClient: PublicClient<HttpTransport, chains.Chain>;
|
|
144
|
+
private walletClient: WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>;
|
|
116
145
|
private account: PrivateKeyAccount;
|
|
117
146
|
|
|
118
147
|
public static PROPOSE_GAS_GUESS: bigint = 500_000n;
|
|
148
|
+
public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = 600_000n;
|
|
119
149
|
|
|
120
150
|
constructor(config: TxSenderConfig & PublisherConfig, client: TelemetryClient) {
|
|
121
151
|
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
|
|
@@ -125,7 +155,8 @@ export class L1Publisher {
|
|
|
125
155
|
const chain = createEthereumChain(rpcUrl, chainId);
|
|
126
156
|
this.account = privateKeyToAccount(publisherPrivateKey);
|
|
127
157
|
this.log.debug(`Publishing from address ${this.account.address}`);
|
|
128
|
-
|
|
158
|
+
|
|
159
|
+
this.walletClient = createWalletClient({
|
|
129
160
|
account: this.account,
|
|
130
161
|
chain: chain.chainInfo,
|
|
131
162
|
transport: http(chain.rpcUrl),
|
|
@@ -134,17 +165,35 @@ export class L1Publisher {
|
|
|
134
165
|
this.publicClient = createPublicClient({
|
|
135
166
|
chain: chain.chainInfo,
|
|
136
167
|
transport: http(chain.rpcUrl),
|
|
168
|
+
pollingInterval: config.viemPollingIntervalMS,
|
|
137
169
|
});
|
|
138
170
|
|
|
139
171
|
this.rollupContract = getContract({
|
|
140
172
|
address: getAddress(l1Contracts.rollupAddress.toString()),
|
|
141
173
|
abi: RollupAbi,
|
|
142
|
-
client: walletClient,
|
|
174
|
+
client: this.walletClient,
|
|
143
175
|
});
|
|
144
176
|
}
|
|
145
177
|
|
|
146
|
-
public getSenderAddress():
|
|
147
|
-
return
|
|
178
|
+
public getSenderAddress(): EthAddress {
|
|
179
|
+
return EthAddress.fromString(this.account.address);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public getClient(): Client<
|
|
183
|
+
HttpTransport,
|
|
184
|
+
Chain,
|
|
185
|
+
PrivateKeyAccount,
|
|
186
|
+
[...WalletRpcSchema, ...PublicRpcSchema],
|
|
187
|
+
PublicActions<HttpTransport, Chain> & WalletActions<Chain, PrivateKeyAccount>
|
|
188
|
+
> {
|
|
189
|
+
return this.walletClient.extend(publicActions);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
public getRollupContract(): GetContractReturnType<
|
|
193
|
+
typeof RollupAbi,
|
|
194
|
+
WalletClient<HttpTransport, chains.Chain, PrivateKeyAccount>
|
|
195
|
+
> {
|
|
196
|
+
return this.rollupContract;
|
|
148
197
|
}
|
|
149
198
|
|
|
150
199
|
/**
|
|
@@ -162,6 +211,60 @@ export class L1Publisher {
|
|
|
162
211
|
return [slot, blockNumber];
|
|
163
212
|
}
|
|
164
213
|
|
|
214
|
+
public async nextEpochToClaim(): Promise<bigint> {
|
|
215
|
+
return await this.rollupContract.read.nextEpochToClaim();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public async getEpochForSlotNumber(slotNumber: bigint): Promise<bigint> {
|
|
219
|
+
return await this.rollupContract.read.getEpochAtSlot([slotNumber]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
public async getEpochToProve(): Promise<bigint | undefined> {
|
|
223
|
+
try {
|
|
224
|
+
return await this.rollupContract.read.getEpochToProve();
|
|
225
|
+
} catch (err: any) {
|
|
226
|
+
// If this is a revert with Rollup__NoEpochToProve, it means there is no epoch to prove, so we return undefined
|
|
227
|
+
// See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
|
|
228
|
+
const errorName = tryGetCustomErrorName(err);
|
|
229
|
+
if (errorName === getAbiItem({ abi: RollupAbi, name: 'Rollup__NoEpochToProve' }).name) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
throw err;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
public async getProofClaim(): Promise<EpochProofClaim | undefined> {
|
|
237
|
+
const [epochToProve, basisPointFee, bondAmount, bondProviderHex, proposerClaimantHex] =
|
|
238
|
+
await this.rollupContract.read.proofClaim();
|
|
239
|
+
|
|
240
|
+
const bondProvider = EthAddress.fromString(bondProviderHex);
|
|
241
|
+
const proposerClaimant = EthAddress.fromString(proposerClaimantHex);
|
|
242
|
+
|
|
243
|
+
if (bondProvider.isZero() && proposerClaimant.isZero() && epochToProve === 0n) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
epochToProve,
|
|
249
|
+
basisPointFee,
|
|
250
|
+
bondAmount,
|
|
251
|
+
bondProvider,
|
|
252
|
+
proposerClaimant,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
public async validateProofQuote(quote: EpochProofQuote): Promise<EpochProofQuote | undefined> {
|
|
257
|
+
const args = [quote.toViemArgs()] as const;
|
|
258
|
+
try {
|
|
259
|
+
await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account });
|
|
260
|
+
} catch (err) {
|
|
261
|
+
const errorName = tryGetCustomErrorName(err);
|
|
262
|
+
this.log.verbose(`Proof quote validation failed: ${errorName}`);
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
return quote;
|
|
266
|
+
}
|
|
267
|
+
|
|
165
268
|
/**
|
|
166
269
|
* @notice Will call `validateHeader` to make sure that it is possible to propose
|
|
167
270
|
*
|
|
@@ -229,14 +332,21 @@ export class L1Publisher {
|
|
|
229
332
|
* @param block - L2 block to propose.
|
|
230
333
|
* @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
|
|
231
334
|
*/
|
|
232
|
-
public async proposeL2Block(
|
|
335
|
+
public async proposeL2Block(
|
|
336
|
+
block: L2Block,
|
|
337
|
+
attestations?: Signature[],
|
|
338
|
+
txHashes?: TxHash[],
|
|
339
|
+
proofQuote?: EpochProofQuote,
|
|
340
|
+
): Promise<boolean> {
|
|
233
341
|
const ctx = {
|
|
234
342
|
blockNumber: block.number,
|
|
235
343
|
slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
|
|
236
344
|
blockHash: block.hash().toString(),
|
|
237
345
|
};
|
|
238
346
|
|
|
239
|
-
const
|
|
347
|
+
const consensusPayload = new ConsensusPayload(block.header, block.archive.root, txHashes ?? []);
|
|
348
|
+
|
|
349
|
+
const digest = getHashedSignaturePayload(consensusPayload);
|
|
240
350
|
const proposeTxArgs = {
|
|
241
351
|
header: block.header.toBuffer(),
|
|
242
352
|
archive: block.archive.root.toBuffer(),
|
|
@@ -247,77 +357,77 @@ export class L1Publisher {
|
|
|
247
357
|
};
|
|
248
358
|
|
|
249
359
|
// Publish body and propose block (if not already published)
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// This means that we can avoid the simulation issues in later checks.
|
|
255
|
-
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
256
|
-
// make time consistency checks break.
|
|
257
|
-
await this.validateBlockForSubmission(block.header, {
|
|
258
|
-
digest,
|
|
259
|
-
signatures: attestations ?? [],
|
|
260
|
-
});
|
|
360
|
+
if (this.interrupted) {
|
|
361
|
+
this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx);
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
261
364
|
|
|
262
|
-
|
|
365
|
+
const timer = new Timer();
|
|
263
366
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
367
|
+
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
368
|
+
// This means that we can avoid the simulation issues in later checks.
|
|
369
|
+
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
370
|
+
// make time consistency checks break.
|
|
371
|
+
await this.validateBlockForSubmission(block.header, {
|
|
372
|
+
digest: digest.toBuffer(),
|
|
373
|
+
signatures: attestations ?? [],
|
|
374
|
+
});
|
|
268
375
|
|
|
269
|
-
|
|
270
|
-
if (!receipt) {
|
|
271
|
-
this.log.info(`Failed to get receipt for tx ${txHash}`, ctx);
|
|
272
|
-
return false;
|
|
273
|
-
}
|
|
376
|
+
this.log.verbose(`Submitting propose transaction`);
|
|
274
377
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const stats: L1PublishBlockStats = {
|
|
279
|
-
...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'),
|
|
280
|
-
...pick(tx!, 'calldataGas', 'calldataSize'),
|
|
281
|
-
...block.getStats(),
|
|
282
|
-
eventName: 'rollup-published-to-l1',
|
|
283
|
-
};
|
|
284
|
-
this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
285
|
-
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
378
|
+
const txHash = proofQuote
|
|
379
|
+
? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote)
|
|
380
|
+
: await this.sendProposeTx(proposeTxArgs);
|
|
286
381
|
|
|
287
|
-
|
|
288
|
-
}
|
|
382
|
+
if (!txHash) {
|
|
383
|
+
this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
289
386
|
|
|
290
|
-
|
|
387
|
+
const receipt = await this.getTransactionReceipt(txHash);
|
|
388
|
+
if (!receipt) {
|
|
389
|
+
this.log.info(`Failed to get receipt for tx ${txHash}`, ctx);
|
|
390
|
+
return false;
|
|
391
|
+
}
|
|
291
392
|
|
|
292
|
-
|
|
293
|
-
|
|
393
|
+
// Tx was mined successfully
|
|
394
|
+
if (receipt.status) {
|
|
395
|
+
const tx = await this.getTransactionStats(txHash);
|
|
396
|
+
const stats: L1PublishBlockStats = {
|
|
397
|
+
...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'),
|
|
398
|
+
...pick(tx!, 'calldataGas', 'calldataSize'),
|
|
399
|
+
...block.getStats(),
|
|
400
|
+
eventName: 'rollup-published-to-l1',
|
|
401
|
+
};
|
|
402
|
+
this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
403
|
+
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
404
|
+
|
|
405
|
+
return true;
|
|
294
406
|
}
|
|
295
407
|
|
|
296
|
-
this.
|
|
408
|
+
this.metrics.recordFailedTx('process');
|
|
409
|
+
|
|
410
|
+
this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx);
|
|
411
|
+
await this.sleepOrInterrupted();
|
|
297
412
|
return false;
|
|
298
413
|
}
|
|
299
414
|
|
|
300
|
-
public async
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
proof: Proof
|
|
306
|
-
): Promise<boolean> {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
const txArgs: L1SubmitProofArgs = {
|
|
310
|
-
header: header.toBuffer(),
|
|
311
|
-
archive: archiveRoot.toBuffer(),
|
|
312
|
-
proverId: proverId.toBuffer(),
|
|
313
|
-
aggregationObject: serializeToBuffer(aggregationObject),
|
|
314
|
-
proof: proof.withoutPublicInputs(),
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
// Process block
|
|
415
|
+
public async submitEpochProof(args: {
|
|
416
|
+
epochNumber: number;
|
|
417
|
+
fromBlock: number;
|
|
418
|
+
toBlock: number;
|
|
419
|
+
publicInputs: RootRollupPublicInputs;
|
|
420
|
+
proof: Proof;
|
|
421
|
+
}): Promise<boolean> {
|
|
422
|
+
const { epochNumber, fromBlock, toBlock } = args;
|
|
423
|
+
const ctx = { epochNumber, fromBlock, toBlock };
|
|
318
424
|
if (!this.interrupted) {
|
|
319
425
|
const timer = new Timer();
|
|
320
|
-
|
|
426
|
+
|
|
427
|
+
// Validate epoch proof range and hashes are correct before submitting
|
|
428
|
+
await this.validateEpochProofSubmission(args);
|
|
429
|
+
|
|
430
|
+
const txHash = await this.sendSubmitEpochProofTx(args);
|
|
321
431
|
if (!txHash) {
|
|
322
432
|
return false;
|
|
323
433
|
}
|
|
@@ -335,13 +445,13 @@ export class L1Publisher {
|
|
|
335
445
|
...pick(tx!, 'calldataGas', 'calldataSize'),
|
|
336
446
|
eventName: 'proof-published-to-l1',
|
|
337
447
|
};
|
|
338
|
-
this.log.info(`Published proof to L1 rollup contract`, { ...stats, ...ctx });
|
|
448
|
+
this.log.info(`Published epoch proof to L1 rollup contract`, { ...stats, ...ctx });
|
|
339
449
|
this.metrics.recordSubmitProof(timer.ms(), stats);
|
|
340
450
|
return true;
|
|
341
451
|
}
|
|
342
452
|
|
|
343
453
|
this.metrics.recordFailedTx('submitProof');
|
|
344
|
-
this.log.error(`Rollup.
|
|
454
|
+
this.log.error(`Rollup.submitEpochProof tx status failed: ${receipt.transactionHash}`, ctx);
|
|
345
455
|
await this.sleepOrInterrupted();
|
|
346
456
|
}
|
|
347
457
|
|
|
@@ -349,6 +459,63 @@ export class L1Publisher {
|
|
|
349
459
|
return false;
|
|
350
460
|
}
|
|
351
461
|
|
|
462
|
+
private async validateEpochProofSubmission(args: {
|
|
463
|
+
fromBlock: number;
|
|
464
|
+
toBlock: number;
|
|
465
|
+
publicInputs: RootRollupPublicInputs;
|
|
466
|
+
proof: Proof;
|
|
467
|
+
}) {
|
|
468
|
+
const { fromBlock, toBlock, publicInputs, proof } = args;
|
|
469
|
+
|
|
470
|
+
// Check that the block numbers match the expected epoch to be proven
|
|
471
|
+
const [pending, proven] = await this.rollupContract.read.tips();
|
|
472
|
+
if (proven !== BigInt(fromBlock) - 1n) {
|
|
473
|
+
throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as proven block is ${proven}`);
|
|
474
|
+
}
|
|
475
|
+
if (toBlock > pending) {
|
|
476
|
+
throw new Error(`Cannot submit epoch proof for ${fromBlock}-${toBlock} as pending block is ${pending}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Check the block hash and archive for the immediate block before the epoch
|
|
480
|
+
const [previousArchive, previousBlockHash] = await this.rollupContract.read.blocks([proven]);
|
|
481
|
+
if (publicInputs.previousArchive.root.toString() !== previousArchive) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Previous archive root mismatch: ${publicInputs.previousArchive.root.toString()} !== ${previousArchive}`,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
// TODO: Remove zero check once we inject the proper zero blockhash
|
|
487
|
+
if (previousBlockHash !== Fr.ZERO.toString() && publicInputs.previousBlockHash.toString() !== previousBlockHash) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Previous block hash mismatch: ${publicInputs.previousBlockHash.toString()} !== ${previousBlockHash}`,
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Check the block hash and archive for the last block in the epoch
|
|
494
|
+
const [endArchive, endBlockHash] = await this.rollupContract.read.blocks([BigInt(toBlock)]);
|
|
495
|
+
if (publicInputs.endArchive.root.toString() !== endArchive) {
|
|
496
|
+
throw new Error(`End archive root mismatch: ${publicInputs.endArchive.root.toString()} !== ${endArchive}`);
|
|
497
|
+
}
|
|
498
|
+
if (publicInputs.endBlockHash.toString() !== endBlockHash) {
|
|
499
|
+
throw new Error(`End block hash mismatch: ${publicInputs.endBlockHash.toString()} !== ${endBlockHash}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Compare the public inputs computed by the contract with the ones injected
|
|
503
|
+
const rollupPublicInputs = await this.rollupContract.read.getEpochProofPublicInputs(
|
|
504
|
+
this.getSubmitEpochProofArgs(args),
|
|
505
|
+
);
|
|
506
|
+
const aggregationObject = proof.isEmpty()
|
|
507
|
+
? times(AGGREGATION_OBJECT_LENGTH, Fr.zero)
|
|
508
|
+
: proof.extractAggregationObject();
|
|
509
|
+
const argsPublicInputs = [...publicInputs.toFields(), ...aggregationObject];
|
|
510
|
+
|
|
511
|
+
if (!areArraysEqual(rollupPublicInputs.map(Fr.fromString), argsPublicInputs, (a, b) => a.equals(b))) {
|
|
512
|
+
const fmt = (inputs: Fr[] | readonly string[]) => inputs.map(x => x.toString()).join(', ');
|
|
513
|
+
throw new Error(
|
|
514
|
+
`Root rollup public inputs mismatch:\nRollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
352
519
|
/**
|
|
353
520
|
* Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap.
|
|
354
521
|
* Be warned, the call may return false even if the tx subsequently gets successfully mined.
|
|
@@ -365,75 +532,122 @@ export class L1Publisher {
|
|
|
365
532
|
this.interrupted = false;
|
|
366
533
|
}
|
|
367
534
|
|
|
368
|
-
private async
|
|
535
|
+
private async sendSubmitEpochProofTx(args: {
|
|
536
|
+
fromBlock: number;
|
|
537
|
+
toBlock: number;
|
|
538
|
+
publicInputs: RootRollupPublicInputs;
|
|
539
|
+
proof: Proof;
|
|
540
|
+
}): Promise<string | undefined> {
|
|
369
541
|
try {
|
|
370
|
-
const
|
|
371
|
-
this.
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
] as const;
|
|
381
|
-
|
|
382
|
-
await this.rollupContract.simulate.submitBlockRootProof(args, {
|
|
383
|
-
account: this.account,
|
|
384
|
-
});
|
|
542
|
+
const proofHex: Hex = `0x${args.proof.withoutPublicInputs().toString('hex')}`;
|
|
543
|
+
const txArgs = [...this.getSubmitEpochProofArgs(args), proofHex] as const;
|
|
544
|
+
this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`);
|
|
545
|
+
await this.rollupContract.simulate.submitEpochRootProof(txArgs, { account: this.account });
|
|
546
|
+
return await this.rollupContract.write.submitEpochRootProof(txArgs, { account: this.account });
|
|
547
|
+
} catch (err) {
|
|
548
|
+
this.log.error(`Rollup submit epoch proof failed`, err);
|
|
549
|
+
return undefined;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
385
552
|
|
|
386
|
-
|
|
553
|
+
private async prepareProposeTx(encodedData: L1ProcessArgs, gasGuess: bigint) {
|
|
554
|
+
// We have to jump a few hoops because viem is not happy around estimating gas for view functions
|
|
555
|
+
const computeTxsEffectsHashGas = await this.publicClient.estimateGas({
|
|
556
|
+
to: this.rollupContract.address,
|
|
557
|
+
data: encodeFunctionData({
|
|
558
|
+
abi: this.rollupContract.abi,
|
|
559
|
+
functionName: 'computeTxsEffectsHash',
|
|
560
|
+
args: [`0x${encodedData.body.toString('hex')}`],
|
|
561
|
+
}),
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
// @note We perform this guesstimate instead of the usual `gasEstimate` since
|
|
565
|
+
// viem will use the current state to simulate against, which means that
|
|
566
|
+
// we will fail estimation in the case where we are simulating for the
|
|
567
|
+
// first ethereum block within our slot (as current time is not in the
|
|
568
|
+
// slot yet).
|
|
569
|
+
const gasGuesstimate = computeTxsEffectsHashGas + gasGuess;
|
|
570
|
+
|
|
571
|
+
const attestations = encodedData.attestations
|
|
572
|
+
? encodedData.attestations.map(attest => attest.toViemSignature())
|
|
573
|
+
: [];
|
|
574
|
+
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : [];
|
|
575
|
+
const args = [
|
|
576
|
+
`0x${encodedData.header.toString('hex')}`,
|
|
577
|
+
`0x${encodedData.archive.toString('hex')}`,
|
|
578
|
+
`0x${encodedData.blockHash.toString('hex')}`,
|
|
579
|
+
txHashes,
|
|
580
|
+
attestations,
|
|
581
|
+
`0x${encodedData.body.toString('hex')}`,
|
|
582
|
+
] as const;
|
|
583
|
+
|
|
584
|
+
return { args, gasGuesstimate };
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
private getSubmitEpochProofArgs(args: {
|
|
588
|
+
fromBlock: number;
|
|
589
|
+
toBlock: number;
|
|
590
|
+
publicInputs: RootRollupPublicInputs;
|
|
591
|
+
proof: Proof;
|
|
592
|
+
}) {
|
|
593
|
+
return [
|
|
594
|
+
BigInt(args.toBlock - args.fromBlock + 1),
|
|
595
|
+
[
|
|
596
|
+
args.publicInputs.previousArchive.root.toString(),
|
|
597
|
+
args.publicInputs.endArchive.root.toString(),
|
|
598
|
+
args.publicInputs.previousBlockHash.toString(),
|
|
599
|
+
args.publicInputs.endBlockHash.toString(),
|
|
600
|
+
args.publicInputs.endTimestamp.toString(),
|
|
601
|
+
args.publicInputs.outHash.toString(),
|
|
602
|
+
args.publicInputs.proverId.toString(),
|
|
603
|
+
],
|
|
604
|
+
makeTuple(64, i =>
|
|
605
|
+
i % 2 === 0
|
|
606
|
+
? args.publicInputs.fees[i / 2].recipient.toField().toString()
|
|
607
|
+
: args.publicInputs.fees[(i - 1) / 2].value.toString(),
|
|
608
|
+
),
|
|
609
|
+
`0x${serializeToBuffer(args.proof.extractAggregationObject()).toString('hex')}`,
|
|
610
|
+
] as const;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private async sendProposeTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
|
|
614
|
+
if (this.interrupted) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
try {
|
|
618
|
+
const { args, gasGuesstimate } = await this.prepareProposeTx(encodedData, L1Publisher.PROPOSE_GAS_GUESS);
|
|
619
|
+
|
|
620
|
+
return await this.rollupContract.write.propose(args, {
|
|
387
621
|
account: this.account,
|
|
622
|
+
gas: gasGuesstimate,
|
|
388
623
|
});
|
|
389
624
|
} catch (err) {
|
|
390
|
-
this.log
|
|
625
|
+
prettyLogViemError(err, this.log);
|
|
626
|
+
this.log.error(`Rollup publish failed`, err);
|
|
391
627
|
return undefined;
|
|
392
628
|
}
|
|
393
629
|
}
|
|
394
630
|
|
|
395
|
-
private async
|
|
396
|
-
if (
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
const attestations = encodedData.attestations
|
|
416
|
-
? encodedData.attestations.map(attest => attest.toViemSignature())
|
|
417
|
-
: [];
|
|
418
|
-
const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : [];
|
|
419
|
-
const args = [
|
|
420
|
-
`0x${encodedData.header.toString('hex')}`,
|
|
421
|
-
`0x${encodedData.archive.toString('hex')}`,
|
|
422
|
-
`0x${encodedData.blockHash.toString('hex')}`,
|
|
423
|
-
txHashes,
|
|
424
|
-
attestations,
|
|
425
|
-
`0x${encodedData.body.toString('hex')}`,
|
|
426
|
-
] as const;
|
|
427
|
-
|
|
428
|
-
return await this.rollupContract.write.propose(args, {
|
|
429
|
-
account: this.account,
|
|
430
|
-
gas: gasGuesstimate,
|
|
431
|
-
});
|
|
432
|
-
} catch (err) {
|
|
433
|
-
prettyLogVeimError(err, this.log);
|
|
434
|
-
this.log.error(`Rollup publish failed`, err);
|
|
435
|
-
return undefined;
|
|
436
|
-
}
|
|
631
|
+
private async sendProposeAndClaimTx(encodedData: L1ProcessArgs, quote: EpochProofQuote): Promise<string | undefined> {
|
|
632
|
+
if (this.interrupted) {
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
try {
|
|
636
|
+
const { args, gasGuesstimate } = await this.prepareProposeTx(
|
|
637
|
+
encodedData,
|
|
638
|
+
L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS,
|
|
639
|
+
);
|
|
640
|
+
this.log.info(`ProposeAndClaim`);
|
|
641
|
+
this.log.info(inspect(quote.payload));
|
|
642
|
+
|
|
643
|
+
return await this.rollupContract.write.proposeAndClaim([...args, quote.toViemArgs()], {
|
|
644
|
+
account: this.account,
|
|
645
|
+
gas: gasGuesstimate,
|
|
646
|
+
});
|
|
647
|
+
} catch (err) {
|
|
648
|
+
prettyLogViemError(err, this.log);
|
|
649
|
+
this.log.error(`Rollup publish failed`, err);
|
|
650
|
+
return undefined;
|
|
437
651
|
}
|
|
438
652
|
}
|
|
439
653
|
|
|
@@ -485,3 +699,18 @@ export class L1Publisher {
|
|
|
485
699
|
function getCalldataGasUsage(data: Uint8Array) {
|
|
486
700
|
return data.filter(byte => byte === 0).length * 4 + data.filter(byte => byte !== 0).length * 16;
|
|
487
701
|
}
|
|
702
|
+
|
|
703
|
+
function tryGetCustomErrorName(err: any) {
|
|
704
|
+
try {
|
|
705
|
+
// See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
|
|
706
|
+
if (err.name === 'ViemError') {
|
|
707
|
+
const baseError = err as BaseError;
|
|
708
|
+
const revertError = baseError.walk(err => (err as Error).name === 'ContractFunctionRevertedError');
|
|
709
|
+
if (revertError) {
|
|
710
|
+
return (revertError as ContractFunctionRevertedError).data?.errorName;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} catch (_e) {
|
|
714
|
+
return undefined;
|
|
715
|
+
}
|
|
716
|
+
}
|
package/src/publisher/utils.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type Logger } from '@aztec/foundation/log';
|
|
|
2
2
|
|
|
3
3
|
import { BaseError, ContractFunctionRevertedError } from 'viem';
|
|
4
4
|
|
|
5
|
-
export function
|
|
5
|
+
export function prettyLogViemError(err: any, logger: Logger) {
|
|
6
6
|
if (err instanceof BaseError) {
|
|
7
7
|
const revertError = err.walk(err => err instanceof ContractFunctionRevertedError);
|
|
8
8
|
if (revertError instanceof ContractFunctionRevertedError) {
|