@aztec/sequencer-client 0.66.0 → 0.67.1-devnet
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.js +2 -1
- package/dest/config.d.ts +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +5 -58
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +3 -4
- package/dest/index.d.ts +0 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -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.map +1 -1
- package/dest/publisher/l1-publisher-metrics.js +2 -8
- package/dest/publisher/l1-publisher.d.ts +3 -2
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +159 -50
- package/dest/publisher/utils.d.ts.map +1 -1
- package/dest/publisher/utils.js +2 -1
- 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.map +1 -1
- package/dest/sequencer/metrics.js +2 -8
- package/dest/sequencer/sequencer.d.ts +2 -6
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +112 -103
- 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 +27 -10
- package/dest/tx_validator/phases_validator.js +3 -3
- 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 +27 -20
- package/src/client/sequencer-client.ts +1 -1
- package/src/config.ts +7 -62
- package/src/global_variable_builder/global_builder.ts +3 -3
- package/src/index.ts +0 -1
- package/src/publisher/index.ts +0 -1
- package/src/publisher/l1-publisher-metrics.ts +1 -7
- package/src/publisher/l1-publisher.ts +200 -73
- package/src/publisher/utils.ts +1 -0
- package/src/sequencer/allowed.ts +36 -0
- package/src/sequencer/config.ts +1 -1
- package/src/sequencer/metrics.ts +0 -7
- package/src/sequencer/sequencer.ts +136 -149
- package/src/tx_validator/gas_validator.ts +34 -8
- package/src/tx_validator/phases_validator.ts +2 -2
- package/src/tx_validator/tx_validator_factory.ts +11 -3
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type L2Block,
|
|
5
5
|
type L2BlockSource,
|
|
6
6
|
type ProcessedTx,
|
|
7
|
+
SequencerConfigSchema,
|
|
7
8
|
Tx,
|
|
8
9
|
type TxHash,
|
|
9
10
|
type TxValidator,
|
|
@@ -13,17 +14,19 @@ import type { AllowedElement, Signature, WorldStateSynchronizerStatus } from '@a
|
|
|
13
14
|
import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
|
|
14
15
|
import {
|
|
15
16
|
AppendOnlyTreeSnapshot,
|
|
17
|
+
BlockHeader,
|
|
16
18
|
ContentCommitment,
|
|
17
19
|
GENESIS_ARCHIVE_ROOT,
|
|
18
20
|
type GlobalVariables,
|
|
19
|
-
Header,
|
|
20
21
|
StateReference,
|
|
21
22
|
} from '@aztec/circuits.js';
|
|
22
23
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
24
|
+
import { omit } from '@aztec/foundation/collection';
|
|
23
25
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
24
26
|
import { Fr } from '@aztec/foundation/fields';
|
|
25
|
-
import {
|
|
27
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
26
28
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
29
|
+
import { pickFromSchema } from '@aztec/foundation/schemas';
|
|
27
30
|
import { Timer, elapsed } from '@aztec/foundation/timer';
|
|
28
31
|
import { type P2P } from '@aztec/p2p';
|
|
29
32
|
import { type BlockBuilderFactory } from '@aztec/prover-client/block-builder';
|
|
@@ -31,12 +34,11 @@ import { type PublicProcessorFactory } from '@aztec/simulator';
|
|
|
31
34
|
import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
32
35
|
import { type ValidatorClient } from '@aztec/validator-client';
|
|
33
36
|
|
|
34
|
-
import { inspect } from 'util';
|
|
35
|
-
|
|
36
37
|
import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
37
38
|
import { type L1Publisher } from '../publisher/l1-publisher.js';
|
|
38
39
|
import { prettyLogViemErrorMsg } from '../publisher/utils.js';
|
|
39
40
|
import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
|
|
41
|
+
import { getDefaultAllowedSetupFunctions } from './allowed.js';
|
|
40
42
|
import { type SequencerConfig } from './config.js';
|
|
41
43
|
import { SequencerMetrics } from './metrics.js';
|
|
42
44
|
import { SequencerState, getSecondsIntoSlot, orderAttestations } from './utils.js';
|
|
@@ -75,13 +77,11 @@ export class Sequencer {
|
|
|
75
77
|
private pollingIntervalMs: number = 1000;
|
|
76
78
|
private maxTxsPerBlock = 32;
|
|
77
79
|
private minTxsPerBLock = 1;
|
|
78
|
-
private minSecondsBetweenBlocks = 0;
|
|
79
|
-
private maxSecondsBetweenBlocks = 0;
|
|
80
80
|
// TODO: zero values should not be allowed for the following 2 values in PROD
|
|
81
81
|
private _coinbase = EthAddress.ZERO;
|
|
82
82
|
private _feeRecipient = AztecAddress.ZERO;
|
|
83
83
|
private state = SequencerState.STOPPED;
|
|
84
|
-
private allowedInSetup: AllowedElement[] =
|
|
84
|
+
private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
|
|
85
85
|
private maxBlockSizeInBytes: number = 1024 * 1024;
|
|
86
86
|
private metrics: SequencerMetrics;
|
|
87
87
|
private isFlushing: boolean = false;
|
|
@@ -108,11 +108,10 @@ export class Sequencer {
|
|
|
108
108
|
private aztecSlotDuration: number,
|
|
109
109
|
telemetry: TelemetryClient,
|
|
110
110
|
private config: SequencerConfig = {},
|
|
111
|
-
private log =
|
|
111
|
+
private log = createLogger('sequencer'),
|
|
112
112
|
) {
|
|
113
113
|
this.updateConfig(config);
|
|
114
114
|
this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
|
|
115
|
-
this.log.verbose(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
|
|
116
115
|
|
|
117
116
|
// Register the block builder with the validator client for re-execution
|
|
118
117
|
this.validatorClient?.registerBlockBuilder(this.buildBlock.bind(this));
|
|
@@ -127,6 +126,8 @@ export class Sequencer {
|
|
|
127
126
|
* @param config - New parameters.
|
|
128
127
|
*/
|
|
129
128
|
public updateConfig(config: SequencerConfig) {
|
|
129
|
+
this.log.info(`Sequencer config set`, omit(pickFromSchema(config, SequencerConfigSchema), 'allowedInSetup'));
|
|
130
|
+
|
|
130
131
|
if (config.transactionPollingIntervalMS !== undefined) {
|
|
131
132
|
this.pollingIntervalMs = config.transactionPollingIntervalMS;
|
|
132
133
|
}
|
|
@@ -136,12 +137,6 @@ export class Sequencer {
|
|
|
136
137
|
if (config.minTxsPerBlock !== undefined) {
|
|
137
138
|
this.minTxsPerBLock = config.minTxsPerBlock;
|
|
138
139
|
}
|
|
139
|
-
if (config.minSecondsBetweenBlocks !== undefined) {
|
|
140
|
-
this.minSecondsBetweenBlocks = config.minSecondsBetweenBlocks;
|
|
141
|
-
}
|
|
142
|
-
if (config.maxSecondsBetweenBlocks !== undefined) {
|
|
143
|
-
this.maxSecondsBetweenBlocks = config.maxSecondsBetweenBlocks;
|
|
144
|
-
}
|
|
145
140
|
if (config.coinbase) {
|
|
146
141
|
this._coinbase = config.coinbase;
|
|
147
142
|
}
|
|
@@ -187,10 +182,10 @@ export class Sequencer {
|
|
|
187
182
|
* Starts the sequencer and moves to IDLE state.
|
|
188
183
|
*/
|
|
189
184
|
public start() {
|
|
190
|
-
this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
|
|
191
|
-
this.runningPromise.start();
|
|
185
|
+
this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.pollingIntervalMs);
|
|
192
186
|
this.setState(SequencerState.IDLE, 0n, true /** force */);
|
|
193
|
-
this.
|
|
187
|
+
this.runningPromise.start();
|
|
188
|
+
this.log.info(`Sequencer started with address ${this.publisher.getSenderAddress().toString()}`);
|
|
194
189
|
return Promise.resolve();
|
|
195
190
|
}
|
|
196
191
|
|
|
@@ -199,6 +194,7 @@ export class Sequencer {
|
|
|
199
194
|
*/
|
|
200
195
|
public async stop(): Promise<void> {
|
|
201
196
|
this.log.debug(`Stopping sequencer`);
|
|
197
|
+
await this.validatorClient?.stop();
|
|
202
198
|
await this.runningPromise?.stop();
|
|
203
199
|
this.publisher.interrupt();
|
|
204
200
|
this.setState(SequencerState.STOPPED, 0n, true /** force */);
|
|
@@ -237,12 +233,9 @@ export class Sequencer {
|
|
|
237
233
|
const prevBlockSynced = await this.isBlockSynced();
|
|
238
234
|
// Do not go forward with new block if the previous one has not been mined and processed
|
|
239
235
|
if (!prevBlockSynced) {
|
|
240
|
-
this.log.debug('Previous block has not been mined and processed yet');
|
|
241
236
|
return;
|
|
242
237
|
}
|
|
243
238
|
|
|
244
|
-
this.log.debug('Previous block has been mined and processed');
|
|
245
|
-
|
|
246
239
|
this.setState(SequencerState.PROPOSER_CHECK, 0n);
|
|
247
240
|
|
|
248
241
|
const chainTip = await this.l2BlockSource.getBlock(-1);
|
|
@@ -278,18 +271,23 @@ export class Sequencer {
|
|
|
278
271
|
return;
|
|
279
272
|
}
|
|
280
273
|
|
|
274
|
+
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
275
|
+
chainTipArchive: new Fr(chainTipArchive),
|
|
276
|
+
blockNumber: newBlockNumber,
|
|
277
|
+
slot,
|
|
278
|
+
});
|
|
279
|
+
|
|
281
280
|
this.setState(SequencerState.WAITING_FOR_TXS, slot);
|
|
282
281
|
|
|
283
282
|
// Get txs to build the new block.
|
|
284
|
-
const pendingTxs = this.p2pClient.
|
|
283
|
+
const pendingTxs = await this.p2pClient.getPendingTxs();
|
|
285
284
|
|
|
286
285
|
if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
|
|
287
286
|
return;
|
|
288
287
|
}
|
|
289
|
-
this.log.debug(`Retrieved ${pendingTxs.length} txs from P2P pool`);
|
|
290
288
|
|
|
291
289
|
// If I created a "partial" header here that should make our job much easier.
|
|
292
|
-
const proposalHeader = new
|
|
290
|
+
const proposalHeader = new BlockHeader(
|
|
293
291
|
new AppendOnlyTreeSnapshot(Fr.fromBuffer(chainTipArchive), 1),
|
|
294
292
|
ContentCommitment.empty(),
|
|
295
293
|
StateReference.empty(),
|
|
@@ -311,6 +309,10 @@ export class Sequencer {
|
|
|
311
309
|
// may break if we start emitting lots of log data from public-land.
|
|
312
310
|
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
|
|
313
311
|
|
|
312
|
+
this.log.verbose(
|
|
313
|
+
`Collected ${validTxs.length} txs out of ${allValidTxs.length} valid txs out of ${pendingTxs.length} total pending txs for block ${newBlockNumber}`,
|
|
314
|
+
);
|
|
315
|
+
|
|
314
316
|
// Bail if we don't have enough valid txs
|
|
315
317
|
if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
|
|
316
318
|
return;
|
|
@@ -322,11 +324,12 @@ export class Sequencer {
|
|
|
322
324
|
// be in for a world of pain.
|
|
323
325
|
await this.buildBlockAndAttemptToPublish(validTxs, proposalHeader, historicalHeader);
|
|
324
326
|
} catch (err) {
|
|
325
|
-
this.log.error(`Error assembling block`,
|
|
327
|
+
this.log.error(`Error assembling block`, err, { blockNumber: newBlockNumber, slot });
|
|
326
328
|
}
|
|
327
329
|
this.setState(SequencerState.IDLE, 0n);
|
|
328
330
|
}
|
|
329
331
|
|
|
332
|
+
@trackSpan('Sequencer.work')
|
|
330
333
|
protected async work() {
|
|
331
334
|
try {
|
|
332
335
|
await this.doRealWork();
|
|
@@ -342,33 +345,20 @@ export class Sequencer {
|
|
|
342
345
|
}
|
|
343
346
|
}
|
|
344
347
|
|
|
345
|
-
/** Whether to skip the check of min txs per block if more than maxSecondsBetweenBlocks has passed since the previous block. */
|
|
346
|
-
private skipMinTxsPerBlockCheck(historicalHeader: Header | undefined): boolean {
|
|
347
|
-
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
|
|
348
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
349
|
-
const elapsed = currentTime - lastBlockTime;
|
|
350
|
-
|
|
351
|
-
return this.maxSecondsBetweenBlocks > 0 && elapsed >= this.maxSecondsBetweenBlocks;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
348
|
async mayProposeBlock(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint> {
|
|
355
349
|
// This checks that we can propose, and gives us the slot that we are to propose for
|
|
356
350
|
try {
|
|
357
351
|
const [slot, blockNumber] = await this.publisher.canProposeAtNextEthBlock(tipArchive);
|
|
358
352
|
|
|
359
353
|
if (proposalBlockNumber !== blockNumber) {
|
|
360
|
-
const msg = `
|
|
361
|
-
this.log.
|
|
354
|
+
const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
|
|
355
|
+
this.log.warn(msg);
|
|
362
356
|
throw new Error(msg);
|
|
363
357
|
}
|
|
364
|
-
|
|
365
|
-
this.log.verbose(`Can propose block ${proposalBlockNumber} at slot ${slot}`, {
|
|
366
|
-
publisherAddress: this.publisher.publisherAddress,
|
|
367
|
-
});
|
|
368
358
|
return slot;
|
|
369
359
|
} catch (err) {
|
|
370
360
|
const msg = prettyLogViemErrorMsg(err);
|
|
371
|
-
this.log.
|
|
361
|
+
this.log.debug(
|
|
372
362
|
`Rejected from being able to propose at next block with ${tipArchive.toString('hex')}: ${msg ? `${msg}` : ''}`,
|
|
373
363
|
);
|
|
374
364
|
throw err;
|
|
@@ -385,7 +375,6 @@ export class Sequencer {
|
|
|
385
375
|
}
|
|
386
376
|
|
|
387
377
|
const bufferSeconds = this.timeTable[proposedState] - secondsIntoSlot;
|
|
388
|
-
this.metrics.recordStateTransitionBufferMs(bufferSeconds * 1000, proposedState);
|
|
389
378
|
|
|
390
379
|
if (bufferSeconds < 0) {
|
|
391
380
|
this.log.warn(
|
|
@@ -393,6 +382,9 @@ export class Sequencer {
|
|
|
393
382
|
);
|
|
394
383
|
return false;
|
|
395
384
|
}
|
|
385
|
+
|
|
386
|
+
this.metrics.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), proposedState);
|
|
387
|
+
|
|
396
388
|
this.log.debug(
|
|
397
389
|
`Enough time to transition to ${proposedState}, max allowed: ${this.timeTable[proposedState]}s, time into slot: ${secondsIntoSlot}s`,
|
|
398
390
|
);
|
|
@@ -410,19 +402,18 @@ export class Sequencer {
|
|
|
410
402
|
*/
|
|
411
403
|
setState(proposedState: SequencerState, currentSlotNumber: bigint, force: boolean = false) {
|
|
412
404
|
if (this.state === SequencerState.STOPPED && force !== true) {
|
|
413
|
-
this.log.warn(
|
|
414
|
-
`Cannot set sequencer from ${this.state} to ${proposedState} as it is stopped. Set force=true to override.`,
|
|
415
|
-
);
|
|
405
|
+
this.log.warn(`Cannot set sequencer from ${this.state} to ${proposedState} as it is stopped.`);
|
|
416
406
|
return;
|
|
417
407
|
}
|
|
418
408
|
const secondsIntoSlot = getSecondsIntoSlot(this.l1GenesisTime, this.aztecSlotDuration, Number(currentSlotNumber));
|
|
419
409
|
if (!this.doIHaveEnoughTimeLeft(proposedState, secondsIntoSlot)) {
|
|
420
410
|
throw new SequencerTooSlowError(this.state, proposedState, this.timeTable[proposedState], secondsIntoSlot);
|
|
421
411
|
}
|
|
412
|
+
this.log.debug(`Transitioning from ${this.state} to ${proposedState}`);
|
|
422
413
|
this.state = proposedState;
|
|
423
414
|
}
|
|
424
415
|
|
|
425
|
-
shouldProposeBlock(historicalHeader:
|
|
416
|
+
shouldProposeBlock(historicalHeader: BlockHeader | undefined, args: ShouldProposeArgs): boolean {
|
|
426
417
|
if (this.isFlushing) {
|
|
427
418
|
this.log.verbose(`Flushing all pending txs in new block`);
|
|
428
419
|
return true;
|
|
@@ -436,53 +427,29 @@ export class Sequencer {
|
|
|
436
427
|
`Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
|
|
437
428
|
);
|
|
438
429
|
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
`Not creating block because not enough time ${this.minSecondsBetweenBlocks} has passed since last block`,
|
|
430
|
+
// We need to have at least minTxsPerBLock txs.
|
|
431
|
+
if (args.pendingTxsCount != undefined && args.pendingTxsCount < this.minTxsPerBLock) {
|
|
432
|
+
this.log.verbose(
|
|
433
|
+
`Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
|
|
444
434
|
);
|
|
445
435
|
return false;
|
|
446
436
|
}
|
|
447
437
|
|
|
448
|
-
const skipCheck = this.skipMinTxsPerBlockCheck(historicalHeader);
|
|
449
|
-
|
|
450
|
-
// If we haven't hit the maxSecondsBetweenBlocks, we need to have at least minTxsPerBLock txs.
|
|
451
|
-
if (args.pendingTxsCount != undefined) {
|
|
452
|
-
if (args.pendingTxsCount < this.minTxsPerBLock) {
|
|
453
|
-
if (skipCheck) {
|
|
454
|
-
this.log.debug(
|
|
455
|
-
`Creating block with only ${args.pendingTxsCount} txs as more than ${this.maxSecondsBetweenBlocks}s have passed since last block`,
|
|
456
|
-
);
|
|
457
|
-
} else {
|
|
458
|
-
this.log.debug(
|
|
459
|
-
`Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
|
|
460
|
-
);
|
|
461
|
-
return false;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
438
|
// Bail if we don't have enough valid txs
|
|
467
|
-
if (args.validTxsCount != undefined) {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
);
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
439
|
+
if (args.validTxsCount != undefined && args.validTxsCount < this.minTxsPerBLock) {
|
|
440
|
+
this.log.verbose(
|
|
441
|
+
`Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
|
|
442
|
+
);
|
|
443
|
+
return false;
|
|
475
444
|
}
|
|
476
445
|
|
|
477
446
|
// TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
|
|
478
447
|
// less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
|
|
479
448
|
// go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
|
|
480
449
|
// we should bail.
|
|
481
|
-
if (args.processedTxsCount
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return false;
|
|
485
|
-
}
|
|
450
|
+
if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
|
|
451
|
+
this.log.verbose('No txs processed correctly to build block.');
|
|
452
|
+
return false;
|
|
486
453
|
}
|
|
487
454
|
|
|
488
455
|
return true;
|
|
@@ -501,13 +468,23 @@ export class Sequencer {
|
|
|
501
468
|
private async buildBlock(
|
|
502
469
|
validTxs: Tx[],
|
|
503
470
|
newGlobalVariables: GlobalVariables,
|
|
504
|
-
historicalHeader?:
|
|
471
|
+
historicalHeader?: BlockHeader,
|
|
505
472
|
interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
|
|
506
473
|
) {
|
|
507
|
-
|
|
508
|
-
const
|
|
474
|
+
const blockNumber = newGlobalVariables.blockNumber.toBigInt();
|
|
475
|
+
const slot = newGlobalVariables.slotNumber.toBigInt();
|
|
476
|
+
|
|
477
|
+
this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
|
|
478
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
479
|
+
|
|
509
480
|
this.log.verbose(
|
|
510
|
-
`
|
|
481
|
+
`Building block ${blockNumber} with ${validTxs.length} txs and ${l1ToL2Messages.length} messages`,
|
|
482
|
+
{
|
|
483
|
+
msgCount: l1ToL2Messages.length,
|
|
484
|
+
txCount: validTxs.length,
|
|
485
|
+
slot,
|
|
486
|
+
blockNumber,
|
|
487
|
+
},
|
|
511
488
|
);
|
|
512
489
|
|
|
513
490
|
const numRealTxs = validTxs.length;
|
|
@@ -515,7 +492,7 @@ export class Sequencer {
|
|
|
515
492
|
|
|
516
493
|
// Sync to the previous block at least
|
|
517
494
|
await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
|
|
518
|
-
this.log.
|
|
495
|
+
this.log.debug(`Synced to previous block ${newGlobalVariables.blockNumber.toNumber() - 1}`);
|
|
519
496
|
|
|
520
497
|
// NB: separating the dbs because both should update the state
|
|
521
498
|
const publicProcessorFork = await this.worldState.fork();
|
|
@@ -525,28 +502,30 @@ export class Sequencer {
|
|
|
525
502
|
const processor = this.publicProcessorFactory.create(publicProcessorFork, historicalHeader, newGlobalVariables);
|
|
526
503
|
const blockBuildingTimer = new Timer();
|
|
527
504
|
const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
|
|
528
|
-
await blockBuilder.startNewBlock(
|
|
505
|
+
await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
|
|
529
506
|
|
|
530
507
|
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
531
|
-
processor.process(
|
|
532
|
-
validTxs,
|
|
533
|
-
blockSize,
|
|
534
|
-
blockBuilder,
|
|
535
|
-
this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork),
|
|
536
|
-
),
|
|
508
|
+
processor.process(validTxs, blockSize, this.txValidatorFactory.validatorForProcessedTxs(publicProcessorFork)),
|
|
537
509
|
);
|
|
538
510
|
if (failedTxs.length > 0) {
|
|
539
511
|
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
540
|
-
this.log.
|
|
512
|
+
this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
|
|
541
513
|
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
542
514
|
}
|
|
515
|
+
await blockBuilder.addTxs(processedTxs);
|
|
543
516
|
|
|
544
517
|
await interrupt?.(processedTxs);
|
|
545
518
|
|
|
546
519
|
// All real transactions have been added, set the block as full and complete the proving.
|
|
547
520
|
const block = await blockBuilder.setBlockCompleted();
|
|
548
521
|
|
|
549
|
-
return {
|
|
522
|
+
return {
|
|
523
|
+
block,
|
|
524
|
+
publicProcessorDuration,
|
|
525
|
+
numMsgs: l1ToL2Messages.length,
|
|
526
|
+
numProcessedTxs: processedTxs.length,
|
|
527
|
+
blockBuildingTimer,
|
|
528
|
+
};
|
|
550
529
|
} finally {
|
|
551
530
|
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
552
531
|
await publicProcessorFork.close();
|
|
@@ -569,21 +548,18 @@ export class Sequencer {
|
|
|
569
548
|
}))
|
|
570
549
|
private async buildBlockAndAttemptToPublish(
|
|
571
550
|
validTxs: Tx[],
|
|
572
|
-
proposalHeader:
|
|
573
|
-
historicalHeader:
|
|
551
|
+
proposalHeader: BlockHeader,
|
|
552
|
+
historicalHeader: BlockHeader | undefined,
|
|
574
553
|
): Promise<void> {
|
|
575
554
|
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
576
555
|
|
|
577
556
|
const newGlobalVariables = proposalHeader.globalVariables;
|
|
557
|
+
const blockNumber = newGlobalVariables.blockNumber.toNumber();
|
|
558
|
+
const slot = newGlobalVariables.slotNumber.toBigInt();
|
|
578
559
|
|
|
579
|
-
this.metrics.recordNewBlock(
|
|
560
|
+
this.metrics.recordNewBlock(blockNumber, validTxs.length);
|
|
580
561
|
const workTimer = new Timer();
|
|
581
|
-
this.setState(SequencerState.CREATING_BLOCK,
|
|
582
|
-
this.log.info(
|
|
583
|
-
`Building blockNumber=${newGlobalVariables.blockNumber.toNumber()} txCount=${
|
|
584
|
-
validTxs.length
|
|
585
|
-
} slotNumber=${newGlobalVariables.slotNumber.toNumber()}`,
|
|
586
|
-
);
|
|
562
|
+
this.setState(SequencerState.CREATING_BLOCK, slot);
|
|
587
563
|
|
|
588
564
|
/**
|
|
589
565
|
* BuildBlock is shared between the sequencer and the validator for re-execution
|
|
@@ -606,12 +582,8 @@ export class Sequencer {
|
|
|
606
582
|
};
|
|
607
583
|
|
|
608
584
|
try {
|
|
609
|
-
const
|
|
610
|
-
|
|
611
|
-
newGlobalVariables,
|
|
612
|
-
historicalHeader,
|
|
613
|
-
interrupt,
|
|
614
|
-
);
|
|
585
|
+
const buildBlockRes = await this.buildBlock(validTxs, newGlobalVariables, historicalHeader, interrupt);
|
|
586
|
+
const { block, publicProcessorDuration, numProcessedTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
|
|
615
587
|
|
|
616
588
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
617
589
|
// block being published before ours instead of just waiting on our block
|
|
@@ -619,43 +591,55 @@ export class Sequencer {
|
|
|
619
591
|
await this.publisher.validateBlockForSubmission(block.header);
|
|
620
592
|
|
|
621
593
|
const workDuration = workTimer.ms();
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
594
|
+
const blockStats: L2BlockBuiltStats = {
|
|
595
|
+
eventName: 'l2-block-built',
|
|
596
|
+
creator: this.publisher.getSenderAddress().toString(),
|
|
597
|
+
duration: workDuration,
|
|
598
|
+
publicProcessDuration: publicProcessorDuration,
|
|
599
|
+
rollupCircuitsDuration: blockBuildingTimer.ms(),
|
|
600
|
+
...block.getStats(),
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
const blockHash = block.hash();
|
|
604
|
+
const txHashes = validTxs.map(tx => tx.getTxHash());
|
|
605
|
+
this.log.info(`Built block ${block.number} with hash ${blockHash}`, {
|
|
606
|
+
blockHash,
|
|
607
|
+
globalVariables: block.header.globalVariables.toInspect(),
|
|
608
|
+
txHashes,
|
|
609
|
+
...blockStats,
|
|
610
|
+
});
|
|
635
611
|
|
|
636
612
|
if (this.isFlushing) {
|
|
637
|
-
this.log.
|
|
613
|
+
this.log.verbose(`Sequencer flushing completed`);
|
|
638
614
|
}
|
|
639
615
|
|
|
640
|
-
const txHashes = validTxs.map(tx => tx.getTxHash());
|
|
641
|
-
|
|
642
616
|
this.isFlushing = false;
|
|
643
|
-
this.log.
|
|
617
|
+
this.log.debug('Collecting attestations');
|
|
644
618
|
const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
|
|
645
619
|
const attestations = await this.collectAttestations(block, txHashes);
|
|
646
|
-
|
|
620
|
+
if (attestations !== undefined) {
|
|
621
|
+
this.log.verbose(`Collected ${attestations.length} attestations`);
|
|
622
|
+
}
|
|
647
623
|
stopCollectingAttestationsTimer();
|
|
648
|
-
this.log.verbose('Collecting proof quotes');
|
|
649
624
|
|
|
625
|
+
this.log.debug('Collecting proof quotes');
|
|
650
626
|
const proofQuote = await this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt());
|
|
651
|
-
this.log.info(proofQuote ? `Using proof quote ${inspect(proofQuote.payload)}` : 'No proof quote available');
|
|
652
627
|
|
|
653
628
|
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
654
629
|
this.metrics.recordPublishedBlock(workDuration);
|
|
655
630
|
this.log.info(
|
|
656
|
-
`
|
|
657
|
-
|
|
658
|
-
|
|
631
|
+
`Published rollup block ${
|
|
632
|
+
block.number
|
|
633
|
+
} with ${numProcessedTxs} transactions and ${numMsgs} messages in ${Math.ceil(workDuration)}ms`,
|
|
634
|
+
{
|
|
635
|
+
blockNumber: block.number,
|
|
636
|
+
blockHash: blockHash,
|
|
637
|
+
slot,
|
|
638
|
+
txCount: numProcessedTxs,
|
|
639
|
+
msgCount: numMsgs,
|
|
640
|
+
duration: Math.ceil(workDuration),
|
|
641
|
+
submitter: this.publisher.getSenderAddress().toString(),
|
|
642
|
+
},
|
|
659
643
|
);
|
|
660
644
|
} catch (err) {
|
|
661
645
|
this.metrics.recordFailedBlock();
|
|
@@ -676,11 +660,12 @@ export class Sequencer {
|
|
|
676
660
|
protected async collectAttestations(block: L2Block, txHashes: TxHash[]): Promise<Signature[] | undefined> {
|
|
677
661
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
|
|
678
662
|
const committee = await this.publisher.getCurrentEpochCommittee();
|
|
679
|
-
this.log.debug(`Attesting committee length ${committee.length}`);
|
|
680
663
|
|
|
681
664
|
if (committee.length === 0) {
|
|
682
|
-
this.log.verbose(`Attesting committee
|
|
665
|
+
this.log.verbose(`Attesting committee is empty`);
|
|
683
666
|
return undefined;
|
|
667
|
+
} else {
|
|
668
|
+
this.log.debug(`Attesting committee length is ${committee.length}`);
|
|
684
669
|
}
|
|
685
670
|
|
|
686
671
|
if (!this.validatorClient) {
|
|
@@ -691,22 +676,21 @@ export class Sequencer {
|
|
|
691
676
|
|
|
692
677
|
const numberOfRequiredAttestations = Math.floor((committee.length * 2) / 3) + 1;
|
|
693
678
|
|
|
694
|
-
this.log.
|
|
679
|
+
this.log.debug('Creating block proposal');
|
|
695
680
|
const proposal = await this.validatorClient.createBlockProposal(block.header, block.archive.root, txHashes);
|
|
696
681
|
if (!proposal) {
|
|
697
|
-
this.log.
|
|
682
|
+
this.log.warn(`Failed to create block proposal, skipping collecting attestations`);
|
|
698
683
|
return undefined;
|
|
699
684
|
}
|
|
700
685
|
|
|
701
686
|
const slotNumber = block.header.globalVariables.slotNumber.toBigInt();
|
|
702
687
|
|
|
703
688
|
this.setState(SequencerState.PUBLISHING_BLOCK_TO_PEERS, slotNumber);
|
|
704
|
-
this.log.
|
|
689
|
+
this.log.debug('Broadcasting block proposal to validators');
|
|
705
690
|
this.validatorClient.broadcastBlockProposal(proposal);
|
|
706
691
|
|
|
707
692
|
this.setState(SequencerState.WAITING_FOR_ATTESTATIONS, slotNumber);
|
|
708
693
|
const attestations = await this.validatorClient.collectAttestations(proposal, numberOfRequiredAttestations);
|
|
709
|
-
this.log.info(`Collected attestations from validators, number of attestations: ${attestations.length}`);
|
|
710
694
|
|
|
711
695
|
// note: the smart contract requires that the signatures are provided in the order of the committee
|
|
712
696
|
return orderAttestations(attestations, committee);
|
|
@@ -717,16 +701,17 @@ export class Sequencer {
|
|
|
717
701
|
// Find out which epoch we are currently in
|
|
718
702
|
const epochToProve = await this.publisher.getClaimableEpoch();
|
|
719
703
|
if (epochToProve === undefined) {
|
|
720
|
-
this.log.
|
|
704
|
+
this.log.debug(`No epoch to prove`);
|
|
721
705
|
return undefined;
|
|
722
706
|
}
|
|
723
707
|
|
|
724
708
|
// Get quotes for the epoch to be proven
|
|
725
709
|
const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
|
|
726
|
-
this.log.
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
710
|
+
this.log.verbose(`Retrieved ${quotes.length} quotes for slot ${slotNumber} epoch ${epochToProve}`, {
|
|
711
|
+
epochToProve,
|
|
712
|
+
slotNumber,
|
|
713
|
+
quotes: quotes.map(q => q.payload),
|
|
714
|
+
});
|
|
730
715
|
// ensure these quotes are still valid for the slot and have the contract validate them
|
|
731
716
|
const validQuotesPromise = Promise.all(
|
|
732
717
|
quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)),
|
|
@@ -741,9 +726,11 @@ export class Sequencer {
|
|
|
741
726
|
const sortedQuotes = validQuotes.sort(
|
|
742
727
|
(a: EpochProofQuote, b: EpochProofQuote) => a.payload.basisPointFee - b.payload.basisPointFee,
|
|
743
728
|
);
|
|
744
|
-
|
|
729
|
+
const quote = sortedQuotes[0];
|
|
730
|
+
this.log.info(`Selected proof quote for proof claim`, quote.payload);
|
|
731
|
+
return quote;
|
|
745
732
|
} catch (err) {
|
|
746
|
-
this.log.error(`Failed to create proof claim for previous epoch
|
|
733
|
+
this.log.error(`Failed to create proof claim for previous epoch`, err, { slotNumber });
|
|
747
734
|
return undefined;
|
|
748
735
|
}
|
|
749
736
|
}
|
|
@@ -788,7 +775,7 @@ export class Sequencer {
|
|
|
788
775
|
for (const tx of txs) {
|
|
789
776
|
const txSize = tx.getSize() - tx.clientIvcProof.clientIvcProofBuffer.length;
|
|
790
777
|
if (totalSize + txSize > maxSize) {
|
|
791
|
-
this.log.
|
|
778
|
+
this.log.debug(
|
|
792
779
|
`Dropping tx ${tx.getTxHash()} with estimated size ${txSize} due to exceeding ${maxSize} block size limit (currently at ${totalSize})`,
|
|
793
780
|
);
|
|
794
781
|
continue;
|
|
@@ -825,7 +812,7 @@ export class Sequencer {
|
|
|
825
812
|
p2p >= l2BlockSource.number &&
|
|
826
813
|
l1ToL2MessageSource >= l2BlockSource.number;
|
|
827
814
|
|
|
828
|
-
this.log.
|
|
815
|
+
this.log.debug(`Sequencer sync check ${result ? 'succeeded' : 'failed'}`, {
|
|
829
816
|
worldStateNumber: worldState.number,
|
|
830
817
|
worldStateHash: worldState.hash,
|
|
831
818
|
l2BlockSourceNumber: l2BlockSource.number,
|