@aztec/sequencer-client 0.51.1 → 0.53.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/client/sequencer-client.d.ts +2 -2
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/index.d.ts +2 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -1
- package/dest/publisher/l1-publisher.d.ts +14 -9
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +32 -40
- package/dest/sequencer/sequencer.d.ts +16 -1
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +101 -96
- package/package.json +19 -19
- package/src/index.ts +2 -0
- package/src/publisher/l1-publisher.ts +35 -52
- package/src/sequencer/sequencer.ts +117 -112
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
EthAddress,
|
|
18
18
|
GENESIS_ARCHIVE_ROOT,
|
|
19
19
|
Header,
|
|
20
|
-
IS_DEV_NET,
|
|
21
20
|
StateReference,
|
|
22
21
|
} from '@aztec/circuits.js';
|
|
23
22
|
import { Fr } from '@aztec/foundation/fields';
|
|
@@ -183,91 +182,99 @@ export class Sequencer {
|
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
/**
|
|
186
|
-
*
|
|
185
|
+
* @notice Performs most of the sequencer duties:
|
|
186
|
+
* - Checks if we are up to date
|
|
187
|
+
* - If we are and we are the sequencer, collect txs and build a block
|
|
188
|
+
* - Collect attestations for the block
|
|
189
|
+
* - Submit block
|
|
190
|
+
* - If our block for some reason is not included, revert the state
|
|
187
191
|
*/
|
|
188
192
|
protected async work() {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
193
|
+
// Update state when the previous block has been synced
|
|
194
|
+
const prevBlockSynced = await this.isBlockSynced();
|
|
195
|
+
// Do not go forward with new block if the previous one has not been mined and processed
|
|
196
|
+
if (!prevBlockSynced) {
|
|
197
|
+
this.log.debug('Previous block has not been mined and processed yet');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
197
200
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
|
|
202
|
+
this.log.debug(`Block has been synced`);
|
|
203
|
+
this.state = SequencerState.IDLE;
|
|
204
|
+
}
|
|
202
205
|
|
|
203
|
-
|
|
204
|
-
|
|
206
|
+
const chainTip = await this.l2BlockSource.getBlock(-1);
|
|
207
|
+
const historicalHeader = chainTip?.header;
|
|
205
208
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
const newBlockNumber =
|
|
210
|
+
(historicalHeader === undefined
|
|
211
|
+
? await this.l2BlockSource.getBlockNumber()
|
|
212
|
+
: Number(historicalHeader.globalVariables.blockNumber.toBigInt())) + 1;
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
+
// If we cannot find a tip archive, assume genesis.
|
|
215
|
+
const chainTipArchive =
|
|
216
|
+
chainTip == undefined ? new Fr(GENESIS_ARCHIVE_ROOT).toBuffer() : chainTip?.archive.root.toBuffer();
|
|
214
217
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
let slot: bigint;
|
|
219
|
+
try {
|
|
220
|
+
slot = await this.mayProposeBlock(chainTipArchive, BigInt(newBlockNumber));
|
|
221
|
+
} catch (err) {
|
|
222
|
+
this.log.debug(`Cannot propose for block ${newBlockNumber}`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
222
225
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
+
if (!this.shouldProposeBlock(historicalHeader, {})) {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
226
229
|
|
|
227
|
-
|
|
230
|
+
this.state = SequencerState.WAITING_FOR_TXS;
|
|
228
231
|
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
// Get txs to build the new block.
|
|
233
|
+
const pendingTxs = this.p2pClient.getTxs('pending');
|
|
231
234
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
|
|
236
239
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
const newGlobalVariables = await this.globalsBuilder.buildGlobalVariables(
|
|
241
|
+
new Fr(newBlockNumber),
|
|
242
|
+
this._coinbase,
|
|
243
|
+
this._feeRecipient,
|
|
244
|
+
slot,
|
|
245
|
+
);
|
|
243
246
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
247
|
+
// If I created a "partial" header here that should make our job much easier.
|
|
248
|
+
const proposalHeader = new Header(
|
|
249
|
+
new AppendOnlyTreeSnapshot(Fr.fromBuffer(chainTipArchive), 1),
|
|
250
|
+
ContentCommitment.empty(),
|
|
251
|
+
StateReference.empty(),
|
|
252
|
+
newGlobalVariables,
|
|
253
|
+
Fr.ZERO,
|
|
254
|
+
);
|
|
252
255
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
256
|
+
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
|
|
257
|
+
const allValidTxs = await this.takeValidTxs(
|
|
258
|
+
pendingTxs,
|
|
259
|
+
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
|
|
260
|
+
);
|
|
258
261
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
262
|
+
// TODO: We are taking the size of the tx from private-land, but we should be doing this after running
|
|
263
|
+
// public functions. Only reason why we do it here now is because the public processor and orchestrator
|
|
264
|
+
// are set up such that they require knowing the total number of txs in advance. Still, main reason for
|
|
265
|
+
// exceeding max block size in bytes is contract class registration, which happens in private-land. This
|
|
266
|
+
// may break if we start emitting lots of log data from public-land.
|
|
267
|
+
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
|
|
265
268
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
269
|
+
// Bail if we don't have enough valid txs
|
|
270
|
+
if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
270
273
|
|
|
274
|
+
try {
|
|
275
|
+
// @note It is very important that the following function will FAIL and not just return early
|
|
276
|
+
// if it have made any state changes. If not, we won't rollback the state, and you will
|
|
277
|
+
// be in for a world of pain.
|
|
271
278
|
await this.buildBlockAndPublish(validTxs, proposalHeader, historicalHeader);
|
|
272
279
|
} catch (err) {
|
|
273
280
|
if (BlockProofError.isBlockProofError(err)) {
|
|
@@ -300,13 +307,18 @@ export class Sequencer {
|
|
|
300
307
|
throw new Error(msg);
|
|
301
308
|
}
|
|
302
309
|
|
|
310
|
+
this.log.debug(`Can propose block ${proposalBlockNumber} at slot ${slot}`);
|
|
303
311
|
return slot;
|
|
304
312
|
} catch (err) {
|
|
305
313
|
if (err instanceof BaseError) {
|
|
306
314
|
const revertError = err.walk(err => err instanceof ContractFunctionRevertedError);
|
|
307
315
|
if (revertError instanceof ContractFunctionRevertedError) {
|
|
308
316
|
const errorName = revertError.data?.errorName ?? '';
|
|
309
|
-
|
|
317
|
+
const args =
|
|
318
|
+
revertError.metaMessages && revertError.metaMessages?.length > 1
|
|
319
|
+
? revertError.metaMessages[1].trimStart()
|
|
320
|
+
: '';
|
|
321
|
+
this.log.debug(`canProposeAtTime failed with "${errorName}${args}"`);
|
|
310
322
|
}
|
|
311
323
|
}
|
|
312
324
|
throw err;
|
|
@@ -319,23 +331,21 @@ export class Sequencer {
|
|
|
319
331
|
return true;
|
|
320
332
|
}
|
|
321
333
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
334
|
+
// Compute time elapsed since the previous block
|
|
335
|
+
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
|
|
336
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
337
|
+
const elapsedSinceLastBlock = currentTime - lastBlockTime;
|
|
338
|
+
this.log.debug(
|
|
339
|
+
`Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
|
|
343
|
+
// Do not go forward with new block if not enough time has passed since last block
|
|
344
|
+
if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
|
|
327
345
|
this.log.debug(
|
|
328
|
-
`
|
|
346
|
+
`Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
|
|
329
347
|
);
|
|
330
|
-
|
|
331
|
-
// If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
|
|
332
|
-
// Do not go forward with new block if not enough time has passed since last block
|
|
333
|
-
if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
|
|
334
|
-
this.log.debug(
|
|
335
|
-
`Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
|
|
336
|
-
);
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
348
|
+
return false;
|
|
339
349
|
}
|
|
340
350
|
|
|
341
351
|
const skipCheck = this.skipMinTxsPerBlockCheck(historicalHeader);
|
|
@@ -381,6 +391,16 @@ export class Sequencer {
|
|
|
381
391
|
return true;
|
|
382
392
|
}
|
|
383
393
|
|
|
394
|
+
/**
|
|
395
|
+
* @notice Build and propose a block to the chain
|
|
396
|
+
*
|
|
397
|
+
* @dev MUST throw instead of exiting early to ensure that world-state
|
|
398
|
+
* is being rolled back if the block is dropped.
|
|
399
|
+
*
|
|
400
|
+
* @param validTxs - The valid transactions to construct the block from
|
|
401
|
+
* @param proposalHeader - The partial header constructed for the proposal
|
|
402
|
+
* @param historicalHeader - The historical header of the parent
|
|
403
|
+
*/
|
|
384
404
|
@trackSpan('Sequencer.buildBlockAndPublish', (_validTxs, proposalHeader, _historicalHeader) => ({
|
|
385
405
|
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
386
406
|
}))
|
|
@@ -389,9 +409,7 @@ export class Sequencer {
|
|
|
389
409
|
proposalHeader: Header,
|
|
390
410
|
historicalHeader: Header | undefined,
|
|
391
411
|
): Promise<void> {
|
|
392
|
-
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
412
|
+
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
395
413
|
|
|
396
414
|
const newGlobalVariables = proposalHeader.globalVariables;
|
|
397
415
|
|
|
@@ -426,15 +444,16 @@ export class Sequencer {
|
|
|
426
444
|
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
427
445
|
}
|
|
428
446
|
|
|
447
|
+
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
448
|
+
|
|
429
449
|
if (
|
|
430
|
-
!(await this.publisher.validateBlockForSubmission(proposalHeader)) ||
|
|
431
450
|
!this.shouldProposeBlock(historicalHeader, {
|
|
432
451
|
validTxsCount: validTxs.length,
|
|
433
452
|
processedTxsCount: processedTxs.length,
|
|
434
453
|
})
|
|
435
454
|
) {
|
|
436
455
|
blockBuilder.cancelBlock();
|
|
437
|
-
|
|
456
|
+
throw new Error('Should not propose the block');
|
|
438
457
|
}
|
|
439
458
|
|
|
440
459
|
// All real transactions have been added, set the block as full and complete the proving.
|
|
@@ -451,9 +470,7 @@ export class Sequencer {
|
|
|
451
470
|
// Block is ready, now finalise
|
|
452
471
|
const { block } = await blockBuilder.finaliseBlock();
|
|
453
472
|
|
|
454
|
-
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
473
|
+
await this.publisher.validateBlockForSubmission(block.header);
|
|
457
474
|
|
|
458
475
|
const workDuration = workTimer.ms();
|
|
459
476
|
this.log.verbose(
|
|
@@ -496,24 +513,6 @@ export class Sequencer {
|
|
|
496
513
|
}
|
|
497
514
|
|
|
498
515
|
protected async collectAttestations(block: L2Block): Promise<Signature[] | undefined> {
|
|
499
|
-
// @todo This should collect attestations properly and fix the ordering of them to make sense
|
|
500
|
-
// the current implementation is a PLACEHOLDER and should be nuked from orbit.
|
|
501
|
-
// It is assuming that there will only be ONE (1) validator, so only one attestation
|
|
502
|
-
// is needed.
|
|
503
|
-
// @note This is quite a sin, but I'm committing war crimes in this code already.
|
|
504
|
-
// _ ._ _ , _ ._
|
|
505
|
-
// (_ ' ( ` )_ .__)
|
|
506
|
-
// ( ( ( ) `) ) _)
|
|
507
|
-
// (__ (_ (_ . _) _) ,__)
|
|
508
|
-
// `~~`\ ' . /`~~`
|
|
509
|
-
// ; ;
|
|
510
|
-
// / \
|
|
511
|
-
// _____________/_ __ \_____________
|
|
512
|
-
|
|
513
|
-
if (IS_DEV_NET || !this.validatorClient) {
|
|
514
|
-
return undefined;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
516
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
|
|
518
517
|
const committee = await this.publisher.getCurrentEpochCommittee();
|
|
519
518
|
|
|
@@ -521,6 +520,12 @@ export class Sequencer {
|
|
|
521
520
|
return undefined;
|
|
522
521
|
}
|
|
523
522
|
|
|
523
|
+
if (!this.validatorClient) {
|
|
524
|
+
const msg = 'Missing validator client: Cannot collect attestations';
|
|
525
|
+
this.log.error(msg);
|
|
526
|
+
throw new Error(msg);
|
|
527
|
+
}
|
|
528
|
+
|
|
524
529
|
const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
|
|
525
530
|
|
|
526
531
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7974): we do not have transaction[] lists in the block for now
|
|
@@ -553,7 +558,7 @@ export class Sequencer {
|
|
|
553
558
|
if (publishedL2Block) {
|
|
554
559
|
this.lastPublishedBlock = block.number;
|
|
555
560
|
} else {
|
|
556
|
-
throw new Error(`Failed to publish block`);
|
|
561
|
+
throw new Error(`Failed to publish block ${block.number}`);
|
|
557
562
|
}
|
|
558
563
|
}
|
|
559
564
|
|