@aztec/sequencer-client 0.0.1-commit.e588bc7e5 → 0.0.1-commit.e5a3663dd
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 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -4
- package/dest/config.d.ts +2 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +16 -14
- package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +128 -0
- package/dest/global_variable_builder/fee_provider.d.ts +21 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +58 -0
- package/dest/global_variable_builder/global_builder.d.ts +4 -9
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +3 -41
- package/dest/global_variable_builder/index.d.ts +3 -1
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +2 -0
- package/dest/publisher/config.d.ts +1 -1
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +2 -2
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +3 -4
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.d.ts +1 -3
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +0 -1
- package/dest/publisher/sequencer-publisher.d.ts +16 -35
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +62 -94
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +33 -22
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +479 -170
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- package/dest/sequencer/events.d.ts +6 -1
- package/dest/sequencer/events.d.ts.map +1 -1
- package/dest/sequencer/metrics.d.ts +9 -10
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +34 -20
- package/dest/sequencer/sequencer.d.ts +16 -5
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +63 -30
- package/dest/sequencer/timetable.d.ts +14 -1
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +45 -36
- package/dest/test/utils.d.ts +1 -1
- package/dest/test/utils.d.ts.map +1 -1
- package/dest/test/utils.js +7 -6
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +5 -7
- package/src/config.ts +15 -11
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +172 -0
- package/src/global_variable_builder/fee_provider.ts +75 -0
- package/src/global_variable_builder/global_builder.ts +7 -54
- package/src/global_variable_builder/index.ts +2 -0
- package/src/publisher/config.ts +6 -4
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +3 -1
- package/src/publisher/sequencer-publisher-factory.ts +0 -3
- package/src/publisher/sequencer-publisher.ts +108 -156
- package/src/sequencer/README.md +82 -13
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +572 -218
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/events.ts +5 -0
- package/src/sequencer/metrics.ts +43 -24
- package/src/sequencer/sequencer.ts +81 -35
- package/src/sequencer/timetable.ts +57 -45
- package/src/test/utils.ts +28 -10
|
@@ -3,17 +3,18 @@ import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/
|
|
|
3
3
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
4
4
|
import type { L1ContractsConfig } from '@aztec/ethereum/config';
|
|
5
5
|
import {
|
|
6
|
-
type EmpireSlashingProposerContract,
|
|
7
6
|
FeeAssetPriceOracle,
|
|
8
|
-
type FeeHeader,
|
|
9
7
|
type GovernanceProposerContract,
|
|
10
8
|
type IEmpireBase,
|
|
11
9
|
MULTI_CALL_3_ADDRESS,
|
|
12
10
|
Multicall3,
|
|
13
11
|
RollupContract,
|
|
14
|
-
|
|
12
|
+
SimulationOverridesBuilder,
|
|
13
|
+
type SimulationOverridesPlan,
|
|
14
|
+
type SlashingProposerContract,
|
|
15
15
|
type ViemCommitteeAttestations,
|
|
16
16
|
type ViemHeader,
|
|
17
|
+
buildSimulationOverridesStateOverride,
|
|
17
18
|
} from '@aztec/ethereum/contracts';
|
|
18
19
|
import { type L1FeeAnalysisResult, L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
19
20
|
import {
|
|
@@ -27,7 +28,6 @@ import {
|
|
|
27
28
|
} from '@aztec/ethereum/l1-tx-utils';
|
|
28
29
|
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
29
30
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
30
|
-
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
31
31
|
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
32
32
|
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
33
33
|
import { pick } from '@aztec/foundation/collection';
|
|
@@ -45,14 +45,12 @@ import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slas
|
|
|
45
45
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
46
46
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
47
47
|
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
48
|
-
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
49
48
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
50
49
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
51
50
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
52
51
|
|
|
53
52
|
import {
|
|
54
53
|
type Hex,
|
|
55
|
-
type StateOverride,
|
|
56
54
|
type TransactionReceipt,
|
|
57
55
|
type TypedDataDefinition,
|
|
58
56
|
encodeFunctionData,
|
|
@@ -100,16 +98,13 @@ export const Actions = [
|
|
|
100
98
|
'invalidate-by-insufficient-attestations',
|
|
101
99
|
'propose',
|
|
102
100
|
'governance-signal',
|
|
103
|
-
'empire-slashing-signal',
|
|
104
|
-
'create-empire-payload',
|
|
105
|
-
'execute-empire-payload',
|
|
106
101
|
'vote-offenses',
|
|
107
102
|
'execute-slash',
|
|
108
103
|
] as const;
|
|
109
104
|
|
|
110
105
|
export type Action = (typeof Actions)[number];
|
|
111
106
|
|
|
112
|
-
type GovernanceSignalAction = Extract<Action, 'governance-signal'
|
|
107
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal'>;
|
|
113
108
|
|
|
114
109
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
115
110
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
@@ -124,12 +119,19 @@ export type InvalidateCheckpointRequest = {
|
|
|
124
119
|
lastArchive: Fr;
|
|
125
120
|
};
|
|
126
121
|
|
|
122
|
+
type EnqueueProposeCheckpointOpts = {
|
|
123
|
+
txTimeoutAt?: Date;
|
|
124
|
+
simulationOverridesPlan?: SimulationOverridesPlan;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
127
|
interface RequestWithExpiry {
|
|
128
128
|
action: Action;
|
|
129
129
|
request: L1TxRequest;
|
|
130
130
|
lastValidL2Slot: SlotNumber;
|
|
131
131
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
132
132
|
blobConfig?: L1BlobInputs;
|
|
133
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
134
|
+
preCheck?: () => Promise<void>;
|
|
133
135
|
checkSuccess: (
|
|
134
136
|
request: L1TxRequest,
|
|
135
137
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -183,8 +185,7 @@ export class SequencerPublisher {
|
|
|
183
185
|
public l1TxUtils: L1TxUtils;
|
|
184
186
|
public rollupContract: RollupContract;
|
|
185
187
|
public govProposerContract: GovernanceProposerContract;
|
|
186
|
-
public slashingProposerContract:
|
|
187
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
188
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
188
189
|
|
|
189
190
|
public readonly tracer: Tracer;
|
|
190
191
|
|
|
@@ -198,9 +199,8 @@ export class SequencerPublisher {
|
|
|
198
199
|
blobClient: BlobClientInterface;
|
|
199
200
|
l1TxUtils: L1TxUtils;
|
|
200
201
|
rollupContract: RollupContract;
|
|
201
|
-
slashingProposerContract:
|
|
202
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
202
203
|
governanceProposerContract: GovernanceProposerContract;
|
|
203
|
-
slashFactoryContract: SlashFactoryContract;
|
|
204
204
|
epochCache: EpochCache;
|
|
205
205
|
dateProvider: DateProvider;
|
|
206
206
|
metrics: SequencerPublisherMetrics;
|
|
@@ -235,8 +235,6 @@ export class SequencerPublisher {
|
|
|
235
235
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
236
236
|
this.slashingProposerContract = newSlashingProposer;
|
|
237
237
|
});
|
|
238
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
239
|
-
|
|
240
238
|
// Initialize L1 fee analyzer for fisherman mode
|
|
241
239
|
if (config.fishermanMode) {
|
|
242
240
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -569,6 +567,28 @@ export class SequencerPublisher {
|
|
|
569
567
|
if (this.interrupted) {
|
|
570
568
|
return undefined;
|
|
571
569
|
}
|
|
570
|
+
|
|
571
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
572
|
+
const validRequests: RequestWithExpiry[] = [];
|
|
573
|
+
for (const request of this.requests) {
|
|
574
|
+
if (!request.preCheck) {
|
|
575
|
+
validRequests.push(request);
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
await request.preCheck();
|
|
581
|
+
validRequests.push(request);
|
|
582
|
+
} catch (err) {
|
|
583
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
this.requests = validRequests;
|
|
588
|
+
if (this.requests.length === 0) {
|
|
589
|
+
return undefined;
|
|
590
|
+
}
|
|
591
|
+
|
|
572
592
|
return this.sendRequests();
|
|
573
593
|
}
|
|
574
594
|
|
|
@@ -647,27 +667,21 @@ export class SequencerPublisher {
|
|
|
647
667
|
* @param tipArchive - The archive to check
|
|
648
668
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
649
669
|
*/
|
|
650
|
-
public canProposeAt(
|
|
651
|
-
tipArchive: Fr,
|
|
652
|
-
msgSender: EthAddress,
|
|
653
|
-
opts: {
|
|
654
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
655
|
-
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
656
|
-
pipelined?: boolean;
|
|
657
|
-
} = {},
|
|
658
|
-
) {
|
|
670
|
+
public async canProposeAt(tipArchive: Fr, msgSender: EthAddress, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
659
671
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
660
672
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
661
673
|
|
|
662
|
-
const pipelined =
|
|
674
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
663
675
|
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
664
676
|
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
665
677
|
|
|
666
678
|
return this.rollupContract
|
|
667
|
-
.canProposeAt(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
679
|
+
.canProposeAt(
|
|
680
|
+
tipArchive.toBuffer(),
|
|
681
|
+
msgSender.toString(),
|
|
682
|
+
nextL1SlotTs,
|
|
683
|
+
await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan),
|
|
684
|
+
)
|
|
671
685
|
.catch(err => {
|
|
672
686
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
673
687
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find(e => err.message.includes(e))}`, {
|
|
@@ -689,13 +703,13 @@ export class SequencerPublisher {
|
|
|
689
703
|
@trackSpan('SequencerPublisher.validateBlockHeader')
|
|
690
704
|
public async validateBlockHeader(
|
|
691
705
|
header: CheckpointHeader,
|
|
692
|
-
|
|
706
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
693
707
|
): Promise<void> {
|
|
694
708
|
const flags = { ignoreDA: true, ignoreSignatures: true };
|
|
695
709
|
|
|
696
710
|
const args = [
|
|
697
711
|
header.toViem(),
|
|
698
|
-
CommitteeAttestationsAndSigners.
|
|
712
|
+
CommitteeAttestationsAndSigners.packAttestations([]),
|
|
699
713
|
[], // no signers
|
|
700
714
|
Signature.empty().toViemSignature(),
|
|
701
715
|
`0x${'0'.repeat(64)}`, // 32 empty bytes
|
|
@@ -704,9 +718,7 @@ export class SequencerPublisher {
|
|
|
704
718
|
] as const;
|
|
705
719
|
|
|
706
720
|
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
707
|
-
const stateOverrides = await this.rollupContract
|
|
708
|
-
opts?.forcePendingCheckpointNumber,
|
|
709
|
-
);
|
|
721
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
710
722
|
let balance = 0n;
|
|
711
723
|
if (this.config.fishermanMode) {
|
|
712
724
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -838,9 +850,7 @@ export class SequencerPublisher {
|
|
|
838
850
|
const logData = { ...checkpoint, reason };
|
|
839
851
|
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
840
852
|
|
|
841
|
-
const attestationsAndSigners =
|
|
842
|
-
validationResult.attestations,
|
|
843
|
-
).getPackedAttestations();
|
|
853
|
+
const attestationsAndSigners = CommitteeAttestationsAndSigners.packAttestations(validationResult.attestations);
|
|
844
854
|
|
|
845
855
|
if (reason === 'invalid-attestation') {
|
|
846
856
|
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
@@ -867,10 +877,7 @@ export class SequencerPublisher {
|
|
|
867
877
|
checkpoint: Checkpoint,
|
|
868
878
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
869
879
|
attestationsAndSignersSignature: Signature,
|
|
870
|
-
|
|
871
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
872
|
-
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
873
|
-
},
|
|
880
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
874
881
|
): Promise<void> {
|
|
875
882
|
const blobFields = checkpoint.toBlobFields();
|
|
876
883
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
@@ -890,7 +897,7 @@ export class SequencerPublisher {
|
|
|
890
897
|
blobInput,
|
|
891
898
|
] as const;
|
|
892
899
|
|
|
893
|
-
await this.simulateProposeTx(args,
|
|
900
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
894
901
|
}
|
|
895
902
|
|
|
896
903
|
private async enqueueCastSignalHelper(
|
|
@@ -1078,55 +1085,6 @@ export class SequencerPublisher {
|
|
|
1078
1085
|
|
|
1079
1086
|
for (const action of actions) {
|
|
1080
1087
|
switch (action.type) {
|
|
1081
|
-
case 'vote-empire-payload': {
|
|
1082
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1083
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
1087
|
-
signerAddress,
|
|
1088
|
-
});
|
|
1089
|
-
await this.enqueueCastSignalHelper(
|
|
1090
|
-
slotNumber,
|
|
1091
|
-
'empire-slashing-signal',
|
|
1092
|
-
action.payload,
|
|
1093
|
-
this.slashingProposerContract,
|
|
1094
|
-
signerAddress,
|
|
1095
|
-
signer,
|
|
1096
|
-
);
|
|
1097
|
-
break;
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
case 'create-empire-payload': {
|
|
1101
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1102
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
1103
|
-
await this.simulateAndEnqueueRequest(
|
|
1104
|
-
'create-empire-payload',
|
|
1105
|
-
request,
|
|
1106
|
-
(receipt: TransactionReceipt) =>
|
|
1107
|
-
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
1108
|
-
slotNumber,
|
|
1109
|
-
);
|
|
1110
|
-
break;
|
|
1111
|
-
}
|
|
1112
|
-
|
|
1113
|
-
case 'execute-empire-payload': {
|
|
1114
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1115
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1116
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
1117
|
-
return false;
|
|
1118
|
-
}
|
|
1119
|
-
const empireSlashingProposer = this.slashingProposerContract as EmpireSlashingProposerContract;
|
|
1120
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
1121
|
-
await this.simulateAndEnqueueRequest(
|
|
1122
|
-
'execute-empire-payload',
|
|
1123
|
-
request,
|
|
1124
|
-
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
1125
|
-
slotNumber,
|
|
1126
|
-
);
|
|
1127
|
-
break;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
1088
|
case 'vote-offenses': {
|
|
1131
1089
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
1132
1090
|
slotNumber,
|
|
@@ -1134,17 +1092,16 @@ export class SequencerPublisher {
|
|
|
1134
1092
|
votesCount: action.votes.length,
|
|
1135
1093
|
signerAddress,
|
|
1136
1094
|
});
|
|
1137
|
-
if (this.slashingProposerContract
|
|
1138
|
-
this.log.error('
|
|
1095
|
+
if (!this.slashingProposerContract) {
|
|
1096
|
+
this.log.error('No slashing proposer contract available');
|
|
1139
1097
|
return false;
|
|
1140
1098
|
}
|
|
1141
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
1142
1099
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1143
|
-
const request = await
|
|
1100
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1144
1101
|
await this.simulateAndEnqueueRequest(
|
|
1145
1102
|
'vote-offenses',
|
|
1146
1103
|
request,
|
|
1147
|
-
(receipt: TransactionReceipt) => !!
|
|
1104
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
1148
1105
|
slotNumber,
|
|
1149
1106
|
);
|
|
1150
1107
|
break;
|
|
@@ -1156,16 +1113,19 @@ export class SequencerPublisher {
|
|
|
1156
1113
|
round: action.round,
|
|
1157
1114
|
signerAddress,
|
|
1158
1115
|
});
|
|
1159
|
-
if (this.slashingProposerContract
|
|
1160
|
-
this.log.error('
|
|
1116
|
+
if (!this.slashingProposerContract) {
|
|
1117
|
+
this.log.error('No slashing proposer contract available');
|
|
1161
1118
|
return false;
|
|
1162
1119
|
}
|
|
1163
|
-
const
|
|
1164
|
-
|
|
1120
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1121
|
+
action.round,
|
|
1122
|
+
action.committees,
|
|
1123
|
+
);
|
|
1165
1124
|
await this.simulateAndEnqueueRequest(
|
|
1166
1125
|
'execute-slash',
|
|
1167
|
-
|
|
1168
|
-
(receipt: TransactionReceipt) =>
|
|
1126
|
+
executeRequest,
|
|
1127
|
+
(receipt: TransactionReceipt) =>
|
|
1128
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
1169
1129
|
slotNumber,
|
|
1170
1130
|
);
|
|
1171
1131
|
break;
|
|
@@ -1186,11 +1146,7 @@ export class SequencerPublisher {
|
|
|
1186
1146
|
checkpoint: Checkpoint,
|
|
1187
1147
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
1188
1148
|
attestationsAndSignersSignature: Signature,
|
|
1189
|
-
opts: {
|
|
1190
|
-
txTimeoutAt?: Date;
|
|
1191
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1192
|
-
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1193
|
-
} = {},
|
|
1149
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1194
1150
|
): Promise<void> {
|
|
1195
1151
|
const checkpointHeader = checkpoint.header;
|
|
1196
1152
|
|
|
@@ -1206,6 +1162,10 @@ export class SequencerPublisher {
|
|
|
1206
1162
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
1207
1163
|
};
|
|
1208
1164
|
|
|
1165
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan)
|
|
1166
|
+
.withoutBlobCheck()
|
|
1167
|
+
.build();
|
|
1168
|
+
|
|
1209
1169
|
try {
|
|
1210
1170
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
1211
1171
|
// This means that we can avoid the simulation issues in later checks.
|
|
@@ -1216,19 +1176,43 @@ export class SequencerPublisher {
|
|
|
1216
1176
|
checkpoint,
|
|
1217
1177
|
attestationsAndSigners,
|
|
1218
1178
|
attestationsAndSignersSignature,
|
|
1219
|
-
|
|
1179
|
+
simulationOverridesPlan,
|
|
1220
1180
|
);
|
|
1221
1181
|
} catch (err: any) {
|
|
1222
1182
|
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1223
1183
|
...checkpoint.getStats(),
|
|
1224
1184
|
slotNumber: checkpoint.header.slotNumber,
|
|
1225
|
-
|
|
1185
|
+
simulationOverridesPlan,
|
|
1226
1186
|
});
|
|
1227
1187
|
throw err;
|
|
1228
1188
|
}
|
|
1229
1189
|
|
|
1230
|
-
|
|
1231
|
-
|
|
1190
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1191
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1192
|
+
let preCheck = undefined;
|
|
1193
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1194
|
+
preCheck = async () => {
|
|
1195
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1196
|
+
await this.validateCheckpointForSubmission(
|
|
1197
|
+
checkpoint,
|
|
1198
|
+
attestationsAndSigners,
|
|
1199
|
+
attestationsAndSignersSignature,
|
|
1200
|
+
simulationOverridesPlan,
|
|
1201
|
+
);
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1206
|
+
...checkpoint.toCheckpointInfo(),
|
|
1207
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1208
|
+
simulationOverridesPlan,
|
|
1209
|
+
});
|
|
1210
|
+
await this.addProposeTx(
|
|
1211
|
+
checkpoint,
|
|
1212
|
+
proposeTxArgs,
|
|
1213
|
+
{ txTimeoutAt: opts.txTimeoutAt, simulationOverridesPlan },
|
|
1214
|
+
preCheck,
|
|
1215
|
+
);
|
|
1232
1216
|
}
|
|
1233
1217
|
|
|
1234
1218
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1358,10 +1342,7 @@ export class SequencerPublisher {
|
|
|
1358
1342
|
this.l1TxUtils.restart();
|
|
1359
1343
|
}
|
|
1360
1344
|
|
|
1361
|
-
private async prepareProposeTx(
|
|
1362
|
-
encodedData: L1ProcessArgs,
|
|
1363
|
-
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1364
|
-
) {
|
|
1345
|
+
private async prepareProposeTx(encodedData: L1ProcessArgs, simulationOverridesPlan?: SimulationOverridesPlan) {
|
|
1365
1346
|
const kzg = Blob.getViemKzgInstance();
|
|
1366
1347
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
1367
1348
|
this.log.debug('Validating blob input', { blobInput });
|
|
@@ -1432,7 +1413,7 @@ export class SequencerPublisher {
|
|
|
1432
1413
|
blobInput,
|
|
1433
1414
|
] as const;
|
|
1434
1415
|
|
|
1435
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1416
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
1436
1417
|
|
|
1437
1418
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1438
1419
|
}
|
|
@@ -1456,10 +1437,7 @@ export class SequencerPublisher {
|
|
|
1456
1437
|
ViemSignature,
|
|
1457
1438
|
`0x${string}`,
|
|
1458
1439
|
],
|
|
1459
|
-
|
|
1460
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1461
|
-
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1462
|
-
},
|
|
1440
|
+
simulationOverridesPlan?: SimulationOverridesPlan,
|
|
1463
1441
|
) {
|
|
1464
1442
|
const rollupData = encodeFunctionData({
|
|
1465
1443
|
abi: RollupAbi,
|
|
@@ -1467,34 +1445,7 @@ export class SequencerPublisher {
|
|
|
1467
1445
|
args,
|
|
1468
1446
|
});
|
|
1469
1447
|
|
|
1470
|
-
|
|
1471
|
-
const forcePendingCheckpointNumberStateDiff = (
|
|
1472
|
-
options.forcePendingCheckpointNumber !== undefined
|
|
1473
|
-
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1474
|
-
: []
|
|
1475
|
-
).flatMap(override => override.stateDiff ?? []);
|
|
1476
|
-
|
|
1477
|
-
// override the fee header for a specific checkpoint number if requested (used when pipelining)
|
|
1478
|
-
const forceProposedFeeHeaderStateDiff = (
|
|
1479
|
-
options.forceProposedFeeHeader !== undefined
|
|
1480
|
-
? await this.rollupContract.makeFeeHeaderOverride(
|
|
1481
|
-
options.forceProposedFeeHeader.checkpointNumber,
|
|
1482
|
-
options.forceProposedFeeHeader.feeHeader,
|
|
1483
|
-
)
|
|
1484
|
-
: []
|
|
1485
|
-
).flatMap(override => override.stateDiff ?? []);
|
|
1486
|
-
|
|
1487
|
-
const stateOverrides: StateOverride = [
|
|
1488
|
-
{
|
|
1489
|
-
address: this.rollupContract.address,
|
|
1490
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
1491
|
-
stateDiff: [
|
|
1492
|
-
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1493
|
-
...forcePendingCheckpointNumberStateDiff,
|
|
1494
|
-
...forceProposedFeeHeaderStateDiff,
|
|
1495
|
-
],
|
|
1496
|
-
},
|
|
1497
|
-
];
|
|
1448
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
1498
1449
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
1499
1450
|
if (this.proposerAddressForSimulation) {
|
|
1500
1451
|
stateOverrides.push({
|
|
@@ -1559,16 +1510,16 @@ export class SequencerPublisher {
|
|
|
1559
1510
|
private async addProposeTx(
|
|
1560
1511
|
checkpoint: Checkpoint,
|
|
1561
1512
|
encodedData: L1ProcessArgs,
|
|
1562
|
-
opts: {
|
|
1563
|
-
|
|
1564
|
-
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1565
|
-
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1566
|
-
} = {},
|
|
1513
|
+
opts: EnqueueProposeCheckpointOpts = {},
|
|
1514
|
+
preCheck?: () => Promise<void>,
|
|
1567
1515
|
): Promise<void> {
|
|
1568
1516
|
const slot = checkpoint.header.slotNumber;
|
|
1569
1517
|
const timer = new Timer();
|
|
1570
1518
|
const kzg = Blob.getViemKzgInstance();
|
|
1571
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1519
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1520
|
+
encodedData,
|
|
1521
|
+
opts.simulationOverridesPlan,
|
|
1522
|
+
);
|
|
1572
1523
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1573
1524
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1574
1525
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1591,7 +1542,8 @@ export class SequencerPublisher {
|
|
|
1591
1542
|
data: rollupData,
|
|
1592
1543
|
},
|
|
1593
1544
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1594
|
-
gasConfig: {
|
|
1545
|
+
gasConfig: { txTimeoutAt: opts.txTimeoutAt, gasLimit },
|
|
1546
|
+
preCheck,
|
|
1595
1547
|
blobConfig: {
|
|
1596
1548
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1597
1549
|
kzg,
|
package/src/sequencer/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Sequencer Timing Model
|
|
2
2
|
|
|
3
|
-
The Aztec sequencer divides each slot into **fixed-duration sub-slots**. Each sub-slot has a pre-defined start and end time based on an initialization offset (how much time we expect syncing the previous slot will take),
|
|
3
|
+
The Aztec sequencer divides each slot into **fixed-duration sub-slots**. Each sub-slot has a pre-defined start and end time based on an initialization offset (how much time we expect syncing the previous slot will take), the configured block duration, and whether checkpoint finalization is paid for in the current slot or deferred under proposer pipelining.
|
|
4
4
|
|
|
5
|
-
**Example: 72-second slot with 8-second sub-slots**
|
|
5
|
+
**Example: 72-second slot with 8-second sub-slots (non-pipelined)**
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
0s: Slot starts
|
|
@@ -31,7 +31,7 @@ Deadlines are fixed relative to slot start, not relative to when work actually c
|
|
|
31
31
|
|
|
32
32
|
## Overview
|
|
33
33
|
|
|
34
|
-
The Aztec sequencer operates in fixed-duration **slots** (typically 72 seconds). During each slot, a designated proposer builds multiple **blocks** containing transactions over multiple **sub-slots
|
|
34
|
+
The Aztec sequencer operates in fixed-duration **slots** (typically 72 seconds). During each slot, a designated proposer builds multiple **blocks** containing transactions over multiple **sub-slots**. In the default mode, the same slot also reserves time to collect attestations for the resulting **checkpoint**, finalize it, and publish it to L1 Ethereum. When proposer pipelining is enabled, the slot budget for block building is larger because checkpoint finalization is deferred to the next target slot.
|
|
35
35
|
|
|
36
36
|
## Key Concepts
|
|
37
37
|
|
|
@@ -42,12 +42,14 @@ The Aztec sequencer operates in fixed-duration **slots** (typically 72 seconds).
|
|
|
42
42
|
- **Checkpoint**: The collection of all blocks built in a slot, attested by validators and published to L1
|
|
43
43
|
- **Sub-slot**: A fixed-duration time window within a slot (e.g., 8 seconds) during which a block should be built
|
|
44
44
|
|
|
45
|
-
In a typical configuration, a 72-second slot contains:
|
|
45
|
+
In a typical configuration without pipelining, a 72-second slot contains:
|
|
46
46
|
- 1 initialization period (2 seconds)
|
|
47
47
|
- 5 block-building sub-slots (8 seconds each = 40 seconds)
|
|
48
48
|
- 1 last validator re-execution sub-slot (8 seconds)
|
|
49
49
|
- 1 attestation and publishing period (17 seconds)
|
|
50
50
|
|
|
51
|
+
With proposer pipelining enabled, the last validator re-execution sub-slot is still reserved, but L1 publishing is deferred to the target slot and removed from the current slot budget. Attestation collection is completed inside the build slot itself, so the proposer can send the L1 transaction immediately at the target-slot boundary.
|
|
52
|
+
|
|
51
53
|
### The Fixed Sub-Slot Model
|
|
52
54
|
|
|
53
55
|
Building multiple blocks per slot uses **fixed sub-slots** with predictable deadlines:
|
|
@@ -75,14 +77,20 @@ These values are configurable but must satisfy certain constraints (explained be
|
|
|
75
77
|
|
|
76
78
|
## Calculating Sub-Slots and Blocks
|
|
77
79
|
|
|
78
|
-
Given a slot configuration, we calculate how many blocks fit using
|
|
80
|
+
Given a slot configuration, we calculate how many blocks fit using these formulas:
|
|
79
81
|
|
|
80
82
|
```
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
checkpointFinalizationTime = propagationTime
|
|
84
|
+
+ propagationTime
|
|
85
|
+
+ finalizationTime
|
|
86
|
+
+ l1PublishingTime
|
|
87
|
+
|
|
88
|
+
timeReservedAtEnd (normal mode) = blockDuration (last sub-slot for reexecution)
|
|
89
|
+
+ checkpointFinalizationTime
|
|
90
|
+
|
|
91
|
+
timeReservedAtEnd (pipelining) = assembleTime
|
|
92
|
+
+ 2 * propagationTime (proposal out + attestations back)
|
|
93
|
+
+ blockDuration (last-block re-execution)
|
|
86
94
|
|
|
87
95
|
timeAvailableForBlocks = slotDuration - initializationOffset - timeReservedAtEnd
|
|
88
96
|
|
|
@@ -101,6 +109,60 @@ This means:
|
|
|
101
109
|
- Sub-slot 6: Reserved for validator re-execution of block 5
|
|
102
110
|
- After sub-slot 6: Attestation collection, finalization, and L1 publishing
|
|
103
111
|
|
|
112
|
+
**The same slot with proposer pipelining enabled:**
|
|
113
|
+
```
|
|
114
|
+
timeReservedAtEnd = 1s + 2*2s + 8s = 13s
|
|
115
|
+
timeAvailableForBlocks = 72s - 2s - 13s = 57s
|
|
116
|
+
numberOfBlocks = floor(57s / 8s) = 7 blocks
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The extra two block opportunities come from not charging the current slot for L1 publishing. The proposal broadcast, attestation round-trip, and last-block re-execution are now all reserved inside the build slot so that attestations are in hand at the slot boundary.
|
|
120
|
+
|
|
121
|
+
### Pipelining Mode
|
|
122
|
+
|
|
123
|
+
When proposer pipelining is enabled, the sequencer uses the current wall-clock slot to build the checkpoint for the **next target slot**, and finishes collecting attestations before the slot boundary so that L1 publishing can happen immediately at the target-slot boundary.
|
|
124
|
+
|
|
125
|
+
It helps to think in terms of two different slots:
|
|
126
|
+
|
|
127
|
+
- **Wall-clock slot N-1**: The sequencer initializes checkpoint `N`, builds its blocks, validators re-execute the last block, and attestations are gathered
|
|
128
|
+
- **Target slot N**: The checkpoint is submitted to L1
|
|
129
|
+
|
|
130
|
+
So the work is split like this:
|
|
131
|
+
|
|
132
|
+
- **During slot N-1**: Initialization, block building, last-block re-execution, proposal broadcast, and attestation collection
|
|
133
|
+
- **At the start of slot N**: The L1 transaction is submitted — attestations are already in hand
|
|
134
|
+
|
|
135
|
+
In other words, pipelining moves **block production, block re-execution, proposal broadcast, and attestation collection** into the build slot, while **L1 submission** happens aligned with slot `N`. With default values (72s slot, 6s block, 2s p2p, 1s assemble), the last build-slot block finishes at `T = slotDuration - timeReservedAtEnd = 61s`, the proposer broadcasts the checkpoint at `T=62s` after `assembleTime=1s`, and attestations are in hand by `T=72s` (the slot boundary).
|
|
136
|
+
|
|
137
|
+
**Example: building checkpoint 12 while wall-clock time is in slot 11**
|
|
138
|
+
```
|
|
139
|
+
Slot 11 (wall clock):
|
|
140
|
+
- Build blocks that will make up checkpoint 12
|
|
141
|
+
- Broadcast checkpoint 12 proposal
|
|
142
|
+
- Validators re-execute the last block of checkpoint 12
|
|
143
|
+
- Collect checkpoint 12 attestations (all complete before slot 11 ends)
|
|
144
|
+
|
|
145
|
+
Slot 12 (target/submission slot):
|
|
146
|
+
- Submit checkpoint 12 to L1 at the slot boundary
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
For timetable purposes:
|
|
150
|
+
|
|
151
|
+
- `maxNumberOfBlocks` is computed by reserving assembly + round-trip p2p + last-block re-execution at the end of the slot
|
|
152
|
+
- `initializeDeadline` no longer subtracts checkpoint finalization time; it only requires enough time for initialization and two execution windows
|
|
153
|
+
|
|
154
|
+
In code, that means:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
initializeDeadline (normal mode) =
|
|
158
|
+
slotDuration - initializationOffset - 2 * minExecutionTime - checkpointFinalizationTime
|
|
159
|
+
|
|
160
|
+
initializeDeadline (pipelining) =
|
|
161
|
+
slotDuration - initializationOffset - 2 * minExecutionTime
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The fixed sub-slot deadlines themselves do not change. Pipelining only changes how much of the slot is considered available for block building, and when the broadcast and attestation windows close.
|
|
165
|
+
|
|
104
166
|
## The Sequencer's Work
|
|
105
167
|
|
|
106
168
|
When elected as proposer for a slot, the sequencer performs these tasks:
|
|
@@ -226,7 +288,9 @@ After the last block is built and validators have re-executed it:
|
|
|
226
288
|
|
|
227
289
|
**Time reserved:** `2*propagationTime + finalizationTime + l1PublishingTime = 2s + 2s + 1s + 12s = 17s`
|
|
228
290
|
|
|
229
|
-
|
|
291
|
+
In the non-pipelined path, this 17s comes after the last sub-slot, ensuring we have enough time to complete the checkpoint. If the sequencer receives the necessary attestations before the reserved time, the L1 tx is submitted earlier.
|
|
292
|
+
|
|
293
|
+
With proposer pipelining enabled, this finalization budget is not charged against the current slot when calculating how many blocks fit. The checkpoint is instead queued for submission at the start of the target slot, so proposal broadcast, attestation gathering, and L1 submission happen in slot `N` while block building and block re-execution already happened in slot `N-1`.
|
|
230
294
|
|
|
231
295
|
## Handling Timing Variations
|
|
232
296
|
|
|
@@ -399,7 +463,7 @@ When configuring timing parameters, ensure these constraints are satisfied:
|
|
|
399
463
|
|
|
400
464
|
### Minimum Slot Duration
|
|
401
465
|
|
|
402
|
-
For a valid configuration:
|
|
466
|
+
For a valid multi-block configuration without pipelining:
|
|
403
467
|
```
|
|
404
468
|
slotDuration >= initializationOffset
|
|
405
469
|
+ blockDuration * 2 (at least 2 blocks)
|
|
@@ -414,6 +478,11 @@ Simplified:
|
|
|
414
478
|
slotDuration >= initializationOffset + 3*blockDuration + 2*propagationTime + finalizationTime + l1PublishingTime
|
|
415
479
|
```
|
|
416
480
|
|
|
481
|
+
With proposer pipelining enabled, the same "at least 2 buildable blocks plus the final validator re-execution sub-slot" requirement becomes:
|
|
482
|
+
```
|
|
483
|
+
slotDuration >= initializationOffset + 3*blockDuration
|
|
484
|
+
```
|
|
485
|
+
|
|
417
486
|
**Example:**
|
|
418
487
|
```
|
|
419
488
|
slotDuration >= 2s + 3*8s + 2*2s + 1s + 12s = 2s + 24s + 4s + 1s + 12s = 43s
|
|
@@ -465,7 +534,7 @@ The sequencer transitions through these states during a slot:
|
|
|
465
534
|
| **WAITING_UNTIL_NEXT_BLOCK** | Until next sub-slot start | Sleep between blocks to maintain intervals |
|
|
466
535
|
| **ASSEMBLING_CHECKPOINT** | assembleTime (1s) | Assemble final checkpoint |
|
|
467
536
|
| **COLLECTING_ATTESTATIONS** | Until L1 publish deadline | Wait for validator signatures |
|
|
468
|
-
| **PUBLISHING_CHECKPOINT** | Until
|
|
537
|
+
| **PUBLISHING_CHECKPOINT** | Until L1 publish deadline | Submit to L1 |
|
|
469
538
|
|
|
470
539
|
## Complete Example: 72-Second Slot with 8-Second Sub-Slots
|
|
471
540
|
|