@aztec/sequencer-client 0.67.1 → 0.68.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/client/sequencer-client.d.ts +3 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +17 -3
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +8 -56
- package/dest/index.d.ts +1 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -2
- package/dest/publisher/index.d.ts +0 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/index.js +1 -2
- package/dest/publisher/l1-publisher-metrics.d.ts +5 -2
- package/dest/publisher/l1-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/l1-publisher-metrics.js +16 -1
- package/dest/publisher/l1-publisher.d.ts +4 -1
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +200 -44
- package/dest/publisher/utils.d.ts +1 -3
- package/dest/publisher/utils.d.ts.map +1 -1
- package/dest/publisher/utils.js +2 -8
- package/dest/sequencer/allowed.d.ts +3 -0
- package/dest/sequencer/allowed.d.ts.map +1 -0
- package/dest/sequencer/allowed.js +34 -0
- package/dest/sequencer/config.d.ts +1 -1
- package/dest/sequencer/config.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +2 -0
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +13 -2
- package/dest/sequencer/sequencer.d.ts +14 -11
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +160 -87
- package/dest/sequencer/utils.d.ts +2 -7
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +3 -11
- package/dest/tx_validator/gas_validator.d.ts +3 -4
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +25 -8
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +6 -4
- package/package.json +26 -23
- package/src/client/sequencer-client.ts +25 -5
- package/src/config.ts +10 -60
- package/src/index.ts +1 -1
- package/src/publisher/index.ts +0 -1
- package/src/publisher/l1-publisher-metrics.ts +24 -3
- package/src/publisher/l1-publisher.ts +241 -68
- package/src/publisher/utils.ts +1 -10
- package/src/sequencer/allowed.ts +36 -0
- package/src/sequencer/config.ts +1 -1
- package/src/sequencer/metrics.ts +15 -1
- package/src/sequencer/sequencer.ts +195 -109
- package/src/sequencer/utils.ts +2 -11
- package/src/tx_validator/gas_validator.ts +32 -6
- package/src/tx_validator/tx_validator_factory.ts +11 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type EpochProofQuote,
|
|
3
|
+
type L1RollupConstants,
|
|
3
4
|
type L1ToL2MessageSource,
|
|
4
5
|
type L2Block,
|
|
5
6
|
type L2BlockSource,
|
|
@@ -27,7 +28,7 @@ import { Fr } from '@aztec/foundation/fields';
|
|
|
27
28
|
import { createLogger } from '@aztec/foundation/log';
|
|
28
29
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
29
30
|
import { pickFromSchema } from '@aztec/foundation/schemas';
|
|
30
|
-
import { Timer, elapsed } from '@aztec/foundation/timer';
|
|
31
|
+
import { type DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
31
32
|
import { type P2P } from '@aztec/p2p';
|
|
32
33
|
import { type BlockBuilderFactory } from '@aztec/prover-client/block-builder';
|
|
33
34
|
import { type PublicProcessorFactory } from '@aztec/simulator';
|
|
@@ -38,9 +39,12 @@ import { type GlobalVariableBuilder } from '../global_variable_builder/global_bu
|
|
|
38
39
|
import { type L1Publisher } from '../publisher/l1-publisher.js';
|
|
39
40
|
import { prettyLogViemErrorMsg } from '../publisher/utils.js';
|
|
40
41
|
import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
|
|
42
|
+
import { getDefaultAllowedSetupFunctions } from './allowed.js';
|
|
41
43
|
import { type SequencerConfig } from './config.js';
|
|
42
44
|
import { SequencerMetrics } from './metrics.js';
|
|
43
|
-
import { SequencerState,
|
|
45
|
+
import { SequencerState, orderAttestations } from './utils.js';
|
|
46
|
+
|
|
47
|
+
export { SequencerState };
|
|
44
48
|
|
|
45
49
|
export type ShouldProposeArgs = {
|
|
46
50
|
pendingTxsCount?: number;
|
|
@@ -62,6 +66,8 @@ export class SequencerTooSlowError extends Error {
|
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
type SequencerRollupConstants = Pick<L1RollupConstants, 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'>;
|
|
70
|
+
|
|
65
71
|
/**
|
|
66
72
|
* Sequencer client
|
|
67
73
|
* - Wins a period of time to become the sequencer (depending on finalized protocol).
|
|
@@ -76,14 +82,14 @@ export class Sequencer {
|
|
|
76
82
|
private pollingIntervalMs: number = 1000;
|
|
77
83
|
private maxTxsPerBlock = 32;
|
|
78
84
|
private minTxsPerBLock = 1;
|
|
79
|
-
private
|
|
80
|
-
private maxSecondsBetweenBlocks = 0;
|
|
85
|
+
private maxL1TxInclusionTimeIntoSlot = 0;
|
|
81
86
|
// TODO: zero values should not be allowed for the following 2 values in PROD
|
|
82
87
|
private _coinbase = EthAddress.ZERO;
|
|
83
88
|
private _feeRecipient = AztecAddress.ZERO;
|
|
84
89
|
private state = SequencerState.STOPPED;
|
|
85
|
-
private allowedInSetup: AllowedElement[] =
|
|
90
|
+
private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
|
|
86
91
|
private maxBlockSizeInBytes: number = 1024 * 1024;
|
|
92
|
+
private processTxTime: number = 12;
|
|
87
93
|
private metrics: SequencerMetrics;
|
|
88
94
|
private isFlushing: boolean = false;
|
|
89
95
|
|
|
@@ -105,8 +111,8 @@ export class Sequencer {
|
|
|
105
111
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
106
112
|
private publicProcessorFactory: PublicProcessorFactory,
|
|
107
113
|
private txValidatorFactory: TxValidatorFactory,
|
|
108
|
-
protected
|
|
109
|
-
private
|
|
114
|
+
protected l1Constants: SequencerRollupConstants,
|
|
115
|
+
private dateProvider: DateProvider,
|
|
110
116
|
telemetry: TelemetryClient,
|
|
111
117
|
private config: SequencerConfig = {},
|
|
112
118
|
private log = createLogger('sequencer'),
|
|
@@ -127,10 +133,7 @@ export class Sequencer {
|
|
|
127
133
|
* @param config - New parameters.
|
|
128
134
|
*/
|
|
129
135
|
public updateConfig(config: SequencerConfig) {
|
|
130
|
-
this.log.info(
|
|
131
|
-
`Sequencer config set`,
|
|
132
|
-
omit(pickFromSchema(this.config, SequencerConfigSchema), 'allowedInSetup', 'allowedInTeardown'),
|
|
133
|
-
);
|
|
136
|
+
this.log.info(`Sequencer config set`, omit(pickFromSchema(config, SequencerConfigSchema), 'allowedInSetup'));
|
|
134
137
|
|
|
135
138
|
if (config.transactionPollingIntervalMS !== undefined) {
|
|
136
139
|
this.pollingIntervalMs = config.transactionPollingIntervalMS;
|
|
@@ -141,12 +144,6 @@ export class Sequencer {
|
|
|
141
144
|
if (config.minTxsPerBlock !== undefined) {
|
|
142
145
|
this.minTxsPerBLock = config.minTxsPerBlock;
|
|
143
146
|
}
|
|
144
|
-
if (config.minSecondsBetweenBlocks !== undefined) {
|
|
145
|
-
this.minSecondsBetweenBlocks = config.minSecondsBetweenBlocks;
|
|
146
|
-
}
|
|
147
|
-
if (config.maxSecondsBetweenBlocks !== undefined) {
|
|
148
|
-
this.maxSecondsBetweenBlocks = config.maxSecondsBetweenBlocks;
|
|
149
|
-
}
|
|
150
147
|
if (config.coinbase) {
|
|
151
148
|
this._coinbase = config.coinbase;
|
|
152
149
|
}
|
|
@@ -162,6 +159,9 @@ export class Sequencer {
|
|
|
162
159
|
if (config.governanceProposerPayload) {
|
|
163
160
|
this.publisher.setPayload(config.governanceProposerPayload);
|
|
164
161
|
}
|
|
162
|
+
if (config.maxL1TxInclusionTimeIntoSlot !== undefined) {
|
|
163
|
+
this.maxL1TxInclusionTimeIntoSlot = config.maxL1TxInclusionTimeIntoSlot;
|
|
164
|
+
}
|
|
165
165
|
this.enforceTimeTable = config.enforceTimeTable === true;
|
|
166
166
|
|
|
167
167
|
this.setTimeTable();
|
|
@@ -171,20 +171,59 @@ export class Sequencer {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
private setTimeTable() {
|
|
174
|
+
// How late into the slot can we be to start working
|
|
175
|
+
const initialTime = 1;
|
|
176
|
+
|
|
177
|
+
// How long it takes to validate the txs collected and get ready to start building
|
|
178
|
+
const blockPrepareTime = 2;
|
|
179
|
+
|
|
180
|
+
// How long it takes to for attestations to travel across the p2p layer.
|
|
181
|
+
const attestationPropagationTime = 2;
|
|
182
|
+
|
|
183
|
+
// How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
|
|
184
|
+
// but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
|
|
185
|
+
// we can just post in the very last second of the L1 slot.
|
|
186
|
+
const l1PublishingTime = this.l1Constants.ethereumSlotDuration - this.maxL1TxInclusionTimeIntoSlot;
|
|
187
|
+
|
|
188
|
+
// How much time we spend validating and processing a block after building it
|
|
189
|
+
const blockValidationTime = 1;
|
|
190
|
+
|
|
191
|
+
// How much time we have left in the slot for actually processing txs and building the block.
|
|
192
|
+
const remainingTimeInSlot =
|
|
193
|
+
this.aztecSlotDuration -
|
|
194
|
+
initialTime -
|
|
195
|
+
blockPrepareTime -
|
|
196
|
+
l1PublishingTime -
|
|
197
|
+
2 * attestationPropagationTime -
|
|
198
|
+
blockValidationTime;
|
|
199
|
+
|
|
200
|
+
// Check that numbers make sense
|
|
201
|
+
if (this.enforceTimeTable && remainingTimeInSlot < 0) {
|
|
202
|
+
throw new Error(`Not enough time for block building in ${this.aztecSlotDuration}s slot`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// How much time we have for actually processing txs. Note that we need both the sequencer and the validators to execute txs.
|
|
206
|
+
const processTxsTime = remainingTimeInSlot / 2;
|
|
207
|
+
this.processTxTime = processTxsTime;
|
|
208
|
+
|
|
174
209
|
const newTimeTable: Record<SequencerState, number> = {
|
|
210
|
+
// No checks needed for any of these transitions
|
|
175
211
|
[SequencerState.STOPPED]: this.aztecSlotDuration,
|
|
176
212
|
[SequencerState.IDLE]: this.aztecSlotDuration,
|
|
177
213
|
[SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
|
|
178
|
-
|
|
179
|
-
[SequencerState.
|
|
180
|
-
|
|
181
|
-
[SequencerState.
|
|
182
|
-
|
|
183
|
-
[SequencerState.
|
|
214
|
+
// We always want to allow the full slot to check if we are the proposer
|
|
215
|
+
[SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration,
|
|
216
|
+
// First transition towards building a block
|
|
217
|
+
[SequencerState.WAITING_FOR_TXS]: initialTime,
|
|
218
|
+
// We then validate the txs and prepare to start building the block
|
|
219
|
+
[SequencerState.CREATING_BLOCK]: initialTime + blockPrepareTime,
|
|
220
|
+
// We start collecting attestations after building the block
|
|
221
|
+
[SequencerState.COLLECTING_ATTESTATIONS]: initialTime + blockPrepareTime + processTxsTime + blockValidationTime,
|
|
222
|
+
// We publish the block after collecting attestations
|
|
223
|
+
[SequencerState.PUBLISHING_BLOCK]: this.aztecSlotDuration - l1PublishingTime,
|
|
184
224
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
225
|
+
|
|
226
|
+
this.log.verbose(`Sequencer time table updated with ${processTxsTime}s for processing txs`, newTimeTable);
|
|
188
227
|
this.timeTable = newTimeTable;
|
|
189
228
|
}
|
|
190
229
|
|
|
@@ -192,7 +231,7 @@ export class Sequencer {
|
|
|
192
231
|
* Starts the sequencer and moves to IDLE state.
|
|
193
232
|
*/
|
|
194
233
|
public start() {
|
|
195
|
-
this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
|
|
234
|
+
this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
|
|
196
235
|
this.setState(SequencerState.IDLE, 0n, true /** force */);
|
|
197
236
|
this.runningPromise.start();
|
|
198
237
|
this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`);
|
|
@@ -293,6 +332,7 @@ export class Sequencer {
|
|
|
293
332
|
const pendingTxs = await this.p2pClient.getPendingTxs();
|
|
294
333
|
|
|
295
334
|
if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
|
|
335
|
+
await this.claimEpochProofRightIfAvailable(slot);
|
|
296
336
|
return;
|
|
297
337
|
}
|
|
298
338
|
|
|
@@ -306,7 +346,8 @@ export class Sequencer {
|
|
|
306
346
|
Fr.ZERO,
|
|
307
347
|
);
|
|
308
348
|
|
|
309
|
-
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
|
|
349
|
+
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here.
|
|
350
|
+
// TODO: We should validate only the number of txs we need to speed up this process.
|
|
310
351
|
const allValidTxs = await this.takeValidTxs(
|
|
311
352
|
pendingTxs,
|
|
312
353
|
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
|
|
@@ -325,6 +366,7 @@ export class Sequencer {
|
|
|
325
366
|
|
|
326
367
|
// Bail if we don't have enough valid txs
|
|
327
368
|
if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
|
|
369
|
+
await this.claimEpochProofRightIfAvailable(slot);
|
|
328
370
|
return;
|
|
329
371
|
}
|
|
330
372
|
|
|
@@ -339,6 +381,7 @@ export class Sequencer {
|
|
|
339
381
|
this.setState(SequencerState.IDLE, 0n);
|
|
340
382
|
}
|
|
341
383
|
|
|
384
|
+
@trackSpan('Sequencer.work')
|
|
342
385
|
protected async work() {
|
|
343
386
|
try {
|
|
344
387
|
await this.doRealWork();
|
|
@@ -354,15 +397,6 @@ export class Sequencer {
|
|
|
354
397
|
}
|
|
355
398
|
}
|
|
356
399
|
|
|
357
|
-
/** Whether to skip the check of min txs per block if more than maxSecondsBetweenBlocks has passed since the previous block. */
|
|
358
|
-
private skipMinTxsPerBlockCheck(historicalHeader: BlockHeader | undefined): boolean {
|
|
359
|
-
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
|
|
360
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
361
|
-
const elapsed = currentTime - lastBlockTime;
|
|
362
|
-
|
|
363
|
-
return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
400
|
async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
|
|
367
401
|
// This checks that we can propose, and gives us the slot that we are to propose for
|
|
368
402
|
try {
|
|
@@ -388,24 +422,21 @@ export class Sequencer {
|
|
|
388
422
|
return true;
|
|
389
423
|
}
|
|
390
424
|
|
|
391
|
-
|
|
425
|
+
const maxAllowedTime = this.timeTable[proposedState];
|
|
426
|
+
if (maxAllowedTime === this.aztecSlotDuration) {
|
|
392
427
|
return true;
|
|
393
428
|
}
|
|
394
429
|
|
|
395
|
-
const bufferSeconds =
|
|
430
|
+
const bufferSeconds = maxAllowedTime - secondsIntoSlot;
|
|
396
431
|
|
|
397
432
|
if (bufferSeconds < 0) {
|
|
398
|
-
this.log.warn(
|
|
399
|
-
`Too far into slot to transition to ${proposedState}. max allowed: ${this.timeTable[proposedState]}s, time into slot: ${secondsIntoSlot}s`,
|
|
400
|
-
);
|
|
433
|
+
this.log.warn(`Too far into slot to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
|
|
401
434
|
return false;
|
|
402
435
|
}
|
|
403
436
|
|
|
404
437
|
this.metrics.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), proposedState);
|
|
405
438
|
|
|
406
|
-
this.log.
|
|
407
|
-
`Enough time to transition to ${proposedState}, max allowed: ${this.timeTable[proposedState]}s, time into slot: ${secondsIntoSlot}s`,
|
|
408
|
-
);
|
|
439
|
+
this.log.trace(`Enough time to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
|
|
409
440
|
return true;
|
|
410
441
|
}
|
|
411
442
|
|
|
@@ -423,7 +454,7 @@ export class Sequencer {
|
|
|
423
454
|
this.log.warn(`Cannot set sequencer from ${this.state} to ${proposedState} as it is stopped.`);
|
|
424
455
|
return;
|
|
425
456
|
}
|
|
426
|
-
const secondsIntoSlot =
|
|
457
|
+
const secondsIntoSlot = this.getSecondsIntoSlot(currentSlotNumber);
|
|
427
458
|
if (!this.doIHaveEnoughTimeLeft(proposedState, secondsIntoSlot)) {
|
|
428
459
|
throw new SequencerTooSlowError(this.state, proposedState, this.timeTable[proposedState], secondsIntoSlot);
|
|
429
460
|
}
|
|
@@ -445,53 +476,29 @@ export class Sequencer {
|
|
|
445
476
|
`Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
|
|
446
477
|
);
|
|
447
478
|
|
|
448
|
-
//
|
|
449
|
-
|
|
450
|
-
if (this.minSecondsBetweenBlocks > 0 && elapsedSinceLastBlock < this.minSecondsBetweenBlocks) {
|
|
479
|
+
// We need to have at least minTxsPerBLock txs.
|
|
480
|
+
if (args.pendingTxsCount !== undefined && args.pendingTxsCount < this.minTxsPerBLock) {
|
|
451
481
|
this.log.verbose(
|
|
452
|
-
`Not creating block because not enough
|
|
482
|
+
`Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
|
|
453
483
|
);
|
|
454
484
|
return false;
|
|
455
485
|
}
|
|
456
486
|
|
|
457
|
-
const skipCheck = this.skipMinTxsPerBlockCheck(historicalHeader);
|
|
458
|
-
|
|
459
|
-
// If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
|
|
460
|
-
if (args.pendingTxsCount != undefined) {
|
|
461
|
-
if (args.pendingTxsCount < this.minTxsPerBLock) {
|
|
462
|
-
if (skipCheck) {
|
|
463
|
-
this.log.debug(
|
|
464
|
-
`Creating block with only ${args.pendingTxsCount} txs as more than ${this.maxSecondsBetweenBlocks}s have passed since last block`,
|
|
465
|
-
);
|
|
466
|
-
} else {
|
|
467
|
-
this.log.verbose(
|
|
468
|
-
`Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
|
|
469
|
-
);
|
|
470
|
-
return false;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
487
|
// Bail if we don't have enough valid txs
|
|
476
|
-
if (args.validTxsCount
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
);
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
488
|
+
if (args.validTxsCount !== undefined && args.validTxsCount < this.minTxsPerBLock) {
|
|
489
|
+
this.log.verbose(
|
|
490
|
+
`Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
|
|
491
|
+
);
|
|
492
|
+
return false;
|
|
484
493
|
}
|
|
485
494
|
|
|
486
495
|
// TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
|
|
487
496
|
// less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
|
|
488
497
|
// go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
|
|
489
498
|
// we should bail.
|
|
490
|
-
if (args.processedTxsCount
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return false;
|
|
494
|
-
}
|
|
499
|
+
if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
|
|
500
|
+
this.log.verbose('No txs processed correctly to build block.');
|
|
501
|
+
return false;
|
|
495
502
|
}
|
|
496
503
|
|
|
497
504
|
return true;
|
|
@@ -506,12 +513,14 @@ export class Sequencer {
|
|
|
506
513
|
* @param newGlobalVariables - The global variables for the new block
|
|
507
514
|
* @param historicalHeader - The historical header of the parent
|
|
508
515
|
* @param interrupt - The interrupt callback, used to validate the block for submission and check if we should propose the block
|
|
516
|
+
* @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
|
|
509
517
|
*/
|
|
510
518
|
private async buildBlock(
|
|
511
519
|
validTxs: Tx[],
|
|
512
520
|
newGlobalVariables: GlobalVariables,
|
|
513
521
|
historicalHeader?: BlockHeader,
|
|
514
522
|
interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
|
|
523
|
+
opts: { validateOnly?: boolean } = {},
|
|
515
524
|
) {
|
|
516
525
|
const blockNumber = newGlobalVariables.blockNumber.toBigInt();
|
|
517
526
|
const slot = newGlobalVariables.slotNumber.toBigInt();
|
|
@@ -541,28 +550,61 @@ export class Sequencer {
|
|
|
541
550
|
const orchestratorFork = await this.worldState.fork();
|
|
542
551
|
|
|
543
552
|
try {
|
|
544
|
-
const processor = this.publicProcessorFactory.create(
|
|
553
|
+
const processor = this.publicProcessorFactory.create(
|
|
554
|
+
publicProcessorFork,
|
|
555
|
+
historicalHeader,
|
|
556
|
+
newGlobalVariables,
|
|
557
|
+
true,
|
|
558
|
+
);
|
|
545
559
|
const blockBuildingTimer = new Timer();
|
|
546
560
|
const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
|
|
547
|
-
await blockBuilder.startNewBlock(
|
|
548
|
-
|
|
561
|
+
await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
|
|
562
|
+
|
|
563
|
+
// We set the deadline for tx processing to the start of the CREATING_BLOCK phase, plus the expected time for tx processing.
|
|
564
|
+
// Deadline is only set if enforceTimeTable is enabled.
|
|
565
|
+
const processingEndTimeWithinSlot = this.timeTable[SequencerState.CREATING_BLOCK] + this.processTxTime;
|
|
566
|
+
const processingDeadline = this.enforceTimeTable
|
|
567
|
+
? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
|
|
568
|
+
: undefined;
|
|
569
|
+
this.log.verbose(`Processing ${validTxs.length} txs`, {
|
|
570
|
+
slot,
|
|
571
|
+
slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
|
|
572
|
+
now: new Date(this.dateProvider.now()),
|
|
573
|
+
deadline: processingDeadline,
|
|
574
|
+
});
|
|
575
|
+
const processingTxValidator = this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork);
|
|
549
576
|
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
550
|
-
processor.process(
|
|
551
|
-
validTxs,
|
|
552
|
-
blockSize,
|
|
553
|
-
blockBuilder,
|
|
554
|
-
this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork),
|
|
555
|
-
),
|
|
577
|
+
processor.process(validTxs, blockSize, processingTxValidator, processingDeadline),
|
|
556
578
|
);
|
|
579
|
+
|
|
557
580
|
if (failedTxs.length > 0) {
|
|
558
581
|
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
559
582
|
this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
|
|
560
583
|
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
561
584
|
}
|
|
562
585
|
|
|
586
|
+
if (
|
|
587
|
+
!opts.validateOnly && // We check for minTxCount only if we are proposing a block, not if we are validating it
|
|
588
|
+
!this.isFlushing && // And we skip the check when flushing, since we want all pending txs to go out, no matter if too few
|
|
589
|
+
this.minTxsPerBLock !== undefined &&
|
|
590
|
+
processedTxs.length < this.minTxsPerBLock
|
|
591
|
+
) {
|
|
592
|
+
this.log.warn(
|
|
593
|
+
`Block ${blockNumber} has too few txs to be proposed (got ${processedTxs.length} but required ${this.minTxsPerBLock})`,
|
|
594
|
+
{ slot, blockNumber, processedTxCount: processedTxs.length },
|
|
595
|
+
);
|
|
596
|
+
throw new Error(`Block has too few successful txs to be proposed`);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const start = process.hrtime.bigint();
|
|
600
|
+
await blockBuilder.addTxs(processedTxs);
|
|
601
|
+
const end = process.hrtime.bigint();
|
|
602
|
+
const duration = Number(end - start) / 1_000;
|
|
603
|
+
this.metrics.recordBlockBuilderTreeInsertions(duration);
|
|
604
|
+
|
|
563
605
|
await interrupt?.(processedTxs);
|
|
564
606
|
|
|
565
|
-
// All real transactions have been added, set the block as full and
|
|
607
|
+
// All real transactions have been added, set the block as full and pad if needed
|
|
566
608
|
const block = await blockBuilder.setBlockCompleted();
|
|
567
609
|
|
|
568
610
|
return {
|
|
@@ -574,8 +616,16 @@ export class Sequencer {
|
|
|
574
616
|
};
|
|
575
617
|
} finally {
|
|
576
618
|
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
577
|
-
|
|
578
|
-
|
|
619
|
+
// We wait a bit to close the forks since the processor may still be working on a dangling tx
|
|
620
|
+
// which was interrupted due to the processingDeadline being hit.
|
|
621
|
+
setTimeout(async () => {
|
|
622
|
+
try {
|
|
623
|
+
await publicProcessorFork.close();
|
|
624
|
+
await orchestratorFork.close();
|
|
625
|
+
} catch (err) {
|
|
626
|
+
this.log.error(`Error closing forks`, err);
|
|
627
|
+
}
|
|
628
|
+
}, 5000);
|
|
579
629
|
}
|
|
580
630
|
}
|
|
581
631
|
|
|
@@ -627,6 +677,9 @@ export class Sequencer {
|
|
|
627
677
|
}
|
|
628
678
|
};
|
|
629
679
|
|
|
680
|
+
// Start collecting proof quotes for the previous epoch if needed in the background
|
|
681
|
+
const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
|
|
682
|
+
|
|
630
683
|
try {
|
|
631
684
|
const buildBlockRes = await this.buildBlock(validTxs, newGlobalVariables, historicalHeader, interrupt);
|
|
632
685
|
const { block, publicProcessorDuration, numProcessedTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
|
|
@@ -649,7 +702,6 @@ export class Sequencer {
|
|
|
649
702
|
const blockHash = block.hash();
|
|
650
703
|
const txHashes = validTxs.map(tx => tx.getTxHash());
|
|
651
704
|
this.log.info(`Built block ${block.number} with hash ${blockHash}`, {
|
|
652
|
-
txEffectsHash: block.header.contentCommitment.txsEffectsHash.toString('hex'),
|
|
653
705
|
blockHash,
|
|
654
706
|
globalVariables: block.header.globalVariables.toInspect(),
|
|
655
707
|
txHashes,
|
|
@@ -665,12 +717,12 @@ export class Sequencer {
|
|
|
665
717
|
const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
|
|
666
718
|
const attestations = await this.collectAttestations(block, txHashes);
|
|
667
719
|
if (attestations !== undefined) {
|
|
668
|
-
this.log.verbose(`Collected ${attestations.length} attestations
|
|
720
|
+
this.log.verbose(`Collected ${attestations.length} attestations`, { blockHash, blockNumber });
|
|
669
721
|
}
|
|
670
722
|
stopCollectingAttestationsTimer();
|
|
671
723
|
|
|
672
|
-
|
|
673
|
-
const proofQuote = await
|
|
724
|
+
// Get the proof quote for the previous epoch, if any
|
|
725
|
+
const proofQuote = await proofQuotePromise;
|
|
674
726
|
|
|
675
727
|
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
676
728
|
this.metrics.recordPublishedBlock(workDuration);
|
|
@@ -722,21 +774,19 @@ export class Sequencer {
|
|
|
722
774
|
}
|
|
723
775
|
|
|
724
776
|
const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
|
|
777
|
+
const slotNumber = block.header.globalVariables.slotNumber.toBigInt();
|
|
778
|
+
this.setState(SequencerState.COLLECTING_ATTESTATIONS, slotNumber);
|
|
725
779
|
|
|
726
|
-
this.log.debug('Creating block proposal');
|
|
780
|
+
this.log.debug('Creating block proposal for validators');
|
|
727
781
|
const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes);
|
|
728
782
|
if (!proposal) {
|
|
729
783
|
this.log.warn(`Failed to create block proposal, skipping collecting attestations`);
|
|
730
784
|
return undefined;
|
|
731
785
|
}
|
|
732
786
|
|
|
733
|
-
const slotNumber = block.header.globalVariables.slotNumber.toBigInt();
|
|
734
|
-
|
|
735
|
-
this.setState(SequencerState.PUBLISHING_BLOCK_TO_PEERS, slotNumber);
|
|
736
787
|
this.log.debug('Broadcasting block proposal to validators');
|
|
737
788
|
this.validatorClient.broadcastBlockProposal(proposal);
|
|
738
789
|
|
|
739
|
-
this.setState(SequencerState.WAITING_FOR_ATTESTATIONS, slotNumber);
|
|
740
790
|
const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations);
|
|
741
791
|
|
|
742
792
|
// note: the smart contract requires that the signatures are provided in the order of the committee
|
|
@@ -748,11 +798,12 @@ export class Sequencer {
|
|
|
748
798
|
// Find out which epoch we are currently in
|
|
749
799
|
const epochToProve = await this.publisher.getClaimableEpoch();
|
|
750
800
|
if (epochToProve === undefined) {
|
|
751
|
-
this.log.
|
|
801
|
+
this.log.trace(`No epoch to prove at slot ${slotNumber}`);
|
|
752
802
|
return undefined;
|
|
753
803
|
}
|
|
754
804
|
|
|
755
805
|
// Get quotes for the epoch to be proven
|
|
806
|
+
this.log.debug(`Collecting proof quotes for epoch ${epochToProve}`);
|
|
756
807
|
const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
|
|
757
808
|
this.log.verbose(`Retrieved ${quotes.length} quotes for slot ${slotNumber} epoch ${epochToProve}`, {
|
|
758
809
|
epochToProve,
|
|
@@ -761,7 +812,10 @@ export class Sequencer {
|
|
|
761
812
|
});
|
|
762
813
|
// ensure these quotes are still valid for the slot and have the contract validate them
|
|
763
814
|
const validQuotesPromise = Promise.all(
|
|
764
|
-
quotes
|
|
815
|
+
quotes
|
|
816
|
+
.filter(x => x.payload.validUntilSlot >= slotNumber)
|
|
817
|
+
.filter(x => x.payload.epochToProve === epochToProve)
|
|
818
|
+
.map(x => this.publisher.validateProofQuote(x)),
|
|
765
819
|
);
|
|
766
820
|
|
|
767
821
|
const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q);
|
|
@@ -774,7 +828,7 @@ export class Sequencer {
|
|
|
774
828
|
(a: EpochProofQuote, b: EpochProofQuote) => a.payload.basisPointFee - b.payload.basisPointFee,
|
|
775
829
|
);
|
|
776
830
|
const quote = sortedQuotes[0];
|
|
777
|
-
this.log.info(`Selected proof quote for proof claim`, quote.
|
|
831
|
+
this.log.info(`Selected proof quote for proof claim`, { quote: quote.toInspect() });
|
|
778
832
|
return quote;
|
|
779
833
|
} catch (err) {
|
|
780
834
|
this.log.error(`Failed to create proof claim for previous epoch`, err, { slotNumber });
|
|
@@ -834,6 +888,29 @@ export class Sequencer {
|
|
|
834
888
|
return toReturn;
|
|
835
889
|
}
|
|
836
890
|
|
|
891
|
+
@trackSpan(
|
|
892
|
+
'Sequencer.claimEpochProofRightIfAvailable',
|
|
893
|
+
slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }),
|
|
894
|
+
epoch => ({ [Attributes.EPOCH_NUMBER]: Number(epoch) }),
|
|
895
|
+
)
|
|
896
|
+
/** Collects an epoch proof quote if there is an epoch to prove, and submits it to the L1 contract. */
|
|
897
|
+
protected async claimEpochProofRightIfAvailable(slotNumber: bigint) {
|
|
898
|
+
const proofQuote = await this.createProofClaimForPreviousEpoch(slotNumber);
|
|
899
|
+
if (proofQuote === undefined) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const epoch = proofQuote.payload.epochToProve;
|
|
904
|
+
const ctx = { slotNumber, epoch, quote: proofQuote.toInspect() };
|
|
905
|
+
this.log.verbose(`Claiming proof right for epoch ${epoch}`, ctx);
|
|
906
|
+
const success = await this.publisher.claimEpochProofRight(proofQuote);
|
|
907
|
+
if (!success) {
|
|
908
|
+
throw new Error(`Failed to claim proof right for epoch ${epoch}`);
|
|
909
|
+
}
|
|
910
|
+
this.log.info(`Claimed proof right for epoch ${epoch}`, ctx);
|
|
911
|
+
return epoch;
|
|
912
|
+
}
|
|
913
|
+
|
|
837
914
|
/**
|
|
838
915
|
* Returns whether all dependencies have caught up.
|
|
839
916
|
* We don't check against the previous block submitted since it may have been reorg'd out.
|
|
@@ -870,6 +947,19 @@ export class Sequencer {
|
|
|
870
947
|
return result;
|
|
871
948
|
}
|
|
872
949
|
|
|
950
|
+
private getSlotStartTimestamp(slotNumber: number | bigint): number {
|
|
951
|
+
return Number(this.l1Constants.l1GenesisTime) + Number(slotNumber) * this.l1Constants.slotDuration;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
private getSecondsIntoSlot(slotNumber: number | bigint): number {
|
|
955
|
+
const slotStartTimestamp = this.getSlotStartTimestamp(slotNumber);
|
|
956
|
+
return Number((this.dateProvider.now() / 1000 - slotStartTimestamp).toFixed(3));
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
get aztecSlotDuration() {
|
|
960
|
+
return this.l1Constants.slotDuration;
|
|
961
|
+
}
|
|
962
|
+
|
|
873
963
|
get coinbase(): EthAddress {
|
|
874
964
|
return this._coinbase;
|
|
875
965
|
}
|
|
@@ -878,7 +968,3 @@ export class Sequencer {
|
|
|
878
968
|
return this._feeRecipient;
|
|
879
969
|
}
|
|
880
970
|
}
|
|
881
|
-
|
|
882
|
-
/**
|
|
883
|
-
* State of the sequencer.
|
|
884
|
-
*/
|
package/src/sequencer/utils.ts
CHANGED
|
@@ -27,13 +27,9 @@ export enum SequencerState {
|
|
|
27
27
|
*/
|
|
28
28
|
CREATING_BLOCK = 'CREATING_BLOCK',
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
30
|
+
* Collecting attestations from its peers. Will move to PUBLISHING_BLOCK.
|
|
31
31
|
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The block has been published to peers, and we are waiting for attestations. Will move to PUBLISHING_CONTRACT_DATA.
|
|
35
|
-
*/
|
|
36
|
-
WAITING_FOR_ATTESTATIONS = 'WAITING_FOR_ATTESTATIONS',
|
|
32
|
+
COLLECTING_ATTESTATIONS = 'COLLECTING_ATTESTATIONS',
|
|
37
33
|
/**
|
|
38
34
|
* Sending the tx to L1 with the L2 block data and awaiting it to be mined. Will move to SYNCHRONIZING.
|
|
39
35
|
*/
|
|
@@ -72,8 +68,3 @@ export function orderAttestations(attestations: BlockAttestation[], orderAddress
|
|
|
72
68
|
|
|
73
69
|
return orderedAttestations;
|
|
74
70
|
}
|
|
75
|
-
|
|
76
|
-
export function getSecondsIntoSlot(l1GenesisTime: number, aztecSlotDuration: number, slotNumber: number): number {
|
|
77
|
-
const slotStartTimestamp = l1GenesisTime + slotNumber * aztecSlotDuration;
|
|
78
|
-
return Number((Date.now() / 1000 - slotStartTimestamp).toFixed(3));
|
|
79
|
-
}
|