@aztec/sequencer-client 0.69.0-devnet → 0.69.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.d.ts +2 -0
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +11 -1
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +3 -2
- package/dest/publisher/config.d.ts +4 -0
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +6 -1
- 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 +21 -2
- package/dest/publisher/l1-publisher.d.ts.map +1 -1
- package/dest/publisher/l1-publisher.js +91 -95
- package/dest/sequencer/index.d.ts +1 -0
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +2 -1
- package/dest/sequencer/sequencer.d.ts +21 -30
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +60 -130
- package/dest/sequencer/utils.d.ts +2 -2
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +3 -3
- package/dest/test/index.d.ts +18 -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.d.ts +2 -3
- package/dest/tx_validator/gas_validator.d.ts.map +1 -1
- package/dest/tx_validator/gas_validator.js +9 -22
- package/dest/tx_validator/nullifier_cache.d.ts +16 -0
- package/dest/tx_validator/nullifier_cache.d.ts.map +1 -0
- package/dest/tx_validator/nullifier_cache.js +24 -0
- package/dest/tx_validator/phases_validator.d.ts +2 -3
- package/dest/tx_validator/phases_validator.d.ts.map +1 -1
- package/dest/tx_validator/phases_validator.js +15 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +15 -14
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/dest/tx_validator/tx_validator_factory.js +41 -24
- package/package.json +23 -20
- package/src/client/sequencer-client.ts +5 -3
- package/src/config.ts +10 -0
- package/src/index.ts +2 -1
- package/src/publisher/config.ts +10 -0
- package/src/publisher/index.ts +0 -1
- package/src/publisher/l1-publisher.ts +119 -93
- package/src/sequencer/index.ts +1 -0
- package/src/sequencer/sequencer.ts +82 -193
- package/src/sequencer/utils.ts +2 -2
- package/src/test/index.ts +23 -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 +11 -24
- package/src/tx_validator/nullifier_cache.ts +29 -0
- package/src/tx_validator/phases_validator.ts +22 -33
- package/src/tx_validator/tx_validator_factory.ts +89 -40
- 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
|
@@ -4,11 +4,9 @@ import {
|
|
|
4
4
|
type L1ToL2MessageSource,
|
|
5
5
|
type L2Block,
|
|
6
6
|
type L2BlockSource,
|
|
7
|
-
type ProcessedTx,
|
|
8
7
|
SequencerConfigSchema,
|
|
9
8
|
Tx,
|
|
10
9
|
type TxHash,
|
|
11
|
-
type TxValidator,
|
|
12
10
|
type WorldStateSynchronizer,
|
|
13
11
|
} from '@aztec/circuit-types';
|
|
14
12
|
import type { AllowedElement, Signature, WorldStateSynchronizerStatus } from '@aztec/circuit-types/interfaces';
|
|
@@ -17,10 +15,13 @@ import {
|
|
|
17
15
|
AppendOnlyTreeSnapshot,
|
|
18
16
|
BlockHeader,
|
|
19
17
|
ContentCommitment,
|
|
18
|
+
type ContractDataSource,
|
|
20
19
|
GENESIS_ARCHIVE_ROOT,
|
|
20
|
+
Gas,
|
|
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';
|
|
@@ -37,9 +38,8 @@ 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
|
-
import {
|
|
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';
|
|
@@ -47,12 +47,6 @@ import { SequencerState, orderAttestations } from './utils.js';
|
|
|
47
47
|
|
|
48
48
|
export { SequencerState };
|
|
49
49
|
|
|
50
|
-
export type ShouldProposeArgs = {
|
|
51
|
-
pendingTxsCount?: number;
|
|
52
|
-
validTxsCount?: number;
|
|
53
|
-
processedTxsCount?: number;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
50
|
export class SequencerTooSlowError extends Error {
|
|
57
51
|
constructor(
|
|
58
52
|
public readonly currentState: SequencerState,
|
|
@@ -90,7 +84,8 @@ export class Sequencer {
|
|
|
90
84
|
private state = SequencerState.STOPPED;
|
|
91
85
|
private allowedInSetup: AllowedElement[] = getDefaultAllowedSetupFunctions();
|
|
92
86
|
private maxBlockSizeInBytes: number = 1024 * 1024;
|
|
93
|
-
private
|
|
87
|
+
private maxBlockGas: Gas = new Gas(10e9, 10e9);
|
|
88
|
+
protected processTxTime: number = 12;
|
|
94
89
|
private metrics: SequencerMetrics;
|
|
95
90
|
private isFlushing: boolean = false;
|
|
96
91
|
|
|
@@ -102,22 +97,22 @@ export class Sequencer {
|
|
|
102
97
|
protected enforceTimeTable: boolean = false;
|
|
103
98
|
|
|
104
99
|
constructor(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
100
|
+
protected publisher: L1Publisher,
|
|
101
|
+
protected validatorClient: ValidatorClient | undefined, // During migration the validator client can be inactive
|
|
102
|
+
protected globalsBuilder: GlobalVariableBuilder,
|
|
103
|
+
protected p2pClient: P2P,
|
|
104
|
+
protected worldState: WorldStateSynchronizer,
|
|
105
|
+
protected slasherClient: SlasherClient,
|
|
106
|
+
protected blockBuilderFactory: BlockBuilderFactory,
|
|
107
|
+
protected l2BlockSource: L2BlockSource,
|
|
108
|
+
protected l1ToL2MessageSource: L1ToL2MessageSource,
|
|
109
|
+
protected publicProcessorFactory: PublicProcessorFactory,
|
|
110
|
+
protected contractDataSource: ContractDataSource,
|
|
116
111
|
protected l1Constants: SequencerRollupConstants,
|
|
117
|
-
|
|
112
|
+
protected dateProvider: DateProvider,
|
|
118
113
|
telemetry: TelemetryClient,
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
protected config: SequencerConfig = {},
|
|
115
|
+
protected log = createLogger('sequencer'),
|
|
121
116
|
) {
|
|
122
117
|
this.updateConfig(config);
|
|
123
118
|
this.metrics = new SequencerMetrics(telemetry, () => this.state, 'Sequencer');
|
|
@@ -149,6 +144,12 @@ export class Sequencer {
|
|
|
149
144
|
if (config.minTxsPerBlock !== undefined) {
|
|
150
145
|
this.minTxsPerBLock = config.minTxsPerBlock;
|
|
151
146
|
}
|
|
147
|
+
if (config.maxDABlockGas !== undefined) {
|
|
148
|
+
this.maxBlockGas = new Gas(config.maxDABlockGas, this.maxBlockGas.l2Gas);
|
|
149
|
+
}
|
|
150
|
+
if (config.maxL2BlockGas !== undefined) {
|
|
151
|
+
this.maxBlockGas = new Gas(this.maxBlockGas.daGas, config.maxL2BlockGas);
|
|
152
|
+
}
|
|
152
153
|
if (config.coinbase) {
|
|
153
154
|
this._coinbase = config.coinbase;
|
|
154
155
|
}
|
|
@@ -179,7 +180,7 @@ export class Sequencer {
|
|
|
179
180
|
// How late into the slot can we be to start working
|
|
180
181
|
const initialTime = 2;
|
|
181
182
|
|
|
182
|
-
// How long it takes to
|
|
183
|
+
// How long it takes to get ready to start building
|
|
183
184
|
const blockPrepareTime = 1;
|
|
184
185
|
|
|
185
186
|
// How long it takes to for attestations to travel across the p2p layer.
|
|
@@ -218,9 +219,9 @@ export class Sequencer {
|
|
|
218
219
|
[SequencerState.SYNCHRONIZING]: this.aztecSlotDuration,
|
|
219
220
|
// We always want to allow the full slot to check if we are the proposer
|
|
220
221
|
[SequencerState.PROPOSER_CHECK]: this.aztecSlotDuration,
|
|
221
|
-
//
|
|
222
|
-
[SequencerState.
|
|
223
|
-
//
|
|
222
|
+
// How late we can start initializing a new block proposal
|
|
223
|
+
[SequencerState.INITIALIZING_PROPOSAL]: initialTime,
|
|
224
|
+
// When we start building a block
|
|
224
225
|
[SequencerState.CREATING_BLOCK]: initialTime + blockPrepareTime,
|
|
225
226
|
// We start collecting attestations after building the block
|
|
226
227
|
[SequencerState.COLLECTING_ATTESTATIONS]: initialTime + blockPrepareTime + processTxsTime + blockValidationTime,
|
|
@@ -323,25 +324,27 @@ export class Sequencer {
|
|
|
323
324
|
void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.GOVERNANCE);
|
|
324
325
|
void this.publisher.castVote(slot, newGlobalVariables.timestamp.toBigInt(), VoteType.SLASHING);
|
|
325
326
|
|
|
326
|
-
|
|
327
|
+
// Check the pool has enough txs to build a block
|
|
328
|
+
const pendingTxCount = this.p2pClient.getPendingTxCount();
|
|
329
|
+
if (pendingTxCount < this.minTxsPerBLock && !this.isFlushing) {
|
|
330
|
+
this.log.verbose(`Not enough txs to propose block. Got ${pendingTxCount} min ${this.minTxsPerBLock}.`, {
|
|
331
|
+
slot,
|
|
332
|
+
blockNumber: newBlockNumber,
|
|
333
|
+
});
|
|
334
|
+
await this.claimEpochProofRightIfAvailable(slot);
|
|
327
335
|
return;
|
|
328
336
|
}
|
|
329
337
|
|
|
338
|
+
this.setState(SequencerState.INITIALIZING_PROPOSAL, slot);
|
|
330
339
|
this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, {
|
|
331
340
|
chainTipArchive: new Fr(chainTipArchive),
|
|
332
341
|
blockNumber: newBlockNumber,
|
|
333
342
|
slot,
|
|
334
343
|
});
|
|
335
344
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
const pendingTxs = await this.p2pClient.getPendingTxs();
|
|
340
|
-
|
|
341
|
-
if (!this.shouldProposeBlock(historicalHeader, { pendingTxsCount: pendingTxs.length })) {
|
|
342
|
-
await this.claimEpochProofRightIfAvailable(slot);
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
+
// We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
|
|
346
|
+
// and also we may need to fetch more if we don't have enough valid txs.
|
|
347
|
+
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
345
348
|
|
|
346
349
|
// If I created a "partial" header here that should make our job much easier.
|
|
347
350
|
const proposalHeader = new BlockHeader(
|
|
@@ -353,35 +356,12 @@ export class Sequencer {
|
|
|
353
356
|
Fr.ZERO,
|
|
354
357
|
);
|
|
355
358
|
|
|
356
|
-
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here.
|
|
357
|
-
// TODO: We should validate only the number of txs we need to speed up this process.
|
|
358
|
-
const allValidTxs = await this.takeValidTxs(
|
|
359
|
-
pendingTxs,
|
|
360
|
-
this.txValidatorFactory.validatorForNewTxs(newGlobalVariables, this.allowedInSetup),
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
// TODO: We are taking the size of the tx from private-land, but we should be doing this after running
|
|
364
|
-
// public functions. Only reason why we do it here now is because the public processor and orchestrator
|
|
365
|
-
// are set up such that they require knowing the total number of txs in advance. Still, main reason for
|
|
366
|
-
// exceeding max block size in bytes is contract class registration, which happens in private-land. This
|
|
367
|
-
// may break if we start emitting lots of log data from public-land.
|
|
368
|
-
const validTxs = this.takeTxsWithinMaxSize(allValidTxs);
|
|
369
|
-
|
|
370
|
-
this.log.verbose(
|
|
371
|
-
`Collected ${validTxs.length} txs out of ${allValidTxs.length} valid txs out of ${pendingTxs.length} total pending txs for block ${newBlockNumber}`,
|
|
372
|
-
);
|
|
373
|
-
|
|
374
|
-
// Bail if we don't have enough valid txs
|
|
375
|
-
if (!this.shouldProposeBlock(historicalHeader, { validTxsCount: validTxs.length })) {
|
|
376
|
-
await this.claimEpochProofRightIfAvailable(slot);
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
359
|
try {
|
|
360
|
+
// TODO(palla/txs) Is the note below still valid? We don't seem to be doing any rollback in there.
|
|
381
361
|
// @note It is very important that the following function will FAIL and not just return early
|
|
382
362
|
// if it have made any state changes. If not, we won't rollback the state, and you will
|
|
383
363
|
// be in for a world of pain.
|
|
384
|
-
await this.buildBlockAndAttemptToPublish(
|
|
364
|
+
await this.buildBlockAndAttemptToPublish(pendingTxs, proposalHeader, historicalHeader);
|
|
385
365
|
} catch (err) {
|
|
386
366
|
this.log.error(`Error assembling block`, err, { blockNumber: newBlockNumber, slot });
|
|
387
367
|
}
|
|
@@ -469,64 +449,20 @@ export class Sequencer {
|
|
|
469
449
|
this.state = proposedState;
|
|
470
450
|
}
|
|
471
451
|
|
|
472
|
-
shouldProposeBlock(historicalHeader: BlockHeader | undefined, args: ShouldProposeArgs): boolean {
|
|
473
|
-
if (this.isFlushing) {
|
|
474
|
-
this.log.verbose(`Flushing all pending txs in new block`);
|
|
475
|
-
return true;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Compute time elapsed since the previous block
|
|
479
|
-
const lastBlockTime = historicalHeader?.globalVariables.timestamp.toNumber() || 0;
|
|
480
|
-
const currentTime = Math.floor(Date.now() / 1000);
|
|
481
|
-
const elapsedSinceLastBlock = currentTime - lastBlockTime;
|
|
482
|
-
this.log.debug(
|
|
483
|
-
`Last block mined at ${lastBlockTime} current time is ${currentTime} (elapsed ${elapsedSinceLastBlock})`,
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
// We need to have at least minTxsPerBLock txs.
|
|
487
|
-
if (args.pendingTxsCount !== undefined && args.pendingTxsCount < this.minTxsPerBLock) {
|
|
488
|
-
this.log.verbose(
|
|
489
|
-
`Not creating block because not enough txs in the pool (got ${args.pendingTxsCount} min ${this.minTxsPerBLock})`,
|
|
490
|
-
);
|
|
491
|
-
return false;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// Bail if we don't have enough valid txs
|
|
495
|
-
if (args.validTxsCount !== undefined && args.validTxsCount < this.minTxsPerBLock) {
|
|
496
|
-
this.log.verbose(
|
|
497
|
-
`Not creating block because not enough valid txs loaded from the pool (got ${args.validTxsCount} min ${this.minTxsPerBLock})`,
|
|
498
|
-
);
|
|
499
|
-
return false;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// TODO: This check should be processedTxs.length < this.minTxsPerBLock, so we don't publish a block with
|
|
503
|
-
// less txs than the minimum. But that'd cause the entire block to be aborted and retried. Instead, we should
|
|
504
|
-
// go back to the p2p pool and load more txs until we hit our minTxsPerBLock target. Only if there are no txs
|
|
505
|
-
// we should bail.
|
|
506
|
-
if (args.processedTxsCount === 0 && this.minTxsPerBLock > 0) {
|
|
507
|
-
this.log.verbose('No txs processed correctly to build block.');
|
|
508
|
-
return false;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
452
|
/**
|
|
515
453
|
* Build a block
|
|
516
454
|
*
|
|
517
455
|
* Shared between the sequencer and the validator for re-execution
|
|
518
456
|
*
|
|
519
|
-
* @param
|
|
457
|
+
* @param pendingTxs - The pending transactions to construct the block from
|
|
520
458
|
* @param newGlobalVariables - The global variables for the new block
|
|
521
459
|
* @param historicalHeader - The historical header of the parent
|
|
522
|
-
* @param interrupt - The interrupt callback, used to validate the block for submission and check if we should propose the block
|
|
523
460
|
* @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
|
|
524
461
|
*/
|
|
525
462
|
private async buildBlock(
|
|
526
|
-
|
|
463
|
+
pendingTxs: Iterable<Tx>,
|
|
527
464
|
newGlobalVariables: GlobalVariables,
|
|
528
465
|
historicalHeader?: BlockHeader,
|
|
529
|
-
interrupt?: (processedTxs: ProcessedTx[]) => Promise<void>,
|
|
530
466
|
opts: { validateOnly?: boolean } = {},
|
|
531
467
|
) {
|
|
532
468
|
const blockNumber = newGlobalVariables.blockNumber.toBigInt();
|
|
@@ -534,19 +470,9 @@ export class Sequencer {
|
|
|
534
470
|
|
|
535
471
|
this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
|
|
536
472
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(blockNumber);
|
|
473
|
+
const msgCount = l1ToL2Messages.length;
|
|
537
474
|
|
|
538
|
-
this.log.verbose(
|
|
539
|
-
`Building block ${blockNumber} with ${validTxs.length} txs and ${l1ToL2Messages.length} messages`,
|
|
540
|
-
{
|
|
541
|
-
msgCount: l1ToL2Messages.length,
|
|
542
|
-
txCount: validTxs.length,
|
|
543
|
-
slot,
|
|
544
|
-
blockNumber,
|
|
545
|
-
},
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
const numRealTxs = validTxs.length;
|
|
549
|
-
const blockSize = Math.max(2, numRealTxs);
|
|
475
|
+
this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, { slot, blockNumber, msgCount });
|
|
550
476
|
|
|
551
477
|
// Sync to the previous block at least
|
|
552
478
|
await this.worldState.syncImmediate(newGlobalVariables.blockNumber.toNumber() - 1);
|
|
@@ -570,18 +496,30 @@ export class Sequencer {
|
|
|
570
496
|
// We set the deadline for tx processing to the start of the CREATING_BLOCK phase, plus the expected time for tx processing.
|
|
571
497
|
// Deadline is only set if enforceTimeTable is enabled.
|
|
572
498
|
const processingEndTimeWithinSlot = this.timeTable[SequencerState.CREATING_BLOCK] + this.processTxTime;
|
|
573
|
-
const
|
|
499
|
+
const deadline = this.enforceTimeTable
|
|
574
500
|
? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
|
|
575
501
|
: undefined;
|
|
576
|
-
this.log.verbose(`Processing
|
|
502
|
+
this.log.verbose(`Processing pending txs`, {
|
|
577
503
|
slot,
|
|
578
504
|
slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
|
|
579
505
|
now: new Date(this.dateProvider.now()),
|
|
580
|
-
deadline
|
|
506
|
+
deadline,
|
|
581
507
|
});
|
|
582
|
-
|
|
508
|
+
|
|
509
|
+
const validators = createValidatorsForBlockBuilding(
|
|
510
|
+
publicProcessorFork,
|
|
511
|
+
this.contractDataSource,
|
|
512
|
+
newGlobalVariables,
|
|
513
|
+
!!this.config.enforceFees,
|
|
514
|
+
this.allowedInSetup,
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
// REFACTOR: Public processor should just handle processing, one tx at a time. It should be responsibility
|
|
518
|
+
// of the sequencer to update world state and iterate over txs. We should refactor this along with unifying the
|
|
519
|
+
// publicProcessorFork and orchestratorFork, to avoid doing tree insertions twice when building the block.
|
|
520
|
+
const limits = { deadline, maxTransactions: this.maxTxsPerBlock, maxBlockSize: this.maxBlockSizeInBytes };
|
|
583
521
|
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
584
|
-
processor.process(
|
|
522
|
+
processor.process(pendingTxs, limits, validators),
|
|
585
523
|
);
|
|
586
524
|
|
|
587
525
|
if (failedTxs.length > 0) {
|
|
@@ -609,8 +547,6 @@ export class Sequencer {
|
|
|
609
547
|
const duration = Number(end - start) / 1_000;
|
|
610
548
|
this.metrics.recordBlockBuilderTreeInsertions(duration);
|
|
611
549
|
|
|
612
|
-
await interrupt?.(processedTxs);
|
|
613
|
-
|
|
614
550
|
// All real transactions have been added, set the block as full and pad if needed
|
|
615
551
|
const block = await blockBuilder.setBlockCompleted();
|
|
616
552
|
|
|
@@ -618,7 +554,7 @@ export class Sequencer {
|
|
|
618
554
|
block,
|
|
619
555
|
publicProcessorDuration,
|
|
620
556
|
numMsgs: l1ToL2Messages.length,
|
|
621
|
-
|
|
557
|
+
numTxs: processedTxs.length,
|
|
622
558
|
blockBuildingTimer,
|
|
623
559
|
};
|
|
624
560
|
} finally {
|
|
@@ -642,7 +578,7 @@ export class Sequencer {
|
|
|
642
578
|
* @dev MUST throw instead of exiting early to ensure that world-state
|
|
643
579
|
* is being rolled back if the block is dropped.
|
|
644
580
|
*
|
|
645
|
-
* @param
|
|
581
|
+
* @param pendingTxs - Iterable of pending transactions to construct the block from
|
|
646
582
|
* @param proposalHeader - The partial header constructed for the proposal
|
|
647
583
|
* @param historicalHeader - The historical header of the parent
|
|
648
584
|
*/
|
|
@@ -650,7 +586,7 @@ export class Sequencer {
|
|
|
650
586
|
[Attributes.BLOCK_NUMBER]: proposalHeader.globalVariables.blockNumber.toNumber(),
|
|
651
587
|
}))
|
|
652
588
|
private async buildBlockAndAttemptToPublish(
|
|
653
|
-
|
|
589
|
+
pendingTxs: Iterable<Tx>,
|
|
654
590
|
proposalHeader: BlockHeader,
|
|
655
591
|
historicalHeader: BlockHeader | undefined,
|
|
656
592
|
): Promise<void> {
|
|
@@ -660,40 +596,19 @@ export class Sequencer {
|
|
|
660
596
|
const blockNumber = newGlobalVariables.blockNumber.toNumber();
|
|
661
597
|
const slot = newGlobalVariables.slotNumber.toBigInt();
|
|
662
598
|
|
|
663
|
-
this.metrics.recordNewBlock(blockNumber, validTxs.length);
|
|
599
|
+
// this.metrics.recordNewBlock(blockNumber, validTxs.length);
|
|
664
600
|
const workTimer = new Timer();
|
|
665
601
|
this.setState(SequencerState.CREATING_BLOCK, slot);
|
|
666
602
|
|
|
667
|
-
/**
|
|
668
|
-
* BuildBlock is shared between the sequencer and the validator for re-execution
|
|
669
|
-
* We use the interrupt callback to validate the block for submission and check if we should propose the block
|
|
670
|
-
*
|
|
671
|
-
* If we fail, we throw an error in order to roll back
|
|
672
|
-
*/
|
|
673
|
-
const interrupt = async (processedTxs: ProcessedTx[]) => {
|
|
674
|
-
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
675
|
-
|
|
676
|
-
if (
|
|
677
|
-
!this.shouldProposeBlock(historicalHeader, {
|
|
678
|
-
validTxsCount: validTxs.length,
|
|
679
|
-
processedTxsCount: processedTxs.length,
|
|
680
|
-
})
|
|
681
|
-
) {
|
|
682
|
-
// TODO: Roll back changes to world state
|
|
683
|
-
throw new Error('Should not propose the block');
|
|
684
|
-
}
|
|
685
|
-
};
|
|
686
|
-
|
|
687
603
|
// Start collecting proof quotes for the previous epoch if needed in the background
|
|
688
604
|
const proofQuotePromise = this.createProofClaimForPreviousEpoch(slot);
|
|
689
605
|
|
|
690
606
|
try {
|
|
691
|
-
const buildBlockRes = await this.buildBlock(
|
|
692
|
-
const { block, publicProcessorDuration,
|
|
607
|
+
const buildBlockRes = await this.buildBlock(pendingTxs, newGlobalVariables, historicalHeader);
|
|
608
|
+
const { block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer } = buildBlockRes;
|
|
693
609
|
|
|
694
610
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
695
611
|
// block being published before ours instead of just waiting on our block
|
|
696
|
-
|
|
697
612
|
await this.publisher.validateBlockForSubmission(block.header);
|
|
698
613
|
|
|
699
614
|
const workDuration = workTimer.ms();
|
|
@@ -707,8 +622,8 @@ export class Sequencer {
|
|
|
707
622
|
};
|
|
708
623
|
|
|
709
624
|
const blockHash = block.hash();
|
|
710
|
-
const txHashes =
|
|
711
|
-
this.log.info(`Built block ${block.number}
|
|
625
|
+
const txHashes = block.body.txEffects.map(tx => tx.txHash);
|
|
626
|
+
this.log.info(`Built block ${block.number} for slot ${slot} with ${numTxs} txs`, {
|
|
712
627
|
blockHash,
|
|
713
628
|
globalVariables: block.header.globalVariables.toInspect(),
|
|
714
629
|
txHashes,
|
|
@@ -734,14 +649,12 @@ export class Sequencer {
|
|
|
734
649
|
await this.publishL2Block(block, attestations, txHashes, proofQuote);
|
|
735
650
|
this.metrics.recordPublishedBlock(workDuration);
|
|
736
651
|
this.log.info(
|
|
737
|
-
`Published
|
|
738
|
-
block.number
|
|
739
|
-
} with ${numProcessedTxs} transactions and ${numMsgs} messages in ${Math.ceil(workDuration)}ms`,
|
|
652
|
+
`Published block ${block.number} with ${numTxs} txs and ${numMsgs} messages in ${Math.ceil(workDuration)}ms`,
|
|
740
653
|
{
|
|
741
654
|
blockNumber: block.number,
|
|
742
655
|
blockHash: blockHash,
|
|
743
656
|
slot,
|
|
744
|
-
txCount:
|
|
657
|
+
txCount: txHashes.length,
|
|
745
658
|
msgCount: numMsgs,
|
|
746
659
|
duration: Math.ceil(workDuration),
|
|
747
660
|
submitter: this.publisher.getSenderAddress().toString(),
|
|
@@ -859,42 +772,18 @@ export class Sequencer {
|
|
|
859
772
|
// Publishes new block to the network and awaits the tx to be mined
|
|
860
773
|
this.setState(SequencerState.PUBLISHING_BLOCK, block.header.globalVariables.slotNumber.toBigInt());
|
|
861
774
|
|
|
862
|
-
|
|
775
|
+
// Time out tx at the end of the slot
|
|
776
|
+
const slot = block.header.globalVariables.slotNumber.toNumber();
|
|
777
|
+
const txTimeoutAt = new Date((this.getSlotStartTimestamp(slot) + this.aztecSlotDuration) * 1000);
|
|
778
|
+
|
|
779
|
+
const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote, {
|
|
780
|
+
txTimeoutAt,
|
|
781
|
+
});
|
|
863
782
|
if (!publishedL2Block) {
|
|
864
783
|
throw new Error(`Failed to publish block ${block.number}`);
|
|
865
784
|
}
|
|
866
785
|
}
|
|
867
786
|
|
|
868
|
-
protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator<T>): Promise<T[]> {
|
|
869
|
-
const [valid, invalid] = await validator.validateTxs(txs);
|
|
870
|
-
if (invalid.length > 0) {
|
|
871
|
-
this.log.debug(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
|
|
872
|
-
await this.p2pClient.deleteTxs(Tx.getHashes(invalid));
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
return valid.slice(0, this.maxTxsPerBlock);
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
protected takeTxsWithinMaxSize(txs: Tx[]): Tx[] {
|
|
879
|
-
const maxSize = this.maxBlockSizeInBytes;
|
|
880
|
-
let totalSize = 0;
|
|
881
|
-
|
|
882
|
-
const toReturn: Tx[] = [];
|
|
883
|
-
for (const tx of txs) {
|
|
884
|
-
const txSize = tx.getSize() - tx.clientIvcProof.clientIvcProofBuffer.length;
|
|
885
|
-
if (totalSize + txSize > maxSize) {
|
|
886
|
-
this.log.debug(
|
|
887
|
-
`Dropping tx ${tx.getTxHash()} with estimated size ${txSize} due to exceeding ${maxSize} block size limit (currently at ${totalSize})`,
|
|
888
|
-
);
|
|
889
|
-
continue;
|
|
890
|
-
}
|
|
891
|
-
toReturn.push(tx);
|
|
892
|
-
totalSize += txSize;
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
return toReturn;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
787
|
@trackSpan(
|
|
899
788
|
'Sequencer.claimEpochProofRightIfAvailable',
|
|
900
789
|
slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }),
|
package/src/sequencer/utils.ts
CHANGED
|
@@ -19,9 +19,9 @@ export enum SequencerState {
|
|
|
19
19
|
*/
|
|
20
20
|
PROPOSER_CHECK = 'PROPOSER_CHECK',
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* Initializing the block proposal. Will move to CREATING_BLOCK if there are valid txs to include, or back to SYNCHRONIZING otherwise.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
INITIALIZING_PROPOSAL = 'INITIALIZING_PROPOSAL',
|
|
25
25
|
/**
|
|
26
26
|
* Creating a new L2 block. Includes processing public function calls and running rollup circuits. Will move to PUBLISHING_CONTRACT_DATA.
|
|
27
27
|
*/
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type PublicProcessorFactory } from '@aztec/simulator';
|
|
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 SequencerState } from '../sequencer/utils.js';
|
|
7
|
+
|
|
8
|
+
class TestSequencer_ extends Sequencer {
|
|
9
|
+
public override publicProcessorFactory!: PublicProcessorFactory;
|
|
10
|
+
public override timeTable!: Record<SequencerState, number>;
|
|
11
|
+
public override processTxTime!: number;
|
|
12
|
+
public override publisher!: L1Publisher;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type TestSequencer = TestSequencer_;
|
|
16
|
+
|
|
17
|
+
class TestSequencerClient_ extends SequencerClient {
|
|
18
|
+
public override sequencer!: TestSequencer;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TestSequencerClient = TestSequencerClient_;
|
|
22
|
+
|
|
23
|
+
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,4 +1,4 @@
|
|
|
1
|
-
import { type Tx, TxExecutionPhase, type TxValidator } from '@aztec/circuit-types';
|
|
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
4
|
import { computeFeePayerBalanceStorageSlot, getExecutionRequestsByPhase } from '@aztec/simulator';
|
|
@@ -27,25 +27,10 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
27
27
|
this.#gasFees = gasFees;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const skippedTxs: Tx[] = [];
|
|
34
|
-
|
|
35
|
-
for (const tx of txs) {
|
|
36
|
-
if (this.#shouldSkip(tx)) {
|
|
37
|
-
skippedTxs.push(tx);
|
|
38
|
-
} else if (await this.#validateTxFee(tx)) {
|
|
39
|
-
validTxs.push(tx);
|
|
40
|
-
} else {
|
|
41
|
-
invalidTxs.push(tx);
|
|
42
|
-
}
|
|
30
|
+
validateTx(tx: Tx): Promise<TxValidationResult> {
|
|
31
|
+
if (this.#shouldSkip(tx)) {
|
|
32
|
+
return Promise.resolve({ result: 'skipped', reason: ['Insufficient fee per gas'] });
|
|
43
33
|
}
|
|
44
|
-
|
|
45
|
-
return [validTxs, invalidTxs, skippedTxs];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
validateTx(tx: Tx): Promise<boolean> {
|
|
49
34
|
return this.#validateTxFee(tx);
|
|
50
35
|
}
|
|
51
36
|
|
|
@@ -57,20 +42,22 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
57
42
|
const notEnoughMaxFees =
|
|
58
43
|
maxFeesPerGas.feePerDaGas.lt(this.#gasFees.feePerDaGas) ||
|
|
59
44
|
maxFeesPerGas.feePerL2Gas.lt(this.#gasFees.feePerL2Gas);
|
|
45
|
+
|
|
60
46
|
if (notEnoughMaxFees) {
|
|
61
47
|
this.#log.warn(`Skipping transaction ${tx.getTxHash()} due to insufficient fee per gas`);
|
|
62
48
|
}
|
|
63
49
|
return notEnoughMaxFees;
|
|
64
50
|
}
|
|
65
51
|
|
|
66
|
-
async #validateTxFee(tx: Tx): Promise<
|
|
52
|
+
async #validateTxFee(tx: Tx): Promise<TxValidationResult> {
|
|
67
53
|
const feePayer = tx.data.feePayer;
|
|
68
54
|
// TODO(@spalladino) Eventually remove the is_zero condition as we should always charge fees to every tx
|
|
69
55
|
if (feePayer.isZero()) {
|
|
70
56
|
if (this.#enforceFees) {
|
|
71
57
|
this.#log.warn(`Rejecting transaction ${tx.getTxHash()} due to missing fee payer`);
|
|
58
|
+
return { result: 'invalid', reason: ['Missing fee payer'] };
|
|
72
59
|
} else {
|
|
73
|
-
return
|
|
60
|
+
return { result: 'valid' };
|
|
74
61
|
}
|
|
75
62
|
}
|
|
76
63
|
|
|
@@ -98,13 +85,13 @@ export class GasTxValidator implements TxValidator<Tx> {
|
|
|
98
85
|
|
|
99
86
|
const balance = claimFunctionCall ? initialBalance.add(claimFunctionCall.args[2]) : initialBalance;
|
|
100
87
|
if (balance.lt(feeLimit)) {
|
|
101
|
-
this.#log.
|
|
88
|
+
this.#log.warn(`Rejecting transaction due to not enough fee payer balance`, {
|
|
102
89
|
feePayer,
|
|
103
90
|
balance: balance.toBigInt(),
|
|
104
91
|
feeLimit: feeLimit.toBigInt(),
|
|
105
92
|
});
|
|
106
|
-
return
|
|
93
|
+
return { result: 'invalid', reason: ['Insufficient fee payer balance'] };
|
|
107
94
|
}
|
|
108
|
-
return
|
|
95
|
+
return { result: 'valid' };
|
|
109
96
|
}
|
|
110
97
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { MerkleTreeId, type MerkleTreeReadOperations } from '@aztec/circuit-types';
|
|
2
|
+
import { type NullifierSource } from '@aztec/p2p';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Implements a nullifier source by checking a DB and an in-memory collection.
|
|
6
|
+
* Intended for validating transactions as they are added to a block.
|
|
7
|
+
*/
|
|
8
|
+
export class NullifierCache implements NullifierSource {
|
|
9
|
+
nullifiers: Set<string>;
|
|
10
|
+
|
|
11
|
+
constructor(private db: MerkleTreeReadOperations) {
|
|
12
|
+
this.nullifiers = new Set();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
public async nullifiersExist(nullifiers: Buffer[]): Promise<boolean[]> {
|
|
16
|
+
const cacheResults = nullifiers.map(n => this.nullifiers.has(n.toString()));
|
|
17
|
+
const toCheckDb = nullifiers.filter((_n, index) => !cacheResults[index]);
|
|
18
|
+
const dbHits = await this.db.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, toCheckDb);
|
|
19
|
+
|
|
20
|
+
let dbIndex = 0;
|
|
21
|
+
return nullifiers.map((_n, index) => cacheResults[index] || dbHits[dbIndex++] !== undefined);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public addNullifiers(nullifiers: Buffer[]) {
|
|
25
|
+
for (const nullifier of nullifiers) {
|
|
26
|
+
this.nullifiers.add(nullifier.toString());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|