@aztec/sequencer-client 0.69.1 → 0.71.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 -0
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +4 -4
- package/dest/config.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.d.ts +9 -3
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +63 -77
- package/dest/sequencer/metrics.d.ts +2 -1
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +8 -2
- package/dest/sequencer/sequencer.d.ts +34 -35
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +62 -101
- package/dest/sequencer/timetable.d.ts +38 -0
- package/dest/sequencer/timetable.d.ts.map +1 -0
- package/dest/sequencer/timetable.js +96 -0
- package/dest/slasher/factory.d.ts.map +1 -1
- package/dest/slasher/factory.js +3 -3
- package/dest/slasher/slasher_client.d.ts.map +1 -1
- package/dest/slasher/slasher_client.js +3 -4
- package/dest/test/index.d.ts +17 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +8 -0
- package/dest/{publisher → test}/test-l1-publisher.d.ts +1 -1
- package/dest/test/test-l1-publisher.d.ts.map +1 -0
- package/dest/test/test-l1-publisher.js +11 -0
- package/dest/tx_validator/archive_cache.d.ts +14 -0
- package/dest/tx_validator/archive_cache.d.ts.map +1 -0
- package/dest/tx_validator/archive_cache.js +22 -0
- package/dest/tx_validator/gas_validator.js +2 -2
- package/dest/tx_validator/phases_validator.js +2 -2
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +9 -6
- package/package.json +22 -20
- package/src/client/sequencer-client.ts +6 -3
- package/src/config.ts +1 -1
- package/src/publisher/index.ts +0 -1
- package/src/publisher/l1-publisher.ts +84 -83
- package/src/sequencer/metrics.ts +11 -1
- package/src/sequencer/sequencer.ts +95 -141
- package/src/sequencer/timetable.ts +123 -0
- package/src/slasher/factory.ts +2 -3
- package/src/slasher/slasher_client.ts +2 -3
- package/src/test/index.ts +22 -0
- package/src/{publisher → test}/test-l1-publisher.ts +1 -1
- package/src/tx_validator/archive_cache.ts +27 -0
- package/src/tx_validator/gas_validator.ts +1 -1
- package/src/tx_validator/phases_validator.ts +1 -1
- package/src/tx_validator/tx_validator_factory.ts +8 -1
- package/dest/publisher/test-l1-publisher.d.ts.map +0 -1
- package/dest/publisher/test-l1-publisher.js +0 -11
- package/dest/publisher/utils.d.ts +0 -2
- package/dest/publisher/utils.d.ts.map +0 -1
- package/dest/publisher/utils.js +0 -13
- package/src/publisher/utils.ts +0 -14
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type GlobalVariables,
|
|
22
22
|
StateReference,
|
|
23
23
|
} from '@aztec/circuits.js';
|
|
24
|
+
import { prettyLogViemErrorMsg } from '@aztec/ethereum';
|
|
24
25
|
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
25
26
|
import { omit } from '@aztec/foundation/collection';
|
|
26
27
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -31,36 +32,22 @@ import { pickFromSchema } from '@aztec/foundation/schemas';
|
|
|
31
32
|
import { type DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
32
33
|
import { type P2P } from '@aztec/p2p';
|
|
33
34
|
import { type BlockBuilderFactory } from '@aztec/prover-client/block-builder';
|
|
34
|
-
import { type PublicProcessorFactory } from '@aztec/simulator';
|
|
35
|
-
import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec/telemetry-client';
|
|
35
|
+
import { type PublicProcessorFactory } from '@aztec/simulator/server';
|
|
36
|
+
import { Attributes, type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
36
37
|
import { type ValidatorClient } from '@aztec/validator-client';
|
|
37
38
|
|
|
38
39
|
import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
39
40
|
import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js';
|
|
40
|
-
import { prettyLogViemErrorMsg } from '../publisher/utils.js';
|
|
41
41
|
import { type SlasherClient } from '../slasher/slasher_client.js';
|
|
42
42
|
import { createValidatorsForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
|
|
43
43
|
import { getDefaultAllowedSetupFunctions } from './allowed.js';
|
|
44
44
|
import { type SequencerConfig } from './config.js';
|
|
45
45
|
import { SequencerMetrics } from './metrics.js';
|
|
46
|
+
import { SequencerTimetable, SequencerTooSlowError } from './timetable.js';
|
|
46
47
|
import { SequencerState, orderAttestations } from './utils.js';
|
|
47
48
|
|
|
48
49
|
export { SequencerState };
|
|
49
50
|
|
|
50
|
-
export class SequencerTooSlowError extends Error {
|
|
51
|
-
constructor(
|
|
52
|
-
public readonly currentState: SequencerState,
|
|
53
|
-
public readonly proposedState: SequencerState,
|
|
54
|
-
public readonly maxAllowedTime: number,
|
|
55
|
-
public readonly currentTime: number,
|
|
56
|
-
) {
|
|
57
|
-
super(
|
|
58
|
-
`Too far into slot to transition to ${proposedState} (max allowed: ${maxAllowedTime}s, time into slot: ${currentTime}s)`,
|
|
59
|
-
);
|
|
60
|
-
this.name = 'SequencerTooSlowError';
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
51
|
type SequencerRollupConstants = Pick<L1RollupConstants, 'ethereumSlotDuration' | 'l1GenesisTime' | 'slotDuration'>;
|
|
65
52
|
|
|
66
53
|
/**
|
|
@@ -76,7 +63,7 @@ export class Sequencer {
|
|
|
76
63
|
private runningPromise?: RunningPromise;
|
|
77
64
|
private pollingIntervalMs: number = 1000;
|
|
78
65
|
private maxTxsPerBlock = 32;
|
|
79
|
-
private
|
|
66
|
+
private minTxsPerBlock = 1;
|
|
80
67
|
private maxL1TxInclusionTimeIntoSlot = 0;
|
|
81
68
|
// TODO: zero values should not be allowed for the following 2 values in PROD
|
|
82
69
|
private _coinbase = EthAddress.ZERO;
|
|
@@ -84,35 +71,32 @@ export class Sequencer {
|
|
|
84
71
|
private state = SequencerState.STOPPED;
|
|
85
72
|
private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
|
|
86
73
|
private maxBlockSizeInBytes: number = 1024 * 1024;
|
|
87
|
-
private maxBlockGas: Gas = new Gas(
|
|
88
|
-
private processTxTime: number = 12;
|
|
74
|
+
private maxBlockGas: Gas = new Gas(100e9, 100e9);
|
|
89
75
|
private metrics: SequencerMetrics;
|
|
90
76
|
private isFlushing: boolean = false;
|
|
91
77
|
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*/
|
|
96
|
-
protected timeTable!: Record<SequencerState, number>;
|
|
78
|
+
/** The maximum number of seconds that the sequencer can be into a slot to transition to a particular state. */
|
|
79
|
+
protected timetable!: SequencerTimetable;
|
|
80
|
+
|
|
97
81
|
protected enforceTimeTable: boolean = false;
|
|
98
82
|
|
|
99
83
|
constructor(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
84
|
+
protected publisher: L1Publisher,
|
|
85
|
+
protected validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive
|
|
86
|
+
protected globalsBuilder: GlobalVariableBuilder,
|
|
87
|
+
protected p2pClient: P2P,
|
|
88
|
+
protected worldState: WorldStateSynchronizer,
|
|
89
|
+
protected slasherClient: SlasherClient,
|
|
90
|
+
protected blockBuilderFactory: BlockBuilderFactory,
|
|
91
|
+
protected l2BlockSource: L2BlockSource,
|
|
92
|
+
protected l1ToL2MessageSource: L1ToL2MessageSource,
|
|
93
|
+
protected publicProcessorFactory: PublicProcessorFactory,
|
|
94
|
+
protected contractDataSource: ContractDataSource,
|
|
111
95
|
protected l1Constants: SequencerRollupConstants,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
96
|
+
protected dateProvider: DateProvider,
|
|
97
|
+
protected config: SequencerConfig = {},
|
|
98
|
+
telemetry: TelemetryClient = getTelemetryClient(),
|
|
99
|
+
protected log = createLogger('sequencer'),
|
|
116
100
|
) {
|
|
117
101
|
this.updateConfig(config);
|
|
118
102
|
this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
|
|
@@ -142,7 +126,7 @@ export class Sequencer {
|
|
|
142
126
|
this.maxTxsPerBlock = config.maxTxsPerBlock;
|
|
143
127
|
}
|
|
144
128
|
if (config.minTxsPerBlock !== undefined) {
|
|
145
|
-
this.
|
|
129
|
+
this.minTxsPerBlock = config.minTxsPerBlock;
|
|
146
130
|
}
|
|
147
131
|
if (config.maxDABlockGas !== undefined) {
|
|
148
132
|
this.maxBlockGas = new Gas(config.maxDABlockGas, this.maxBlockGas.l2Gas);
|
|
@@ -168,7 +152,9 @@ export class Sequencer {
|
|
|
168
152
|
if (config.maxL1TxInclusionTimeIntoSlot !== undefined) {
|
|
169
153
|
this.maxL1TxInclusionTimeIntoSlot = config.maxL1TxInclusionTimeIntoSlot;
|
|
170
154
|
}
|
|
171
|
-
|
|
155
|
+
if (config.enforceTimeTable !== undefined) {
|
|
156
|
+
this.enforceTimeTable = config.enforceTimeTable;
|
|
157
|
+
}
|
|
172
158
|
|
|
173
159
|
this.setTimeTable();
|
|
174
160
|
|
|
@@ -177,60 +163,15 @@ export class Sequencer {
|
|
|
177
163
|
}
|
|
178
164
|
|
|
179
165
|
private setTimeTable() {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
|
|
190
|
-
// but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
|
|
191
|
-
// we can just post in the very last second of the L1 slot.
|
|
192
|
-
const l1PublishingTime = this.l1Constants.ethereumSlotDuration - this.maxL1TxInclusionTimeIntoSlot;
|
|
193
|
-
|
|
194
|
-
// How much time we spend validating and processing a block after building it
|
|
195
|
-
const blockValidationTime = 1;
|
|
196
|
-
|
|
197
|
-
// How much time we have left in the slot for actually processing txs and building the block.
|
|
198
|
-
const remainingTimeInSlot =
|
|
199
|
-
this.aztecSlotDuration -
|
|
200
|
-
initialTime -
|
|
201
|
-
blockPrepareTime -
|
|
202
|
-
l1PublishingTime -
|
|
203
|
-
2 * attestationPropagationTime -
|
|
204
|
-
blockValidationTime;
|
|
205
|
-
|
|
206
|
-
// Check that numbers make sense
|
|
207
|
-
if (this.enforceTimeTable && remainingTimeInSlot < 0) {
|
|
208
|
-
throw new Error(`Not enough time for block building in ${this.aztecSlotDuration}s slot`);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// How much time we have for actually processing txs. Note that we need both the sequencer and the validators to execute txs.
|
|
212
|
-
const processTxsTime = remainingTimeInSlot / 2;
|
|
213
|
-
this.processTxTime = processTxsTime;
|
|
214
|
-
|
|
215
|
-
const newTimeTable: Record<SequencerState, number> = {
|
|
216
|
-
// No checks needed for any of these transitions
|
|
217
|
-
[SequencerState.STOPPED]: this.aztecSlotDuration,
|
|
218
|
-
[SequencerState.IDLE]: this.aztecSlotDuration,
|
|
219
|
-
[SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
|
|
220
|
-
// We always want to allow the full slot to check if we are the proposer
|
|
221
|
-
[SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration,
|
|
222
|
-
// How late we can start initializing a new block proposal
|
|
223
|
-
[SequencerState.INITIALIZING_PROPOSAL]: initialTime,
|
|
224
|
-
// When we start building a block
|
|
225
|
-
[SequencerState.CREATING_BLOCK]: initialTime + blockPrepareTime,
|
|
226
|
-
// We start collecting attestations after building the block
|
|
227
|
-
[SequencerState.COLLECTING_ATTESTATIONS]: initialTime + blockPrepareTime + processTxsTime + blockValidationTime,
|
|
228
|
-
// We publish the block after collecting attestations
|
|
229
|
-
[SequencerState.PUBLISHING_BLOCK]: this.aztecSlotDuration - l1PublishingTime,
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
this.log.verbose(`Sequencer time table updated with ${processTxsTime}s for processing txs`, newTimeTable);
|
|
233
|
-
this.timeTable = newTimeTable;
|
|
166
|
+
this.timetable = new SequencerTimetable(
|
|
167
|
+
this.l1Constants.ethereumSlotDuration,
|
|
168
|
+
this.aztecSlotDuration,
|
|
169
|
+
this.maxL1TxInclusionTimeIntoSlot,
|
|
170
|
+
this.enforceTimeTable,
|
|
171
|
+
this.metrics,
|
|
172
|
+
this.log,
|
|
173
|
+
);
|
|
174
|
+
this.log.verbose(`Sequencer timetable updated`, { enforceTimeTable: this.enforceTimeTable });
|
|
234
175
|
}
|
|
235
176
|
|
|
236
177
|
/**
|
|
@@ -326,8 +267,8 @@ export class Sequencer {
|
|
|
326
267
|
|
|
327
268
|
// Check the pool has enough txs to build a block
|
|
328
269
|
const pendingTxCount = this.p2pClient.getPendingTxCount();
|
|
329
|
-
if (pendingTxCount < this.
|
|
330
|
-
this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.
|
|
270
|
+
if (pendingTxCount < this.minTxsPerBlock && !this.isFlushing) {
|
|
271
|
+
this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.minTxsPerBlock}.`, {
|
|
331
272
|
slot,
|
|
332
273
|
blockNumber: newBlockNumber,
|
|
333
274
|
});
|
|
@@ -404,29 +345,6 @@ export class Sequencer {
|
|
|
404
345
|
}
|
|
405
346
|
}
|
|
406
347
|
|
|
407
|
-
doIHaveEnoughTimeLeft(proposedState: SequencerState, secondsIntoSlot: number): boolean {
|
|
408
|
-
if (!this.enforceTimeTable) {
|
|
409
|
-
return true;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const maxAllowedTime = this.timeTable[proposedState];
|
|
413
|
-
if (maxAllowedTime === this.aztecSlotDuration) {
|
|
414
|
-
return true;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
const bufferSeconds = maxAllowedTime - secondsIntoSlot;
|
|
418
|
-
|
|
419
|
-
if (bufferSeconds < 0) {
|
|
420
|
-
this.log.debug(`Too far into slot to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
|
|
421
|
-
return false;
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
this.metrics.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), proposedState);
|
|
425
|
-
|
|
426
|
-
this.log.trace(`Enough time to transition to ${proposedState}`, { maxAllowedTime, secondsIntoSlot });
|
|
427
|
-
return true;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
348
|
/**
|
|
431
349
|
* Sets the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
432
350
|
* @param proposedState - The new state to transition to.
|
|
@@ -442,9 +360,7 @@ export class Sequencer {
|
|
|
442
360
|
return;
|
|
443
361
|
}
|
|
444
362
|
const secondsIntoSlot = this.getSecondsIntoSlot(currentSlotNumber);
|
|
445
|
-
|
|
446
|
-
throw new SequencerTooSlowError(this.state, proposedState, this.timeTable[proposedState], secondsIntoSlot);
|
|
447
|
-
}
|
|
363
|
+
this.timetable.assertTimeLeft(proposedState, secondsIntoSlot);
|
|
448
364
|
this.log.debug(`Transitioning from ${this.state} to ${proposedState}`);
|
|
449
365
|
this.state = proposedState;
|
|
450
366
|
}
|
|
@@ -459,7 +375,7 @@ export class Sequencer {
|
|
|
459
375
|
* @param historicalHeader - The historical header of the parent
|
|
460
376
|
* @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
|
|
461
377
|
*/
|
|
462
|
-
|
|
378
|
+
protected async buildBlock(
|
|
463
379
|
pendingTxs: Iterable<Tx>,
|
|
464
380
|
newGlobalVariables: GlobalVariables,
|
|
465
381
|
historicalHeader?: BlockHeader,
|
|
@@ -472,7 +388,12 @@ export class Sequencer {
|
|
|
472
388
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
473
389
|
const msgCount = l1ToL2Messages.length;
|
|
474
390
|
|
|
475
|
-
this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, {
|
|
391
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, {
|
|
392
|
+
slot,
|
|
393
|
+
blockNumber,
|
|
394
|
+
msgCount,
|
|
395
|
+
validator: opts.validateOnly,
|
|
396
|
+
});
|
|
476
397
|
|
|
477
398
|
// Sync to the previous block at least
|
|
478
399
|
await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
|
|
@@ -493,12 +414,17 @@ export class Sequencer {
|
|
|
493
414
|
const blockBuilder = this.blockBuilderFactory.create(orchestratorFork);
|
|
494
415
|
await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages);
|
|
495
416
|
|
|
496
|
-
//
|
|
417
|
+
// Deadline for processing depends on whether we're proposing a block
|
|
418
|
+
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
419
|
+
const processingEndTimeWithinSlot = opts.validateOnly
|
|
420
|
+
? this.timetable.getValidatorReexecTimeEnd(secondsIntoSlot)
|
|
421
|
+
: this.timetable.getBlockProposalExecTimeEnd(secondsIntoSlot);
|
|
422
|
+
|
|
497
423
|
// Deadline is only set if enforceTimeTable is enabled.
|
|
498
|
-
const processingEndTimeWithinSlot = this.timeTable[SequencerState.CREATING_BLOCK] + this.processTxTime;
|
|
499
424
|
const deadline = this.enforceTimeTable
|
|
500
425
|
? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
|
|
501
426
|
: undefined;
|
|
427
|
+
|
|
502
428
|
this.log.verbose(`Processing pending txs`, {
|
|
503
429
|
slot,
|
|
504
430
|
slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
|
|
@@ -514,15 +440,20 @@ export class Sequencer {
|
|
|
514
440
|
this.allowedInSetup,
|
|
515
441
|
);
|
|
516
442
|
|
|
517
|
-
//
|
|
443
|
+
// TODO(#11000): Public processor should just handle processing, one tx at a time. It should be responsibility
|
|
518
444
|
// of the sequencer to update world state and iterate over txs. We should refactor this along with unifying the
|
|
519
445
|
// publicProcessorFork and orchestratorFork, to avoid doing tree insertions twice when building the block.
|
|
520
|
-
const
|
|
446
|
+
const proposerLimits = {
|
|
447
|
+
maxTransactions: this.maxTxsPerBlock,
|
|
448
|
+
maxBlockSize: this.maxBlockSizeInBytes,
|
|
449
|
+
maxBlockGas: this.maxBlockGas,
|
|
450
|
+
};
|
|
451
|
+
const limits = opts.validateOnly ? { deadline } : { deadline, ...proposerLimits };
|
|
521
452
|
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
522
453
|
processor.process(pendingTxs, limits, validators),
|
|
523
454
|
);
|
|
524
455
|
|
|
525
|
-
if (failedTxs.length > 0) {
|
|
456
|
+
if (!opts.validateOnly && failedTxs.length > 0) {
|
|
526
457
|
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
527
458
|
this.log.verbose(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
|
|
528
459
|
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
@@ -531,11 +462,11 @@ export class Sequencer {
|
|
|
531
462
|
if (
|
|
532
463
|
!opts.validateOnly && // We check for minTxCount only if we are proposing a block, not if we are validating it
|
|
533
464
|
!this.isFlushing && // And we skip the check when flushing, since we want all pending txs to go out, no matter if too few
|
|
534
|
-
this.
|
|
535
|
-
processedTxs.length < this.
|
|
465
|
+
this.minTxsPerBlock !== undefined &&
|
|
466
|
+
processedTxs.length < this.minTxsPerBlock
|
|
536
467
|
) {
|
|
537
468
|
this.log.warn(
|
|
538
|
-
`Block ${blockNumber} has too few txs to be proposed (got ${processedTxs.length} but required ${this.
|
|
469
|
+
`Block ${blockNumber} has too few txs to be proposed (got ${processedTxs.length} but required ${this.minTxsPerBlock})`,
|
|
539
470
|
{ slot, blockNumber, processedTxCount: processedTxs.length },
|
|
540
471
|
);
|
|
541
472
|
throw new Error(`Block has too few successful txs to be proposed`);
|
|
@@ -550,11 +481,16 @@ export class Sequencer {
|
|
|
550
481
|
// All real transactions have been added, set the block as full and pad if needed
|
|
551
482
|
const block = await blockBuilder.setBlockCompleted();
|
|
552
483
|
|
|
484
|
+
// How much public gas was processed
|
|
485
|
+
const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
|
|
486
|
+
|
|
553
487
|
return {
|
|
554
488
|
block,
|
|
489
|
+
publicGas,
|
|
555
490
|
publicProcessorDuration,
|
|
556
491
|
numMsgs: l1ToL2Messages.length,
|
|
557
492
|
numTxs: processedTxs.length,
|
|
493
|
+
numFailedTxs: failedTxs.length,
|
|
558
494
|
blockBuildingTimer,
|
|
559
495
|
};
|
|
560
496
|
} finally {
|
|
@@ -566,7 +502,8 @@ export class Sequencer {
|
|
|
566
502
|
await publicProcessorFork.close();
|
|
567
503
|
await orchestratorFork.close();
|
|
568
504
|
} catch (err) {
|
|
569
|
-
|
|
505
|
+
// This can happen if the sequencer is stopped before we hit this timeout.
|
|
506
|
+
this.log.warn(`Error closing forks for block processing`, err);
|
|
570
507
|
}
|
|
571
508
|
}, 5000);
|
|
572
509
|
}
|
|
@@ -605,7 +542,7 @@ export class Sequencer {
|
|
|
605
542
|
|
|
606
543
|
try {
|
|
607
544
|
const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables, historicalHeader);
|
|
608
|
-
const { block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
|
|
545
|
+
const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
|
|
609
546
|
|
|
610
547
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
611
548
|
// block being published before ours instead of just waiting on our block
|
|
@@ -647,16 +584,19 @@ export class Sequencer {
|
|
|
647
584
|
const proofQuote = await proofQuotePromise;
|
|
648
585
|
|
|
649
586
|
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
650
|
-
this.metrics.recordPublishedBlock(workDuration);
|
|
587
|
+
this.metrics.recordPublishedBlock(workDuration, publicGas.l2Gas);
|
|
588
|
+
const duration = Math.ceil(workDuration);
|
|
589
|
+
const manaPerSecond = Math.ceil((publicGas.l2Gas * 1000) / duration);
|
|
651
590
|
this.log.info(
|
|
652
|
-
`Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${
|
|
591
|
+
`Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${duration} ms at ${manaPerSecond} mana/s`,
|
|
653
592
|
{
|
|
593
|
+
publicGas,
|
|
654
594
|
blockNumber: block.number,
|
|
655
595
|
blockHash: blockHash,
|
|
656
596
|
slot,
|
|
657
597
|
txCount: txHashes.length,
|
|
658
598
|
msgCount: numMsgs,
|
|
659
|
-
duration
|
|
599
|
+
duration,
|
|
660
600
|
submitter: this.publisher.getSenderAddress().toString(),
|
|
661
601
|
},
|
|
662
602
|
);
|
|
@@ -707,7 +647,15 @@ export class Sequencer {
|
|
|
707
647
|
this.log.debug('Broadcasting block proposal to validators');
|
|
708
648
|
this.validatorClient.broadcastBlockProposal(proposal);
|
|
709
649
|
|
|
710
|
-
const
|
|
650
|
+
const attestationTimeAllowed = this.enforceTimeTable
|
|
651
|
+
? this.timetable.getMaxAllowedTime(SequencerState.PUBLISHING_BLOCK)!
|
|
652
|
+
: this.aztecSlotDuration;
|
|
653
|
+
const attestationDeadline = new Date(this.dateProvider.now() + attestationTimeAllowed * 1000);
|
|
654
|
+
const attestations = await this.validatorClient.collectAttestations(
|
|
655
|
+
proposal,
|
|
656
|
+
numberOfRequiredAttestations,
|
|
657
|
+
attestationDeadline,
|
|
658
|
+
);
|
|
711
659
|
|
|
712
660
|
// note: the smart contract requires that the signatures are provided in the order of the committee
|
|
713
661
|
return orderAttestations(attestations, committee);
|
|
@@ -772,7 +720,13 @@ export class Sequencer {
|
|
|
772
720
|
// Publishes new block to the network and awaits the tx to be mined
|
|
773
721
|
this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber.toBigInt());
|
|
774
722
|
|
|
775
|
-
|
|
723
|
+
// Time out tx at the end of the slot
|
|
724
|
+
const slot = block.header.globalVariables.slotNumber.toNumber();
|
|
725
|
+
const txTimeoutAt = new Date((this.getSlotStartTimestamp(slot) + this.aztecSlotDuration) * 1000);
|
|
726
|
+
|
|
727
|
+
const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote, {
|
|
728
|
+
txTimeoutAt,
|
|
729
|
+
});
|
|
776
730
|
if (!publishedL2Block) {
|
|
777
731
|
throw new Error(`Failed to publish block ${block.number}`);
|
|
778
732
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createLogger } from '@aztec/aztec.js';
|
|
2
|
+
|
|
3
|
+
import { type SequencerMetrics } from './metrics.js';
|
|
4
|
+
import { SequencerState } from './utils.js';
|
|
5
|
+
|
|
6
|
+
export class SequencerTimetable {
|
|
7
|
+
/** How late into the slot can we be to start working */
|
|
8
|
+
public readonly initialTime = 3;
|
|
9
|
+
|
|
10
|
+
/** How long it takes to get ready to start building */
|
|
11
|
+
public readonly blockPrepareTime = 1;
|
|
12
|
+
|
|
13
|
+
/** How long it takes to for proposals and attestations to travel across the p2p layer (one-way) */
|
|
14
|
+
public readonly attestationPropagationTime = 2;
|
|
15
|
+
|
|
16
|
+
/** How much time we spend validating and processing a block after building it, and assembling the proposal to send to attestors */
|
|
17
|
+
public readonly blockValidationTime = 1;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How long it takes to get a published block into L1. L1 builders typically accept txs up to 4 seconds into their slot,
|
|
21
|
+
* but we'll timeout sooner to give it more time to propagate (remember we also have blobs!). Still, when working in anvil,
|
|
22
|
+
* we can just post in the very last second of the L1 slot and still expect the tx to be accepted.
|
|
23
|
+
*/
|
|
24
|
+
public readonly l1PublishingTime;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly ethereumSlotDuration: number,
|
|
28
|
+
private readonly aztecSlotDuration: number,
|
|
29
|
+
private readonly maxL1TxInclusionTimeIntoSlot: number,
|
|
30
|
+
private readonly enforce: boolean = true,
|
|
31
|
+
private readonly metrics?: SequencerMetrics,
|
|
32
|
+
private readonly log = createLogger('sequencer:timetable'),
|
|
33
|
+
) {
|
|
34
|
+
this.l1PublishingTime = this.ethereumSlotDuration - this.maxL1TxInclusionTimeIntoSlot;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private get afterBlockBuildingTimeNeededWithoutReexec() {
|
|
38
|
+
return this.blockValidationTime + this.attestationPropagationTime * 2 + this.l1PublishingTime;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public getBlockProposalExecTimeEnd(secondsIntoSlot: number): number {
|
|
42
|
+
// We are N seconds into the slot. We need to account for `afterBlockBuildingTimeNeededWithoutReexec` seconds,
|
|
43
|
+
// send then split the remaining time between the re-execution and the block building.
|
|
44
|
+
const maxAllowed = this.aztecSlotDuration - this.afterBlockBuildingTimeNeededWithoutReexec;
|
|
45
|
+
const available = maxAllowed - secondsIntoSlot;
|
|
46
|
+
const executionTimeEnd = secondsIntoSlot + available / 2;
|
|
47
|
+
this.log.debug(`Block proposal execution time deadline is ${executionTimeEnd}`, {
|
|
48
|
+
secondsIntoSlot,
|
|
49
|
+
maxAllowed,
|
|
50
|
+
available,
|
|
51
|
+
executionTimeEnd,
|
|
52
|
+
});
|
|
53
|
+
return executionTimeEnd;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private get afterBlockReexecTimeNeeded() {
|
|
57
|
+
return this.attestationPropagationTime + this.l1PublishingTime;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public getValidatorReexecTimeEnd(secondsIntoSlot: number): number {
|
|
61
|
+
// We need to leave for `afterBlockReexecTimeNeeded` seconds available.
|
|
62
|
+
const validationTimeEnd = this.aztecSlotDuration - this.afterBlockReexecTimeNeeded;
|
|
63
|
+
this.log.debug(`Validator re-execution time deadline is ${validationTimeEnd}`, {
|
|
64
|
+
secondsIntoSlot,
|
|
65
|
+
validationTimeEnd,
|
|
66
|
+
});
|
|
67
|
+
return validationTimeEnd;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public getMaxAllowedTime(state: SequencerState): number | undefined {
|
|
71
|
+
switch (state) {
|
|
72
|
+
case SequencerState.STOPPED:
|
|
73
|
+
case SequencerState.IDLE:
|
|
74
|
+
case SequencerState.SYNCHRONIZING:
|
|
75
|
+
case SequencerState.PROPOSER_CHECK:
|
|
76
|
+
return; // We don't really care about times for this states
|
|
77
|
+
case SequencerState.INITIALIZING_PROPOSAL:
|
|
78
|
+
return this.initialTime;
|
|
79
|
+
case SequencerState.CREATING_BLOCK:
|
|
80
|
+
return this.initialTime + this.blockPrepareTime;
|
|
81
|
+
case SequencerState.COLLECTING_ATTESTATIONS:
|
|
82
|
+
return this.aztecSlotDuration - this.l1PublishingTime - 2 * this.attestationPropagationTime;
|
|
83
|
+
case SequencerState.PUBLISHING_BLOCK:
|
|
84
|
+
return this.aztecSlotDuration - this.l1PublishingTime;
|
|
85
|
+
default: {
|
|
86
|
+
const _exhaustiveCheck: never = state;
|
|
87
|
+
throw new Error(`Unexpected state: ${state}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public assertTimeLeft(newState: SequencerState, secondsIntoSlot: number) {
|
|
93
|
+
if (!this.enforce) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const maxAllowedTime = this.getMaxAllowedTime(newState);
|
|
98
|
+
if (maxAllowedTime === undefined) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const bufferSeconds = maxAllowedTime - secondsIntoSlot;
|
|
103
|
+
if (bufferSeconds < 0) {
|
|
104
|
+
throw new SequencerTooSlowError(newState, maxAllowedTime, secondsIntoSlot);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
this.metrics?.recordStateTransitionBufferMs(Math.floor(bufferSeconds * 1000), newState);
|
|
108
|
+
this.log.trace(`Enough time to transition to ${newState}`, { maxAllowedTime, secondsIntoSlot });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class SequencerTooSlowError extends Error {
|
|
113
|
+
constructor(
|
|
114
|
+
public readonly proposedState: SequencerState,
|
|
115
|
+
public readonly maxAllowedTime: number,
|
|
116
|
+
public readonly currentTime: number,
|
|
117
|
+
) {
|
|
118
|
+
super(
|
|
119
|
+
`Too far into slot for ${proposedState} (time into slot ${currentTime}s greater than ${maxAllowedTime}s allowance)`,
|
|
120
|
+
);
|
|
121
|
+
this.name = 'SequencerTooSlowError';
|
|
122
|
+
}
|
|
123
|
+
}
|
package/src/slasher/factory.ts
CHANGED
|
@@ -4,8 +4,7 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
4
4
|
import { type AztecKVStore } from '@aztec/kv-store';
|
|
5
5
|
import { type DataStoreConfig } from '@aztec/kv-store/config';
|
|
6
6
|
import { createStore } from '@aztec/kv-store/lmdb';
|
|
7
|
-
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
8
|
-
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
|
|
7
|
+
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
9
8
|
|
|
10
9
|
import { SlasherClient } from './slasher_client.js';
|
|
11
10
|
import { type SlasherConfig } from './slasher_client.js';
|
|
@@ -13,7 +12,7 @@ import { type SlasherConfig } from './slasher_client.js';
|
|
|
13
12
|
export const createSlasherClient = async (
|
|
14
13
|
_config: SlasherConfig & DataStoreConfig & L1ContractsConfig & L1ReaderConfig,
|
|
15
14
|
l2BlockSource: L2BlockSource,
|
|
16
|
-
telemetry: TelemetryClient =
|
|
15
|
+
telemetry: TelemetryClient = getTelemetryClient(),
|
|
17
16
|
deps: { store?: AztecKVStore } = {},
|
|
18
17
|
) => {
|
|
19
18
|
const config = { ..._config };
|
|
@@ -12,8 +12,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
12
12
|
import { createLogger } from '@aztec/foundation/log';
|
|
13
13
|
import { type AztecKVStore, type AztecMap, type AztecSingleton } from '@aztec/kv-store';
|
|
14
14
|
import { SlashFactoryAbi } from '@aztec/l1-artifacts';
|
|
15
|
-
import { type TelemetryClient, WithTracer } from '@aztec/telemetry-client';
|
|
16
|
-
import { NoopTelemetryClient } from '@aztec/telemetry-client/noop';
|
|
15
|
+
import { type TelemetryClient, WithTracer, getTelemetryClient } from '@aztec/telemetry-client';
|
|
17
16
|
|
|
18
17
|
import {
|
|
19
18
|
type Chain,
|
|
@@ -113,7 +112,7 @@ export class SlasherClient extends WithTracer {
|
|
|
113
112
|
private config: SlasherConfig & L1ContractsConfig & L1ReaderConfig,
|
|
114
113
|
private store: AztecKVStore,
|
|
115
114
|
private l2BlockSource: L2BlockSource,
|
|
116
|
-
telemetry: TelemetryClient =
|
|
115
|
+
telemetry: TelemetryClient = getTelemetryClient(),
|
|
117
116
|
private log = createLogger('slasher'),
|
|
118
117
|
) {
|
|
119
118
|
super(telemetry, 'slasher');
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PublicProcessorFactory } from '@aztec/simulator/server';
|
|
2
|
+
|
|
3
|
+
import { SequencerClient } from '../client/sequencer-client.js';
|
|
4
|
+
import { type L1Publisher } from '../publisher/l1-publisher.js';
|
|
5
|
+
import { Sequencer } from '../sequencer/sequencer.js';
|
|
6
|
+
import { type SequencerTimetable } from '../sequencer/timetable.js';
|
|
7
|
+
|
|
8
|
+
class TestSequencer_ extends Sequencer {
|
|
9
|
+
public override publicProcessorFactory!: PublicProcessorFactory;
|
|
10
|
+
public override timetable!: SequencerTimetable;
|
|
11
|
+
public override publisher!: L1Publisher;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type TestSequencer = TestSequencer_;
|
|
15
|
+
|
|
16
|
+
class TestSequencerClient_ extends SequencerClient {
|
|
17
|
+
public override sequencer!: TestSequencer;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type TestSequencerClient = TestSequencerClient_;
|
|
21
|
+
|
|
22
|
+
export * from './test-l1-publisher.js';
|
|
@@ -3,7 +3,7 @@ import { type Delayer, withDelayer } from '@aztec/ethereum/test';
|
|
|
3
3
|
|
|
4
4
|
import { type Chain, type HttpTransport, type PrivateKeyAccount, type WalletClient } from 'viem';
|
|
5
5
|
|
|
6
|
-
import { L1Publisher } from '
|
|
6
|
+
import { L1Publisher } from '../publisher/l1-publisher.js';
|
|
7
7
|
|
|
8
8
|
export class TestL1Publisher extends L1Publisher {
|
|
9
9
|
public delayer: Delayer | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MerkleTreeId, type MerkleTreeReadOperations } from '@aztec/circuit-types';
|
|
2
|
+
import { type Fr } from '@aztec/circuits.js';
|
|
3
|
+
import { type ArchiveSource } from '@aztec/p2p';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Implements an archive source by checking a DB and an in-memory collection.
|
|
7
|
+
* Intended for validating transactions as they are added to a block.
|
|
8
|
+
*/
|
|
9
|
+
export class ArchiveCache implements ArchiveSource {
|
|
10
|
+
archives: Map<string, bigint>;
|
|
11
|
+
|
|
12
|
+
constructor(private db: MerkleTreeReadOperations) {
|
|
13
|
+
this.archives = new Map<string, bigint>();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public async getArchiveIndices(archives: Fr[]): Promise<(bigint | undefined)[]> {
|
|
17
|
+
const toCheckDb = archives.filter(n => !this.archives.has(n.toString()));
|
|
18
|
+
const dbHits = await this.db.findLeafIndices(MerkleTreeId.ARCHIVE, toCheckDb);
|
|
19
|
+
dbHits.forEach((x, index) => {
|
|
20
|
+
if (x !== undefined) {
|
|
21
|
+
this.archives.set(toCheckDb[index].toString(), x);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return archives.map(n => this.archives.get(n.toString()));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Tx, TxExecutionPhase, type TxValidationResult, type TxValidator } from '@aztec/circuit-types';
|
|
2
2
|
import { type AztecAddress, type Fr, FunctionSelector, type GasFees } from '@aztec/circuits.js';
|
|
3
3
|
import { createLogger } from '@aztec/foundation/log';
|
|
4
|
-
import { computeFeePayerBalanceStorageSlot, getExecutionRequestsByPhase } from '@aztec/simulator';
|
|
4
|
+
import { computeFeePayerBalanceStorageSlot, getExecutionRequestsByPhase } from '@aztec/simulator/server';
|
|
5
5
|
|
|
6
6
|
/** Provides a view into public contract state */
|
|
7
7
|
export interface PublicStateSource {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '@aztec/circuit-types';
|
|
9
9
|
import { type ContractDataSource } from '@aztec/circuits.js';
|
|
10
10
|
import { createLogger } from '@aztec/foundation/log';
|
|
11
|
-
import { ContractsDataSourcePublicDB, getExecutionRequestsByPhase } from '@aztec/simulator';
|
|
11
|
+
import { ContractsDataSourcePublicDB, getExecutionRequestsByPhase } from '@aztec/simulator/server';
|
|
12
12
|
|
|
13
13
|
export class PhasesTxValidator implements TxValidator<Tx> {
|
|
14
14
|
#log = createLogger('sequencer:tx_validator:tx_phases');
|