@aztec/sequencer-client 4.1.2-rc.1 → 4.2.0-aztecnr-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +3 -12
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +19 -72
- package/dest/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +6 -0
- package/dest/global_variable_builder/global_builder.d.ts +13 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +22 -21
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.d.ts +5 -3
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +36 -7
- package/dest/sequencer/checkpoint_proposal_job.d.ts +3 -1
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +33 -25
- package/dest/sequencer/sequencer.d.ts +3 -2
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +3 -3
- package/dest/test/mock_checkpoint_builder.d.ts +4 -8
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -1
- package/package.json +28 -28
- package/src/client/sequencer-client.ts +29 -94
- package/src/config.ts +7 -0
- package/src/global_variable_builder/global_builder.ts +22 -23
- package/src/global_variable_builder/index.ts +1 -1
- package/src/publisher/sequencer-publisher.ts +39 -7
- package/src/sequencer/checkpoint_proposal_job.ts +56 -45
- package/src/sequencer/sequencer.ts +3 -3
- package/src/test/mock_checkpoint_builder.ts +3 -3
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
2
|
-
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
3
1
|
import { RollupContract } from '@aztec/ethereum/contracts';
|
|
4
|
-
import type {
|
|
2
|
+
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
5
3
|
import type { ViemPublicClient } from '@aztec/ethereum/types';
|
|
6
4
|
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
7
5
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
8
6
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
9
7
|
import { createLogger } from '@aztec/foundation/log';
|
|
8
|
+
import type { DateProvider } from '@aztec/foundation/timer';
|
|
10
9
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
11
|
-
import { type L1RollupConstants, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
10
|
+
import { type L1RollupConstants, getNextL1SlotTimestamp, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
12
11
|
import { GasFees } from '@aztec/stdlib/gas';
|
|
13
12
|
import type {
|
|
14
13
|
CheckpointGlobalVariables,
|
|
@@ -16,7 +15,12 @@ import type {
|
|
|
16
15
|
} from '@aztec/stdlib/tx';
|
|
17
16
|
import { GlobalVariables } from '@aztec/stdlib/tx';
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
/** Configuration for the GlobalVariableBuilder (excludes L1 client config). */
|
|
19
|
+
export type GlobalVariableBuilderConfig = {
|
|
20
|
+
l1Contracts: Pick<L1ContractAddresses, 'rollupAddress'>;
|
|
21
|
+
ethereumSlotDuration: number;
|
|
22
|
+
rollupVersion: bigint;
|
|
23
|
+
} & Pick<L1RollupConstants, 'slotDuration' | 'l1GenesisTime'>;
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Simple global variables builder.
|
|
@@ -27,7 +31,6 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
27
31
|
private currentL1BlockNumber: bigint | undefined = undefined;
|
|
28
32
|
|
|
29
33
|
private readonly rollupContract: RollupContract;
|
|
30
|
-
private readonly publicClient: ViemPublicClient;
|
|
31
34
|
private readonly ethereumSlotDuration: number;
|
|
32
35
|
private readonly aztecSlotDuration: number;
|
|
33
36
|
private readonly l1GenesisTime: bigint;
|
|
@@ -36,28 +39,18 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
36
39
|
private version: Fr;
|
|
37
40
|
|
|
38
41
|
constructor(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
private readonly dateProvider: DateProvider,
|
|
43
|
+
private readonly publicClient: ViemPublicClient,
|
|
44
|
+
config: GlobalVariableBuilderConfig,
|
|
42
45
|
) {
|
|
43
|
-
const { l1RpcUrls, l1ChainId: chainId, l1Contracts } = config;
|
|
44
|
-
|
|
45
|
-
const chain = createEthereumChain(l1RpcUrls, chainId);
|
|
46
|
-
|
|
47
46
|
this.version = new Fr(config.rollupVersion);
|
|
48
|
-
this.chainId = new Fr(
|
|
47
|
+
this.chainId = new Fr(this.publicClient.chain!.id);
|
|
49
48
|
|
|
50
49
|
this.ethereumSlotDuration = config.ethereumSlotDuration;
|
|
51
50
|
this.aztecSlotDuration = config.slotDuration;
|
|
52
51
|
this.l1GenesisTime = config.l1GenesisTime;
|
|
53
52
|
|
|
54
|
-
this.
|
|
55
|
-
chain: chain.chainInfo,
|
|
56
|
-
transport: fallback(chain.rpcUrls.map(url => http(url, { batch: false }))),
|
|
57
|
-
pollingInterval: config.viemPollingIntervalMS,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
this.rollupContract = new RollupContract(this.publicClient, l1Contracts.rollupAddress);
|
|
53
|
+
this.rollupContract = new RollupContract(this.publicClient, config.l1Contracts.rollupAddress);
|
|
61
54
|
}
|
|
62
55
|
|
|
63
56
|
/**
|
|
@@ -73,7 +66,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
73
66
|
const earliestTimestamp = await this.rollupContract.getTimestampForSlot(
|
|
74
67
|
SlotNumber.fromBigInt(BigInt(lastCheckpoint.slotNumber) + 1n),
|
|
75
68
|
);
|
|
76
|
-
const nextEthTimestamp =
|
|
69
|
+
const nextEthTimestamp = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
|
|
70
|
+
l1GenesisTime: this.l1GenesisTime,
|
|
71
|
+
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
72
|
+
});
|
|
77
73
|
const timestamp = earliestTimestamp > nextEthTimestamp ? earliestTimestamp : nextEthTimestamp;
|
|
78
74
|
|
|
79
75
|
return new GasFees(0, await this.rollupContract.getManaMinFeeAt(timestamp, true));
|
|
@@ -108,7 +104,10 @@ export class GlobalVariableBuilder implements GlobalVariableBuilderInterface {
|
|
|
108
104
|
const slot: SlotNumber =
|
|
109
105
|
maybeSlot ??
|
|
110
106
|
(await this.rollupContract.getSlotAt(
|
|
111
|
-
|
|
107
|
+
getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), {
|
|
108
|
+
l1GenesisTime: this.l1GenesisTime,
|
|
109
|
+
ethereumSlotDuration: this.ethereumSlotDuration,
|
|
110
|
+
}),
|
|
112
111
|
));
|
|
113
112
|
|
|
114
113
|
const checkpointGlobalVariables = await this.buildCheckpointGlobalVariables(coinbase, feeRecipient, slot);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { GlobalVariableBuilder } from './global_builder.js';
|
|
1
|
+
export { GlobalVariableBuilder, type GlobalVariableBuilderConfig } from './global_builder.js';
|
|
@@ -28,6 +28,7 @@ import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from
|
|
|
28
28
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
29
29
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
30
30
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
31
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
31
32
|
import { pick } from '@aztec/foundation/collection';
|
|
32
33
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
33
34
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -40,6 +41,7 @@ import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
|
40
41
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
41
42
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
42
43
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
44
|
+
import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
43
45
|
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
44
46
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
45
47
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
@@ -120,6 +122,7 @@ export class SequencerPublisher {
|
|
|
120
122
|
|
|
121
123
|
protected log: Logger;
|
|
122
124
|
protected ethereumSlotDuration: bigint;
|
|
125
|
+
private dateProvider: DateProvider;
|
|
123
126
|
|
|
124
127
|
private blobClient: BlobClientInterface;
|
|
125
128
|
|
|
@@ -168,6 +171,7 @@ export class SequencerPublisher {
|
|
|
168
171
|
) {
|
|
169
172
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
170
173
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
174
|
+
this.dateProvider = deps.dateProvider;
|
|
171
175
|
this.epochCache = deps.epochCache;
|
|
172
176
|
this.lastActions = deps.lastActions;
|
|
173
177
|
|
|
@@ -356,8 +360,8 @@ export class SequencerPublisher {
|
|
|
356
360
|
// @note - we can only have one blob config per bundle
|
|
357
361
|
// find requests with gas and blob configs
|
|
358
362
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
359
|
-
const gasConfigs =
|
|
360
|
-
const blobConfigs =
|
|
363
|
+
const gasConfigs = validRequests.filter(request => request.gasConfig).map(request => request.gasConfig);
|
|
364
|
+
const blobConfigs = validRequests.filter(request => request.blobConfig).map(request => request.blobConfig);
|
|
361
365
|
|
|
362
366
|
if (blobConfigs.length > 1) {
|
|
363
367
|
throw new Error('Multiple blob configs found');
|
|
@@ -425,7 +429,16 @@ export class SequencerPublisher {
|
|
|
425
429
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
426
430
|
return { failedActions: requests.map(r => r.action) };
|
|
427
431
|
} else {
|
|
428
|
-
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
432
|
+
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
433
|
+
result,
|
|
434
|
+
requests: requests.map(r => ({
|
|
435
|
+
...r,
|
|
436
|
+
// Avoid logging large blob data
|
|
437
|
+
blobConfig: r.blobConfig
|
|
438
|
+
? { ...r.blobConfig, blobs: r.blobConfig.blobs.map(b => ({ size: trimmedBytesLength(b) })) }
|
|
439
|
+
: undefined,
|
|
440
|
+
})),
|
|
441
|
+
});
|
|
429
442
|
const successfulActions: Action[] = [];
|
|
430
443
|
const failedActions: Action[] = [];
|
|
431
444
|
for (const request of requests) {
|
|
@@ -440,11 +453,11 @@ export class SequencerPublisher {
|
|
|
440
453
|
}
|
|
441
454
|
|
|
442
455
|
/**
|
|
443
|
-
* @notice Will call `
|
|
456
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
444
457
|
* @param tipArchive - The archive to check
|
|
445
458
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
446
459
|
*/
|
|
447
|
-
public
|
|
460
|
+
public async canProposeAt(
|
|
448
461
|
tipArchive: Fr,
|
|
449
462
|
msgSender: EthAddress,
|
|
450
463
|
opts: { forcePendingCheckpointNumber?: CheckpointNumber } = {},
|
|
@@ -452,8 +465,10 @@ export class SequencerPublisher {
|
|
|
452
465
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
453
466
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
454
467
|
|
|
468
|
+
const nextL1SlotTs = await this.getNextL1SlotTimestampWithL1Floor();
|
|
469
|
+
|
|
455
470
|
return this.rollupContract
|
|
456
|
-
.
|
|
471
|
+
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
457
472
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
458
473
|
})
|
|
459
474
|
.catch(err => {
|
|
@@ -490,7 +505,7 @@ export class SequencerPublisher {
|
|
|
490
505
|
flags,
|
|
491
506
|
] as const;
|
|
492
507
|
|
|
493
|
-
const ts =
|
|
508
|
+
const ts = await this.getNextL1SlotTimestampWithL1Floor();
|
|
494
509
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
495
510
|
opts?.forcePendingCheckpointNumber,
|
|
496
511
|
);
|
|
@@ -1345,4 +1360,21 @@ export class SequencerPublisher {
|
|
|
1345
1360
|
},
|
|
1346
1361
|
});
|
|
1347
1362
|
}
|
|
1363
|
+
|
|
1364
|
+
/**
|
|
1365
|
+
* Returns the timestamp to use when simulating L1 proposal calls.
|
|
1366
|
+
* Uses the wall-clock-based next L1 slot boundary, but floors it with the latest L1 block timestamp
|
|
1367
|
+
* plus one slot duration. This prevents the sequencer from targeting a future L2 slot when the L1
|
|
1368
|
+
* chain hasn't caught up to the wall clock yet (e.g., the dateProvider is one L1 slot ahead of the
|
|
1369
|
+
* latest mined block), which would cause the propose tx to land in an L1 block with block.timestamp
|
|
1370
|
+
* still in the previous L2 slot.
|
|
1371
|
+
* TODO(palla): Properly fix by keeping dateProvider synced with anvil's chain time on every block.
|
|
1372
|
+
*/
|
|
1373
|
+
private async getNextL1SlotTimestampWithL1Floor(): Promise<bigint> {
|
|
1374
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1375
|
+
const fromWallClock = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1376
|
+
const latestBlock = await this.l1TxUtils.client.getBlock();
|
|
1377
|
+
const fromL1Block = latestBlock.timestamp + BigInt(l1Constants.ethereumSlotDuration);
|
|
1378
|
+
return fromWallClock > fromL1Block ? fromWallClock : fromL1Block;
|
|
1379
|
+
}
|
|
1348
1380
|
}
|
|
@@ -34,13 +34,18 @@ import { type Checkpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
|
34
34
|
import { getSlotStartBuildTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
35
35
|
import { Gas } from '@aztec/stdlib/gas';
|
|
36
36
|
import {
|
|
37
|
+
type BlockBuilderOptions,
|
|
37
38
|
InsufficientValidTxsError,
|
|
38
|
-
type PublicProcessorLimits,
|
|
39
39
|
type ResolvedSequencerConfig,
|
|
40
40
|
type WorldStateSynchronizer,
|
|
41
41
|
} from '@aztec/stdlib/interfaces/server';
|
|
42
42
|
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
|
|
43
|
-
import type {
|
|
43
|
+
import type {
|
|
44
|
+
BlockProposal,
|
|
45
|
+
BlockProposalOptions,
|
|
46
|
+
CheckpointProposal,
|
|
47
|
+
CheckpointProposalOptions,
|
|
48
|
+
} from '@aztec/stdlib/p2p';
|
|
44
49
|
import { orderAttestations, trimAttestations } from '@aztec/stdlib/p2p';
|
|
45
50
|
import type { L2BlockBuiltStats } from '@aztec/stdlib/stats';
|
|
46
51
|
import { type FailedTx, Tx } from '@aztec/stdlib/tx';
|
|
@@ -265,7 +270,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
265
270
|
this.setStateFn(SequencerState.ASSEMBLING_CHECKPOINT, this.slot);
|
|
266
271
|
const checkpoint = await checkpointBuilder.completeCheckpoint();
|
|
267
272
|
|
|
268
|
-
// Final validation
|
|
273
|
+
// Final validation: per-block limits are only checked if the operator set them explicitly.
|
|
274
|
+
// Otherwise, checkpoint-level budgets were already enforced by the redistribution logic.
|
|
269
275
|
try {
|
|
270
276
|
validateCheckpoint(checkpoint, {
|
|
271
277
|
rollupManaLimit: this.l1Constants.rollupManaLimit,
|
|
@@ -402,6 +408,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
402
408
|
const blocksInCheckpoint: L2Block[] = [];
|
|
403
409
|
const txHashesAlreadyIncluded = new Set<string>();
|
|
404
410
|
const initialBlockNumber = BlockNumber(this.syncedToBlockNumber + 1);
|
|
411
|
+
const slot = this.slot;
|
|
405
412
|
|
|
406
413
|
// Last block in the checkpoint will usually be flagged as pending broadcast, so we send it along with the checkpoint proposal
|
|
407
414
|
let blockPendingBroadcast: { block: L2Block; txs: Tx[] } | undefined = undefined;
|
|
@@ -415,11 +422,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
415
422
|
const timingInfo = this.timetable.canStartNextBlock(secondsIntoSlot);
|
|
416
423
|
|
|
417
424
|
if (!timingInfo.canStart) {
|
|
418
|
-
this.log.debug(`Not enough time left in slot to start another block`, {
|
|
419
|
-
slot: this.slot,
|
|
420
|
-
blocksBuilt,
|
|
421
|
-
secondsIntoSlot,
|
|
422
|
-
});
|
|
425
|
+
this.log.debug(`Not enough time left in slot to start another block`, { slot, blocksBuilt, secondsIntoSlot });
|
|
423
426
|
break;
|
|
424
427
|
}
|
|
425
428
|
|
|
@@ -451,53 +454,37 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
451
454
|
} else if ('error' in buildResult) {
|
|
452
455
|
// If there was an error building the block, just exit the loop and give up the rest of the slot
|
|
453
456
|
if (!(buildResult.error instanceof SequencerInterruptedError)) {
|
|
454
|
-
this.log.warn(`Halting block building for slot ${
|
|
455
|
-
slot: this.slot,
|
|
456
|
-
blocksBuilt,
|
|
457
|
-
error: buildResult.error,
|
|
458
|
-
});
|
|
457
|
+
this.log.warn(`Halting block building for slot ${slot}`, { slot, blocksBuilt, error: buildResult.error });
|
|
459
458
|
}
|
|
460
459
|
break;
|
|
461
460
|
}
|
|
462
461
|
|
|
463
462
|
const { block, usedTxs } = buildResult;
|
|
464
463
|
blocksInCheckpoint.push(block);
|
|
465
|
-
|
|
466
|
-
// Sync the proposed block to the archiver to make it available
|
|
467
|
-
// Note that the checkpoint builder uses its own fork so it should not need to wait for this syncing
|
|
468
|
-
// Eventually we should refactor the checkpoint builder to not need a separate long-lived fork
|
|
469
|
-
// Fire and forget - don't block the critical path, but log errors
|
|
470
|
-
this.syncProposedBlockToArchiver(block).catch(err => {
|
|
471
|
-
this.log.error(`Failed to sync proposed block ${block.number} to archiver`, { blockNumber: block.number, err });
|
|
472
|
-
});
|
|
473
|
-
|
|
474
464
|
usedTxs.forEach(tx => txHashesAlreadyIncluded.add(tx.txHash.toString()));
|
|
475
465
|
|
|
476
|
-
// If this is the last block,
|
|
466
|
+
// If this is the last block, send the proposed block to the archiver,
|
|
467
|
+
// and exit the loop now so we can build the checkpoint and start collecting attestations.
|
|
477
468
|
if (timingInfo.isLastBlock) {
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
blockNumber,
|
|
481
|
-
blocksBuilt,
|
|
482
|
-
});
|
|
469
|
+
await this.syncProposedBlockToArchiver(block);
|
|
470
|
+
this.log.verbose(`Completed final block ${blockNumber} for slot ${slot}`, { slot, blockNumber, blocksBuilt });
|
|
483
471
|
blockPendingBroadcast = { block, txs: usedTxs };
|
|
484
472
|
break;
|
|
485
473
|
}
|
|
486
474
|
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
if
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
475
|
+
// Broadcast the block proposal (unless we're in fisherman mode) unless the block is the last one,
|
|
476
|
+
// in which case we'll broadcast it along with the checkpoint at the end of the loop.
|
|
477
|
+
// Note that we only send the block to the archiver if we manage to create the proposal, so if there's
|
|
478
|
+
// a HA error we don't pollute our archiver with a block that won't make it to the chain.
|
|
479
|
+
const proposal = await this.createBlockProposal(block, inHash, usedTxs, blockProposalOptions);
|
|
480
|
+
|
|
481
|
+
// Sync the proposed block to the archiver to make it available, only after we've managed to sign the proposal.
|
|
482
|
+
// We wait for the sync to succeed, as this helps catch consistency errors, even if it means we lose some time for block-building.
|
|
483
|
+
// If this throws, we abort the entire checkpoint.
|
|
484
|
+
await this.syncProposedBlockToArchiver(block);
|
|
485
|
+
|
|
486
|
+
// Once we have a signed proposal and the archiver agreed with our proposed block, then we broadcast it.
|
|
487
|
+
proposal && (await this.p2pClient.broadcastProposal(proposal));
|
|
501
488
|
|
|
502
489
|
// Wait until the next block's start time
|
|
503
490
|
await this.waitUntilNextSubslot(timingInfo.deadline);
|
|
@@ -511,6 +498,28 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
511
498
|
return { blocksInCheckpoint, blockPendingBroadcast };
|
|
512
499
|
}
|
|
513
500
|
|
|
501
|
+
/** Creates a block proposal for a given block via the validator client (unless in fisherman mode) */
|
|
502
|
+
private createBlockProposal(
|
|
503
|
+
block: L2Block,
|
|
504
|
+
inHash: Fr,
|
|
505
|
+
usedTxs: Tx[],
|
|
506
|
+
blockProposalOptions: BlockProposalOptions,
|
|
507
|
+
): Promise<BlockProposal | undefined> {
|
|
508
|
+
if (this.config.fishermanMode) {
|
|
509
|
+
this.log.info(`Skipping block proposal for block ${block.number} in fisherman mode`);
|
|
510
|
+
return Promise.resolve(undefined);
|
|
511
|
+
}
|
|
512
|
+
return this.validatorClient.createBlockProposal(
|
|
513
|
+
block.header,
|
|
514
|
+
block.indexWithinCheckpoint,
|
|
515
|
+
inHash,
|
|
516
|
+
block.archive.root,
|
|
517
|
+
usedTxs,
|
|
518
|
+
this.proposer,
|
|
519
|
+
blockProposalOptions,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
514
523
|
/** Sleeps until it is time to produce the next block in the slot */
|
|
515
524
|
@trackSpan('CheckpointProposalJob.waitUntilNextSubslot')
|
|
516
525
|
private async waitUntilNextSubslot(nextSubslotStart: number) {
|
|
@@ -566,11 +575,11 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
566
575
|
);
|
|
567
576
|
this.setStateFn(SequencerState.CREATING_BLOCK, this.slot);
|
|
568
577
|
|
|
569
|
-
// Per-block limits
|
|
578
|
+
// Per-block limits are operator overrides (from SEQ_MAX_L2_BLOCK_GAS etc.) further capped
|
|
570
579
|
// by remaining checkpoint-level budgets inside CheckpointBuilder before each block is built.
|
|
571
580
|
// minValidTxs is passed into the builder so it can reject the block *before* updating state.
|
|
572
581
|
const minValidTxs = forceCreate ? 0 : (this.config.minValidTxsPerBlock ?? minTxs);
|
|
573
|
-
const blockBuilderOptions:
|
|
582
|
+
const blockBuilderOptions: BlockBuilderOptions = {
|
|
574
583
|
maxTransactions: this.config.maxTxsPerBlock,
|
|
575
584
|
maxBlockGas:
|
|
576
585
|
this.config.maxL2BlockGas !== undefined || this.config.maxDABlockGas !== undefined
|
|
@@ -579,6 +588,8 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
579
588
|
deadline: buildDeadline,
|
|
580
589
|
isBuildingProposal: true,
|
|
581
590
|
minValidTxs,
|
|
591
|
+
maxBlocksPerCheckpoint: this.timetable.maxNumberOfBlocks,
|
|
592
|
+
perBlockAllocationMultiplier: this.config.perBlockAllocationMultiplier,
|
|
582
593
|
};
|
|
583
594
|
|
|
584
595
|
// Actually build the block by executing txs. The builder throws InsufficientValidTxsError
|
|
@@ -649,7 +660,7 @@ export class CheckpointProposalJob implements Traceable {
|
|
|
649
660
|
pendingTxs: AsyncIterable<Tx>,
|
|
650
661
|
blockNumber: BlockNumber,
|
|
651
662
|
blockTimestamp: bigint,
|
|
652
|
-
blockBuilderOptions:
|
|
663
|
+
blockBuilderOptions: BlockBuilderOptions,
|
|
653
664
|
) {
|
|
654
665
|
try {
|
|
655
666
|
const workTimer = new Timer();
|
|
@@ -327,7 +327,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
327
327
|
|
|
328
328
|
// Check with the rollup contract if we can indeed propose at the next L2 slot. This check should not fail
|
|
329
329
|
// if all the previous checks are good, but we do it just in case.
|
|
330
|
-
const canProposeCheck = await publisher.
|
|
330
|
+
const canProposeCheck = await publisher.canProposeAt(
|
|
331
331
|
syncedTo.archive,
|
|
332
332
|
proposer ?? EthAddress.ZERO,
|
|
333
333
|
invalidateCheckpoint,
|
|
@@ -475,8 +475,8 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter<Sequ
|
|
|
475
475
|
*/
|
|
476
476
|
protected async checkSync(args: { ts: bigint; slot: SlotNumber }): Promise<SequencerSyncCheckResult | undefined> {
|
|
477
477
|
// Check that the archiver has fully synced the L2 slot before the one we want to propose in.
|
|
478
|
-
//
|
|
479
|
-
//
|
|
478
|
+
// The archiver reports sync progress via L1 block timestamps and synced checkpoint slots.
|
|
479
|
+
// See getSyncedL2SlotNumber for how missed L1 blocks are handled.
|
|
480
480
|
const syncedL2Slot = await this.l2BlockSource.getSyncedL2SlotNumber();
|
|
481
481
|
const { slot } = args;
|
|
482
482
|
if (syncedL2Slot === undefined || syncedL2Slot + 1 < slot) {
|
|
@@ -4,11 +4,11 @@ import { unfreeze } from '@aztec/foundation/types';
|
|
|
4
4
|
import { L2Block } from '@aztec/stdlib/block';
|
|
5
5
|
import { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
6
6
|
import type {
|
|
7
|
+
BlockBuilderOptions,
|
|
7
8
|
FullNodeBlockBuilderConfig,
|
|
8
9
|
ICheckpointBlockBuilder,
|
|
9
10
|
ICheckpointsBuilder,
|
|
10
11
|
MerkleTreeWriteOperations,
|
|
11
|
-
PublicProcessorLimits,
|
|
12
12
|
} from '@aztec/stdlib/interfaces/server';
|
|
13
13
|
import { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
14
14
|
import { makeAppendOnlyTreeSnapshot } from '@aztec/stdlib/testing';
|
|
@@ -32,7 +32,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
32
32
|
public buildBlockCalls: Array<{
|
|
33
33
|
blockNumber: BlockNumber;
|
|
34
34
|
timestamp: bigint;
|
|
35
|
-
opts:
|
|
35
|
+
opts: BlockBuilderOptions;
|
|
36
36
|
}> = [];
|
|
37
37
|
/** Track all consumed transaction hashes across buildBlock calls */
|
|
38
38
|
public consumedTxHashes: Set<string> = new Set();
|
|
@@ -74,7 +74,7 @@ export class MockCheckpointBuilder implements ICheckpointBlockBuilder {
|
|
|
74
74
|
pendingTxs: Iterable<Tx> | AsyncIterable<Tx>,
|
|
75
75
|
blockNumber: BlockNumber,
|
|
76
76
|
timestamp: bigint,
|
|
77
|
-
opts:
|
|
77
|
+
opts: BlockBuilderOptions,
|
|
78
78
|
): Promise<BuildBlockInCheckpointResult> {
|
|
79
79
|
this.buildBlockCalls.push({ blockNumber, timestamp, opts });
|
|
80
80
|
|