@aztec/sequencer-client 0.87.6 → 1.0.0-nightly.20250604
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 +5 -5
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +8 -8
- package/dest/global_variable_builder/global_builder.d.ts +4 -1
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +14 -2
- package/dest/index.d.ts +1 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/config.js +1 -4
- package/dest/publisher/sequencer-publisher.d.ts +4 -4
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +23 -14
- package/dest/sequencer/block_builder.d.ts +33 -0
- package/dest/sequencer/block_builder.d.ts.map +1 -0
- package/dest/sequencer/block_builder.js +109 -0
- package/dest/sequencer/index.d.ts +1 -0
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +1 -0
- package/dest/sequencer/sequencer.d.ts +17 -64
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +74 -173
- package/dest/sequencer/utils.d.ts +2 -2
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +6 -4
- package/dest/tx_validator/tx_validator_factory.d.ts +2 -6
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -1
- package/package.json +26 -25
- package/src/client/sequencer-client.ts +12 -20
- package/src/global_variable_builder/global_builder.ts +16 -2
- package/src/index.ts +1 -2
- package/src/publisher/config.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +32 -21
- package/src/sequencer/block_builder.ts +192 -0
- package/src/sequencer/index.ts +1 -0
- package/src/sequencer/sequencer.ts +96 -221
- package/src/sequencer/utils.ts +14 -6
- package/src/tx_validator/tx_validator_factory.ts +2 -4
- package/dest/slasher/factory.d.ts +0 -7
- package/dest/slasher/factory.d.ts.map +0 -1
- package/dest/slasher/factory.js +0 -8
- package/dest/slasher/index.d.ts +0 -3
- package/dest/slasher/index.d.ts.map +0 -1
- package/dest/slasher/index.js +0 -2
- package/dest/slasher/slasher_client.d.ts +0 -75
- package/dest/slasher/slasher_client.d.ts.map +0 -1
- package/dest/slasher/slasher_client.js +0 -135
- package/src/slasher/factory.ts +0 -15
- package/src/slasher/index.ts +0 -2
- package/src/slasher/slasher_client.ts +0 -199
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { L2Block } from '@aztec/aztec.js';
|
|
2
2
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
3
|
-
import type
|
|
3
|
+
import { FormattedViemError, type ViemPublicClient } from '@aztec/ethereum';
|
|
4
4
|
import { omit } from '@aztec/foundation/collection';
|
|
5
5
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
6
|
-
import type { Signature } from '@aztec/foundation/eth-signature';
|
|
7
6
|
import { Fr } from '@aztec/foundation/fields';
|
|
8
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
9
8
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
10
|
-
import { type DateProvider, Timer
|
|
9
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
11
10
|
import type { P2P } from '@aztec/p2p';
|
|
12
11
|
import { getDefaultAllowedSetupFunctions } from '@aztec/p2p/msg_validators';
|
|
13
|
-
import type {
|
|
14
|
-
import type { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
12
|
+
import type { SlasherClient } from '@aztec/slasher';
|
|
15
13
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
16
|
-
import type { L2BlockSource } from '@aztec/stdlib/block';
|
|
17
|
-
import type { ContractDataSource } from '@aztec/stdlib/contract';
|
|
14
|
+
import type { CommitteeAttestation, L2BlockSource } from '@aztec/stdlib/block';
|
|
18
15
|
import type { L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
19
16
|
import { Gas } from '@aztec/stdlib/gas';
|
|
20
17
|
import {
|
|
21
18
|
type AllowedElement,
|
|
19
|
+
type BuildBlockOptions,
|
|
20
|
+
type IFullNodeBlockBuilder,
|
|
22
21
|
SequencerConfigSchema,
|
|
23
22
|
type WorldStateSynchronizer,
|
|
24
23
|
} from '@aztec/stdlib/interfaces/server';
|
|
@@ -27,7 +26,14 @@ import type { BlockProposalOptions } from '@aztec/stdlib/p2p';
|
|
|
27
26
|
import { pickFromSchema } from '@aztec/stdlib/schemas';
|
|
28
27
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
29
28
|
import { MerkleTreeId } from '@aztec/stdlib/trees';
|
|
30
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
ContentCommitment,
|
|
31
|
+
type FailedTx,
|
|
32
|
+
GlobalVariables,
|
|
33
|
+
ProposedBlockHeader,
|
|
34
|
+
Tx,
|
|
35
|
+
type TxHash,
|
|
36
|
+
} from '@aztec/stdlib/tx';
|
|
31
37
|
import {
|
|
32
38
|
Attributes,
|
|
33
39
|
L1Metrics,
|
|
@@ -40,8 +46,6 @@ import type { ValidatorClient } from '@aztec/validator-client';
|
|
|
40
46
|
|
|
41
47
|
import type { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
42
48
|
import { type SequencerPublisher, VoteType } from '../publisher/sequencer-publisher.js';
|
|
43
|
-
import type { SlasherClient } from '../slasher/slasher_client.js';
|
|
44
|
-
import { createValidatorForBlockBuilding } from '../tx_validator/tx_validator_factory.js';
|
|
45
49
|
import type { SequencerConfig } from './config.js';
|
|
46
50
|
import { SequencerMetrics } from './metrics.js';
|
|
47
51
|
import { SequencerTimetable, SequencerTooSlowError } from './timetable.js';
|
|
@@ -89,15 +93,13 @@ export class Sequencer {
|
|
|
89
93
|
protected p2pClient: P2P,
|
|
90
94
|
protected worldState: WorldStateSynchronizer,
|
|
91
95
|
protected slasherClient: SlasherClient,
|
|
92
|
-
protected blockBuilderFactory: BlockBuilderFactory,
|
|
93
96
|
protected l2BlockSource: L2BlockSource,
|
|
94
97
|
protected l1ToL2MessageSource: L1ToL2MessageSource,
|
|
95
|
-
protected
|
|
96
|
-
protected contractDataSource: ContractDataSource,
|
|
98
|
+
protected blockBuilder: IFullNodeBlockBuilder,
|
|
97
99
|
protected l1Constants: SequencerRollupConstants,
|
|
98
100
|
protected dateProvider: DateProvider,
|
|
99
101
|
protected config: SequencerConfig = {},
|
|
100
|
-
telemetry: TelemetryClient = getTelemetryClient(),
|
|
102
|
+
protected telemetry: TelemetryClient = getTelemetryClient(),
|
|
101
103
|
protected log = createLogger('sequencer'),
|
|
102
104
|
) {
|
|
103
105
|
this.metrics = new SequencerMetrics(
|
|
@@ -113,9 +115,6 @@ export class Sequencer {
|
|
|
113
115
|
[publisher.getSenderAddress()],
|
|
114
116
|
);
|
|
115
117
|
|
|
116
|
-
// Register the block builder with the validator client for re-execution
|
|
117
|
-
this.validatorClient?.registerBlockBuilder(this.buildBlockFromProposal.bind(this));
|
|
118
|
-
|
|
119
118
|
// Register the slasher on the publisher to fetch slashing payloads
|
|
120
119
|
this.publisher.registerSlashPayloadGetter(this.slasherClient.getSlashPayload.bind(this.slasherClient));
|
|
121
120
|
}
|
|
@@ -124,8 +123,8 @@ export class Sequencer {
|
|
|
124
123
|
return this.metrics.tracer;
|
|
125
124
|
}
|
|
126
125
|
|
|
127
|
-
public
|
|
128
|
-
return this.validatorClient?.
|
|
126
|
+
public getValidatorAddresses() {
|
|
127
|
+
return this.validatorClient?.getValidatorAddresses();
|
|
129
128
|
}
|
|
130
129
|
|
|
131
130
|
/**
|
|
@@ -217,7 +216,7 @@ export class Sequencer {
|
|
|
217
216
|
this.metrics.stop();
|
|
218
217
|
await this.validatorClient?.stop();
|
|
219
218
|
await this.runningPromise?.stop();
|
|
220
|
-
this.slasherClient.stop();
|
|
219
|
+
await this.slasherClient.stop();
|
|
221
220
|
this.publisher.interrupt();
|
|
222
221
|
this.setState(SequencerState.STOPPED, 0n, true /** force */);
|
|
223
222
|
this.l1Metrics.stop();
|
|
@@ -271,10 +270,19 @@ export class Sequencer {
|
|
|
271
270
|
// If we cannot find a tip archive, assume genesis.
|
|
272
271
|
const chainTipArchive = chainTip.archive;
|
|
273
272
|
|
|
274
|
-
const slot =
|
|
273
|
+
const { slot } = this.publisher.epochCache.getEpochAndSlotInNextSlot();
|
|
275
274
|
this.metrics.observeSlotChange(slot, this.publisher.getSenderAddress().toString());
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
|
|
276
|
+
const proposerInNextSlot = await this.publisher.epochCache.getProposerAttesterAddressInNextSlot();
|
|
277
|
+
const validatorAddresses = this.validatorClient!.getValidatorAddresses();
|
|
278
|
+
|
|
279
|
+
// If get proposer in next slot is undefined, then there is no proposer set, and it is in free for all (sandbox) so we continue
|
|
280
|
+
// If we calculate a proposer in the next slot, and it is not us, then stop
|
|
281
|
+
if (proposerInNextSlot !== undefined && !validatorAddresses.some(addr => addr.equals(proposerInNextSlot))) {
|
|
282
|
+
this.log.debug(`Cannot propose block ${newBlockNumber}`, {
|
|
283
|
+
us: validatorAddresses,
|
|
284
|
+
proposer: proposerInNextSlot,
|
|
285
|
+
});
|
|
278
286
|
return;
|
|
279
287
|
}
|
|
280
288
|
|
|
@@ -316,14 +324,22 @@ export class Sequencer {
|
|
|
316
324
|
|
|
317
325
|
let finishedFlushing = false;
|
|
318
326
|
const pendingTxCount = await this.p2pClient.getPendingTxCount();
|
|
327
|
+
this.log.debug(`Pending tx count: ${pendingTxCount}`);
|
|
319
328
|
if (pendingTxCount >= this.minTxsPerBlock || this.isFlushing) {
|
|
320
329
|
// We don't fetch exactly maxTxsPerBlock txs here because we may not need all of them if we hit a limit before,
|
|
321
330
|
// and also we may need to fetch more if we don't have enough valid txs.
|
|
322
331
|
const pendingTxs = this.p2pClient.iteratePendingTxs();
|
|
323
332
|
|
|
324
|
-
await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables).catch(
|
|
325
|
-
|
|
326
|
-
|
|
333
|
+
await this.buildBlockAndEnqueuePublish(pendingTxs, proposalHeader, newGlobalVariables, proposerInNextSlot).catch(
|
|
334
|
+
err => {
|
|
335
|
+
if (err instanceof FormattedViemError) {
|
|
336
|
+
this.log.verbose(`Unable to build/enqueue block ${err.message}`);
|
|
337
|
+
return;
|
|
338
|
+
} else {
|
|
339
|
+
this.log.error(`Error building/enqueuing block`, err, { blockNumber: newBlockNumber, slot });
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
);
|
|
327
343
|
finishedFlushing = true;
|
|
328
344
|
} else {
|
|
329
345
|
this.log.verbose(
|
|
@@ -374,29 +390,6 @@ export class Sequencer {
|
|
|
374
390
|
return this.publisher.getForwarderAddress();
|
|
375
391
|
}
|
|
376
392
|
|
|
377
|
-
/**
|
|
378
|
-
* Checks if we can propose at the next block and returns the slot number if we can.
|
|
379
|
-
* @param tipArchive - The archive of the previous block.
|
|
380
|
-
* @param proposalBlockNumber - The block number of the proposal.
|
|
381
|
-
* @returns The slot number if we can propose at the next block, otherwise undefined.
|
|
382
|
-
*/
|
|
383
|
-
async slotForProposal(tipArchive: Buffer, proposalBlockNumber: bigint): Promise<bigint | undefined> {
|
|
384
|
-
const result = await this.publisher.canProposeAtNextEthBlock(tipArchive);
|
|
385
|
-
|
|
386
|
-
if (!result) {
|
|
387
|
-
return undefined;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const [slot, blockNumber] = result;
|
|
391
|
-
|
|
392
|
-
if (proposalBlockNumber !== blockNumber) {
|
|
393
|
-
const msg = `Sequencer block number mismatch. Expected ${proposalBlockNumber} but got ${blockNumber}.`;
|
|
394
|
-
this.log.warn(msg);
|
|
395
|
-
throw new Error(msg);
|
|
396
|
-
}
|
|
397
|
-
return slot;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
393
|
/**
|
|
401
394
|
* Sets the sequencer state and checks if we have enough time left in the slot to transition to the new state.
|
|
402
395
|
* @param proposedState - The new state to transition to.
|
|
@@ -417,175 +410,32 @@ export class Sequencer {
|
|
|
417
410
|
this.state = proposedState;
|
|
418
411
|
}
|
|
419
412
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
* @param pendingTxs - The pending transactions to construct the block from
|
|
424
|
-
* @param newGlobalVariables - The global variables for the new block
|
|
425
|
-
* @param opts - Whether to just validate the block as a validator, as opposed to building it as a proposal
|
|
426
|
-
*/
|
|
427
|
-
protected async buildBlock(
|
|
428
|
-
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
429
|
-
newGlobalVariables: GlobalVariables,
|
|
430
|
-
opts: { validateOnly?: boolean } = {},
|
|
431
|
-
) {
|
|
432
|
-
const blockNumber = newGlobalVariables.blockNumber.toNumber();
|
|
433
|
-
const slot = newGlobalVariables.slotNumber.toBigInt();
|
|
434
|
-
this.log.debug(`Requesting L1 to L2 messages from contract for block ${blockNumber}`);
|
|
435
|
-
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(blockNumber));
|
|
436
|
-
const msgCount = l1ToL2Messages.length;
|
|
437
|
-
|
|
438
|
-
this.log.verbose(`Building block ${blockNumber} for slot ${slot}`, {
|
|
439
|
-
slot,
|
|
440
|
-
blockNumber,
|
|
441
|
-
msgCount,
|
|
442
|
-
validator: opts.validateOnly,
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
// Sync to the previous block at least. If we cannot sync to that block because the archiver hasn't caught up,
|
|
446
|
-
// we keep retrying until the reexecution deadline. Note that this could only happen when we are a validator,
|
|
447
|
-
// for if we are the proposer, then world-state should already be caught up, as we check this earlier.
|
|
448
|
-
await retryUntil(
|
|
449
|
-
() => this.worldState.syncImmediate(blockNumber - 1, true).then(syncedTo => syncedTo >= blockNumber - 1),
|
|
450
|
-
'sync to previous block',
|
|
451
|
-
this.timetable.getValidatorReexecTimeEnd(),
|
|
452
|
-
0.1,
|
|
453
|
-
);
|
|
454
|
-
this.log.debug(`Synced to previous block ${blockNumber - 1}`);
|
|
455
|
-
|
|
456
|
-
// NB: separating the dbs because both should update the state
|
|
457
|
-
const publicProcessorDBFork = await this.worldState.fork();
|
|
458
|
-
const orchestratorDBFork = await this.worldState.fork();
|
|
459
|
-
|
|
460
|
-
const previousBlockHeader =
|
|
461
|
-
(await this.l2BlockSource.getBlock(blockNumber - 1))?.header ?? orchestratorDBFork.getInitialHeader();
|
|
462
|
-
|
|
463
|
-
try {
|
|
464
|
-
const processor = this.publicProcessorFactory.create(publicProcessorDBFork, newGlobalVariables, true);
|
|
465
|
-
const blockBuildingTimer = new Timer();
|
|
466
|
-
const blockBuilder = this.blockBuilderFactory.create(orchestratorDBFork);
|
|
467
|
-
await blockBuilder.startNewBlock(newGlobalVariables, l1ToL2Messages, previousBlockHeader);
|
|
468
|
-
|
|
469
|
-
// Deadline for processing depends on whether we're proposing a block
|
|
470
|
-
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
471
|
-
const processingEndTimeWithinSlot = opts.validateOnly
|
|
472
|
-
? this.timetable.getValidatorReexecTimeEnd(secondsIntoSlot)
|
|
473
|
-
: this.timetable.getBlockProposalExecTimeEnd(secondsIntoSlot);
|
|
474
|
-
|
|
475
|
-
// Deadline is only set if enforceTimeTable is enabled.
|
|
476
|
-
const deadline = this.enforceTimeTable
|
|
477
|
-
? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
|
|
478
|
-
: undefined;
|
|
479
|
-
|
|
480
|
-
this.log.verbose(`Processing pending txs`, {
|
|
481
|
-
slot,
|
|
482
|
-
slotStart: new Date(this.getSlotStartTimestamp(slot) * 1000),
|
|
483
|
-
now: new Date(this.dateProvider.now()),
|
|
484
|
-
deadline,
|
|
485
|
-
});
|
|
486
|
-
|
|
487
|
-
const validator = createValidatorForBlockBuilding(
|
|
488
|
-
publicProcessorDBFork,
|
|
489
|
-
this.contractDataSource,
|
|
490
|
-
newGlobalVariables,
|
|
491
|
-
this.txPublicSetupAllowList,
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
// TODO(#11000): Public processor should just handle processing, one tx at a time. It should be responsibility
|
|
495
|
-
// of the sequencer to update world state and iterate over txs. We should refactor this along with unifying the
|
|
496
|
-
// publicProcessorFork and orchestratorFork, to avoid doing tree insertions twice when building the block.
|
|
497
|
-
const proposerLimits = {
|
|
498
|
-
maxTransactions: this.maxTxsPerBlock,
|
|
499
|
-
maxBlockSize: this.maxBlockSizeInBytes,
|
|
500
|
-
maxBlockGas: this.maxBlockGas,
|
|
501
|
-
};
|
|
502
|
-
const limits = opts.validateOnly ? { deadline } : { deadline, ...proposerLimits };
|
|
503
|
-
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
|
|
504
|
-
processor.process(pendingTxs, limits, validator),
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
if (!opts.validateOnly && failedTxs.length > 0) {
|
|
508
|
-
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
509
|
-
const failedTxHashes = await Tx.getHashes(failedTxData);
|
|
510
|
-
this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
|
|
511
|
-
await this.p2pClient.deleteTxs(failedTxHashes);
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (
|
|
515
|
-
!opts.validateOnly && // We check for minTxCount only if we are proposing a block, not if we are validating it
|
|
516
|
-
!this.isFlushing && // And we skip the check when flushing, since we want all pending txs to go out, no matter if too few
|
|
517
|
-
this.minTxsPerBlock !== undefined &&
|
|
518
|
-
processedTxs.length < this.minTxsPerBlock
|
|
519
|
-
) {
|
|
520
|
-
this.log.warn(
|
|
521
|
-
`Block ${blockNumber} has too few txs to be proposed (got ${processedTxs.length} but required ${this.minTxsPerBlock})`,
|
|
522
|
-
{ slot, blockNumber, processedTxCount: processedTxs.length },
|
|
523
|
-
);
|
|
524
|
-
throw new Error(`Block has too few successful txs to be proposed`);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
const start = process.hrtime.bigint();
|
|
528
|
-
await blockBuilder.addTxs(processedTxs);
|
|
529
|
-
const end = process.hrtime.bigint();
|
|
530
|
-
const duration = Number(end - start) / 1_000;
|
|
531
|
-
this.metrics.recordBlockBuilderTreeInsertions(duration);
|
|
532
|
-
|
|
533
|
-
// All real transactions have been added, set the block as full and pad if needed
|
|
534
|
-
const block = await blockBuilder.setBlockCompleted();
|
|
535
|
-
|
|
536
|
-
// How much public gas was processed
|
|
537
|
-
const publicGas = processedTxs.reduce((acc, tx) => acc.add(tx.gasUsed.publicGas), Gas.empty());
|
|
538
|
-
|
|
539
|
-
return {
|
|
540
|
-
block,
|
|
541
|
-
publicGas,
|
|
542
|
-
publicProcessorDuration,
|
|
543
|
-
numMsgs: l1ToL2Messages.length,
|
|
544
|
-
numTxs: processedTxs.length,
|
|
545
|
-
numFailedTxs: failedTxs.length,
|
|
546
|
-
blockBuildingTimer,
|
|
547
|
-
usedTxs,
|
|
548
|
-
};
|
|
549
|
-
} finally {
|
|
550
|
-
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
551
|
-
// We wait a bit to close the forks since the processor may still be working on a dangling tx
|
|
552
|
-
// which was interrupted due to the processingDeadline being hit.
|
|
553
|
-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
554
|
-
setTimeout(async () => {
|
|
555
|
-
try {
|
|
556
|
-
await publicProcessorDBFork.close();
|
|
557
|
-
await orchestratorDBFork.close();
|
|
558
|
-
} catch (err) {
|
|
559
|
-
// This can happen if the sequencer is stopped before we hit this timeout.
|
|
560
|
-
this.log.warn(`Error closing forks for block processing`, err);
|
|
561
|
-
}
|
|
562
|
-
}, 5000);
|
|
413
|
+
private async dropFailedTxsFromP2P(failedTxs: FailedTx[]) {
|
|
414
|
+
if (failedTxs.length === 0) {
|
|
415
|
+
return;
|
|
563
416
|
}
|
|
417
|
+
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
418
|
+
const failedTxHashes = await Tx.getHashes(failedTxData);
|
|
419
|
+
this.log.verbose(`Dropping failed txs ${failedTxHashes.join(', ')}`);
|
|
420
|
+
await this.p2pClient.deleteTxs(failedTxHashes);
|
|
564
421
|
}
|
|
565
422
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
...header,
|
|
583
|
-
blockNumber,
|
|
584
|
-
timestamp: new Fr(header.timestamp),
|
|
585
|
-
chainId,
|
|
586
|
-
version,
|
|
587
|
-
});
|
|
588
|
-
return await this.buildBlock(pendingTxs, globalVariables, opts);
|
|
423
|
+
protected getDefaultBlockBuilderOptions(slot: number): BuildBlockOptions {
|
|
424
|
+
// Deadline for processing depends on whether we're proposing a block
|
|
425
|
+
const secondsIntoSlot = this.getSecondsIntoSlot(slot);
|
|
426
|
+
const processingEndTimeWithinSlot = this.timetable.getBlockProposalExecTimeEnd(secondsIntoSlot);
|
|
427
|
+
|
|
428
|
+
// Deadline is only set if enforceTimeTable is enabled.
|
|
429
|
+
const deadline = this.enforceTimeTable
|
|
430
|
+
? new Date((this.getSlotStartTimestamp(slot) + processingEndTimeWithinSlot) * 1000)
|
|
431
|
+
: undefined;
|
|
432
|
+
return {
|
|
433
|
+
maxTransactions: this.maxTxsPerBlock,
|
|
434
|
+
maxBlockSize: this.maxBlockSizeInBytes,
|
|
435
|
+
maxBlockGas: this.maxBlockGas,
|
|
436
|
+
txPublicSetupAllowList: this.txPublicSetupAllowList,
|
|
437
|
+
deadline,
|
|
438
|
+
};
|
|
589
439
|
}
|
|
590
440
|
|
|
591
441
|
/**
|
|
@@ -596,6 +446,8 @@ export class Sequencer {
|
|
|
596
446
|
*
|
|
597
447
|
* @param pendingTxs - Iterable of pending transactions to construct the block from
|
|
598
448
|
* @param proposalHeader - The partial header constructed for the proposal
|
|
449
|
+
* @param newGlobalVariables - The global variables for the new block
|
|
450
|
+
* @param proposerAddress - The address of the proposer
|
|
599
451
|
*/
|
|
600
452
|
@trackSpan('Sequencer.buildBlockAndEnqueuePublish', (_validTxs, _proposalHeader, newGlobalVariables) => ({
|
|
601
453
|
[Attributes.BLOCK_NUMBER]: newGlobalVariables.blockNumber.toNumber(),
|
|
@@ -604,6 +456,7 @@ export class Sequencer {
|
|
|
604
456
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
605
457
|
proposalHeader: ProposedBlockHeader,
|
|
606
458
|
newGlobalVariables: GlobalVariables,
|
|
459
|
+
proposerAddress: EthAddress,
|
|
607
460
|
): Promise<void> {
|
|
608
461
|
await this.publisher.validateBlockForSubmission(proposalHeader);
|
|
609
462
|
|
|
@@ -615,9 +468,22 @@ export class Sequencer {
|
|
|
615
468
|
this.setState(SequencerState.CREATING_BLOCK, slot);
|
|
616
469
|
|
|
617
470
|
try {
|
|
618
|
-
const
|
|
619
|
-
const
|
|
471
|
+
const blockBuilderOptions = this.getDefaultBlockBuilderOptions(Number(slot));
|
|
472
|
+
const buildBlockRes = await this.blockBuilder.buildBlock(pendingTxs, newGlobalVariables, blockBuilderOptions);
|
|
473
|
+
const { publicGas, block, publicProcessorDuration, numTxs, numMsgs, blockBuildingTimer, usedTxs, failedTxs } =
|
|
474
|
+
buildBlockRes;
|
|
620
475
|
this.metrics.recordBuiltBlock(workTimer.ms(), publicGas.l2Gas);
|
|
476
|
+
await this.dropFailedTxsFromP2P(failedTxs);
|
|
477
|
+
|
|
478
|
+
const minTxsPerBlock = this.isFlushing ? 0 : this.minTxsPerBlock;
|
|
479
|
+
|
|
480
|
+
if (numTxs < minTxsPerBlock) {
|
|
481
|
+
this.log.warn(
|
|
482
|
+
`Block ${blockNumber} has too few txs to be proposed (got ${numTxs} but required ${minTxsPerBlock})`,
|
|
483
|
+
{ slot, blockNumber, numTxs },
|
|
484
|
+
);
|
|
485
|
+
throw new Error(`Block has too few successful txs to be proposed`);
|
|
486
|
+
}
|
|
621
487
|
|
|
622
488
|
// TODO(@PhilWindle) We should probably periodically check for things like another
|
|
623
489
|
// block being published before ours instead of just waiting on our block
|
|
@@ -648,7 +514,7 @@ export class Sequencer {
|
|
|
648
514
|
|
|
649
515
|
this.log.debug('Collecting attestations');
|
|
650
516
|
const stopCollectingAttestationsTimer = this.metrics.startCollectingAttestationsTimer();
|
|
651
|
-
const attestations = await this.collectAttestations(block, usedTxs);
|
|
517
|
+
const attestations = await this.collectAttestations(block, usedTxs, proposerAddress);
|
|
652
518
|
if (attestations !== undefined) {
|
|
653
519
|
this.log.verbose(`Collected ${attestations.length} attestations`, { blockHash, blockNumber });
|
|
654
520
|
}
|
|
@@ -666,7 +532,11 @@ export class Sequencer {
|
|
|
666
532
|
[Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
|
|
667
533
|
[Attributes.BLOCK_TXS_COUNT]: txHashes.length,
|
|
668
534
|
}))
|
|
669
|
-
protected async collectAttestations(
|
|
535
|
+
protected async collectAttestations(
|
|
536
|
+
block: L2Block,
|
|
537
|
+
txs: Tx[],
|
|
538
|
+
proposerAddress: EthAddress,
|
|
539
|
+
): Promise<CommitteeAttestation[] | undefined> {
|
|
670
540
|
// TODO(https://github.com/AztecProtocol/aztec-packages/issues/7962): inefficient to have a round trip in here - this should be cached
|
|
671
541
|
const committee = await this.publisher.getCurrentEpochCommittee();
|
|
672
542
|
|
|
@@ -695,6 +565,7 @@ export class Sequencer {
|
|
|
695
565
|
block.archive.root,
|
|
696
566
|
block.header.state,
|
|
697
567
|
txs,
|
|
568
|
+
proposerAddress,
|
|
698
569
|
blockProposalOptions,
|
|
699
570
|
);
|
|
700
571
|
if (!proposal) {
|
|
@@ -728,7 +599,7 @@ export class Sequencer {
|
|
|
728
599
|
}))
|
|
729
600
|
protected async enqueuePublishL2Block(
|
|
730
601
|
block: L2Block,
|
|
731
|
-
attestations?:
|
|
602
|
+
attestations?: CommitteeAttestation[],
|
|
732
603
|
txHashes?: TxHash[],
|
|
733
604
|
): Promise<void> {
|
|
734
605
|
// Publishes new block to the network and awaits the tx to be mined
|
|
@@ -823,4 +694,8 @@ export class Sequencer {
|
|
|
823
694
|
get maxL2BlockGas(): number | undefined {
|
|
824
695
|
return this.config.maxL2BlockGas;
|
|
825
696
|
}
|
|
697
|
+
|
|
698
|
+
public getSlasherClient(): SlasherClient {
|
|
699
|
+
return this.slasherClient;
|
|
700
|
+
}
|
|
826
701
|
}
|
package/src/sequencer/utils.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
|
-
import {
|
|
2
|
+
import { CommitteeAttestation } from '@aztec/stdlib/block';
|
|
3
3
|
import type { BlockAttestation } from '@aztec/stdlib/p2p';
|
|
4
4
|
|
|
5
5
|
export enum SequencerState {
|
|
@@ -50,19 +50,27 @@ export function sequencerStateToNumber(state: SequencerState): number {
|
|
|
50
50
|
*
|
|
51
51
|
* @todo: perform this logic within the memory attestation store instead?
|
|
52
52
|
*/
|
|
53
|
-
export function orderAttestations(
|
|
53
|
+
export function orderAttestations(
|
|
54
|
+
attestations: BlockAttestation[],
|
|
55
|
+
orderAddresses: EthAddress[],
|
|
56
|
+
): CommitteeAttestation[] {
|
|
54
57
|
// Create a map of sender addresses to BlockAttestations
|
|
55
|
-
const attestationMap = new Map<string,
|
|
58
|
+
const attestationMap = new Map<string, CommitteeAttestation>();
|
|
56
59
|
|
|
57
60
|
for (const attestation of attestations) {
|
|
58
61
|
const sender = attestation.getSender();
|
|
59
|
-
|
|
62
|
+
if (sender) {
|
|
63
|
+
attestationMap.set(
|
|
64
|
+
sender.toString(),
|
|
65
|
+
CommitteeAttestation.fromAddressAndSignature(sender, attestation.signature),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
60
68
|
}
|
|
61
69
|
|
|
62
|
-
// Create the ordered array based on the orderAddresses, else return an empty
|
|
70
|
+
// Create the ordered array based on the orderAddresses, else return an empty attestation
|
|
63
71
|
const orderedAttestations = orderAddresses.map(address => {
|
|
64
72
|
const addressString = address.toString();
|
|
65
|
-
return attestationMap.get(addressString)
|
|
73
|
+
return attestationMap.get(addressString) || CommitteeAttestation.fromAddress(address);
|
|
66
74
|
});
|
|
67
75
|
|
|
68
76
|
return orderedAttestations;
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
AllowedElement,
|
|
19
19
|
ClientProtocolCircuitVerifier,
|
|
20
20
|
MerkleTreeReadOperations,
|
|
21
|
+
PublicProcessorValidator,
|
|
21
22
|
} from '@aztec/stdlib/interfaces/server';
|
|
22
23
|
import { DatabasePublicStateSource, type PublicStateSource } from '@aztec/stdlib/trees';
|
|
23
24
|
import { GlobalVariables, type Tx, type TxValidator } from '@aztec/stdlib/tx';
|
|
@@ -74,10 +75,7 @@ export function createValidatorForBlockBuilding(
|
|
|
74
75
|
contractDataSource: ContractDataSource,
|
|
75
76
|
globalVariables: GlobalVariables,
|
|
76
77
|
setupAllowList: AllowedElement[],
|
|
77
|
-
): {
|
|
78
|
-
preprocessValidator: TxValidator<Tx>;
|
|
79
|
-
nullifierCache: NullifierCache;
|
|
80
|
-
} {
|
|
78
|
+
): PublicProcessorValidator {
|
|
81
79
|
const nullifierCache = new NullifierCache(db);
|
|
82
80
|
const archiveCache = new ArchiveCache(db);
|
|
83
81
|
const publicStateSource = new DatabasePublicStateSource(db);
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { L1ContractsConfig, L1ReaderConfig } from '@aztec/ethereum';
|
|
2
|
-
import type { L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
3
|
-
import { type TelemetryClient } from '@aztec/telemetry-client';
|
|
4
|
-
import { SlasherClient } from './slasher_client.js';
|
|
5
|
-
import type { SlasherConfig } from './slasher_client.js';
|
|
6
|
-
export declare const createSlasherClient: (_config: SlasherConfig & L1ContractsConfig & L1ReaderConfig, l2BlockSource: L2BlockSourceEventEmitter, telemetry?: TelemetryClient) => SlasherClient;
|
|
7
|
-
//# sourceMappingURL=factory.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../src/slasher/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AACrE,OAAO,EAAE,KAAK,eAAe,EAAsB,MAAM,yBAAyB,CAAC;AAEnF,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,mBAAmB,GAC9B,SAAS,aAAa,GAAG,iBAAiB,GAAG,cAAc,EAC3D,eAAe,yBAAyB,EACxC,YAAW,eAAsC,kBAIlD,CAAC"}
|
package/dest/slasher/factory.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { getTelemetryClient } from '@aztec/telemetry-client';
|
|
2
|
-
import { SlasherClient } from './slasher_client.js';
|
|
3
|
-
export const createSlasherClient = (_config, l2BlockSource, telemetry = getTelemetryClient())=>{
|
|
4
|
-
const config = {
|
|
5
|
-
..._config
|
|
6
|
-
};
|
|
7
|
-
return new SlasherClient(config, l2BlockSource, telemetry);
|
|
8
|
-
};
|
package/dest/slasher/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/slasher/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC"}
|
package/dest/slasher/index.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { type L1ContractsConfig, type L1ReaderConfig, type ViemPublicClient } from '@aztec/ethereum';
|
|
2
|
-
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
3
|
-
import { SlashFactoryAbi } from '@aztec/l1-artifacts';
|
|
4
|
-
import { type L2BlockId, type L2BlockSourceEvent, type L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
5
|
-
import { type TelemetryClient, WithTracer } from '@aztec/telemetry-client';
|
|
6
|
-
import { type GetContractReturnType } from 'viem';
|
|
7
|
-
/**
|
|
8
|
-
* Enum defining the possible states of the Slasher client.
|
|
9
|
-
*/
|
|
10
|
-
export declare enum SlasherClientState {
|
|
11
|
-
IDLE = 0,
|
|
12
|
-
RUNNING = 1,
|
|
13
|
-
STOPPED = 2
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* The synchronization status of the Slasher client.
|
|
17
|
-
*/
|
|
18
|
-
export interface SlasherSyncState {
|
|
19
|
-
/**
|
|
20
|
-
* The current state of the slasher client.
|
|
21
|
-
*/
|
|
22
|
-
state: SlasherClientState;
|
|
23
|
-
/**
|
|
24
|
-
* The block number that the slasher client is synced to.
|
|
25
|
-
*/
|
|
26
|
-
syncedToL2Block: L2BlockId;
|
|
27
|
-
}
|
|
28
|
-
export interface SlasherConfig {
|
|
29
|
-
blockCheckIntervalMS: number;
|
|
30
|
-
blockRequestBatchSize: number;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* @notice A Hypomeiones slasher client implementation
|
|
34
|
-
*
|
|
35
|
-
* Hypomeiones: a class of individuals in ancient Sparta who were considered inferior or lesser citizens compared
|
|
36
|
-
* to the full Spartan citizens.
|
|
37
|
-
*
|
|
38
|
-
* The implementation here is less than ideal. It exists, not to be the end all be all, but to show that
|
|
39
|
-
* slashing can be done with this mechanism.
|
|
40
|
-
*
|
|
41
|
-
* The implementation is VERY brute in the sense that it only looks for pruned blocks and then tries to slash
|
|
42
|
-
* the full committee of that.
|
|
43
|
-
* If it sees a prune, it will mark the full epoch as "to be slashed".
|
|
44
|
-
*
|
|
45
|
-
* Also, it is not particularly smart around what it should if there were to be multiple slashing events.
|
|
46
|
-
*
|
|
47
|
-
* A few improvements:
|
|
48
|
-
* - Only vote on the proposal if it is possible to reach, e.g., if 6 votes are needed and only 4 slots are left don't vote.
|
|
49
|
-
* - Stop voting on a payload once it is processed.
|
|
50
|
-
* - Only vote on the proposal if it have not already been executed
|
|
51
|
-
* - Caveat, we need to fully decide if it is acceptable to have the same payload address multiple times. In the current
|
|
52
|
-
* slash factory that could mean slashing the same committee for the same error multiple times.
|
|
53
|
-
* - Decide how to deal with multiple slashing events in the same round.
|
|
54
|
-
* - This could be that multiple epochs are pruned in the same round, but with the current naive implementation we could end up
|
|
55
|
-
* slashing only the first, because the "lifetime" of the second would have passed after that vote
|
|
56
|
-
*/
|
|
57
|
-
export declare class SlasherClient extends WithTracer {
|
|
58
|
-
private config;
|
|
59
|
-
private l2BlockSource;
|
|
60
|
-
private log;
|
|
61
|
-
private slashEvents;
|
|
62
|
-
protected slashFactoryContract?: GetContractReturnType<typeof SlashFactoryAbi, ViemPublicClient>;
|
|
63
|
-
private slashingAmount;
|
|
64
|
-
constructor(config: SlasherConfig & L1ContractsConfig & L1ReaderConfig, l2BlockSource: L2BlockSourceEventEmitter, telemetry?: TelemetryClient, log?: import("@aztec/foundation/log").Logger);
|
|
65
|
-
start(): void;
|
|
66
|
-
getSlashPayload(slotNumber: bigint): Promise<EthAddress | undefined>;
|
|
67
|
-
handleBlockStreamEvent(event: L2BlockSourceEvent): Promise<void>;
|
|
68
|
-
/**
|
|
69
|
-
* Allows consumers to stop the instance of the slasher client.
|
|
70
|
-
* 'ready' will now return 'false' and the running promise that keeps the client synced is interrupted.
|
|
71
|
-
*/
|
|
72
|
-
stop(): void;
|
|
73
|
-
private handlePruneL2Blocks;
|
|
74
|
-
}
|
|
75
|
-
//# sourceMappingURL=slasher_client.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"slasher_client.d.ts","sourceRoot":"","sources":["../../src/slasher/slasher_client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EACL,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,KAAK,eAAe,EAAE,UAAU,EAAsB,MAAM,yBAAyB,CAAC;AAE/F,OAAO,EAAE,KAAK,qBAAqB,EAA+D,MAAM,MAAM,CAAC;AAE/G;;GAEG;AACH,oBAAY,kBAAkB;IAC5B,IAAI,IAAA;IACJ,OAAO,IAAA;IACP,OAAO,IAAA;CACR;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,KAAK,EAAE,kBAAkB,CAAC;IAC1B;;OAEG;IACH,eAAe,EAAE,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,aAAa;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAQD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,aAAc,SAAQ,UAAU;IAWzC,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,aAAa;IAErB,OAAO,CAAC,GAAG;IAbb,OAAO,CAAC,WAAW,CAAoB;IAEvC,SAAS,CAAC,oBAAoB,CAAC,EAAE,qBAAqB,CAAC,OAAO,eAAe,EAAE,gBAAgB,CAAC,CAAa;IAK7G,OAAO,CAAC,cAAc,CAAc;gBAG1B,MAAM,EAAE,aAAa,GAAG,iBAAiB,GAAG,cAAc,EAC1D,aAAa,EAAE,yBAAyB,EAChD,SAAS,GAAE,eAAsC,EACzC,GAAG,yCAA0B;IAwBhC,KAAK;IAMC,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAgC1E,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;IAavE;;;OAGG;IACI,IAAI;IAMX,OAAO,CAAC,mBAAmB;CAqB5B"}
|