@aztec/sequencer-client 0.50.1 → 0.51.1
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/global_variable_builder/global_builder.d.ts +2 -1
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +9 -7
- package/dest/publisher/l1-publisher.d.ts +22 -23
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +124 -67
- package/dest/sequencer/sequencer.d.ts +9 -1
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +120 -66
- package/package.json +19 -18
- package/src/global_variable_builder/global_builder.ts +8 -5
- package/src/publisher/l1-publisher.ts +147 -76
- package/src/sequencer/sequencer.ts +169 -88
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type L2Block, type Signature } from '@aztec/circuit-types';
|
|
2
2
|
import { type L1PublishBlockStats, type L1PublishProofStats } from '@aztec/circuit-types/stats';
|
|
3
|
-
import { ETHEREUM_SLOT_DURATION, EthAddress, type Header, type Proof } from '@aztec/circuits.js';
|
|
3
|
+
import { ETHEREUM_SLOT_DURATION, EthAddress, GENESIS_ARCHIVE_ROOT, type Header, type Proof } from '@aztec/circuits.js';
|
|
4
4
|
import { createEthereumChain } from '@aztec/ethereum';
|
|
5
|
-
import {
|
|
5
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
6
6
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
7
7
|
import { serializeToBuffer } from '@aztec/foundation/serialize';
|
|
8
8
|
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
@@ -12,6 +12,7 @@ import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
|
12
12
|
|
|
13
13
|
import pick from 'lodash.pick';
|
|
14
14
|
import {
|
|
15
|
+
ContractFunctionRevertedError,
|
|
15
16
|
type GetContractReturnType,
|
|
16
17
|
type Hex,
|
|
17
18
|
type HttpTransport,
|
|
@@ -59,13 +60,6 @@ export type MinimalTransactionReceipt = {
|
|
|
59
60
|
logs: any[];
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
/**
|
|
63
|
-
* @notice An attestation for the sequencing model.
|
|
64
|
-
* @todo This is not where it belongs. But I think we should do a bigger rewrite of some of
|
|
65
|
-
* this spaghetti.
|
|
66
|
-
*/
|
|
67
|
-
export type Attestation = { isEmpty: boolean; v: number; r: `0x${string}`; s: `0x${string}` };
|
|
68
|
-
|
|
69
63
|
/** Arguments to the process method of the rollup contract */
|
|
70
64
|
export type L1ProcessArgs = {
|
|
71
65
|
/** The L2 block header. */
|
|
@@ -94,6 +88,13 @@ export type L1SubmitProofArgs = {
|
|
|
94
88
|
aggregationObject: Buffer;
|
|
95
89
|
};
|
|
96
90
|
|
|
91
|
+
export type MetadataForSlot = {
|
|
92
|
+
proposer: EthAddress;
|
|
93
|
+
slot: bigint;
|
|
94
|
+
pendingBlockNumber: bigint;
|
|
95
|
+
archive: Buffer;
|
|
96
|
+
};
|
|
97
|
+
|
|
97
98
|
/**
|
|
98
99
|
* Publishes L2 blocks to L1. This implementation does *not* retry a transaction in
|
|
99
100
|
* the event of network congestion, but should work for local development.
|
|
@@ -103,6 +104,15 @@ export type L1SubmitProofArgs = {
|
|
|
103
104
|
* Adapted from https://github.com/AztecProtocol/aztec2-internal/blob/master/falafel/src/rollup_publisher.ts.
|
|
104
105
|
*/
|
|
105
106
|
export class L1Publisher {
|
|
107
|
+
// @note If we want to simulate in the future, we have to skip the viem simulations and use `reads` instead
|
|
108
|
+
// This is because the viem simulations are not able to simulate the future, only the current state.
|
|
109
|
+
// This means that we will be simulating as if `block.timestamp` is the same for the next block
|
|
110
|
+
// as for the last block.
|
|
111
|
+
// Nevertheless, it can be quite useful for figuring out why exactly the transaction is failing
|
|
112
|
+
// as a middle ground right now, we will be skipping the simulation and just sending the transaction
|
|
113
|
+
// but only after we have done a successful run of the `validateHeader` for the timestamp in the future.
|
|
114
|
+
public static SKIP_SIMULATION = true;
|
|
115
|
+
|
|
106
116
|
private interruptibleSleep = new InterruptibleSleep();
|
|
107
117
|
private sleepTimeMs: number;
|
|
108
118
|
private interrupted = false;
|
|
@@ -154,24 +164,76 @@ export class L1Publisher {
|
|
|
154
164
|
return Promise.resolve(EthAddress.fromString(this.account.address));
|
|
155
165
|
}
|
|
156
166
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
167
|
+
/**
|
|
168
|
+
* @notice Calls `canProposeAtTime` with the time of the next Ethereum block and the sender address
|
|
169
|
+
*
|
|
170
|
+
* @dev Throws if unable to propose
|
|
171
|
+
*
|
|
172
|
+
* @param archive - The archive that we expect to be current state
|
|
173
|
+
* @return slot - The L2 slot number of the next Ethereum block,
|
|
174
|
+
* @return blockNumber - The L2 block number of the next L2 block
|
|
175
|
+
*/
|
|
176
|
+
public async canProposeAtNextEthBlock(archive: Buffer): Promise<[bigint, bigint]> {
|
|
177
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
178
|
+
const [slot, blockNumber] = await this.rollupContract.read.canProposeAtTime([
|
|
179
|
+
ts,
|
|
180
|
+
this.account.address,
|
|
181
|
+
`0x${archive.toString('hex')}`,
|
|
182
|
+
]);
|
|
183
|
+
return [slot, blockNumber];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public async validateBlockForSubmission(
|
|
187
|
+
header: Header,
|
|
188
|
+
digest: Buffer = new Fr(GENESIS_ARCHIVE_ROOT).toBuffer(),
|
|
189
|
+
attestations: Signature[] = [],
|
|
190
|
+
): Promise<boolean> {
|
|
191
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
192
|
+
|
|
193
|
+
const formattedAttestations = attestations.map(attest => attest.toViemSignature());
|
|
194
|
+
const flags = { ignoreDA: true, ignoreSignatures: attestations.length == 0 };
|
|
195
|
+
|
|
196
|
+
const args = [
|
|
197
|
+
`0x${header.toBuffer().toString('hex')}`,
|
|
198
|
+
formattedAttestations,
|
|
199
|
+
`0x${digest.toString('hex')}`,
|
|
200
|
+
ts,
|
|
201
|
+
flags,
|
|
202
|
+
] as const;
|
|
203
|
+
|
|
161
204
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
205
|
+
await this.rollupContract.read.validateHeader(args, { account: this.account });
|
|
206
|
+
return true;
|
|
207
|
+
} catch (error: unknown) {
|
|
208
|
+
// Specify the type of error
|
|
209
|
+
if (error instanceof ContractFunctionRevertedError) {
|
|
210
|
+
const err = error as ContractFunctionRevertedError;
|
|
211
|
+
this.log.debug(`Validation failed: ${err.message}`, err.data);
|
|
212
|
+
} else {
|
|
213
|
+
this.log.debug(`Unexpected error during validation: ${error}`);
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
168
216
|
}
|
|
169
217
|
}
|
|
170
218
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
219
|
+
// @note Assumes that all ethereum slots have blocks
|
|
220
|
+
// Using next Ethereum block so we do NOT need to wait for it being mined before seeing the effect
|
|
221
|
+
public async getMetadataForSlotAtNextEthBlock(): Promise<MetadataForSlot> {
|
|
222
|
+
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
|
|
223
|
+
|
|
224
|
+
const [submitter, slot, pendingBlockCount, archive] = await Promise.all([
|
|
225
|
+
this.rollupContract.read.getProposerAt([ts]),
|
|
226
|
+
this.rollupContract.read.getSlotAt([ts]),
|
|
227
|
+
this.rollupContract.read.pendingBlockCount(),
|
|
228
|
+
this.rollupContract.read.archive(),
|
|
229
|
+
]);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
proposer: EthAddress.fromString(submitter),
|
|
233
|
+
slot,
|
|
234
|
+
pendingBlockNumber: pendingBlockCount - 1n,
|
|
235
|
+
archive: Buffer.from(archive.replace('0x', ''), 'hex'),
|
|
236
|
+
};
|
|
175
237
|
}
|
|
176
238
|
|
|
177
239
|
public async getCurrentEpochCommittee(): Promise<EthAddress[]> {
|
|
@@ -203,44 +265,49 @@ export class L1Publisher {
|
|
|
203
265
|
* @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
|
|
204
266
|
*/
|
|
205
267
|
public async processL2Block(block: L2Block, attestations?: Signature[]): Promise<boolean> {
|
|
206
|
-
const ctx = {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
268
|
+
const ctx = {
|
|
269
|
+
blockNumber: block.number,
|
|
270
|
+
slotNumber: block.header.globalVariables.slotNumber.toBigInt(),
|
|
271
|
+
blockHash: block.hash().toString(),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
275
|
+
// This means that we can avoid the simulation issues in later checks.
|
|
276
|
+
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
277
|
+
// make time consistency checks break.
|
|
278
|
+
if (!(await this.validateBlockForSubmission(block.header, block.archive.root.toBuffer(), attestations))) {
|
|
211
279
|
return false;
|
|
212
280
|
}
|
|
213
|
-
const encodedBody = block.body.toBuffer();
|
|
214
281
|
|
|
215
282
|
const processTxArgs = {
|
|
216
283
|
header: block.header.toBuffer(),
|
|
217
284
|
archive: block.archive.root.toBuffer(),
|
|
218
285
|
blockHash: block.header.hash().toBuffer(),
|
|
219
|
-
body:
|
|
286
|
+
body: block.body.toBuffer(),
|
|
220
287
|
attestations,
|
|
221
288
|
};
|
|
222
289
|
|
|
223
|
-
//
|
|
224
|
-
|
|
290
|
+
// Publish body and propose block (if not already published)
|
|
291
|
+
if (!this.interrupted) {
|
|
225
292
|
let txHash;
|
|
226
293
|
const timer = new Timer();
|
|
227
294
|
|
|
228
295
|
if (await this.checkIfTxsAreAvailable(block)) {
|
|
229
296
|
this.log.verbose(`Transaction effects of block ${block.number} already published.`, ctx);
|
|
230
|
-
txHash = await this.
|
|
297
|
+
txHash = await this.sendProposeWithoutBodyTx(processTxArgs);
|
|
231
298
|
} else {
|
|
232
|
-
txHash = await this.
|
|
299
|
+
txHash = await this.sendProposeTx(processTxArgs);
|
|
233
300
|
}
|
|
234
301
|
|
|
235
302
|
if (!txHash) {
|
|
236
303
|
this.log.info(`Failed to publish block ${block.number} to L1`, ctx);
|
|
237
|
-
|
|
304
|
+
return false;
|
|
238
305
|
}
|
|
239
306
|
|
|
240
307
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
241
308
|
if (!receipt) {
|
|
242
309
|
this.log.info(`Failed to get receipt for tx ${txHash}`, ctx);
|
|
243
|
-
|
|
310
|
+
return false;
|
|
244
311
|
}
|
|
245
312
|
|
|
246
313
|
// Tx was mined successfully
|
|
@@ -254,17 +321,12 @@ export class L1Publisher {
|
|
|
254
321
|
};
|
|
255
322
|
this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx });
|
|
256
323
|
this.metrics.recordProcessBlockTx(timer.ms(), stats);
|
|
324
|
+
|
|
257
325
|
return true;
|
|
258
326
|
}
|
|
259
327
|
|
|
260
328
|
this.metrics.recordFailedTx('process');
|
|
261
329
|
|
|
262
|
-
// Check if someone else incremented the block number
|
|
263
|
-
if (!(await this.checkLastArchiveHash(lastArchive))) {
|
|
264
|
-
this.log.warn('Publish failed. Detected different last archive hash.', ctx);
|
|
265
|
-
break;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
330
|
this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx);
|
|
269
331
|
await this.sleepOrInterrupted();
|
|
270
332
|
}
|
|
@@ -280,7 +342,7 @@ export class L1Publisher {
|
|
|
280
342
|
aggregationObject: Fr[],
|
|
281
343
|
proof: Proof,
|
|
282
344
|
): Promise<boolean> {
|
|
283
|
-
const ctx = { blockNumber: header.globalVariables.blockNumber };
|
|
345
|
+
const ctx = { blockNumber: header.globalVariables.blockNumber, slotNumber: header.globalVariables.slotNumber };
|
|
284
346
|
|
|
285
347
|
const txArgs: L1SubmitProofArgs = {
|
|
286
348
|
header: header.toBuffer(),
|
|
@@ -291,16 +353,16 @@ export class L1Publisher {
|
|
|
291
353
|
};
|
|
292
354
|
|
|
293
355
|
// Process block
|
|
294
|
-
|
|
356
|
+
if (!this.interrupted) {
|
|
295
357
|
const timer = new Timer();
|
|
296
358
|
const txHash = await this.sendSubmitProofTx(txArgs);
|
|
297
359
|
if (!txHash) {
|
|
298
|
-
|
|
360
|
+
return false;
|
|
299
361
|
}
|
|
300
362
|
|
|
301
363
|
const receipt = await this.getTransactionReceipt(txHash);
|
|
302
364
|
if (!receipt) {
|
|
303
|
-
|
|
365
|
+
return false;
|
|
304
366
|
}
|
|
305
367
|
|
|
306
368
|
// Tx was mined successfully
|
|
@@ -341,26 +403,6 @@ export class L1Publisher {
|
|
|
341
403
|
this.interrupted = false;
|
|
342
404
|
}
|
|
343
405
|
|
|
344
|
-
async getCurrentArchive(): Promise<Buffer> {
|
|
345
|
-
const archive = await this.rollupContract.read.archive();
|
|
346
|
-
return Buffer.from(archive.replace('0x', ''), 'hex');
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* Verifies that the given value of last archive in a block header equals current archive of the rollup contract
|
|
351
|
-
* @param lastArchive - The last archive of the block we wish to publish.
|
|
352
|
-
* @returns Boolean indicating if the hashes are equal.
|
|
353
|
-
*/
|
|
354
|
-
private async checkLastArchiveHash(lastArchive: Buffer): Promise<boolean> {
|
|
355
|
-
const fromChain = await this.getCurrentArchive();
|
|
356
|
-
const areSame = lastArchive.equals(fromChain);
|
|
357
|
-
if (!areSame) {
|
|
358
|
-
this.log.debug(`Contract archive: ${fromChain.toString('hex')}`);
|
|
359
|
-
this.log.debug(`New block last archive: ${lastArchive.toString('hex')}`);
|
|
360
|
-
}
|
|
361
|
-
return areSame;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
406
|
private async sendSubmitProofTx(submitProofArgs: L1SubmitProofArgs): Promise<string | undefined> {
|
|
365
407
|
try {
|
|
366
408
|
const size = Object.values(submitProofArgs).reduce((acc, arg) => acc + arg.length, 0);
|
|
@@ -375,6 +417,12 @@ export class L1Publisher {
|
|
|
375
417
|
`0x${proof.toString('hex')}`,
|
|
376
418
|
] as const;
|
|
377
419
|
|
|
420
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
421
|
+
await this.rollupContract.simulate.submitBlockRootProof(args, {
|
|
422
|
+
account: this.account,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
378
426
|
return await this.rollupContract.write.submitBlockRootProof(args, {
|
|
379
427
|
account: this.account,
|
|
380
428
|
});
|
|
@@ -384,12 +432,17 @@ export class L1Publisher {
|
|
|
384
432
|
}
|
|
385
433
|
}
|
|
386
434
|
|
|
435
|
+
// This is used in `integration_l1_publisher.test.ts` currently. Could be removed though.
|
|
387
436
|
private async sendPublishTx(encodedBody: Buffer): Promise<string | undefined> {
|
|
388
|
-
|
|
437
|
+
if (!this.interrupted) {
|
|
389
438
|
try {
|
|
390
439
|
this.log.info(`TxEffects size=${encodedBody.length} bytes`);
|
|
391
440
|
const args = [`0x${encodedBody.toString('hex')}`] as const;
|
|
392
441
|
|
|
442
|
+
await this.availabilityOracleContract.simulate.publish(args, {
|
|
443
|
+
account: this.account,
|
|
444
|
+
});
|
|
445
|
+
|
|
393
446
|
return await this.availabilityOracleContract.write.publish(args, {
|
|
394
447
|
account: this.account,
|
|
395
448
|
});
|
|
@@ -400,8 +453,8 @@ export class L1Publisher {
|
|
|
400
453
|
}
|
|
401
454
|
}
|
|
402
455
|
|
|
403
|
-
private async
|
|
404
|
-
|
|
456
|
+
private async sendProposeWithoutBodyTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
|
|
457
|
+
if (!this.interrupted) {
|
|
405
458
|
try {
|
|
406
459
|
if (encodedData.attestations) {
|
|
407
460
|
const attestations = encodedData.attestations.map(attest => attest.toViemSignature());
|
|
@@ -412,7 +465,11 @@ export class L1Publisher {
|
|
|
412
465
|
attestations,
|
|
413
466
|
] as const;
|
|
414
467
|
|
|
415
|
-
|
|
468
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
469
|
+
await this.rollupContract.simulate.propose(args, { account: this.account });
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return await this.rollupContract.write.propose(args, {
|
|
416
473
|
account: this.account,
|
|
417
474
|
});
|
|
418
475
|
} else {
|
|
@@ -422,7 +479,10 @@ export class L1Publisher {
|
|
|
422
479
|
`0x${encodedData.blockHash.toString('hex')}`,
|
|
423
480
|
] as const;
|
|
424
481
|
|
|
425
|
-
|
|
482
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
483
|
+
await this.rollupContract.simulate.propose(args, { account: this.account });
|
|
484
|
+
}
|
|
485
|
+
return await this.rollupContract.write.propose(args, {
|
|
426
486
|
account: this.account,
|
|
427
487
|
});
|
|
428
488
|
}
|
|
@@ -433,10 +493,9 @@ export class L1Publisher {
|
|
|
433
493
|
}
|
|
434
494
|
}
|
|
435
495
|
|
|
436
|
-
private async
|
|
437
|
-
|
|
496
|
+
private async sendProposeTx(encodedData: L1ProcessArgs): Promise<string | undefined> {
|
|
497
|
+
if (!this.interrupted) {
|
|
438
498
|
try {
|
|
439
|
-
// @note This is quite a sin, but I'm committing war crimes in this code already.
|
|
440
499
|
if (encodedData.attestations) {
|
|
441
500
|
const attestations = encodedData.attestations.map(attest => attest.toViemSignature());
|
|
442
501
|
const args = [
|
|
@@ -447,7 +506,13 @@ export class L1Publisher {
|
|
|
447
506
|
`0x${encodedData.body.toString('hex')}`,
|
|
448
507
|
] as const;
|
|
449
508
|
|
|
450
|
-
|
|
509
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
510
|
+
await this.rollupContract.simulate.propose(args, {
|
|
511
|
+
account: this.account,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
return await this.rollupContract.write.propose(args, {
|
|
451
516
|
account: this.account,
|
|
452
517
|
});
|
|
453
518
|
} else {
|
|
@@ -458,7 +523,13 @@ export class L1Publisher {
|
|
|
458
523
|
`0x${encodedData.body.toString('hex')}`,
|
|
459
524
|
] as const;
|
|
460
525
|
|
|
461
|
-
|
|
526
|
+
if (!L1Publisher.SKIP_SIMULATION) {
|
|
527
|
+
await this.rollupContract.simulate.propose(args, {
|
|
528
|
+
account: this.account,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return await this.rollupContract.write.propose(args, {
|
|
462
533
|
account: this.account,
|
|
463
534
|
});
|
|
464
535
|
}
|