@aztec/sequencer-client 0.0.1-commit.e0f15ab9b → 0.0.1-commit.e304674f1
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 +1 -1
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +0 -4
- package/dest/global_variable_builder/global_builder.d.ts +3 -3
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +7 -4
- 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 +52 -31
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +106 -87
- package/dest/sequencer/checkpoint_proposal_job.d.ts +31 -10
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_proposal_job.js +179 -108
- package/dest/sequencer/checkpoint_voter.d.ts +1 -2
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -1
- package/dest/sequencer/checkpoint_voter.js +2 -5
- package/dest/sequencer/sequencer.d.ts +14 -4
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +67 -18
- package/dest/sequencer/timetable.d.ts +4 -1
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +15 -5
- package/package.json +27 -27
- package/src/client/sequencer-client.ts +0 -7
- package/src/global_variable_builder/global_builder.ts +15 -3
- package/src/publisher/sequencer-publisher-factory.ts +0 -3
- package/src/publisher/sequencer-publisher.ts +174 -124
- package/src/sequencer/README.md +81 -12
- package/src/sequencer/checkpoint_proposal_job.ts +215 -117
- package/src/sequencer/checkpoint_voter.ts +1 -12
- package/src/sequencer/sequencer.ts +97 -20
- package/src/sequencer/timetable.ts +19 -8
|
@@ -3,14 +3,14 @@ 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,
|
|
7
|
+
type FeeHeader,
|
|
8
8
|
type GovernanceProposerContract,
|
|
9
9
|
type IEmpireBase,
|
|
10
10
|
MULTI_CALL_3_ADDRESS,
|
|
11
11
|
Multicall3,
|
|
12
12
|
RollupContract,
|
|
13
|
-
type
|
|
13
|
+
type SlashingProposerContract,
|
|
14
14
|
type ViemCommitteeAttestations,
|
|
15
15
|
type ViemHeader,
|
|
16
16
|
} from '@aztec/ethereum/contracts';
|
|
@@ -36,14 +36,14 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
36
36
|
import { Signature, type ViemSignature } from '@aztec/foundation/eth-signature';
|
|
37
37
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
38
38
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
39
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
39
40
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
40
|
-
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
41
|
+
import { type DateProvider, Timer } from '@aztec/foundation/timer';
|
|
41
42
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
42
43
|
import { type ProposerSlashAction, encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
43
44
|
import { CommitteeAttestationsAndSigners, type ValidateCheckpointResult } from '@aztec/stdlib/block';
|
|
44
45
|
import type { Checkpoint } from '@aztec/stdlib/checkpoint';
|
|
45
|
-
import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
46
|
-
import { SlashFactoryContract } from '@aztec/stdlib/l1-contracts';
|
|
46
|
+
import { getLastL1SlotTimestampForL2Slot, getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
47
47
|
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
48
48
|
import type { L1PublishCheckpointStats } from '@aztec/stdlib/stats';
|
|
49
49
|
import { type TelemetryClient, type Tracer, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
@@ -63,6 +63,20 @@ import type { SequencerPublisherConfig } from './config.js';
|
|
|
63
63
|
import { type FailedL1Tx, type L1TxFailedStore, createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
64
64
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
65
65
|
|
|
66
|
+
/** Result of a sendRequests call, returned by both sendRequests() and sendRequestsAt(). */
|
|
67
|
+
export type SendRequestsResult = {
|
|
68
|
+
/** The L1 transaction receipt or error from the bundled multicall. */
|
|
69
|
+
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError;
|
|
70
|
+
/** Actions that expired (past their deadline) before the request was sent. */
|
|
71
|
+
expiredActions: Action[];
|
|
72
|
+
/** Actions that were included in the sent L1 transaction. */
|
|
73
|
+
sentActions: Action[];
|
|
74
|
+
/** Actions whose L1 simulation succeeded (subset of sentActions). */
|
|
75
|
+
successfulActions: Action[];
|
|
76
|
+
/** Actions whose L1 simulation failed (subset of sentActions). */
|
|
77
|
+
failedActions: Action[];
|
|
78
|
+
};
|
|
79
|
+
|
|
66
80
|
/** Arguments to the process method of the rollup contract */
|
|
67
81
|
type L1ProcessArgs = {
|
|
68
82
|
/** The L2 block header. */
|
|
@@ -84,16 +98,13 @@ export const Actions = [
|
|
|
84
98
|
'invalidate-by-insufficient-attestations',
|
|
85
99
|
'propose',
|
|
86
100
|
'governance-signal',
|
|
87
|
-
'empire-slashing-signal',
|
|
88
|
-
'create-empire-payload',
|
|
89
|
-
'execute-empire-payload',
|
|
90
101
|
'vote-offenses',
|
|
91
102
|
'execute-slash',
|
|
92
103
|
] as const;
|
|
93
104
|
|
|
94
105
|
export type Action = (typeof Actions)[number];
|
|
95
106
|
|
|
96
|
-
type GovernanceSignalAction = Extract<Action, 'governance-signal'
|
|
107
|
+
type GovernanceSignalAction = Extract<Action, 'governance-signal'>;
|
|
97
108
|
|
|
98
109
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
99
110
|
export const compareActions = (a: Action, b: Action) => Actions.indexOf(a) - Actions.indexOf(b);
|
|
@@ -104,6 +115,8 @@ export type InvalidateCheckpointRequest = {
|
|
|
104
115
|
gasUsed: bigint;
|
|
105
116
|
checkpointNumber: CheckpointNumber;
|
|
106
117
|
forcePendingCheckpointNumber: CheckpointNumber;
|
|
118
|
+
/** Archive at the rollback target checkpoint (checkpoint N-1). */
|
|
119
|
+
lastArchive: Fr;
|
|
107
120
|
};
|
|
108
121
|
|
|
109
122
|
interface RequestWithExpiry {
|
|
@@ -112,6 +125,8 @@ interface RequestWithExpiry {
|
|
|
112
125
|
lastValidL2Slot: SlotNumber;
|
|
113
126
|
gasConfig?: Pick<L1TxConfig, 'txTimeoutAt' | 'gasLimit'>;
|
|
114
127
|
blobConfig?: L1BlobInputs;
|
|
128
|
+
/** Optional pre-send validation. If it rejects, the request is discarded. */
|
|
129
|
+
preCheck?: () => Promise<void>;
|
|
115
130
|
checkSuccess: (
|
|
116
131
|
request: L1TxRequest,
|
|
117
132
|
result?: { receipt: TransactionReceipt; stats?: TransactionStats; errorMsg?: string },
|
|
@@ -135,7 +150,9 @@ export class SequencerPublisher {
|
|
|
135
150
|
protected log: Logger;
|
|
136
151
|
protected ethereumSlotDuration: bigint;
|
|
137
152
|
protected aztecSlotDuration: bigint;
|
|
138
|
-
|
|
153
|
+
|
|
154
|
+
/** Date provider for wall-clock time. */
|
|
155
|
+
private readonly dateProvider: DateProvider;
|
|
139
156
|
|
|
140
157
|
private blobClient: BlobClientInterface;
|
|
141
158
|
|
|
@@ -151,6 +168,9 @@ export class SequencerPublisher {
|
|
|
151
168
|
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */
|
|
152
169
|
private feeAssetPriceOracle: FeeAssetPriceOracle;
|
|
153
170
|
|
|
171
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */
|
|
172
|
+
private readonly interruptibleSleep = new InterruptibleSleep();
|
|
173
|
+
|
|
154
174
|
// A CALL to a cold address is 2700 gas
|
|
155
175
|
public static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
156
176
|
|
|
@@ -160,8 +180,7 @@ export class SequencerPublisher {
|
|
|
160
180
|
public l1TxUtils: L1TxUtils;
|
|
161
181
|
public rollupContract: RollupContract;
|
|
162
182
|
public govProposerContract: GovernanceProposerContract;
|
|
163
|
-
public slashingProposerContract:
|
|
164
|
-
public slashFactoryContract: SlashFactoryContract;
|
|
183
|
+
public slashingProposerContract: SlashingProposerContract | undefined;
|
|
165
184
|
|
|
166
185
|
public readonly tracer: Tracer;
|
|
167
186
|
|
|
@@ -175,9 +194,8 @@ export class SequencerPublisher {
|
|
|
175
194
|
blobClient: BlobClientInterface;
|
|
176
195
|
l1TxUtils: L1TxUtils;
|
|
177
196
|
rollupContract: RollupContract;
|
|
178
|
-
slashingProposerContract:
|
|
197
|
+
slashingProposerContract: SlashingProposerContract | undefined;
|
|
179
198
|
governanceProposerContract: GovernanceProposerContract;
|
|
180
|
-
slashFactoryContract: SlashFactoryContract;
|
|
181
199
|
epochCache: EpochCache;
|
|
182
200
|
dateProvider: DateProvider;
|
|
183
201
|
metrics: SequencerPublisherMetrics;
|
|
@@ -194,6 +212,7 @@ export class SequencerPublisher {
|
|
|
194
212
|
this.lastActions = deps.lastActions;
|
|
195
213
|
|
|
196
214
|
this.blobClient = deps.blobClient;
|
|
215
|
+
this.dateProvider = deps.dateProvider;
|
|
197
216
|
|
|
198
217
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
199
218
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
@@ -211,8 +230,6 @@ export class SequencerPublisher {
|
|
|
211
230
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
212
231
|
this.slashingProposerContract = newSlashingProposer;
|
|
213
232
|
});
|
|
214
|
-
this.slashFactoryContract = deps.slashFactoryContract;
|
|
215
|
-
|
|
216
233
|
// Initialize L1 fee analyzer for fisherman mode
|
|
217
234
|
if (config.fishermanMode) {
|
|
218
235
|
this.l1FeeAnalyzer = new L1FeeAnalyzer(
|
|
@@ -369,9 +386,10 @@ export class SequencerPublisher {
|
|
|
369
386
|
* - undefined if no valid requests are found OR the tx failed to send.
|
|
370
387
|
*/
|
|
371
388
|
@trackSpan('SequencerPublisher.sendRequests')
|
|
372
|
-
public async sendRequests() {
|
|
389
|
+
public async sendRequests(): Promise<SendRequestsResult | undefined> {
|
|
373
390
|
const requestsToProcess = [...this.requests];
|
|
374
391
|
this.requests = [];
|
|
392
|
+
|
|
375
393
|
if (this.interrupted || requestsToProcess.length === 0) {
|
|
376
394
|
return undefined;
|
|
377
395
|
}
|
|
@@ -530,6 +548,45 @@ export class SequencerPublisher {
|
|
|
530
548
|
}
|
|
531
549
|
}
|
|
532
550
|
|
|
551
|
+
/*
|
|
552
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
553
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
554
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
555
|
+
*/
|
|
556
|
+
public async sendRequestsAt(submitAfter: Date): Promise<SendRequestsResult | undefined> {
|
|
557
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
558
|
+
if (ms > 0) {
|
|
559
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, { submitAfter });
|
|
560
|
+
await this.interruptibleSleep.sleep(ms);
|
|
561
|
+
}
|
|
562
|
+
if (this.interrupted) {
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
567
|
+
const validRequests: RequestWithExpiry[] = [];
|
|
568
|
+
for (const request of this.requests) {
|
|
569
|
+
if (!request.preCheck) {
|
|
570
|
+
validRequests.push(request);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
try {
|
|
575
|
+
await request.preCheck();
|
|
576
|
+
validRequests.push(request);
|
|
577
|
+
} catch (err) {
|
|
578
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
this.requests = validRequests;
|
|
583
|
+
if (this.requests.length === 0) {
|
|
584
|
+
return undefined;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return this.sendRequests();
|
|
588
|
+
}
|
|
589
|
+
|
|
533
590
|
private callbackBundledTransactions(
|
|
534
591
|
requests: RequestWithExpiry[],
|
|
535
592
|
result: { receipt: TransactionReceipt; errorMsg?: string } | FormattedViemError | undefined,
|
|
@@ -608,7 +665,11 @@ export class SequencerPublisher {
|
|
|
608
665
|
public canProposeAt(
|
|
609
666
|
tipArchive: Fr,
|
|
610
667
|
msgSender: EthAddress,
|
|
611
|
-
opts: {
|
|
668
|
+
opts: {
|
|
669
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
670
|
+
forceArchive?: { checkpointNumber: CheckpointNumber; archive: Fr };
|
|
671
|
+
pipelined?: boolean;
|
|
672
|
+
} = {},
|
|
612
673
|
) {
|
|
613
674
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
614
675
|
const ignoredErrors = ['SlotAlreadyInChain', 'InvalidProposer', 'InvalidArchive'];
|
|
@@ -620,6 +681,7 @@ export class SequencerPublisher {
|
|
|
620
681
|
return this.rollupContract
|
|
621
682
|
.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
622
683
|
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
684
|
+
forceArchive: opts.forceArchive,
|
|
623
685
|
})
|
|
624
686
|
.catch(err => {
|
|
625
687
|
if (err instanceof FormattedViemError && ignoredErrors.find(e => err.message.includes(e))) {
|
|
@@ -656,7 +718,7 @@ export class SequencerPublisher {
|
|
|
656
718
|
flags,
|
|
657
719
|
] as const;
|
|
658
720
|
|
|
659
|
-
const ts = this.
|
|
721
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
660
722
|
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(
|
|
661
723
|
opts?.forcePendingCheckpointNumber,
|
|
662
724
|
);
|
|
@@ -679,7 +741,7 @@ export class SequencerPublisher {
|
|
|
679
741
|
data: encodeFunctionData({ abi: RollupAbi, functionName: 'validateHeaderWithAttestations', args }),
|
|
680
742
|
from: MULTI_CALL_3_ADDRESS,
|
|
681
743
|
},
|
|
682
|
-
{ time: ts
|
|
744
|
+
{ time: ts },
|
|
683
745
|
stateOverrides,
|
|
684
746
|
);
|
|
685
747
|
this.log.debug(`Simulated validateHeader`);
|
|
@@ -732,6 +794,7 @@ export class SequencerPublisher {
|
|
|
732
794
|
gasUsed,
|
|
733
795
|
checkpointNumber,
|
|
734
796
|
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
797
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
735
798
|
reason,
|
|
736
799
|
};
|
|
737
800
|
} catch (err) {
|
|
@@ -744,8 +807,8 @@ export class SequencerPublisher {
|
|
|
744
807
|
`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`,
|
|
745
808
|
{ ...logData, request, error: viemError.message },
|
|
746
809
|
);
|
|
747
|
-
const
|
|
748
|
-
if (
|
|
810
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
811
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
749
812
|
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, { ...logData });
|
|
750
813
|
return undefined;
|
|
751
814
|
} else {
|
|
@@ -819,11 +882,11 @@ export class SequencerPublisher {
|
|
|
819
882
|
checkpoint: Checkpoint,
|
|
820
883
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
821
884
|
attestationsAndSignersSignature: Signature,
|
|
822
|
-
options: {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
885
|
+
options: {
|
|
886
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
887
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
888
|
+
},
|
|
889
|
+
): Promise<void> {
|
|
827
890
|
const blobFields = checkpoint.toBlobFields();
|
|
828
891
|
const blobs = await getBlobsPerL1Block(blobFields);
|
|
829
892
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
@@ -842,13 +905,11 @@ export class SequencerPublisher {
|
|
|
842
905
|
blobInput,
|
|
843
906
|
] as const;
|
|
844
907
|
|
|
845
|
-
await this.simulateProposeTx(args,
|
|
846
|
-
return ts;
|
|
908
|
+
await this.simulateProposeTx(args, options);
|
|
847
909
|
}
|
|
848
910
|
|
|
849
911
|
private async enqueueCastSignalHelper(
|
|
850
912
|
slotNumber: SlotNumber,
|
|
851
|
-
timestamp: bigint,
|
|
852
913
|
signalType: GovernanceSignalAction,
|
|
853
914
|
payload: EthAddress,
|
|
854
915
|
base: IEmpireBase,
|
|
@@ -927,13 +988,17 @@ export class SequencerPublisher {
|
|
|
927
988
|
});
|
|
928
989
|
|
|
929
990
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
991
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
930
992
|
|
|
931
993
|
try {
|
|
932
994
|
await this.l1TxUtils.simulate(request, { time: timestamp }, [], mergeAbis([request.abi ?? [], ErrorsAbi]));
|
|
933
995
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, { request });
|
|
934
996
|
} catch (err) {
|
|
935
997
|
const viemError = formatViemError(err);
|
|
936
|
-
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError
|
|
998
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
999
|
+
simulationTimestamp: timestamp,
|
|
1000
|
+
l1BlockNumber,
|
|
1001
|
+
});
|
|
937
1002
|
this.backupFailedTx({
|
|
938
1003
|
id: keccak256(request.data!),
|
|
939
1004
|
failureType: 'simulation',
|
|
@@ -996,19 +1061,16 @@ export class SequencerPublisher {
|
|
|
996
1061
|
/**
|
|
997
1062
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
998
1063
|
* @param slotNumber - The slot number to cast a signal for.
|
|
999
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
1000
1064
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
1001
1065
|
*/
|
|
1002
1066
|
public enqueueGovernanceCastSignal(
|
|
1003
1067
|
governancePayload: EthAddress,
|
|
1004
1068
|
slotNumber: SlotNumber,
|
|
1005
|
-
timestamp: bigint,
|
|
1006
1069
|
signerAddress: EthAddress,
|
|
1007
1070
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
1008
1071
|
): Promise<boolean> {
|
|
1009
1072
|
return this.enqueueCastSignalHelper(
|
|
1010
1073
|
slotNumber,
|
|
1011
|
-
timestamp,
|
|
1012
1074
|
'governance-signal',
|
|
1013
1075
|
governancePayload,
|
|
1014
1076
|
this.govProposerContract,
|
|
@@ -1021,7 +1083,6 @@ export class SequencerPublisher {
|
|
|
1021
1083
|
public async enqueueSlashingActions(
|
|
1022
1084
|
actions: ProposerSlashAction[],
|
|
1023
1085
|
slotNumber: SlotNumber,
|
|
1024
|
-
timestamp: bigint,
|
|
1025
1086
|
signerAddress: EthAddress,
|
|
1026
1087
|
signer: (msg: TypedDataDefinition) => Promise<`0x${string}`>,
|
|
1027
1088
|
): Promise<boolean> {
|
|
@@ -1032,58 +1093,6 @@ export class SequencerPublisher {
|
|
|
1032
1093
|
|
|
1033
1094
|
for (const action of actions) {
|
|
1034
1095
|
switch (action.type) {
|
|
1035
|
-
case 'vote-empire-payload': {
|
|
1036
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1037
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
1038
|
-
break;
|
|
1039
|
-
}
|
|
1040
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
1041
|
-
signerAddress,
|
|
1042
|
-
});
|
|
1043
|
-
await this.enqueueCastSignalHelper(
|
|
1044
|
-
slotNumber,
|
|
1045
|
-
timestamp,
|
|
1046
|
-
'empire-slashing-signal',
|
|
1047
|
-
action.payload,
|
|
1048
|
-
this.slashingProposerContract,
|
|
1049
|
-
signerAddress,
|
|
1050
|
-
signer,
|
|
1051
|
-
);
|
|
1052
|
-
break;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
case 'create-empire-payload': {
|
|
1056
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1057
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
1058
|
-
await this.simulateAndEnqueueRequest(
|
|
1059
|
-
'create-empire-payload',
|
|
1060
|
-
request,
|
|
1061
|
-
(receipt: TransactionReceipt) =>
|
|
1062
|
-
!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs),
|
|
1063
|
-
slotNumber,
|
|
1064
|
-
timestamp,
|
|
1065
|
-
);
|
|
1066
|
-
break;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
case 'execute-empire-payload': {
|
|
1070
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, { slotNumber, signerAddress });
|
|
1071
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
1072
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
1073
|
-
return false;
|
|
1074
|
-
}
|
|
1075
|
-
const empireSlashingProposer = this.slashingProposerContract as EmpireSlashingProposerContract;
|
|
1076
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
1077
|
-
await this.simulateAndEnqueueRequest(
|
|
1078
|
-
'execute-empire-payload',
|
|
1079
|
-
request,
|
|
1080
|
-
(receipt: TransactionReceipt) => !!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs),
|
|
1081
|
-
slotNumber,
|
|
1082
|
-
timestamp,
|
|
1083
|
-
);
|
|
1084
|
-
break;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
1096
|
case 'vote-offenses': {
|
|
1088
1097
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
1089
1098
|
slotNumber,
|
|
@@ -1091,19 +1100,17 @@ export class SequencerPublisher {
|
|
|
1091
1100
|
votesCount: action.votes.length,
|
|
1092
1101
|
signerAddress,
|
|
1093
1102
|
});
|
|
1094
|
-
if (this.slashingProposerContract
|
|
1095
|
-
this.log.error('
|
|
1103
|
+
if (!this.slashingProposerContract) {
|
|
1104
|
+
this.log.error('No slashing proposer contract available');
|
|
1096
1105
|
return false;
|
|
1097
1106
|
}
|
|
1098
|
-
const tallySlashingProposer = this.slashingProposerContract as TallySlashingProposerContract;
|
|
1099
1107
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
1100
|
-
const request = await
|
|
1108
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1101
1109
|
await this.simulateAndEnqueueRequest(
|
|
1102
1110
|
'vote-offenses',
|
|
1103
1111
|
request,
|
|
1104
|
-
(receipt: TransactionReceipt) => !!
|
|
1112
|
+
(receipt: TransactionReceipt) => !!this.slashingProposerContract!.tryExtractVoteCastEvent(receipt.logs),
|
|
1105
1113
|
slotNumber,
|
|
1106
|
-
timestamp,
|
|
1107
1114
|
);
|
|
1108
1115
|
break;
|
|
1109
1116
|
}
|
|
@@ -1114,18 +1121,20 @@ export class SequencerPublisher {
|
|
|
1114
1121
|
round: action.round,
|
|
1115
1122
|
signerAddress,
|
|
1116
1123
|
});
|
|
1117
|
-
if (this.slashingProposerContract
|
|
1118
|
-
this.log.error('
|
|
1124
|
+
if (!this.slashingProposerContract) {
|
|
1125
|
+
this.log.error('No slashing proposer contract available');
|
|
1119
1126
|
return false;
|
|
1120
1127
|
}
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1128
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(
|
|
1129
|
+
action.round,
|
|
1130
|
+
action.committees,
|
|
1131
|
+
);
|
|
1123
1132
|
await this.simulateAndEnqueueRequest(
|
|
1124
1133
|
'execute-slash',
|
|
1125
|
-
|
|
1126
|
-
(receipt: TransactionReceipt) =>
|
|
1134
|
+
executeRequest,
|
|
1135
|
+
(receipt: TransactionReceipt) =>
|
|
1136
|
+
!!this.slashingProposerContract!.tryExtractRoundExecutedEvent(receipt.logs),
|
|
1127
1137
|
slotNumber,
|
|
1128
|
-
timestamp,
|
|
1129
1138
|
);
|
|
1130
1139
|
break;
|
|
1131
1140
|
}
|
|
@@ -1145,7 +1154,11 @@ export class SequencerPublisher {
|
|
|
1145
1154
|
checkpoint: Checkpoint,
|
|
1146
1155
|
attestationsAndSigners: CommitteeAttestationsAndSigners,
|
|
1147
1156
|
attestationsAndSignersSignature: Signature,
|
|
1148
|
-
opts: {
|
|
1157
|
+
opts: {
|
|
1158
|
+
txTimeoutAt?: Date;
|
|
1159
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1160
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1161
|
+
} = {},
|
|
1149
1162
|
): Promise<void> {
|
|
1150
1163
|
const checkpointHeader = checkpoint.header;
|
|
1151
1164
|
|
|
@@ -1161,15 +1174,13 @@ export class SequencerPublisher {
|
|
|
1161
1174
|
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier,
|
|
1162
1175
|
};
|
|
1163
1176
|
|
|
1164
|
-
let ts: bigint;
|
|
1165
|
-
|
|
1166
1177
|
try {
|
|
1167
1178
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
1168
1179
|
// This means that we can avoid the simulation issues in later checks.
|
|
1169
1180
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
1170
1181
|
// make time consistency checks break.
|
|
1171
1182
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
1172
|
-
|
|
1183
|
+
await this.validateCheckpointForSubmission(
|
|
1173
1184
|
checkpoint,
|
|
1174
1185
|
attestationsAndSigners,
|
|
1175
1186
|
attestationsAndSignersSignature,
|
|
@@ -1184,8 +1195,26 @@ export class SequencerPublisher {
|
|
|
1184
1195
|
throw err;
|
|
1185
1196
|
}
|
|
1186
1197
|
|
|
1198
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1199
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1200
|
+
let preCheck = undefined;
|
|
1201
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1202
|
+
preCheck = async () => {
|
|
1203
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1204
|
+
await this.validateCheckpointForSubmission(
|
|
1205
|
+
checkpoint,
|
|
1206
|
+
attestationsAndSigners,
|
|
1207
|
+
attestationsAndSignersSignature,
|
|
1208
|
+
{
|
|
1209
|
+
// Forcing pending checkpoint number is included its required if an invalidation request is included
|
|
1210
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber,
|
|
1211
|
+
},
|
|
1212
|
+
);
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1187
1216
|
this.log.verbose(`Enqueuing checkpoint propose transaction`, { ...checkpoint.toCheckpointInfo(), ...opts });
|
|
1188
|
-
await this.addProposeTx(checkpoint, proposeTxArgs, opts,
|
|
1217
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, preCheck);
|
|
1189
1218
|
}
|
|
1190
1219
|
|
|
1191
1220
|
public enqueueInvalidateCheckpoint(
|
|
@@ -1228,8 +1257,8 @@ export class SequencerPublisher {
|
|
|
1228
1257
|
request: L1TxRequest,
|
|
1229
1258
|
checkSuccess: (receipt: TransactionReceipt) => boolean | undefined,
|
|
1230
1259
|
slotNumber: SlotNumber,
|
|
1231
|
-
timestamp: bigint,
|
|
1232
1260
|
) {
|
|
1261
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
1233
1262
|
const logData = { slotNumber, timestamp, gasLimit: undefined as bigint | undefined };
|
|
1234
1263
|
if (this.lastActions[action] && this.lastActions[action] === slotNumber) {
|
|
1235
1264
|
this.log.debug(`Skipping duplicate action ${action} for slot ${slotNumber}`);
|
|
@@ -1245,8 +1274,9 @@ export class SequencerPublisher {
|
|
|
1245
1274
|
|
|
1246
1275
|
let gasUsed: bigint;
|
|
1247
1276
|
const simulateAbi = mergeAbis([request.abi ?? [], ErrorsAbi]);
|
|
1277
|
+
|
|
1248
1278
|
try {
|
|
1249
|
-
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1279
|
+
({ gasUsed } = await this.l1TxUtils.simulate(request, { time: timestamp }, [], simulateAbi));
|
|
1250
1280
|
this.log.verbose(`Simulation for ${action} succeeded`, { ...logData, request, gasUsed });
|
|
1251
1281
|
} catch (err) {
|
|
1252
1282
|
const viemError = formatViemError(err, simulateAbi);
|
|
@@ -1304,6 +1334,7 @@ export class SequencerPublisher {
|
|
|
1304
1334
|
*/
|
|
1305
1335
|
public interrupt() {
|
|
1306
1336
|
this.interrupted = true;
|
|
1337
|
+
this.interruptibleSleep.interrupt();
|
|
1307
1338
|
this.l1TxUtils.interrupt();
|
|
1308
1339
|
}
|
|
1309
1340
|
|
|
@@ -1315,7 +1346,6 @@ export class SequencerPublisher {
|
|
|
1315
1346
|
|
|
1316
1347
|
private async prepareProposeTx(
|
|
1317
1348
|
encodedData: L1ProcessArgs,
|
|
1318
|
-
timestamp: bigint,
|
|
1319
1349
|
options: { forcePendingCheckpointNumber?: CheckpointNumber },
|
|
1320
1350
|
) {
|
|
1321
1351
|
const kzg = Blob.getViemKzgInstance();
|
|
@@ -1388,7 +1418,7 @@ export class SequencerPublisher {
|
|
|
1388
1418
|
blobInput,
|
|
1389
1419
|
] as const;
|
|
1390
1420
|
|
|
1391
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1421
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, options);
|
|
1392
1422
|
|
|
1393
1423
|
return { args, blobEvaluationGas, rollupData, simulationResult };
|
|
1394
1424
|
}
|
|
@@ -1396,7 +1426,6 @@ export class SequencerPublisher {
|
|
|
1396
1426
|
/**
|
|
1397
1427
|
* Simulates the propose tx with eth_simulateV1
|
|
1398
1428
|
* @param args - The propose tx args
|
|
1399
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
1400
1429
|
* @returns The simulation result
|
|
1401
1430
|
*/
|
|
1402
1431
|
private async simulateProposeTx(
|
|
@@ -1413,8 +1442,10 @@ export class SequencerPublisher {
|
|
|
1413
1442
|
ViemSignature,
|
|
1414
1443
|
`0x${string}`,
|
|
1415
1444
|
],
|
|
1416
|
-
|
|
1417
|
-
|
|
1445
|
+
options: {
|
|
1446
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1447
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1448
|
+
},
|
|
1418
1449
|
) {
|
|
1419
1450
|
const rollupData = encodeFunctionData({
|
|
1420
1451
|
abi: RollupAbi,
|
|
@@ -1422,13 +1453,23 @@ export class SequencerPublisher {
|
|
|
1422
1453
|
args,
|
|
1423
1454
|
});
|
|
1424
1455
|
|
|
1425
|
-
// override the
|
|
1456
|
+
// override the proposed checkpoint number if requested
|
|
1426
1457
|
const forcePendingCheckpointNumberStateDiff = (
|
|
1427
1458
|
options.forcePendingCheckpointNumber !== undefined
|
|
1428
1459
|
? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber)
|
|
1429
1460
|
: []
|
|
1430
1461
|
).flatMap(override => override.stateDiff ?? []);
|
|
1431
1462
|
|
|
1463
|
+
// override the fee header for a specific checkpoint number if requested (used when pipelining)
|
|
1464
|
+
const forceProposedFeeHeaderStateDiff = (
|
|
1465
|
+
options.forceProposedFeeHeader !== undefined
|
|
1466
|
+
? await this.rollupContract.makeFeeHeaderOverride(
|
|
1467
|
+
options.forceProposedFeeHeader.checkpointNumber,
|
|
1468
|
+
options.forceProposedFeeHeader.feeHeader,
|
|
1469
|
+
)
|
|
1470
|
+
: []
|
|
1471
|
+
).flatMap(override => override.stateDiff ?? []);
|
|
1472
|
+
|
|
1432
1473
|
const stateOverrides: StateOverride = [
|
|
1433
1474
|
{
|
|
1434
1475
|
address: this.rollupContract.address,
|
|
@@ -1436,6 +1477,7 @@ export class SequencerPublisher {
|
|
|
1436
1477
|
stateDiff: [
|
|
1437
1478
|
{ slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true), value: toPaddedHex(0n, true) },
|
|
1438
1479
|
...forcePendingCheckpointNumberStateDiff,
|
|
1480
|
+
...forceProposedFeeHeaderStateDiff,
|
|
1439
1481
|
],
|
|
1440
1482
|
},
|
|
1441
1483
|
];
|
|
@@ -1448,6 +1490,7 @@ export class SequencerPublisher {
|
|
|
1448
1490
|
}
|
|
1449
1491
|
|
|
1450
1492
|
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1493
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
1451
1494
|
|
|
1452
1495
|
const simulationResult = await this.l1TxUtils
|
|
1453
1496
|
.simulate(
|
|
@@ -1458,8 +1501,7 @@ export class SequencerPublisher {
|
|
|
1458
1501
|
...(this.proposerAddressForSimulation && { from: this.proposerAddressForSimulation.toString() }),
|
|
1459
1502
|
},
|
|
1460
1503
|
{
|
|
1461
|
-
|
|
1462
|
-
time: timestamp + 1n,
|
|
1504
|
+
time: simTs,
|
|
1463
1505
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
1464
1506
|
gasLimit: MAX_L1_TX_LIMIT * 2n,
|
|
1465
1507
|
},
|
|
@@ -1481,7 +1523,7 @@ export class SequencerPublisher {
|
|
|
1481
1523
|
logs: [],
|
|
1482
1524
|
};
|
|
1483
1525
|
}
|
|
1484
|
-
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1526
|
+
this.log.error(`Failed to simulate propose tx`, viemError, { simulationTimestamp: simTs });
|
|
1485
1527
|
this.backupFailedTx({
|
|
1486
1528
|
id: keccak256(rollupData),
|
|
1487
1529
|
failureType: 'simulation',
|
|
@@ -1503,17 +1545,17 @@ export class SequencerPublisher {
|
|
|
1503
1545
|
private async addProposeTx(
|
|
1504
1546
|
checkpoint: Checkpoint,
|
|
1505
1547
|
encodedData: L1ProcessArgs,
|
|
1506
|
-
opts: {
|
|
1507
|
-
|
|
1548
|
+
opts: {
|
|
1549
|
+
txTimeoutAt?: Date;
|
|
1550
|
+
forcePendingCheckpointNumber?: CheckpointNumber;
|
|
1551
|
+
forceProposedFeeHeader?: { checkpointNumber: CheckpointNumber; feeHeader: FeeHeader };
|
|
1552
|
+
} = {},
|
|
1553
|
+
preCheck?: () => Promise<void>,
|
|
1508
1554
|
): Promise<void> {
|
|
1509
1555
|
const slot = checkpoint.header.slotNumber;
|
|
1510
1556
|
const timer = new Timer();
|
|
1511
1557
|
const kzg = Blob.getViemKzgInstance();
|
|
1512
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(
|
|
1513
|
-
encodedData,
|
|
1514
|
-
timestamp,
|
|
1515
|
-
opts,
|
|
1516
|
-
);
|
|
1558
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts);
|
|
1517
1559
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
1518
1560
|
const gasLimit = this.l1TxUtils.bumpGasLimit(
|
|
1519
1561
|
BigInt(Math.ceil((Number(simulationResult.gasUsed) * 64) / 63)) +
|
|
@@ -1537,6 +1579,7 @@ export class SequencerPublisher {
|
|
|
1537
1579
|
},
|
|
1538
1580
|
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
1539
1581
|
gasConfig: { ...opts, gasLimit },
|
|
1582
|
+
preCheck,
|
|
1540
1583
|
blobConfig: {
|
|
1541
1584
|
blobs: encodedData.blobs.map(b => b.data),
|
|
1542
1585
|
kzg,
|
|
@@ -1590,7 +1633,14 @@ export class SequencerPublisher {
|
|
|
1590
1633
|
});
|
|
1591
1634
|
}
|
|
1592
1635
|
|
|
1593
|
-
/** Returns the timestamp
|
|
1636
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1637
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */
|
|
1638
|
+
private getSimulationTimestamp(slot: SlotNumber): bigint {
|
|
1639
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1640
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */
|
|
1594
1644
|
private getNextL1SlotTimestamp(): bigint {
|
|
1595
1645
|
const l1Constants = this.epochCache.getL1Constants();
|
|
1596
1646
|
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|